[BROWSEUI] Port some IAutoComplete changes from wine + fix a memory leak. This allows...
[reactos.git] / reactos / dll / win32 / browseui / CAutoComplete.cpp
1 /*
2 * AutoComplete interfaces implementation.
3 *
4 * Copyright 2004 Maxime Bellengé <maxime.bellenge@laposte.net>
5 * Copyright 2009 Andrew Hill
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 /*
23 Implemented:
24 - ACO_AUTOAPPEND style
25 - ACO_AUTOSUGGEST style
26 - ACO_UPDOWNKEYDROPSLIST style
27
28 - Handle pwzsRegKeyPath and pwszQuickComplete in Init
29
30 TODO:
31 - implement ACO_SEARCH style
32 - implement ACO_FILTERPREFIXES style
33 - implement ACO_USETAB style
34 - implement ACO_RTLREADING style
35
36 */
37
38 #include "precomp.h"
39
40 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o',
41 'c','o','m','p','l','e','t','e',' ',
42 'c','o','n','t','r','o','l',0};
43
44 /**************************************************************************
45 * IAutoComplete_Constructor
46 */
47 CAutoComplete::CAutoComplete()
48 {
49 enabled = TRUE;
50 initialized = FALSE;
51 options = ACO_AUTOAPPEND;
52 wpOrigEditProc = NULL;
53 hwndListBox = NULL;
54 txtbackup = NULL;
55 quickComplete = NULL;
56 hwndEdit = NULL;
57 wpOrigLBoxProc = NULL;
58 }
59
60 /**************************************************************************
61 * IAutoComplete_Destructor
62 */
63 CAutoComplete::~CAutoComplete()
64 {
65 TRACE(" destroying IAutoComplete(%p)\n", this);
66 HeapFree(GetProcessHeap(), 0, quickComplete);
67 HeapFree(GetProcessHeap(), 0, txtbackup);
68 RemovePropW(hwndEdit, autocomplete_propertyW);
69 SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR)wpOrigEditProc);
70 if (hwndListBox)
71 DestroyWindow(hwndListBox);
72 }
73
74 /******************************************************************************
75 * IAutoComplete_fnEnable
76 */
77 HRESULT WINAPI CAutoComplete::Enable(BOOL fEnable)
78 {
79 HRESULT hr = S_OK;
80
81 TRACE("(%p)->(%s)\n", this, (fEnable) ? "true" : "false");
82
83 enabled = fEnable;
84
85 return hr;
86 }
87
88 /******************************************************************************
89 * create_listbox
90 */
91 void CAutoComplete::CreateListbox()
92 {
93 HWND hwndParent = GetParent(hwndEdit);
94
95 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
96 hwndListBox = CreateWindowExW(0, WC_LISTBOXW, NULL,
97 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
98 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
99 hwndParent, NULL,
100 (HINSTANCE)GetWindowLongPtrW(hwndParent, GWLP_HINSTANCE), NULL);
101
102 if (hwndListBox)
103 {
104 wpOrigLBoxProc = (WNDPROC)SetWindowLongPtrW(hwndListBox, GWLP_WNDPROC, (LONG_PTR)ACLBoxSubclassProc);
105 SetWindowLongPtrW(hwndListBox, GWLP_USERDATA, (LONG_PTR)this);
106 }
107 }
108
109
110 /******************************************************************************
111 * IAutoComplete_fnInit
112 */
113 HRESULT WINAPI CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete)
114 {
115 TRACE("(%p)->(0x%08lx, %p, %s, %s)\n",
116 this, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
117
118 if (options & ACO_AUTOSUGGEST)
119 TRACE(" ACO_AUTOSUGGEST\n");
120 if (options & ACO_AUTOAPPEND)
121 TRACE(" ACO_AUTOAPPEND\n");
122 if (options & ACO_SEARCH)
123 FIXME(" ACO_SEARCH not supported\n");
124 if (options & ACO_FILTERPREFIXES)
125 FIXME(" ACO_FILTERPREFIXES not supported\n");
126 if (options & ACO_USETAB)
127 FIXME(" ACO_USETAB not supported\n");
128 if (options & ACO_UPDOWNKEYDROPSLIST)
129 TRACE(" ACO_UPDOWNKEYDROPSLIST\n");
130 if (options & ACO_RTLREADING)
131 FIXME(" ACO_RTLREADING not supported\n");
132
133 if (!hwndEdit || !punkACL)
134 return E_INVALIDARG;
135
136 if (this->initialized)
137 {
138 WARN("Autocompletion object is already initialized\n");
139 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
140 return this->hwndEdit ? E_FAIL : E_UNEXPECTED;
141 }
142
143 if (!SUCCEEDED(punkACL->QueryInterface(IID_PPV_ARG(IEnumString,&enumstr))))
144 {
145 TRACE("No IEnumString interface\n");
146 return E_NOINTERFACE;
147 }
148
149 this->hwndEdit = hwndEdit;
150 this->initialized = TRUE;
151 this->wpOrigEditProc = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
152 /* Keep at least one reference to the object until the edit window is destroyed. */
153 this->AddRef();
154 SetPropW( this->hwndEdit, autocomplete_propertyW, (HANDLE)this );
155
156 if (options & ACO_AUTOSUGGEST)
157 {
158 this->CreateListbox();
159 }
160
161 if (pwzsRegKeyPath)
162 {
163 WCHAR *key;
164 WCHAR result[MAX_PATH];
165 WCHAR *value;
166 HKEY hKey = 0;
167 LONG res;
168 LONG len;
169
170 /* pwszRegKeyPath contains the key as well as the value, so we split */
171 key = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
172
173 if (key)
174 {
175 wcscpy(key, pwzsRegKeyPath);
176 value = const_cast<WCHAR *>(wcsrchr(key, '\\'));
177
178 if (value)
179 {
180 *value = 0;
181 value++;
182 /* Now value contains the value and buffer the key */
183 res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey);
184
185 if (res != ERROR_SUCCESS)
186 {
187 /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */
188 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey);
189 }
190
191 if (res == ERROR_SUCCESS)
192 {
193 len = sizeof(result);
194 res = RegQueryValueW(hKey, value, result, &len);
195 if (res == ERROR_SUCCESS)
196 {
197 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
198 wcscpy(quickComplete, result);
199 }
200 RegCloseKey(hKey);
201 }
202 }
203
204 HeapFree(GetProcessHeap(), 0, key);
205 }
206 else
207 {
208 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
209 return S_FALSE;
210 }
211 }
212
213 if ((pwszQuickComplete) && (!quickComplete))
214 {
215 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
216
217 if (quickComplete)
218 {
219 wcscpy(quickComplete, pwszQuickComplete);
220 }
221 else
222 {
223 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
224 return S_FALSE;
225 }
226 }
227
228 return S_OK;
229 }
230
231 /**************************************************************************
232 * IAutoComplete_fnGetOptions
233 */
234 HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag)
235 {
236 HRESULT hr = S_OK;
237
238 TRACE("(%p) -> (%p)\n", this, pdwFlag);
239
240 *pdwFlag = options;
241
242 return hr;
243 }
244
245 /**************************************************************************
246 * IAutoComplete_fnSetOptions
247 */
248 HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag)
249 {
250 HRESULT hr = S_OK;
251
252 TRACE("(%p) -> (0x%x)\n", this, dwFlag);
253
254 options = (AUTOCOMPLETEOPTIONS)dwFlag;
255
256 if ((options & ACO_AUTOSUGGEST) && hwndEdit && !hwndListBox)
257 CreateListbox();
258
259 return hr;
260 }
261
262 /*
263 Window procedure for autocompletion
264 */
265 LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
266 {
267 CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW));
268 HRESULT hr;
269 WCHAR hwndText[255];
270 WCHAR *hwndQCText;
271 RECT r;
272 BOOL control, filled, displayall = FALSE;
273 int cpt, height, sel;
274 ULONG fetched;
275
276 if (!pThis->enabled)
277 {
278 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
279 }
280
281 switch (uMsg)
282 {
283 case CB_SHOWDROPDOWN:
284 {
285 ShowWindow(pThis->hwndListBox, SW_HIDE);
286 }; break;
287
288 case WM_KILLFOCUS:
289 {
290 if ((pThis->options & ACO_AUTOSUGGEST) && ((HWND)wParam != pThis->hwndListBox))
291 {
292 ShowWindow(pThis->hwndListBox, SW_HIDE);
293 }
294 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
295 }; break;
296
297 case WM_KEYUP:
298 {
299 GetWindowTextW(hwnd, (LPWSTR)hwndText, 255);
300
301 switch(wParam)
302 {
303 case VK_RETURN:
304 {
305 /* If quickComplete is set and control is pressed, replace the string */
306 control = GetKeyState(VK_CONTROL) & 0x8000;
307 if (control && pThis->quickComplete)
308 {
309 hwndQCText = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
310 (wcslen(pThis->quickComplete)+wcslen(hwndText))*sizeof(WCHAR));
311 sel = swprintf(hwndQCText, pThis->quickComplete, hwndText);
312 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText);
313 SendMessageW(hwnd, EM_SETSEL, 0, sel);
314 HeapFree(GetProcessHeap(), 0, hwndQCText);
315 }
316
317 ShowWindow(pThis->hwndListBox, SW_HIDE);
318 return 0;
319 }; break;
320
321 case VK_LEFT:
322 case VK_RIGHT:
323 {
324 return 0;
325 }; break;
326
327 case VK_UP:
328 case VK_DOWN:
329 {
330 /* Two cases here :
331 - if the listbox is not visible, displays it
332 with all the entries if the style ACO_UPDOWNKEYDROPSLIST
333 is present but does not select anything.
334 - if the listbox is visible, change the selection
335 */
336 if ( (pThis->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST))
337 && (!IsWindowVisible(pThis->hwndListBox) && (! *hwndText)) )
338 {
339 /* We must display all the entries */
340 displayall = TRUE;
341 }
342 else
343 {
344 if (IsWindowVisible(pThis->hwndListBox))
345 {
346 int count;
347
348 count = SendMessageW(pThis->hwndListBox, LB_GETCOUNT, 0, 0);
349 /* Change the selection */
350 sel = SendMessageW(pThis->hwndListBox, LB_GETCURSEL, 0, 0);
351 if (wParam == VK_UP)
352 sel = ((sel-1) < 0) ? count-1 : sel-1;
353 else
354 sel = ((sel+1) >= count) ? -1 : sel+1;
355
356 SendMessageW(pThis->hwndListBox, LB_SETCURSEL, sel, 0);
357
358 if (sel != -1)
359 {
360 WCHAR *msg;
361 int len;
362
363 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL);
364 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
365
366 if (msg)
367 {
368 SendMessageW(pThis->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg);
369 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg);
370 SendMessageW(hwnd, EM_SETSEL, wcslen(msg), wcslen(msg));
371
372 HeapFree(GetProcessHeap(), 0, msg);
373 }
374 else
375 {
376 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
377 }
378 }
379 else
380 {
381 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)pThis->txtbackup);
382 SendMessageW(hwnd, EM_SETSEL, wcslen(pThis->txtbackup), wcslen(pThis->txtbackup));
383 }
384 }
385 return 0;
386 }
387 }; break;
388
389 case VK_BACK:
390 case VK_DELETE:
391 {
392 if ((! *hwndText) && (pThis->options & ACO_AUTOSUGGEST))
393 {
394 ShowWindow(pThis->hwndListBox, SW_HIDE);
395 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
396 }
397
398 if (pThis->options & ACO_AUTOAPPEND)
399 {
400 DWORD b;
401 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL);
402 if (b>1)
403 {
404 hwndText[b-1] = '\0';
405 }
406 else
407 {
408 hwndText[0] = '\0';
409 SetWindowTextW(hwnd, hwndText);
410 }
411 }
412 }; break;
413
414 default:
415 ;
416 }
417
418 SendMessageW(pThis->hwndListBox, LB_RESETCONTENT, 0, 0);
419
420 HeapFree(GetProcessHeap(), 0, pThis->txtbackup);
421
422 pThis->txtbackup = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(hwndText)+1)*sizeof(WCHAR));
423
424 if (pThis->txtbackup)
425 {
426 wcscpy(pThis->txtbackup, hwndText);
427 }
428 else
429 {
430 TRACE("HeapAlloc failed to allocate %d bytes\n", (wcslen(hwndText)+1)*sizeof(WCHAR));
431 }
432
433 /* Returns if there is no text to search and we doesn't want to display all the entries */
434 if ((!displayall) && (! *hwndText) )
435 break;
436
437 pThis->enumstr->Reset();
438 filled = FALSE;
439 size_t curlen = wcslen(hwndText);
440
441 for(cpt = 0;;)
442 {
443 CComHeapPtr<OLECHAR> strs;
444 hr = pThis->enumstr->Next(1, &strs, &fetched);
445 if (hr != S_OK)
446 break;
447
448 if (!_wcsnicmp(hwndText, strs, curlen))
449 {
450
451 if (pThis->options & ACO_AUTOAPPEND && *hwndText)
452 {
453 CComBSTR str((PCWSTR)strs);
454 memcpy(str.m_str, hwndText, curlen * sizeof(WCHAR));
455 SetWindowTextW(hwnd, str);
456 SendMessageW(hwnd, EM_SETSEL, curlen, str.Length());
457 if (!(pThis->options & ACO_AUTOSUGGEST))
458 break;
459 }
460
461 if (pThis->options & ACO_AUTOSUGGEST)
462 {
463 SendMessageW(pThis->hwndListBox, LB_ADDSTRING, 0, (LPARAM)(LPOLESTR)strs);
464 filled = TRUE;
465 cpt++;
466 }
467 }
468 }
469
470 if (pThis->options & ACO_AUTOSUGGEST)
471 {
472 if (filled)
473 {
474 height = SendMessageW(pThis->hwndListBox, LB_GETITEMHEIGHT, 0, 0);
475 SendMessageW(pThis->hwndListBox, LB_CARETOFF, 0, 0);
476 GetWindowRect(hwnd, &r);
477 SetParent(pThis->hwndListBox, HWND_DESKTOP);
478 /* It seems that Windows XP displays 7 lines at most
479 and otherwise displays a vertical scroll bar */
480 SetWindowPos(pThis->hwndListBox, HWND_TOP,
481 r.left, r.bottom + 1, r.right - r.left, min(height * 7, height * (cpt + 1)),
482 SWP_SHOWWINDOW );
483 }
484 else
485 {
486 ShowWindow(pThis->hwndListBox, SW_HIDE);
487 }
488 }
489
490 }; break;
491
492 case WM_DESTROY:
493 {
494 /* Release our reference that we had since ->Init() */
495 pThis->Release();
496 return 0;
497 }
498
499
500 default:
501 {
502 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
503 }
504
505 }
506
507 return 0;
508 }
509
510 LRESULT APIENTRY CAutoComplete::ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
511 {
512 CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
513 WCHAR *msg;
514 int sel, len;
515
516 switch (uMsg)
517 {
518 case WM_MOUSEMOVE:
519 {
520 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
521 SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0);
522 }; break;
523
524 case WM_LBUTTONDOWN:
525 {
526 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
527
528 if (sel < 0)
529 break;
530
531 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, 0);
532 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
533
534 if (msg)
535 {
536 SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
537 SendMessageW(pThis->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg);
538 SendMessageW(pThis->hwndEdit, EM_SETSEL, 0, wcslen(msg));
539 ShowWindow(hwnd, SW_HIDE);
540
541 HeapFree(GetProcessHeap(), 0, msg);
542 }
543 else
544 {
545 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
546 }
547
548 }; break;
549
550 default:
551 return CallWindowProcW(pThis->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
552 }
553 return 0;
554 }
555
556 /**************************************************************************
557 * IAutoCompleteDropDown
558 */
559 HRESULT STDMETHODCALLTYPE CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
560 {
561 BOOL dropped = IsWindowVisible(hwndListBox);
562
563 if (pdwFlags)
564 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
565
566 if (ppwszString)
567 {
568 *ppwszString = NULL;
569
570 if (dropped)
571 {
572 int sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0);
573 if (sel >= 0)
574 {
575 DWORD len = SendMessageW(hwndListBox, LB_GETTEXTLEN, sel, 0);
576 *ppwszString = (LPWSTR)CoTaskMemAlloc((len+1)*sizeof(WCHAR));
577 SendMessageW(hwndListBox, LB_GETTEXT, sel, (LPARAM)*ppwszString);
578 }
579 }
580 }
581
582 return S_OK;
583 }
584
585 HRESULT STDMETHODCALLTYPE CAutoComplete::ResetEnumerator()
586 {
587 FIXME("(%p): stub\n", this);
588 return E_NOTIMPL;
589 }
590
591 /**************************************************************************
592 * IEnumString
593 */
594 HRESULT STDMETHODCALLTYPE CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
595 {
596 FIXME("(%p, %d, %p, %p): stub\n", this, celt, rgelt, pceltFetched);
597 *pceltFetched = 0;
598 return E_NOTIMPL;
599 }
600
601 HRESULT STDMETHODCALLTYPE CAutoComplete::Skip(ULONG celt)
602 {
603 FIXME("(%p, %d): stub\n", this, celt);
604 return E_NOTIMPL;
605 }
606
607 HRESULT STDMETHODCALLTYPE CAutoComplete::Reset()
608 {
609 FIXME("(%p): stub\n", this);
610 return E_NOTIMPL;
611 }
612
613 HRESULT STDMETHODCALLTYPE CAutoComplete::Clone(IEnumString **ppOut)
614 {
615 FIXME("(%p, %p): stub\n", this, ppOut);
616 *ppOut = NULL;
617 return E_NOTIMPL;
618 }