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 }
619