Start source tree (final, I hope!) restructuration. Part 1/X
[reactos.git] / reactos / base / shell / 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 res = RegQueryValueW(hKey, value, result, &len);
188 if (res == ERROR_SUCCESS)
189 {
190 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(WCHAR));
191 wcscpy(quickComplete, result);
192 }
193 RegCloseKey(hKey);
194 }
195 }
196
197 HeapFree(GetProcessHeap(), 0, key);
198 }
199 else
200 {
201 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwzsRegKeyPath) + 1) * sizeof(WCHAR));
202 return S_FALSE;
203 }
204 }
205
206 if ((pwszQuickComplete) && (!quickComplete))
207 {
208 quickComplete = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
209
210 if (quickComplete)
211 {
212 wcscpy(quickComplete, pwszQuickComplete);
213 }
214 else
215 {
216 TRACE("HeapAlloc Failed when trying to alloca %d bytes\n", (wcslen(pwszQuickComplete) + 1) * sizeof(WCHAR));
217 return S_FALSE;
218 }
219 }
220
221 return S_OK;
222 }
223
224 /**************************************************************************
225 * IAutoComplete_fnGetOptions
226 */
227 HRESULT WINAPI CAutoComplete::GetOptions(DWORD *pdwFlag)
228 {
229 HRESULT hr = S_OK;
230
231 TRACE("(%p) -> (%p)\n", this, pdwFlag);
232
233 *pdwFlag = options;
234
235 return hr;
236 }
237
238 /**************************************************************************
239 * IAutoComplete_fnSetOptions
240 */
241 HRESULT WINAPI CAutoComplete::SetOptions(DWORD dwFlag)
242 {
243 HRESULT hr = S_OK;
244
245 TRACE("(%p) -> (0x%x)\n", this, dwFlag);
246
247 options = (AUTOCOMPLETEOPTIONS)dwFlag;
248
249 return hr;
250 }
251
252 /*
253 Window procedure for autocompletion
254 */
255 LRESULT APIENTRY CAutoComplete::ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
256 {
257 CAutoComplete *pThis = static_cast<CAutoComplete *>(GetPropW(hwnd, autocomplete_propertyW));//GetWindowLongPtrW(hwnd, GWLP_USERDATA);
258 LPOLESTR strs;
259 HRESULT hr;
260 WCHAR hwndText[255];
261 WCHAR *hwndQCText;
262 RECT r;
263 BOOL control, filled, displayall = FALSE;
264 int cpt, height, sel;
265
266 if (!pThis->enabled)
267 {
268 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
269 }
270
271 switch (uMsg)
272 {
273 case CB_SHOWDROPDOWN:
274 {
275 ShowWindow(pThis->hwndListBox, SW_HIDE);
276 }; break;
277
278 case WM_KILLFOCUS:
279 {
280 if ((pThis->options & ACO_AUTOSUGGEST) && ((HWND)wParam != pThis->hwndListBox))
281 {
282 ShowWindow(pThis->hwndListBox, SW_HIDE);
283 }
284 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
285 }; break;
286
287 case WM_KEYUP:
288 {
289 GetWindowTextW(hwnd, (LPWSTR)hwndText, 255);
290
291 switch(wParam)
292 {
293 case VK_RETURN:
294 {
295 /* If quickComplete is set and control is pressed, replace the string */
296 control = GetKeyState(VK_CONTROL) & 0x8000;
297 if (control && pThis->quickComplete)
298 {
299 hwndQCText = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
300 (wcslen(pThis->quickComplete)+wcslen(hwndText))*sizeof(WCHAR));
301 sel = swprintf(hwndQCText, pThis->quickComplete, hwndText);
302 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText);
303 SendMessageW(hwnd, EM_SETSEL, 0, sel);
304 HeapFree(GetProcessHeap(), 0, hwndQCText);
305 }
306
307 ShowWindow(pThis->hwndListBox, SW_HIDE);
308 return 0;
309 }; break;
310
311 case VK_LEFT:
312 case VK_RIGHT:
313 {
314 return 0;
315 }; break;
316
317 case VK_UP:
318 case VK_DOWN:
319 {
320 /* Two cases here :
321 - if the listbox is not visible, displays it
322 with all the entries if the style ACO_UPDOWNKEYDROPSLIST
323 is present but does not select anything.
324 - if the listbox is visible, change the selection
325 */
326 if ( (pThis->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST))
327 && (!IsWindowVisible(pThis->hwndListBox) && (! *hwndText)) )
328 {
329 /* We must display all the entries */
330 displayall = TRUE;
331 }
332 else
333 {
334 if (IsWindowVisible(pThis->hwndListBox))
335 {
336 int count;
337
338 count = SendMessageW(pThis->hwndListBox, LB_GETCOUNT, 0, 0);
339 /* Change the selection */
340 sel = SendMessageW(pThis->hwndListBox, LB_GETCURSEL, 0, 0);
341 if (wParam == VK_UP)
342 sel = ((sel-1)<0)?count-1:sel-1;
343 else
344 sel = ((sel+1)>= count)?-1:sel+1;
345
346 SendMessageW(pThis->hwndListBox, LB_SETCURSEL, sel, 0);
347
348 if (sel != -1)
349 {
350 WCHAR *msg;
351 int len;
352
353 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL);
354 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
355
356 if (msg)
357 {
358 SendMessageW(pThis->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg);
359 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg);
360 SendMessageW(hwnd, EM_SETSEL, wcslen(msg), wcslen(msg));
361
362 HeapFree(GetProcessHeap(), 0, msg);
363 }
364 else
365 {
366 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
367 }
368 }
369 else
370 {
371 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)pThis->txtbackup);
372 SendMessageW(hwnd, EM_SETSEL, wcslen(pThis->txtbackup), wcslen(pThis->txtbackup));
373 }
374 }
375 return 0;
376 }
377 }; break;
378
379 case VK_BACK:
380 case VK_DELETE:
381 {
382 if ((! *hwndText) && (pThis->options & ACO_AUTOSUGGEST))
383 {
384 ShowWindow(pThis->hwndListBox, SW_HIDE);
385 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
386 }
387
388 if (pThis->options & ACO_AUTOAPPEND)
389 {
390 DWORD b;
391 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL);
392 if (b>1)
393 {
394 hwndText[b-1] = '\0';
395 }
396 else
397 {
398 hwndText[0] = '\0';
399 SetWindowTextW(hwnd, hwndText);
400 }
401 }
402 }; break;
403
404 default:
405 ;
406 }
407
408 SendMessageW(pThis->hwndListBox, LB_RESETCONTENT, 0, 0);
409
410 HeapFree(GetProcessHeap(), 0, pThis->txtbackup);
411
412 pThis->txtbackup = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (wcslen(hwndText)+1)*sizeof(WCHAR));
413
414 if (pThis->txtbackup)
415 {
416 wcscpy(pThis->txtbackup, hwndText);
417 }
418 else
419 {
420 TRACE("HeapAlloc failed to allocate %d bytes\n", (wcslen(hwndText)+1)*sizeof(WCHAR));
421 }
422
423 /* Returns if there is no text to search and we doesn't want to display all the entries */
424 if ((!displayall) && (! *hwndText) )
425 break;
426
427 pThis->enumstr->Reset();
428 filled = FALSE;
429
430 for(cpt = 0;;)
431 {
432 hr = pThis->enumstr->Next(1, &strs, NULL);
433 if (hr != S_OK)
434 break;
435
436 if ((LPWSTR)wcsstr(strs, hwndText) == strs)
437 {
438
439 if (pThis->options & ACO_AUTOAPPEND)
440 {
441 SetWindowTextW(hwnd, strs);
442 SendMessageW(hwnd, EM_SETSEL, wcslen(hwndText), wcslen(strs));
443 break;
444 }
445
446 if (pThis->options & ACO_AUTOSUGGEST)
447 {
448 SendMessageW(pThis->hwndListBox, LB_ADDSTRING, 0, (LPARAM)strs);
449 filled = TRUE;
450 cpt++;
451 }
452 }
453 }
454
455 if (pThis->options & ACO_AUTOSUGGEST)
456 {
457 if (filled)
458 {
459 height = SendMessageW(pThis->hwndListBox, LB_GETITEMHEIGHT, 0, 0);
460 SendMessageW(pThis->hwndListBox, LB_CARETOFF, 0, 0);
461 GetWindowRect(hwnd, &r);
462 SetParent(pThis->hwndListBox, HWND_DESKTOP);
463 /* It seems that Windows XP displays 7 lines at most
464 and otherwise displays a vertical scroll bar */
465 SetWindowPos(pThis->hwndListBox, HWND_TOP,
466 r.left, r.bottom + 1, r.right - r.left, min(height * 7, height * (cpt + 1)),
467 SWP_SHOWWINDOW );
468 }
469 else
470 {
471 ShowWindow(pThis->hwndListBox, SW_HIDE);
472 }
473 }
474
475 }; break;
476
477 default:
478 {
479 return CallWindowProcW(pThis->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
480 }
481
482 }
483
484 return 0;
485 }
486
487 LRESULT APIENTRY CAutoComplete::ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
488 {
489 CAutoComplete *pThis = reinterpret_cast<CAutoComplete *>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
490 WCHAR *msg;
491 int sel, len;
492
493 switch (uMsg)
494 {
495 case WM_MOUSEMOVE:
496 {
497 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
498 SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0);
499 }; break;
500
501 case WM_LBUTTONDOWN:
502 {
503 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
504
505 if (sel < 0)
506 break;
507
508 len = SendMessageW(pThis->hwndListBox, LB_GETTEXTLEN, sel, 0);
509 msg = (WCHAR *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len + 1) * sizeof(WCHAR));
510
511 if (msg)
512 {
513 SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
514 SendMessageW(pThis->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg);
515 SendMessageW(pThis->hwndEdit, EM_SETSEL, 0, wcslen(msg));
516 ShowWindow(hwnd, SW_HIDE);
517
518 HeapFree(GetProcessHeap(), 0, msg);
519 }
520 else
521 {
522 TRACE("HeapAlloc failed to allocate %d bytes\n", (len + 1) * sizeof(WCHAR));
523 }
524
525 }; break;
526
527 default:
528 return CallWindowProcW(pThis->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
529 }
530 return 0;
531 }