shell32: Move SHAppBarMessage implementation from shell32 to explorer.
[wine] / dlls / shell32 / autocomplete.c
1 /*
2  *      AutoComplete interfaces implementation.
3  *
4  *      Copyright 2004  Maxime Bellengé <maxime.bellenge@laposte.net>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22   Implemented:
23   - ACO_AUTOAPPEND style
24   - ACO_AUTOSUGGEST style
25   - ACO_UPDOWNKEYDROPSLIST style
26
27   - Handle pwzsRegKeyPath and pwszQuickComplete in Init
28
29   TODO:
30   - implement ACO_SEARCH style
31   - implement ACO_FILTERPREFIXES style
32   - implement ACO_USETAB style
33   - implement ACO_RTLREADING style
34   
35  */
36 #include "config.h"
37
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #define COBJMACROS
43
44 #include "wine/debug.h"
45 #include "windef.h"
46 #include "winbase.h"
47 #include "winreg.h"
48 #include "undocshell.h"
49 #include "shlwapi.h"
50 #include "winerror.h"
51 #include "objbase.h"
52
53 #include "pidl.h"
54 #include "shlobj.h"
55 #include "shldisp.h"
56 #include "debughlp.h"
57
58 #include "wine/unicode.h"
59
60 WINE_DEFAULT_DEBUG_CHANNEL(shell);
61
62 typedef struct
63 {
64     const IAutoComplete2Vtbl  *lpVtbl;
65     LONG ref;
66     BOOL  enabled;
67     HWND hwndEdit;
68     HWND hwndListBox;
69     WNDPROC wpOrigEditProc;
70     WNDPROC wpOrigLBoxProc;
71     WCHAR *txtbackup;
72     WCHAR *quickComplete;
73     IEnumString *enumstr;
74     AUTOCOMPLETEOPTIONS options;
75 } IAutoCompleteImpl;
76
77 static const IAutoComplete2Vtbl acvt;
78
79
80 /*
81   converts This to an interface pointer
82 */
83 #define _IUnknown_(This) (IUnknown*)&(This->lpVtbl)
84 #define _IAutoComplete2_(This)  (IAutoComplete2*)&(This->lpvtbl)
85
86 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
87 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
88
89 /**************************************************************************
90  *  IAutoComplete_Constructor
91  */
92 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
93 {
94     IAutoCompleteImpl *lpac;
95
96     if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
97         return CLASS_E_NOAGGREGATION;
98
99     lpac = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IAutoCompleteImpl));
100     if (!lpac) 
101         return E_OUTOFMEMORY;
102
103     lpac->ref = 1;
104     lpac->lpVtbl = &acvt;
105     lpac->enabled = TRUE;
106     lpac->enumstr = NULL;
107     lpac->options = ACO_AUTOAPPEND;
108     lpac->wpOrigEditProc = NULL;
109     lpac->hwndListBox = NULL;
110     lpac->txtbackup = NULL;
111     lpac->quickComplete = NULL;
112     
113     if (!SUCCEEDED (IUnknown_QueryInterface (_IUnknown_ (lpac), riid, ppv))) {
114         IUnknown_Release (_IUnknown_ (lpac));
115         return E_NOINTERFACE;
116     }
117     
118     TRACE("-- (%p)->\n",lpac);
119
120     return S_OK;
121 }
122
123 /**************************************************************************
124  *  AutoComplete_QueryInterface
125  */
126 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
127     IAutoComplete2 * iface,
128     REFIID riid,
129     LPVOID *ppvObj)
130 {
131     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
132     
133     TRACE("(%p)->(\n\tIID:\t%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
134     *ppvObj = NULL;
135
136     if (IsEqualIID(riid, &IID_IUnknown) ||
137         IsEqualIID(riid, &IID_IAutoComplete) ||
138         IsEqualIID(riid, &IID_IAutoComplete2))
139     {
140         *ppvObj = (IAutoComplete2*)This;
141     }
142
143     if (*ppvObj)
144     {
145         IUnknown_AddRef((IUnknown*)*ppvObj);
146         TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
147         return S_OK;
148     }
149     TRACE("-- Interface: E_NOINTERFACE\n");
150     return E_NOINTERFACE;       
151 }
152
153 /******************************************************************************
154  * IAutoComplete2_fnAddRef
155  */
156 static ULONG WINAPI IAutoComplete2_fnAddRef(
157         IAutoComplete2 * iface)
158 {
159     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
160     ULONG refCount = InterlockedIncrement(&This->ref);
161     
162     TRACE("(%p)->(%u)\n", This, refCount - 1);
163
164     return refCount;
165 }
166
167 /******************************************************************************
168  * IAutoComplete2_fnRelease
169  */
170 static ULONG WINAPI IAutoComplete2_fnRelease(
171         IAutoComplete2 * iface)
172 {
173     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
174     ULONG refCount = InterlockedDecrement(&This->ref);
175     
176     TRACE("(%p)->(%u)\n", This, refCount + 1);
177
178     if (!refCount) {
179         TRACE(" destroying IAutoComplete(%p)\n",This);
180         HeapFree(GetProcessHeap(), 0, This->quickComplete);
181         HeapFree(GetProcessHeap(), 0, This->txtbackup);
182         if (This->hwndListBox)
183             DestroyWindow(This->hwndListBox);
184         if (This->enumstr)
185             IEnumString_Release(This->enumstr);
186         HeapFree(GetProcessHeap(), 0, This);
187     }
188     return refCount;
189 }
190
191 /******************************************************************************
192  * IAutoComplete2_fnEnable
193  */
194 static HRESULT WINAPI IAutoComplete2_fnEnable(
195     IAutoComplete2 * iface,
196     BOOL fEnable)
197 {
198     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
199
200     HRESULT hr = S_OK;
201
202     TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
203
204     This->enabled = fEnable;
205
206     return hr;
207 }
208
209 /******************************************************************************
210  * IAutoComplete2_fnInit
211  */
212 static HRESULT WINAPI IAutoComplete2_fnInit(
213     IAutoComplete2 * iface,
214     HWND hwndEdit,
215     IUnknown *punkACL,
216     LPCOLESTR pwzsRegKeyPath,
217     LPCOLESTR pwszQuickComplete)
218 {
219     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
220     static const WCHAR lbName[] = {'L','i','s','t','B','o','x',0};
221
222     TRACE("(%p)->(0x%08lx, %p, %s, %s)\n", 
223           This, (long)hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
224
225     if (This->options & ACO_AUTOSUGGEST) TRACE(" ACO_AUTOSUGGEST\n");
226     if (This->options & ACO_AUTOAPPEND) TRACE(" ACO_AUTOAPPEND\n");
227     if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
228     if (This->options & ACO_FILTERPREFIXES) FIXME(" ACO_FILTERPREFIXES not supported\n");
229     if (This->options & ACO_USETAB) FIXME(" ACO_USETAB not supported\n");
230     if (This->options & ACO_UPDOWNKEYDROPSLIST) TRACE(" ACO_UPDOWNKEYDROPSLIST\n");
231     if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
232
233     This->hwndEdit = hwndEdit;
234
235     if (!SUCCEEDED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
236         TRACE("No IEnumString interface\n");
237         return  E_NOINTERFACE;
238     }
239
240     This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW( hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
241     SetWindowLongPtrW( hwndEdit, GWLP_USERDATA, (LONG_PTR)This);
242
243     if (This->options & ACO_AUTOSUGGEST) {
244         HWND hwndParent;
245
246         hwndParent = GetParent(This->hwndEdit);
247         
248         /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
249         This->hwndListBox = CreateWindowExW(0, lbName, NULL, 
250                                             WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT, 
251                                             CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
252                                             hwndParent, NULL, 
253                                             (HINSTANCE)GetWindowLongPtrW( hwndParent, GWLP_HINSTANCE ), NULL);
254                                             
255         if (This->hwndListBox) {
256             This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
257             SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
258         }
259     }
260
261     if (pwzsRegKeyPath) {
262         WCHAR *key;
263         WCHAR result[MAX_PATH];
264         WCHAR *value;
265         HKEY hKey = 0;
266         LONG res;
267         LONG len;
268
269         /* pwszRegKeyPath contains the key as well as the value, so we split */
270         key = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (lstrlenW(pwzsRegKeyPath)+1)*sizeof(WCHAR));
271         strcpyW(key, pwzsRegKeyPath);
272         value = strrchrW(key, '\\');
273         *value = 0;
274         value++;
275         /* Now value contains the value and buffer the key */
276         res = RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_READ, &hKey);
277         if (res != ERROR_SUCCESS) {
278             /* if the key is not found, MSDN states we must seek in HKEY_LOCAL_MACHINE */
279             res = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey);  
280         }
281         if (res == ERROR_SUCCESS) {
282             res = RegQueryValueW(hKey, value, result, &len);
283             if (res == ERROR_SUCCESS) {
284                 This->quickComplete = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len*sizeof(WCHAR));
285                 strcpyW(This->quickComplete, result);
286             }
287             RegCloseKey(hKey);
288         }
289         HeapFree(GetProcessHeap(), 0, key);
290     }
291
292     if ((pwszQuickComplete) && (!This->quickComplete)) {
293         This->quickComplete = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (lstrlenW(pwszQuickComplete)+1)*sizeof(WCHAR));
294         lstrcpyW(This->quickComplete, pwszQuickComplete);
295     }
296
297     return S_OK;
298 }
299
300 /**************************************************************************
301  *  IAutoComplete2_fnGetOptions
302  */
303 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
304     IAutoComplete2 * iface,
305     DWORD *pdwFlag)
306 {
307     HRESULT hr = S_OK;
308
309     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
310
311     TRACE("(%p) -> (%p)\n", This, pdwFlag);
312
313     *pdwFlag = This->options;
314
315     return hr;
316 }
317
318 /**************************************************************************
319  *  IAutoComplete2_fnSetOptions
320  */
321 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
322     IAutoComplete2 * iface,
323     DWORD dwFlag)
324 {
325     HRESULT hr = S_OK;
326
327     IAutoCompleteImpl *This = (IAutoCompleteImpl *)iface;
328
329     TRACE("(%p) -> (0x%x)\n", This, dwFlag);
330
331     This->options = dwFlag;
332
333     return hr;
334 }
335
336 /**************************************************************************
337  *  IAutoComplete2_fnVTable
338  */
339 static const IAutoComplete2Vtbl acvt =
340 {
341     IAutoComplete2_fnQueryInterface,
342     IAutoComplete2_fnAddRef,
343     IAutoComplete2_fnRelease,
344     IAutoComplete2_fnInit,
345     IAutoComplete2_fnEnable,
346     /* IAutoComplete2 */
347     IAutoComplete2_fnSetOptions,
348     IAutoComplete2_fnGetOptions,
349 };
350
351 /*
352   Window procedure for autocompletion
353  */
354 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
355 {
356     IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
357     LPOLESTR strs;
358     HRESULT hr;
359     WCHAR hwndText[255];
360     WCHAR *hwndQCText;
361     RECT r;
362     BOOL control, filled, displayall = FALSE;
363     int cpt, height, sel;
364
365     if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
366
367     switch (uMsg)
368     {
369         case CB_SHOWDROPDOWN:
370             ShowWindow(This->hwndListBox, SW_HIDE);
371             break;
372         case WM_KILLFOCUS:
373             if ((This->options && ACO_AUTOSUGGEST) && 
374                 ((HWND)wParam != This->hwndListBox))
375             {
376                 ShowWindow(This->hwndListBox, SW_HIDE);
377             }
378             break;
379         case WM_KEYUP:
380             
381             GetWindowTextW( hwnd, (LPWSTR)hwndText, 255);
382       
383             switch(wParam) {
384                 case VK_RETURN:
385                     /* If quickComplete is set and control is pressed, replace the string */
386                     control = GetKeyState(VK_CONTROL) & 0x8000;             
387                     if (control && This->quickComplete) {
388                         hwndQCText = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 
389                                                        (lstrlenW(This->quickComplete)+lstrlenW(hwndText))*sizeof(WCHAR));
390                         sel = sprintfW(hwndQCText, This->quickComplete, hwndText);
391                         SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)hwndQCText);
392                         SendMessageW(hwnd, EM_SETSEL, 0, sel);
393                         HeapFree(GetProcessHeap(), 0, hwndQCText);
394                     }
395
396                     ShowWindow(This->hwndListBox, SW_HIDE);
397                     return 0;
398                 case VK_LEFT:
399                 case VK_RIGHT:
400                     return 0;
401                 case VK_UP:           
402                 case VK_DOWN:
403                     /* Two cases here : 
404                        - if the listbox is not visible, displays it 
405                        with all the entries if the style ACO_UPDOWNKEYDROPSLIST
406                        is present but does not select anything.
407                        - if the listbox is visible, change the selection
408                     */
409                     if ( (This->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST)) 
410                          && (!IsWindowVisible(This->hwndListBox) && (! *hwndText)) )
411                     {
412                          /* We must display all the entries */
413                          displayall = TRUE;
414                     } else {
415                         if (IsWindowVisible(This->hwndListBox)) {
416                             int count;
417
418                             count = SendMessageW(This->hwndListBox, LB_GETCOUNT, 0, 0);
419                             /* Change the selection */
420                             sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
421                             if (wParam == VK_UP)
422                                 sel = ((sel-1)<0)?count-1:sel-1;
423                             else
424                                 sel = ((sel+1)>= count)?-1:sel+1;
425                             SendMessageW(This->hwndListBox, LB_SETCURSEL, sel, 0);
426                             if (sel != -1) {
427                                 WCHAR *msg;
428                                 int len;
429                                 
430                                 len = SendMessageW(This->hwndListBox, LB_GETTEXTLEN, sel, (LPARAM)NULL);
431                                 msg = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len+1)*sizeof(WCHAR));
432                                 SendMessageW(This->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg);
433                                 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg);
434                                 SendMessageW(hwnd, EM_SETSEL, lstrlenW(msg), lstrlenW(msg));
435                                 HeapFree(GetProcessHeap(), 0, msg);
436                             } else {
437                                 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)This->txtbackup);
438                                 SendMessageW(hwnd, EM_SETSEL, lstrlenW(This->txtbackup), lstrlenW(This->txtbackup));
439                             }                   
440                         }               
441                         return 0;
442                     }
443                     break;
444                 case VK_BACK:
445                 case VK_DELETE:
446                     if ((! *hwndText) && (This->options & ACO_AUTOSUGGEST)) {
447                         ShowWindow(This->hwndListBox, SW_HIDE);
448                         return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
449                     }
450                     if (This->options & ACO_AUTOAPPEND) {
451                         DWORD b;
452                         SendMessageW(hwnd, EM_GETSEL, (WPARAM)&b, (LPARAM)NULL);
453                         if (b>1) {
454                             hwndText[b-1] = '\0';
455                         } else {
456                             hwndText[0] = '\0';
457                             SetWindowTextW(hwnd, hwndText); 
458                         }                       
459                     }
460                     break;
461                 default:                    
462                     ;
463             }
464       
465             SendMessageW(This->hwndListBox, LB_RESETCONTENT, 0, 0);
466
467             HeapFree(GetProcessHeap(), 0, This->txtbackup);
468             This->txtbackup = HeapAlloc(GetProcessHeap(),
469                                                  HEAP_ZERO_MEMORY, (lstrlenW(hwndText)+1)*sizeof(WCHAR));                                                             
470             lstrcpyW(This->txtbackup, hwndText);
471
472             /* Returns if there is no text to search and we doesn't want to display all the entries */
473             if ((!displayall) && (! *hwndText) )
474                 break;
475             
476             IEnumString_Reset(This->enumstr);
477             filled = FALSE;
478             for(cpt = 0;;) {
479                 ULONG fetched;
480                 hr = IEnumString_Next(This->enumstr, 1, &strs, &fetched);
481                 if (hr != S_OK)
482                     break;
483
484                 if (strstrW(strs, hwndText) == strs) {
485                     if (This->options & ACO_AUTOAPPEND) {
486                         SetWindowTextW(hwnd, strs);
487                         SendMessageW(hwnd, EM_SETSEL, lstrlenW(hwndText), lstrlenW(strs));
488                         break;
489                     }           
490
491                     if (This->options & ACO_AUTOSUGGEST) {
492                         SendMessageW(This->hwndListBox, LB_ADDSTRING, 0, (LPARAM)strs);
493                         filled = TRUE;
494                         cpt++;
495                     }
496                 }               
497             }
498             
499             if (This->options & ACO_AUTOSUGGEST) {
500                 if (filled) {
501                     height = SendMessageW(This->hwndListBox, LB_GETITEMHEIGHT, 0, 0);
502                     SendMessageW(This->hwndListBox, LB_CARETOFF, 0, 0);
503                     GetWindowRect(hwnd, &r);
504                     SetParent(This->hwndListBox, HWND_DESKTOP);
505                     /* It seems that Windows XP displays 7 lines at most 
506                        and otherwise displays a vertical scroll bar */
507                     SetWindowPos(This->hwndListBox, HWND_TOP, 
508                                  r.left, r.bottom + 1, r.right - r.left, min(height * 7, height*(cpt+1)), 
509                                  SWP_SHOWWINDOW );
510                 } else {
511                     ShowWindow(This->hwndListBox, SW_HIDE);
512                 }
513             }
514             
515             break; 
516         default:
517             return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
518             
519     }
520
521     return 0;
522 }
523
524 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
525 {
526     IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
527     WCHAR *msg;
528     int sel, len;
529
530     switch (uMsg) {
531         case WM_MOUSEMOVE:
532             sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
533             SendMessageW(hwnd, LB_SETCURSEL, (WPARAM)sel, (LPARAM)0);
534             break;
535         case WM_LBUTTONDOWN:
536             sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
537             if (sel < 0)
538                 break;
539             len = SendMessageW(This->hwndListBox, LB_GETTEXTLEN, sel, 0);
540             msg = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (len+1)*sizeof(WCHAR));
541             SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
542             SendMessageW(This->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg);
543             SendMessageW(This->hwndEdit, EM_SETSEL, 0, lstrlenW(msg));
544             ShowWindow(hwnd, SW_HIDE);
545             HeapFree(GetProcessHeap(), 0, msg);
546             break;
547         default:
548             return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
549     }
550     return 0;
551 }