shell32/autocomplete: Reset the enumerator when the text is empty even when auto...
[wine.git] / dlls / shell32 / autocomplete.c
bloba6404ca5358c42dfc89ba65b0ba0a06d07d3848f
1 /*
2 * AutoComplete interfaces implementation.
4 * Copyright 2004 Maxime Bellengé <maxime.bellenge@laposte.net>
5 * Copyright 2018 Gabriel Ivăncescu <gabrielopcode@gmail.com>
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.
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.
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
23 TODO:
24 - implement ACO_SEARCH style
25 - implement ACO_FILTERPREFIXES style
26 - implement ACO_RTLREADING style
27 - implement ResetEnumerator
30 #include "config.h"
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
36 #define COBJMACROS
38 #include "wine/debug.h"
39 #include "windef.h"
40 #include "winbase.h"
41 #include "winreg.h"
42 #include "undocshell.h"
43 #include "shlwapi.h"
44 #include "winerror.h"
45 #include "objbase.h"
47 #include "pidl.h"
48 #include "shlobj.h"
49 #include "shldisp.h"
50 #include "debughlp.h"
51 #include "shell32_main.h"
53 #include "wine/unicode.h"
55 WINE_DEFAULT_DEBUG_CHANNEL(shell);
57 typedef struct
59 IAutoComplete2 IAutoComplete2_iface;
60 IAutoCompleteDropDown IAutoCompleteDropDown_iface;
61 LONG ref;
62 BOOL initialized;
63 BOOL enabled;
64 UINT enum_strs_num;
65 WCHAR **enum_strs;
66 HWND hwndEdit;
67 HWND hwndListBox;
68 WNDPROC wpOrigEditProc;
69 WNDPROC wpOrigLBoxProc;
70 WCHAR *txtbackup;
71 WCHAR *quickComplete;
72 IEnumString *enumstr;
73 IACList *aclist;
74 AUTOCOMPLETEOPTIONS options;
75 WCHAR no_fwd_char;
76 } IAutoCompleteImpl;
78 enum autoappend_flag
80 autoappend_flag_yes,
81 autoappend_flag_no,
82 autoappend_flag_displayempty
85 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o',
86 'c','o','m','p','l','e','t','e',' ',
87 'c','o','n','t','r','o','l',0};
89 static inline IAutoCompleteImpl *impl_from_IAutoComplete2(IAutoComplete2 *iface)
91 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoComplete2_iface);
94 static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown *iface)
96 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface);
99 static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end)
101 /* Send it directly to the edit control to match Windows behavior */
102 WNDPROC proc = ac->wpOrigEditProc;
103 if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text))
104 CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
107 static int enumerate_strings_cmpfn(const void *a, const void *b)
109 return strcmpiW(*(WCHAR* const*)a, *(WCHAR* const*)b);
113 Enumerate all of the strings and sort them in the internal list.
115 We don't free the enumerated strings (except on error) to avoid needless
116 copies, until the next reset (or the object itself is destroyed)
118 static void enumerate_strings(IAutoCompleteImpl *ac)
120 UINT cur = 0, array_size = 1024;
121 LPOLESTR *strs = NULL, *tmp;
122 ULONG read;
126 if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
127 goto fail;
128 strs = tmp;
132 if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
133 read = 0;
134 } while (read != 0 && (cur += read) < array_size);
136 array_size *= 2;
137 } while (read != 0);
139 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
140 if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
142 strs = tmp;
143 if (cur > 0)
144 qsort(strs, cur, sizeof(*strs), enumerate_strings_cmpfn);
146 ac->enum_strs = strs;
147 ac->enum_strs_num = cur;
148 return;
151 fail:
152 while (cur--)
153 CoTaskMemFree(strs[cur]);
154 heap_free(strs);
157 static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
158 UINT len, int direction)
160 WCHAR **strs = ac->enum_strs;
161 UINT index = ~0, a = start, b = ac->enum_strs_num;
162 while (a < b)
164 UINT i = (a + b - 1) / 2;
165 int cmp = strncmpiW(text, strs[i], len);
166 if (cmp == 0)
168 index = i;
169 cmp = direction;
171 if (cmp <= 0) b = i;
172 else a = i + 1;
174 return index;
177 static void free_enum_strs(IAutoCompleteImpl *ac)
179 WCHAR **strs = ac->enum_strs;
180 if (strs)
182 UINT i = ac->enum_strs_num;
183 ac->enum_strs = NULL;
184 while (i--)
185 CoTaskMemFree(strs[i]);
186 heap_free(strs);
190 static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
192 ShowWindow(hwnd, SW_HIDE);
193 SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
194 if (reset) free_enum_strs(ac);
197 static void show_listbox(IAutoCompleteImpl *ac, UINT cnt)
199 RECT r;
200 UINT width, height;
202 GetWindowRect(ac->hwndEdit, &r);
203 SendMessageW(ac->hwndListBox, LB_CARETOFF, 0, 0);
205 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
206 height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7);
207 width = r.right - r.left;
209 SetWindowPos(ac->hwndListBox, HWND_TOP, r.left, r.bottom + 1, width, height, SWP_SHOWWINDOW);
212 static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len)
214 /* Replace the first %s directly without using snprintf, to avoid
215 exploits since the format string can be retrieved from the registry */
216 WCHAR *base = dst;
217 UINT args = 0;
218 while (*qc != '\0')
220 if (qc[0] == '%')
222 if (args < 1 && qc[1] == 's')
224 memcpy(dst, str, str_len * sizeof(WCHAR));
225 dst += str_len;
226 qc += 2;
227 args++;
228 continue;
230 qc += (qc[1] == '%');
232 *dst++ = *qc++;
234 *dst = '\0';
235 return dst - base;
238 static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
240 WCHAR *text;
241 HWND hwndListBox = ac->hwndListBox;
242 if (!(ac->options & ACO_AUTOSUGGEST))
243 return FALSE;
245 if (IsWindowVisible(hwndListBox))
247 INT sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0);
248 if (sel >= 0)
250 UINT len = SendMessageW(hwndListBox, LB_GETTEXTLEN, sel, 0);
251 if ((text = heap_alloc((len + 1) * sizeof(WCHAR))))
253 len = SendMessageW(hwndListBox, LB_GETTEXT, sel, (LPARAM)text);
254 set_text_and_selection(ac, hwnd, text, 0, len);
255 hide_listbox(ac, hwndListBox, TRUE);
256 ac->no_fwd_char = '\r'; /* RETURN char */
257 heap_free(text);
258 return TRUE;
262 hide_listbox(ac, hwndListBox, TRUE);
263 return FALSE;
266 static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
268 INT count = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
269 INT sel = SendMessageW(ac->hwndListBox, LB_GETCURSEL, 0, 0);
270 if (key == VK_PRIOR || key == VK_NEXT)
272 if (sel < 0)
273 sel = (key == VK_PRIOR) ? count - 1 : 0;
274 else
276 INT base = SendMessageW(ac->hwndListBox, LB_GETTOPINDEX, 0, 0);
277 INT pgsz = SendMessageW(ac->hwndListBox, LB_GETLISTBOXINFO, 0, 0);
278 pgsz = max(pgsz - 1, 1);
279 if (key == VK_PRIOR)
281 if (sel == 0)
282 sel = -1;
283 else
285 if (sel == base) base -= min(base, pgsz);
286 sel = base;
289 else
291 if (sel == count - 1)
292 sel = -1;
293 else
295 base += pgsz;
296 if (sel >= base) base += pgsz;
297 sel = min(base, count - 1);
302 else if (key == VK_UP || (key == VK_TAB && (GetKeyState(VK_SHIFT) & 0x8000)))
303 sel = ((sel - 1) < -1) ? count - 1 : sel - 1;
304 else
305 sel = ((sel + 1) >= count) ? -1 : sel + 1;
307 SendMessageW(ac->hwndListBox, LB_SETCURSEL, sel, 0);
308 if (sel >= 0)
310 WCHAR *msg;
311 UINT len = SendMessageW(ac->hwndListBox, LB_GETTEXTLEN, sel, 0);
312 if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR))))
313 return 0;
314 len = SendMessageW(ac->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg);
315 set_text_and_selection(ac, hwnd, msg, len, len);
316 heap_free(msg);
318 else
320 UINT len = strlenW(ac->txtbackup);
321 set_text_and_selection(ac, hwnd, ac->txtbackup, len, len);
323 return 0;
326 static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
328 WCHAR c = last_delim[1];
330 free_enum_strs(ac);
331 IEnumString_Reset(ac->enumstr); /* call before expand */
333 last_delim[1] = '\0';
334 IACList_Expand(ac->aclist, txt);
335 last_delim[1] = c;
336 return TRUE;
339 static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
341 /* call IACList::Expand only when needed, if the
342 new txt and old_txt require different expansions */
343 WCHAR c, *p, *last_delim, *old_txt = ac->txtbackup;
344 size_t i = 0;
346 /* '/' is allowed as a delim for unix paths */
347 static const WCHAR delims[] = { '\\', '/', 0 };
349 /* skip the shared prefix */
350 while ((c = tolowerW(txt[i])) == tolowerW(old_txt[i]))
352 if (c == '\0') return FALSE;
353 i++;
356 /* they differ at this point, check for a delim further in txt */
357 for (last_delim = NULL, p = &txt[i]; (p = strpbrkW(p, delims)) != NULL; p++)
358 last_delim = p;
359 if (last_delim) return do_aclist_expand(ac, txt, last_delim);
361 /* txt has no delim after i, check for a delim further in old_txt */
362 if (strpbrkW(&old_txt[i], delims))
364 /* scan backwards to find the first delim before txt[i] (if any) */
365 while (i--)
366 if (strchrW(delims, txt[i]))
367 return do_aclist_expand(ac, txt, &txt[i]);
369 /* Windows doesn't expand without a delim, but it does reset */
370 free_enum_strs(ac);
373 return FALSE;
376 static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR *str, HWND hwnd)
378 DWORD sel_start;
379 WCHAR *tmp;
380 size_t size;
382 /* Don't auto-append unless the caret is at the end */
383 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&sel_start, 0);
384 if (sel_start != len)
385 return;
387 /* The character capitalization can be different,
388 so merge text and str into a new string */
389 size = len + strlenW(&str[len]) + 1;
391 if ((tmp = heap_alloc(size * sizeof(*tmp))))
393 memcpy(tmp, text, len * sizeof(*tmp));
394 memcpy(&tmp[len], &str[len], (size - len) * sizeof(*tmp));
396 else tmp = str;
398 set_text_and_selection(ac, hwnd, tmp, len, size - 1);
399 if (tmp != str)
400 heap_free(tmp);
403 static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
404 HWND hwnd, enum autoappend_flag flag)
406 /* Return FALSE if we need to hide the listbox */
407 WCHAR **str = ac->enum_strs;
408 UINT cnt, start, end;
409 if (!str) return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
411 if (len)
413 start = find_matching_enum_str(ac, 0, text, len, -1);
414 if (start == ~0)
415 return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
417 if (flag == autoappend_flag_yes)
418 autoappend_str(ac, text, len, str[start], hwnd);
419 if (!(ac->options & ACO_AUTOSUGGEST))
420 return TRUE;
422 /* Find the index beyond the last string that matches */
423 end = find_matching_enum_str(ac, start + 1, text, len, 1);
424 end = (end == ~0 ? start : end) + 1;
426 else
428 if (!(ac->options & ACO_AUTOSUGGEST))
429 return TRUE;
430 start = 0;
431 end = ac->enum_strs_num;
432 if (end == 0)
433 return FALSE;
435 cnt = end - start;
437 SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
438 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
439 SendMessageW(ac->hwndListBox, LB_INITSTORAGE, cnt, 0);
440 for (; start < end; start++)
441 SendMessageW(ac->hwndListBox, LB_INSERTSTRING, -1, (LPARAM)str[start]);
443 show_listbox(ac, cnt);
444 SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
445 return TRUE;
448 static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
450 WCHAR *text;
451 BOOL expanded = FALSE;
452 UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
454 if (flag != autoappend_flag_displayempty && len == 0)
456 if (ac->options & ACO_AUTOSUGGEST)
457 hide_listbox(ac, ac->hwndListBox, FALSE);
458 free_enum_strs(ac);
459 return;
462 size = len + 1;
463 if (!(text = heap_alloc(size * sizeof(WCHAR))))
464 return;
465 len = SendMessageW(hwnd, WM_GETTEXT, size, (LPARAM)text);
466 if (len + 1 != size)
467 text = heap_realloc(text, (len + 1) * sizeof(WCHAR));
469 if (ac->aclist)
471 if (text[len - 1] == '\\' || text[len - 1] == '/')
472 flag = autoappend_flag_no;
473 expanded = aclist_expand(ac, text);
475 if (expanded || !ac->enum_strs)
477 if (!expanded) IEnumString_Reset(ac->enumstr);
478 enumerate_strings(ac);
481 /* Set txtbackup to point to text itself (which must not be released),
482 and it must be done here since aclist_expand uses it to track changes */
483 heap_free(ac->txtbackup);
484 ac->txtbackup = text;
486 if (!display_matching_strs(ac, text, len, hwnd, flag))
487 hide_listbox(ac, ac->hwndListBox, FALSE);
490 static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
492 ac->hwndEdit = NULL;
493 free_enum_strs(ac);
494 if (ac->hwndListBox)
495 DestroyWindow(ac->hwndListBox);
496 IAutoComplete2_Release(&ac->IAutoComplete2_iface);
500 Helper for ACEditSubclassProc
502 static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT uMsg,
503 WPARAM wParam, LPARAM lParam)
505 switch (wParam)
507 case VK_ESCAPE:
508 /* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
509 if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBox))
511 hide_listbox(ac, ac->hwndListBox, FALSE);
512 ac->no_fwd_char = 0x1B; /* ESC char */
513 return 0;
515 break;
516 case VK_RETURN:
517 /* If quickComplete is set and control is pressed, replace the string */
518 if (ac->quickComplete && (GetKeyState(VK_CONTROL) & 0x8000))
520 WCHAR *text, *buf;
521 size_t sz;
522 UINT len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
523 ac->no_fwd_char = '\n'; /* CTRL+RETURN char */
525 if (!(text = heap_alloc((len + 1) * sizeof(WCHAR))))
526 return 0;
527 len = SendMessageW(hwnd, WM_GETTEXT, len + 1, (LPARAM)text);
528 sz = strlenW(ac->quickComplete) + 1 + len;
530 if ((buf = heap_alloc(sz * sizeof(WCHAR))))
532 len = format_quick_complete(buf, ac->quickComplete, text, len);
533 set_text_and_selection(ac, hwnd, buf, 0, len);
534 heap_free(buf);
537 if (ac->options & ACO_AUTOSUGGEST)
538 hide_listbox(ac, ac->hwndListBox, TRUE);
539 heap_free(text);
540 return 0;
543 if (select_item_with_return_key(ac, hwnd))
544 return 0;
545 break;
546 case VK_TAB:
547 if ((ac->options & (ACO_AUTOSUGGEST | ACO_USETAB)) == (ACO_AUTOSUGGEST | ACO_USETAB)
548 && IsWindowVisible(ac->hwndListBox) && !(GetKeyState(VK_CONTROL) & 0x8000))
550 ac->no_fwd_char = '\t';
551 return change_selection(ac, hwnd, wParam);
553 break;
554 case VK_UP:
555 case VK_DOWN:
556 case VK_PRIOR:
557 case VK_NEXT:
558 /* Two cases here:
559 - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
560 set, display it with all the entries, without selecting any
561 - if the listbox is visible, change the selection
563 if (!(ac->options & ACO_AUTOSUGGEST))
564 break;
566 if (!IsWindowVisible(ac->hwndListBox))
568 if (ac->options & ACO_UPDOWNKEYDROPSLIST)
570 autocomplete_text(ac, hwnd, autoappend_flag_displayempty);
571 return 0;
574 else
575 return change_selection(ac, hwnd, wParam);
576 break;
577 case VK_DELETE:
579 LRESULT ret = CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
580 autocomplete_text(ac, hwnd, autoappend_flag_no);
581 return ret;
584 ac->no_fwd_char = '\0';
585 return CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
589 Window procedure for autocompletion
591 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
593 IAutoCompleteImpl *This = GetPropW(hwnd, autocomplete_propertyW);
594 LRESULT ret;
596 if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
598 switch (uMsg)
600 case CB_SHOWDROPDOWN:
601 if (This->options & ACO_AUTOSUGGEST)
602 hide_listbox(This, This->hwndListBox, TRUE);
603 return 0;
604 case WM_KILLFOCUS:
605 if (This->options & ACO_AUTOSUGGEST)
607 if ((HWND)wParam == This->hwndListBox) break;
608 hide_listbox(This, This->hwndListBox, FALSE);
611 /* Reset the enumerator if it's not visible anymore */
612 if (!IsWindowVisible(hwnd)) free_enum_strs(This);
613 break;
614 case WM_KEYDOWN:
615 return ACEditSubclassProc_KeyDown(This, hwnd, uMsg, wParam, lParam);
616 case WM_CHAR:
617 case WM_UNICHAR:
618 if (wParam == This->no_fwd_char) return 0;
619 This->no_fwd_char = '\0';
621 /* Don't autocomplete at all on most control characters */
622 if (iscntrlW(wParam) && !(wParam >= '\b' && wParam <= '\r'))
623 break;
625 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
626 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' '
627 ? autoappend_flag_yes : autoappend_flag_no);
628 return ret;
629 case WM_SETTEXT:
630 case WM_CUT:
631 case WM_CLEAR:
632 case WM_UNDO:
633 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
634 autocomplete_text(This, hwnd, autoappend_flag_no);
635 return ret;
636 case WM_PASTE:
637 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
638 autocomplete_text(This, hwnd, autoappend_flag_yes);
639 return ret;
640 case WM_SETFONT:
641 if (This->hwndListBox)
642 SendMessageW(This->hwndListBox, WM_SETFONT, wParam, lParam);
643 break;
644 case WM_DESTROY:
646 WNDPROC proc = This->wpOrigEditProc;
648 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)proc);
649 RemovePropW(hwnd, autocomplete_propertyW);
650 destroy_autocomplete_object(This);
651 return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
654 return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
657 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
659 IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
660 WCHAR *msg;
661 int sel, len;
663 switch (uMsg) {
664 case WM_MOUSEMOVE:
665 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
666 SendMessageW(hwnd, LB_SETCURSEL, sel, 0);
667 break;
668 case WM_LBUTTONDOWN:
669 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
670 if (sel < 0)
671 break;
672 len = SendMessageW(hwnd, LB_GETTEXTLEN, sel, 0);
673 if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR))))
674 break;
675 len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
676 set_text_and_selection(This, This->hwndEdit, msg, 0, len);
677 hide_listbox(This, hwnd, TRUE);
678 heap_free(msg);
679 break;
680 default:
681 return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
683 return 0;
686 static void create_listbox(IAutoCompleteImpl *This)
688 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
689 This->hwndListBox = CreateWindowExW(0, WC_LISTBOXW, NULL,
690 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
691 0, 0, 0, 0, GetParent(This->hwndEdit), NULL, shell32_hInstance, NULL);
693 if (This->hwndListBox) {
694 HFONT edit_font;
696 This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
697 SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
698 SetParent(This->hwndListBox, HWND_DESKTOP);
700 /* Use the same font as the edit control, as it gets destroyed before it anyway */
701 edit_font = (HFONT)SendMessageW(This->hwndEdit, WM_GETFONT, 0, 0);
702 if (edit_font)
703 SendMessageW(This->hwndListBox, WM_SETFONT, (WPARAM)edit_font, FALSE);
705 else
706 This->options &= ~ACO_AUTOSUGGEST;
709 /**************************************************************************
710 * AutoComplete_QueryInterface
712 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
713 IAutoComplete2 * iface,
714 REFIID riid,
715 LPVOID *ppvObj)
717 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
719 TRACE("(%p)->(IID:%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
720 *ppvObj = NULL;
722 if (IsEqualIID(riid, &IID_IUnknown) ||
723 IsEqualIID(riid, &IID_IAutoComplete) ||
724 IsEqualIID(riid, &IID_IAutoComplete2))
726 *ppvObj = &This->IAutoComplete2_iface;
728 else if (IsEqualIID(riid, &IID_IAutoCompleteDropDown))
730 *ppvObj = &This->IAutoCompleteDropDown_iface;
733 if (*ppvObj)
735 IUnknown_AddRef((IUnknown*)*ppvObj);
736 TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
737 return S_OK;
739 WARN("unsupported interface: %s\n", debugstr_guid(riid));
740 return E_NOINTERFACE;
743 /******************************************************************************
744 * IAutoComplete2_fnAddRef
746 static ULONG WINAPI IAutoComplete2_fnAddRef(
747 IAutoComplete2 * iface)
749 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
750 ULONG refCount = InterlockedIncrement(&This->ref);
752 TRACE("(%p)->(%u)\n", This, refCount - 1);
754 return refCount;
757 /******************************************************************************
758 * IAutoComplete2_fnRelease
760 static ULONG WINAPI IAutoComplete2_fnRelease(
761 IAutoComplete2 * iface)
763 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
764 ULONG refCount = InterlockedDecrement(&This->ref);
766 TRACE("(%p)->(%u)\n", This, refCount + 1);
768 if (!refCount) {
769 TRACE("destroying IAutoComplete(%p)\n", This);
770 heap_free(This->quickComplete);
771 heap_free(This->txtbackup);
772 if (This->enumstr)
773 IEnumString_Release(This->enumstr);
774 if (This->aclist)
775 IACList_Release(This->aclist);
776 heap_free(This);
778 return refCount;
781 /******************************************************************************
782 * IAutoComplete2_fnEnable
784 static HRESULT WINAPI IAutoComplete2_fnEnable(
785 IAutoComplete2 * iface,
786 BOOL fEnable)
788 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
789 HRESULT hr = S_OK;
791 TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
793 This->enabled = fEnable;
795 return hr;
798 /******************************************************************************
799 * IAutoComplete2_fnInit
801 static HRESULT WINAPI IAutoComplete2_fnInit(
802 IAutoComplete2 * iface,
803 HWND hwndEdit,
804 IUnknown *punkACL,
805 LPCOLESTR pwzsRegKeyPath,
806 LPCOLESTR pwszQuickComplete)
808 IAutoCompleteImpl *prev, *This = impl_from_IAutoComplete2(iface);
810 TRACE("(%p)->(%p, %p, %s, %s)\n",
811 This, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
813 if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
814 if (This->options & ACO_FILTERPREFIXES) FIXME(" ACO_FILTERPREFIXES not supported\n");
815 if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
817 if (!hwndEdit || !punkACL)
818 return E_INVALIDARG;
820 if (This->initialized)
822 WARN("Autocompletion object is already initialized\n");
823 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
824 return This->hwndEdit ? E_FAIL : E_UNEXPECTED;
827 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
828 WARN("No IEnumString interface\n");
829 return E_NOINTERFACE;
832 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
833 if ((This->txtbackup = heap_alloc_zero(sizeof(WCHAR))) == NULL)
835 IEnumString_Release(This->enumstr);
836 This->enumstr = NULL;
837 return E_OUTOFMEMORY;
840 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist)))
841 This->aclist = NULL;
843 This->initialized = TRUE;
844 This->hwndEdit = hwndEdit;
846 /* If another AutoComplete object was previously assigned to this edit control,
847 release it but keep the same callback on the control, to avoid an infinite
848 recursive loop in ACEditSubclassProc while the property is set to this object */
849 prev = GetPropW(hwndEdit, autocomplete_propertyW);
850 SetPropW(hwndEdit, autocomplete_propertyW, This);
852 if (prev && prev->initialized) {
853 This->wpOrigEditProc = prev->wpOrigEditProc;
854 destroy_autocomplete_object(prev);
856 else
857 This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
859 /* Keep at least one reference to the object until the edit window is destroyed */
860 IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
862 if (This->options & ACO_AUTOSUGGEST)
863 create_listbox(This);
865 if (pwzsRegKeyPath)
867 static const HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
868 WCHAR *key, *value;
869 DWORD type, sz;
870 BYTE *qc;
871 HKEY hKey;
872 LSTATUS res;
873 size_t len;
874 UINT i;
876 /* pwszRegKeyPath contains the key as well as the value, so split it */
877 value = strrchrW(pwzsRegKeyPath, '\\');
878 len = value - pwzsRegKeyPath;
880 if (value && (key = heap_alloc((len+1) * sizeof(*key))) != NULL)
882 memcpy(key, pwzsRegKeyPath, len * sizeof(*key));
883 key[len] = '\0';
884 value++;
886 for (i = 0; i < ARRAY_SIZE(roots); i++)
888 if (RegOpenKeyExW(roots[i], key, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
889 continue;
890 sz = MAX_PATH * sizeof(WCHAR);
892 while ((qc = heap_alloc(sz)) != NULL)
894 res = RegQueryValueExW(hKey, value, NULL, &type, qc, &sz);
895 if (res == ERROR_SUCCESS && type == REG_SZ)
897 This->quickComplete = heap_realloc(qc, sz);
898 i = ARRAY_SIZE(roots);
899 break;
901 heap_free(qc);
902 if (res != ERROR_MORE_DATA || type != REG_SZ)
903 break;
905 RegCloseKey(hKey);
907 heap_free(key);
911 if (!This->quickComplete && pwszQuickComplete)
913 size_t len = strlenW(pwszQuickComplete)+1;
914 if ((This->quickComplete = heap_alloc(len * sizeof(WCHAR))) != NULL)
915 memcpy(This->quickComplete, pwszQuickComplete, len * sizeof(WCHAR));
918 return S_OK;
921 /**************************************************************************
922 * IAutoComplete2_fnGetOptions
924 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
925 IAutoComplete2 * iface,
926 DWORD *pdwFlag)
928 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
929 HRESULT hr = S_OK;
931 TRACE("(%p) -> (%p)\n", This, pdwFlag);
933 *pdwFlag = This->options;
935 return hr;
938 /**************************************************************************
939 * IAutoComplete2_fnSetOptions
941 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
942 IAutoComplete2 * iface,
943 DWORD dwFlag)
945 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
946 HRESULT hr = S_OK;
948 TRACE("(%p) -> (0x%x)\n", This, dwFlag);
950 This->options = dwFlag;
952 if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
953 create_listbox(This);
954 else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
955 hide_listbox(This, This->hwndListBox, TRUE);
957 return hr;
960 /**************************************************************************
961 * IAutoComplete2 VTable
963 static const IAutoComplete2Vtbl acvt =
965 IAutoComplete2_fnQueryInterface,
966 IAutoComplete2_fnAddRef,
967 IAutoComplete2_fnRelease,
968 IAutoComplete2_fnInit,
969 IAutoComplete2_fnEnable,
970 /* IAutoComplete2 */
971 IAutoComplete2_fnSetOptions,
972 IAutoComplete2_fnGetOptions,
976 static HRESULT WINAPI IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown *iface,
977 REFIID riid, LPVOID *ppvObj)
979 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
980 return IAutoComplete2_QueryInterface(&This->IAutoComplete2_iface, riid, ppvObj);
983 static ULONG WINAPI IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown *iface)
985 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
986 return IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
989 static ULONG WINAPI IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown *iface)
991 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
992 return IAutoComplete2_Release(&This->IAutoComplete2_iface);
995 /**************************************************************************
996 * IAutoCompleteDropDown_fnGetDropDownStatus
998 static HRESULT WINAPI IAutoCompleteDropDown_fnGetDropDownStatus(
999 IAutoCompleteDropDown *iface,
1000 DWORD *pdwFlags,
1001 LPWSTR *ppwszString)
1003 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1004 BOOL dropped;
1006 TRACE("(%p) -> (%p, %p)\n", This, pdwFlags, ppwszString);
1008 dropped = IsWindowVisible(This->hwndListBox);
1010 if (pdwFlags)
1011 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1013 if (ppwszString) {
1014 if (dropped) {
1015 int sel;
1017 sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
1018 if (sel >= 0)
1020 DWORD len;
1022 len = SendMessageW(This->hwndListBox, LB_GETTEXTLEN, sel, 0);
1023 *ppwszString = CoTaskMemAlloc((len+1)*sizeof(WCHAR));
1024 SendMessageW(This->hwndListBox, LB_GETTEXT, sel, (LPARAM)*ppwszString);
1026 else
1027 *ppwszString = NULL;
1029 else
1030 *ppwszString = NULL;
1033 return S_OK;
1036 /**************************************************************************
1037 * IAutoCompleteDropDown_fnResetEnumarator
1039 static HRESULT WINAPI IAutoCompleteDropDown_fnResetEnumerator(
1040 IAutoCompleteDropDown *iface)
1042 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1044 FIXME("(%p): stub\n", This);
1046 return E_NOTIMPL;
1049 /**************************************************************************
1050 * IAutoCompleteDropDown VTable
1052 static const IAutoCompleteDropDownVtbl acdropdownvt =
1054 IAutoCompleteDropDown_fnQueryInterface,
1055 IAutoCompleteDropDown_fnAddRef,
1056 IAutoCompleteDropDown_fnRelease,
1057 IAutoCompleteDropDown_fnGetDropDownStatus,
1058 IAutoCompleteDropDown_fnResetEnumerator,
1061 /**************************************************************************
1062 * IAutoComplete_Constructor
1064 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
1066 IAutoCompleteImpl *lpac;
1067 HRESULT hr;
1069 if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
1070 return CLASS_E_NOAGGREGATION;
1072 lpac = heap_alloc_zero(sizeof(*lpac));
1073 if (!lpac)
1074 return E_OUTOFMEMORY;
1076 lpac->ref = 1;
1077 lpac->IAutoComplete2_iface.lpVtbl = &acvt;
1078 lpac->IAutoCompleteDropDown_iface.lpVtbl = &acdropdownvt;
1079 lpac->enabled = TRUE;
1080 lpac->options = ACO_AUTOAPPEND;
1082 hr = IAutoComplete2_QueryInterface(&lpac->IAutoComplete2_iface, riid, ppv);
1083 IAutoComplete2_Release(&lpac->IAutoComplete2_iface);
1085 TRACE("-- (%p)->\n",lpac);
1087 return hr;