4c8488190d5757fe67e405dfb42ec45606ba7715
[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 * IAutoComplete_fnInit
90 */
91 HRESULT WINAPI CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete)
92 {
93 static const WCHAR lbName[] = {'L','i','s','t','B','o','x',0};
94
95 TRACE("(%p)->(0x%08lx, %p, %s, %s)\n",
96 this, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
97
98 if (options & ACO_AUTOSUGGEST)
99 TRACE(" ACO_AUTOSUGGEST\n");
100 if (options & ACO_AUTOAPPEND)
101 TRACE(" ACO_AUTOAPPEND\n");
102 if (options & ACO_SEARCH)
103 FIXME(" ACO_SEARCH not supported\n");
104 if (options & ACO_FILTERPREFIXES)
105 FIXME(" ACO_FILTERPREFIXES not supported\n");
106 if (options & ACO_USETAB)
107 FIXME(" ACO_USETAB not supported\n");
108 if (options & ACO_UPDOWNKEYDROPSLIST)
109 TRACE(" ACO_UPDOWNKEYDROPSLIST\n");
110 if (options & ACO_RTLREADING)
111 FIXME(" ACO_RTLREADING not supported\n");
112
113 if (!hwndEdit || !punkACL)
114 return E_INVALIDARG;
115
116 if (this->initialized)
117 {
118 WARN("Autocompletion object is already initialized\n");
119 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
120 return this->hwndEdit ? E_FAIL : E_UNEXPECTED;
121 }
122
123 if (!SUCCEEDED(punkACL->QueryInterface(IID_PPV_ARG(IEnumString,&enumstr))))
124 {
125 TRACE("No IEnumString interface\n");
126 return E_NOINTERFACE;
127 }
128
129 this->hwndEdit = hwndEdit;
130 this->initialized = TRUE;
131 wpOrigEditProc = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
132 // SetWindowLongPtrW(hwndEdit, GWLP_USERDATA, (LONG_PTR)this);
133 SetPropW( hwndEdit, autocomplete_propertyW, (HANDLE)this );
134
135 if (options & ACO_AUTOSUGGEST)
136 {
137 HWND hwndParent;
138
139 hwndParent = GetParent(hwndEdit);
140
141 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
142 hwndListBox = CreateWindowExW(0, lbName, NULL,
143 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
144 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
145 hwndParent, NULL,
146 (HINSTANCE)GetWindowLongPtrW(hwndParent, GWLP_HINSTANCE), NULL);
147
148 if (hwndListBox)
149 {
150 wpOrigLBoxProc = (WNDPROC)SetWindowLongPtrW(hwndListBox, GWLP_WNDPROC, (LONG_PTR)ACLBoxSubclassProc);
151 SetWindowLongPtrW(hwndListBox, GWLP_USERDATA, (LONG_PTR)this);
152 }
153 }
154
155 if (pwzsRegKeyPath)
156 {
157 WCHAR *key;
158 WCHAR result[MAX_PATH];
159 WCHAR *value;
160 HKEY hKey = 0;
161 LONG res;
162 LONG len;
163
164 /* pwszRegKeyPath contains the key as well as the value, so we split */
165 key = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
166
167 if (key)
168 {
169 wcscpy(key, pwzsRegKeyPath);
170 value = const_cast<WCHAR *>(wcsrchr(key, '\\'));
171
172 if (value)
173 {
174 *value = 0;
175 value++;
176 /* Now value contains the value and buffer the key */
177 res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey);
178
179 if (res != ERROR_SUCCESS)
180 {
181 /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */
182 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey);
183 }
184
185 if (res == ERROR_SUCCESS)
186 {
187 len = sizeof(result);
188 res = RegQueryValueW(hKey, value, result, &len);
189 if (res == ERROR_SUCCESS)
190 {
191 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
192 wcscpy(quickComplete, result);
193 }
194 RegCloseKey(hKey);
195 }
196 }
197
198 HeapFree(GetProcessHeap(), 0, key);
199 }
200 else
201 {
202 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
203 return S_FALSE;
204 }
205 }
206
207 if ((pwszQuickComplete) && (!quickComplete))
208 {
209 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
210
211 if (quickComplete)
212 {
213 wcscpy(quickComplete, pwszQuickComplete);
214 }
215 else
216 {
217 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
218 return S_FALSE;
219 }
220 }
221
222 return S_OK;
223 }
224
225 /**************************************************************************
226 * IAutoComplete_fnGetOptions
227 */
228 HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag)
229 {
230 HRESULT hr = S_OK;
231
232 TRACE("(%p) -> (%p)\n", this, pdwFlag);
233
234 *pdwFlag = options;
235
236 return hr;
237 }
238
239 /**************************************************************************
240 * IAutoComplete_fnSetOptions
241 */
242 HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag)
243 {
244 HRESULT hr = S_OK;
245
246 TRACE("(%p) -> (0x%x)\n", this, dwFlag);
247
248 options = (AUTOCOMPLETEOPTIONS)dwFlag;
249
250 return hr;
251 }
252
253 /*
254 Window procedure for autocompletion
255 */
256 LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
257 {
258 CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW));//GetWindowLongPtrW(hwnd, GWLP_USERDATA);
259 LPOLESTR strs;
260 HRESULT hr;
261 WCHAR hwndText[255];
262 WCHAR *hwndQCText;
263 RECT r;
264 BOOL control, filled, displayall = FALSE;
265 int cpt, height, sel;
266 ULONG fetched;
267
268 if (!pThis->enabled)
269 {
270 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
271 }
272
273 switch (uMsg)
274 {
275 case CB_SHOWDROPDOWN:
276 {
277 ShowWindow(pThis->hwndListBox, SW_HIDE);
278 }; break;
279
280 case WM_KILLFOCUS:
281 {
282 if ((pThis->options & ACO_AUTOSUGGEST) && ((HWND)wParam != pThis->hwndListBox))
283 {
284 ShowWindow(pThis->hwndListBox, SW_HIDE);
285 }
286 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
287 }; break;
288
289 case WM_KEYUP:
290 {
291 GetWindowTextW(hwnd, (LPWSTR)hwndText, 255);
292
293 switch(wParam)
294 {
295 case VK_RETURN:
296 {
297 /* If quickComplete is set and control is pressed, replace the string */
298 control = GetKeyState(VK_CONTROL) & 0x8000;
299 if (control && pThis->quickComplete)
300 {
301 hwndQCText = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
302 (wcslen(pThis->quickComplete)+wcslen(hwndText))*sizeof(WCHAR));
303 sel = swprintf(hwndQCText, pThis->quickComplete, hwndText);
304 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText);
305 SendMessageW(hwnd, EM_SETSEL, 0, sel);
306 HeapFree(GetProcessHeap(), 0, hwndQCText);
307 }
308
309 ShowWindow(pThis->hwndListBox, SW_HIDE);
310 return 0;
311 }; break;
312
313 case VK_LEFT:
314 case VK_RIGHT:
315 {
316 return 0;
317 }; break;
318
319 case VK_UP:
320 case VK_DOWN:
321 {
322 /* Two cases here :
323 - if the listbox is not visible, displays it
324 with all the entries if the style ACO_UPDOWNKEYDROPSLIST
325 is present but does not select anything.
326 - if the listbox is visible, change the selection
327 */
328 if ( (pThis->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST))
329 && (!IsWindowVisible(pThis->hwndListBox) && (! *hwndText)) )
330 {
331 /* We must display all the entries */
332 displayall = TRUE;
333 }
334 else
335 {
336 if (IsWindowVisible(pThis->hwndListBox))
337 {
338 int count;
339
340 count = SendMessageW(pThis->hwndListBox, LB_GETCOUNT, 0, 0);
341 /* Change the selection */
342 sel = SendMessageW(pThis->hwndListBox, LB_GETCURSEL, 0, 0);
343 if (wParam == VK_UP)
344 sel = ((sel-1)<0)?count-1:sel-1;
345 else
346 sel = ((sel+1)>= count)?-1:sel+1;
347
348 SendMessageW(pThis->hwndListBox, LB_SETCURSEL, sel, 0);
349
350 if (sel != -1)
351 {
352 WCHAR *msg;
353 int len;
354
355 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL);
356 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
357
358 if (msg)
359 {
360 SendMessageW(pThis->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg);
361 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg);
362 SendMessageW(hwnd, EM_SETSEL, wcslen(msg), wcslen(msg));
363
364 HeapFree(GetProcessHeap(), 0, msg);
365 }
366 else
367 {
368 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
369 }
370 }
371 else
372 {
373 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)pThis->txtbackup);
374 SendMessageW(hwnd, EM_SETSEL, wcslen(pThis->txtbackup), wcslen(pThis->txtbackup));
375 }
376 }
377 return 0;
378 }
379 }; break;
380
381 case VK_BACK:
382 case VK_DELETE:
383 {
384 if ((! *hwndText) && (pThis->options & ACO_AUTOSUGGEST))
385 {
386 ShowWindow(pThis->hwndListBox, SW_HIDE);
387 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
388 }
389
390 if (pThis->options & ACO_AUTOAPPEND)
391 {
392 DWORD b;
393 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL);
394 if (b>1)
395 {
396 hwndText[b-1] = '\0';
397 }
398 else
399 {
400 hwndText[0] = '\0';
401 SetWindowTextW(hwnd, hwndText);
402 }
403 }
404 }; break;
405
406 default:
407 ;
408 }
409
410 SendMessageW(pThis->hwndListBox, LB_RESETCONTENT, 0, 0);
411
412 HeapFree(GetProcessHeap(), 0, pThis->txtbackup);
413
414 pThis->txtbackup = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(hwndText)+1)*sizeof(WCHAR));
415
416 if (pThis->txtbackup)
417 {
418 wcscpy(pThis->txtbackup, hwndText);
419 }
420 else
421 {
422 TRACE("HeapAlloc failed to allocate %d bytes\n", (wcslen(hwndText)+1)*sizeof(WCHAR));
423 }
424
425 /* Returns if there is no text to search and we doesn't want to display all the entries */
426 if ((!displayall) && (! *hwndText) )
427 break;
428
429 pThis->enumstr->Reset();
430 filled = FALSE;
431
432 for(cpt = 0;;)
433 {
434 hr = pThis->enumstr->Next(1, &strs, &fetched);
435 if (hr != S_OK)
436 break;
437
438 if ((LPWSTR)wcsstr(strs, hwndText) == strs)
439 {
440
441 if (pThis->options & ACO_AUTOAPPEND)
442 {
443 SetWindowTextW(hwnd, strs);
444 SendMessageW(hwnd, EM_SETSEL, wcslen(hwndText), wcslen(strs));
445 break;
446 }
447
448 if (pThis->options & ACO_AUTOSUGGEST)
449 {
450 SendMessageW(pThis->hwndListBox, LB_ADDSTRING, 0, (LPARAM)strs);
451 filled = TRUE;
452 cpt++;
453 }
454 }
455 }
456
457 if (pThis->options & ACO_AUTOSUGGEST)
458 {
459 if (filled)
460 {
461 height = SendMessageW(pThis->hwndListBox, LB_GETITEMHEIGHT, 0, 0);
462 SendMessageW(pThis->hwndListBox, LB_CARETOFF, 0, 0);
463 GetWindowRect(hwnd, &r);
464 SetParent(pThis->hwndListBox, HWND_DESKTOP);
465 /* It seems that Windows XP displays 7 lines at most
466 and otherwise displays a vertical scroll bar */
467 SetWindowPos(pThis->hwndListBox, HWND_TOP,
468 r.left, r.bottom + 1, r.right - r.left, min(height * 7, height * (cpt + 1)),
469 SWP_SHOWWINDOW );
470 }
471 else
472 {
473 ShowWindow(pThis->hwndListBox, SW_HIDE);
474 }
475 }
476
477 }; break;
478
479 default:
480 {
481 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
482 }
483
484 }
485
486 return 0;
487 }
488
489 LRESULT APIENTRY CAutoComplete::ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
490 {
491 CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
492 WCHAR *msg;
493 int sel, len;
494
495 switch (uMsg)
496 {
497 case WM_MOUSEMOVE:
498 {
499 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
500 SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0);
501 }; break;
502
503 case WM_LBUTTONDOWN:
504 {
505 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
506
507 if (sel < 0)
508 break;
509
510 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, 0);
511 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
512
513 if (msg)
514 {
515 SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
516 SendMessageW(pThis->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg);
517 SendMessageW(pThis->hwndEdit, EM_SETSEL, 0, wcslen(msg));
518 ShowWindow(hwnd, SW_HIDE);
519
520 HeapFree(GetProcessHeap(), 0, msg);
521 }
522 else
523 {
524 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
525 }
526
527 }; break;
528
529 default:
530 return CallWindowProcW(pThis->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
531 }
532 return 0;
533 }
534
535 /**************************************************************************
536 * IAutoCompleteDropDown
537 */
538 HRESULT STDMETHODCALLTYPE CAutoComplete::GetDropDownStatus(DWORD *pdwFlags, LPWSTR *ppwszString)
539 {
540 FIXME("(%p, %p, %p): stub\n", this, pdwFlags, ppwszString);
541 if (pdwFlags)
542 *pdwFlags = 0;
543 if (ppwszString)
544 *ppwszString = NULL;
545 return E_NOTIMPL;
546 }
547
548 HRESULT STDMETHODCALLTYPE CAutoComplete::ResetEnumerator()
549 {
550 FIXME("(%p): stub\n", this);
551 return E_NOTIMPL;
552 }
553
554 /**************************************************************************
555 * IEnumString
556 */
557 HRESULT STDMETHODCALLTYPE CAutoComplete::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
558 {
559 FIXME("(%p, %d, %p, %p): stub\n", this, celt, rgelt, pceltFetched);
560 *pceltFetched = 0;
561 return E_NOTIMPL;
562 }
563
564 HRESULT STDMETHODCALLTYPE CAutoComplete::Skip(ULONG celt)
565 {
566 FIXME("(%p, %d): stub\n", this, celt);
567 return E_NOTIMPL;
568 }
569
570 HRESULT STDMETHODCALLTYPE CAutoComplete::Reset()
571 {
572 FIXME("(%p): stub\n", this);
573 return E_NOTIMPL;
574 }
575
576 HRESULT STDMETHODCALLTYPE CAutoComplete::Clone(IEnumString **ppOut)
577 {
578 FIXME("(%p, %p): stub\n", this, ppOut);
579 *ppOut = NULL;
580 return E_NOTIMPL;
581 }