Sync with trunk r63430.
[reactos.git] / dll / win32 / shell32 / autocomplete.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 WINE_DEFAULT_DEBUG_CHANNEL(shell);
41
42 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o',
43 'c','o','m','p','l','e','t','e',' ',
44 'c','o','n','t','r','o','l',0};
45
46 /**************************************************************************
47 * IAutoComplete_Constructor
48 */
49 CAutoComplete::CAutoComplete()
50 {
51 enabled = TRUE;
52 initialized = FALSE;
53 options = ACO_AUTOAPPEND;
54 wpOrigEditProc = NULL;
55 hwndListBox = NULL;
56 txtbackup = NULL;
57 quickComplete = NULL;
58 hwndEdit = NULL;
59 wpOrigLBoxProc = NULL;
60 }
61
62 /**************************************************************************
63 * IAutoComplete_Destructor
64 */
65 CAutoComplete::~CAutoComplete()
66 {
67 TRACE(" destroying IAutoComplete(%p)\n", this);
68 HeapFree(GetProcessHeap(), 0, quickComplete);
69 HeapFree(GetProcessHeap(), 0, txtbackup);
70 RemovePropW(hwndEdit, autocomplete_propertyW);
71 SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR)wpOrigEditProc);
72 if (hwndListBox)
73 DestroyWindow(hwndListBox);
74 }
75
76 /******************************************************************************
77 * IAutoComplete_fnEnable
78 */
79 HRESULT WINAPI CAutoComplete::Enable(BOOL fEnable)
80 {
81 HRESULT hr = S_OK;
82
83 TRACE("(%p)->(%s)\n", this, (fEnable) ? "true" : "false");
84
85 enabled = fEnable;
86
87 return hr;
88 }
89
90 /******************************************************************************
91 * IAutoComplete_fnInit
92 */
93 HRESULT WINAPI CAutoComplete::Init(HWND hwndEdit, IUnknown *punkACL, LPCOLESTR pwzsRegKeyPath, LPCOLESTR pwszQuickComplete)
94 {
95 static const WCHAR lbName[] = {'L','i','s','t','B','o','x',0};
96
97 TRACE("(%p)->(0x%08lx, %p, %s, %s)\n",
98 this, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
99
100 if (options & ACO_AUTOSUGGEST)
101 TRACE(" ACO_AUTOSUGGEST\n");
102 if (options & ACO_AUTOAPPEND)
103 TRACE(" ACO_AUTOAPPEND\n");
104 if (options & ACO_SEARCH)
105 FIXME(" ACO_SEARCH not supported\n");
106 if (options & ACO_FILTERPREFIXES)
107 FIXME(" ACO_FILTERPREFIXES not supported\n");
108 if (options & ACO_USETAB)
109 FIXME(" ACO_USETAB not supported\n");
110 if (options & ACO_UPDOWNKEYDROPSLIST)
111 TRACE(" ACO_UPDOWNKEYDROPSLIST\n");
112 if (options & ACO_RTLREADING)
113 FIXME(" ACO_RTLREADING not supported\n");
114
115 if (!hwndEdit || !punkACL)
116 return E_INVALIDARG;
117
118 if (this->initialized)
119 {
120 WARN("Autocompletion object is already initialized\n");
121 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
122 return this->hwndEdit ? E_FAIL : E_UNEXPECTED;
123 }
124
125 if (!SUCCEEDED(punkACL->QueryInterface(IID_PPV_ARG(IEnumString,&enumstr))))
126 {
127 TRACE("No IEnumString interface\n");
128 return E_NOINTERFACE;
129 }
130
131 this->hwndEdit = hwndEdit;
132 this->initialized = TRUE;
133 wpOrigEditProc = (WNDPROC)SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
134 // SetWindowLongPtrW(hwndEdit, GWLP_USERDATA, (LONG_PTR)this);
135 SetPropW( hwndEdit, autocomplete_propertyW, (HANDLE)this );
136
137 if (options & ACO_AUTOSUGGEST)
138 {
139 HWND hwndParent;
140
141 hwndParent = GetParent(hwndEdit);
142
143 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
144 hwndListBox = CreateWindowExW(0, lbName, NULL,
145 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
146 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
147 hwndParent, NULL,
148 (HINSTANCE)GetWindowLongPtrW(hwndParent, GWLP_HINSTANCE), NULL);
149
150 if (hwndListBox)
151 {
152 wpOrigLBoxProc = (WNDPROC)SetWindowLongPtrW(hwndListBox, GWLP_WNDPROC, (LONG_PTR)ACLBoxSubclassProc);
153 SetWindowLongPtrW(hwndListBox, GWLP_USERDATA, (LONG_PTR)this);
154 }
155 }
156
157 if (pwzsRegKeyPath)
158 {
159 WCHAR *key;
160 WCHAR result[MAX_PATH];
161 WCHAR *value;
162 HKEY hKey = 0;
163 LONG res;
164 LONG len;
165
166 /* pwszRegKeyPath contains the key as well as the value, so we split */
167 key = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
168
169 if (key)
170 {
171 wcscpy(key, pwzsRegKeyPath);
172 value = const_cast<WCHAR *>(strrchrW(key, '\\'));
173
174 if (value)
175 {
176 *value = 0;
177 value++;
178 /* Now value contains the value and buffer the key */
179 res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey);
180
181 if (res != ERROR_SUCCESS)
182 {
183 /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */
184 res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey);
185 }
186
187 if (res == ERROR_SUCCESS)
188 {
189 res = RegQueryValueW(hKey, value, result, &len);
190 if (res == ERROR_SUCCESS)
191 {
192 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
193 wcscpy(quickComplete, result);
194 }
195 RegCloseKey(hKey);
196 }
197 }
198
199 HeapFree(GetProcessHeap(), 0, key);
200 }
201 else
202 {
203 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
204 return S_FALSE;
205 }
206 }
207
208 if ((pwszQuickComplete) && (!quickComplete))
209 {
210 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
211
212 if (quickComplete)
213 {
214 wcscpy(quickComplete, pwszQuickComplete);
215 }
216 else
217 {
218 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
219 return S_FALSE;
220 }
221 }
222
223 return S_OK;
224 }
225
226 /**************************************************************************
227 * IAutoComplete_fnGetOptions
228 */
229 HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag)
230 {
231 HRESULT hr = S_OK;
232
233 TRACE("(%p) -> (%p)\n", this, pdwFlag);
234
235 *pdwFlag = options;
236
237 return hr;
238 }
239
240 /**************************************************************************
241 * IAutoComplete_fnSetOptions
242 */
243 HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag)
244 {
245 HRESULT hr = S_OK;
246
247 TRACE("(%p) -> (0x%x)\n", this, dwFlag);
248
249 options = (AUTOCOMPLETEOPTIONS)dwFlag;
250
251 return hr;
252 }
253
254 /*
255 Window procedure for autocompletion
256 */
257 LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
258 {
259 CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW));//GetWindowLongPtrW(hwnd, GWLP_USERDATA);
260 LPOLESTR strs;
261 HRESULT hr;
262 WCHAR hwndText[255];
263 WCHAR *hwndQCText;
264 RECT r;
265 BOOL control, filled, displayall = FALSE;
266 int cpt, height, sel;
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, NULL);
435 if (hr != S_OK)
436 break;
437
438 if ((LPWSTR)strstrW(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 }