shell32/autocomplete: Retrieve the count in show_listbox.
[wine.git] / dlls / shell32 / autocomplete.c
blobc9bf9fff855116a7acf63a857be3a3c3a530986e
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 ACO_WORD_FILTER style
29 #include "config.h"
31 #include <stdarg.h>
32 #include <stdlib.h>
33 #include <string.h>
35 #define COBJMACROS
37 #include "wine/debug.h"
38 #include "windef.h"
39 #include "winbase.h"
40 #include "winreg.h"
41 #include "undocshell.h"
42 #include "shlwapi.h"
43 #include "winerror.h"
44 #include "objbase.h"
46 #include "pidl.h"
47 #include "shlobj.h"
48 #include "shldisp.h"
49 #include "debughlp.h"
50 #include "shell32_main.h"
52 #include "wine/unicode.h"
54 WINE_DEFAULT_DEBUG_CHANNEL(shell);
56 typedef struct
58 IAutoComplete2 IAutoComplete2_iface;
59 IAutoCompleteDropDown IAutoCompleteDropDown_iface;
60 LONG ref;
61 BOOL initialized;
62 BOOL enabled;
63 UINT enum_strs_num;
64 WCHAR **enum_strs;
65 HWND hwndEdit;
66 HWND hwndListBox;
67 WNDPROC wpOrigEditProc;
68 WNDPROC wpOrigLBoxProc;
69 WCHAR *txtbackup;
70 WCHAR *quickComplete;
71 IEnumString *enumstr;
72 IACList *aclist;
73 AUTOCOMPLETEOPTIONS options;
74 WCHAR no_fwd_char;
75 } IAutoCompleteImpl;
77 enum autoappend_flag
79 autoappend_flag_yes,
80 autoappend_flag_no,
81 autoappend_flag_displayempty
84 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o',
85 'c','o','m','p','l','e','t','e',' ',
86 'c','o','n','t','r','o','l',0};
88 static inline IAutoCompleteImpl *impl_from_IAutoComplete2(IAutoComplete2 *iface)
90 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoComplete2_iface);
93 static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown *iface)
95 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface);
98 static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end)
100 /* Send it directly to the edit control to match Windows behavior */
101 WNDPROC proc = ac->wpOrigEditProc;
102 if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text))
103 CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
106 static int enumerate_strings_cmpfn(const void *a, const void *b)
108 return strcmpiW(*(WCHAR* const*)a, *(WCHAR* const*)b);
112 Enumerate all of the strings and sort them in the internal list.
114 We don't free the enumerated strings (except on error) to avoid needless
115 copies, until the next reset (or the object itself is destroyed)
117 static void enumerate_strings(IAutoCompleteImpl *ac)
119 UINT cur = 0, array_size = 1024;
120 LPOLESTR *strs = NULL, *tmp;
121 ULONG read;
125 if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
126 goto fail;
127 strs = tmp;
131 if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
132 read = 0;
133 } while (read != 0 && (cur += read) < array_size);
135 array_size *= 2;
136 } while (read != 0);
138 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
139 if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
141 strs = tmp;
142 if (cur > 0)
143 qsort(strs, cur, sizeof(*strs), enumerate_strings_cmpfn);
145 ac->enum_strs = strs;
146 ac->enum_strs_num = cur;
147 return;
150 fail:
151 while (cur--)
152 CoTaskMemFree(strs[cur]);
153 heap_free(strs);
156 static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
157 UINT len, int direction)
159 WCHAR **strs = ac->enum_strs;
160 UINT index = ~0, a = start, b = ac->enum_strs_num;
161 while (a < b)
163 UINT i = (a + b - 1) / 2;
164 int cmp = strncmpiW(text, strs[i], len);
165 if (cmp == 0)
167 index = i;
168 cmp = direction;
170 if (cmp <= 0) b = i;
171 else a = i + 1;
173 return index;
176 static void free_enum_strs(IAutoCompleteImpl *ac)
178 WCHAR **strs = ac->enum_strs;
179 if (strs)
181 UINT i = ac->enum_strs_num;
182 ac->enum_strs = NULL;
183 while (i--)
184 CoTaskMemFree(strs[i]);
185 heap_free(strs);
189 static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
191 ShowWindow(hwnd, SW_HIDE);
192 SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
193 if (reset) free_enum_strs(ac);
196 static void show_listbox(IAutoCompleteImpl *ac)
198 RECT r;
199 UINT cnt, width, height;
201 GetWindowRect(ac->hwndEdit, &r);
202 SendMessageW(ac->hwndListBox, LB_CARETOFF, 0, 0);
204 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
205 cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
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 start, end;
409 if (!str) return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
411 /* Windows seems to disable autoappend if ACO_NOPREFIXFILTERING is set */
412 if (!(ac->options & ACO_NOPREFIXFILTERING) && len)
414 start = find_matching_enum_str(ac, 0, text, len, -1);
415 if (start == ~0)
416 return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
418 if (flag == autoappend_flag_yes)
419 autoappend_str(ac, text, len, str[start], hwnd);
420 if (!(ac->options & ACO_AUTOSUGGEST))
421 return TRUE;
423 /* Find the index beyond the last string that matches */
424 end = find_matching_enum_str(ac, start + 1, text, len, 1);
425 end = (end == ~0 ? start : end) + 1;
427 else
429 if (!(ac->options & ACO_AUTOSUGGEST))
430 return TRUE;
431 start = 0;
432 end = ac->enum_strs_num;
433 if (end == 0)
434 return FALSE;
437 SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
438 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
439 SendMessageW(ac->hwndListBox, LB_INITSTORAGE, end - start, 0);
440 for (; start < end; start++)
441 SendMessageW(ac->hwndListBox, LB_INSERTSTRING, -1, (LPARAM)str[start]);
443 show_listbox(ac);
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 if (This->options & ACO_AUTOSUGGEST)
631 hide_listbox(This, This->hwndListBox, TRUE);
632 break;
633 case WM_CUT:
634 case WM_CLEAR:
635 case WM_UNDO:
636 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
637 autocomplete_text(This, hwnd, autoappend_flag_no);
638 return ret;
639 case WM_PASTE:
640 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
641 autocomplete_text(This, hwnd, autoappend_flag_yes);
642 return ret;
643 case WM_MOUSEWHEEL:
644 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBox))
645 return SendMessageW(This->hwndListBox, WM_MOUSEWHEEL, wParam, lParam);
646 break;
647 case WM_SETFONT:
648 if (This->hwndListBox)
649 SendMessageW(This->hwndListBox, WM_SETFONT, wParam, lParam);
650 break;
651 case WM_DESTROY:
653 WNDPROC proc = This->wpOrigEditProc;
655 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)proc);
656 RemovePropW(hwnd, autocomplete_propertyW);
657 destroy_autocomplete_object(This);
658 return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
661 return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
664 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
666 IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
667 WCHAR *msg;
668 int sel, len;
670 switch (uMsg) {
671 case WM_MOUSEMOVE:
672 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
673 SendMessageW(hwnd, LB_SETCURSEL, sel, 0);
674 break;
675 case WM_LBUTTONDOWN:
676 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
677 if (sel < 0)
678 break;
679 len = SendMessageW(hwnd, LB_GETTEXTLEN, sel, 0);
680 if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR))))
681 break;
682 len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
683 set_text_and_selection(This, This->hwndEdit, msg, 0, len);
684 hide_listbox(This, hwnd, TRUE);
685 heap_free(msg);
686 break;
687 default:
688 return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
690 return 0;
693 static void create_listbox(IAutoCompleteImpl *This)
695 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
696 This->hwndListBox = CreateWindowExW(0, WC_LISTBOXW, NULL,
697 WS_BORDER | WS_CHILD | WS_VSCROLL | LBS_HASSTRINGS | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
698 0, 0, 0, 0, GetParent(This->hwndEdit), NULL, shell32_hInstance, NULL);
700 if (This->hwndListBox) {
701 HFONT edit_font;
703 This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
704 SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
705 SetParent(This->hwndListBox, HWND_DESKTOP);
707 /* Use the same font as the edit control, as it gets destroyed before it anyway */
708 edit_font = (HFONT)SendMessageW(This->hwndEdit, WM_GETFONT, 0, 0);
709 if (edit_font)
710 SendMessageW(This->hwndListBox, WM_SETFONT, (WPARAM)edit_font, FALSE);
712 else
713 This->options &= ~ACO_AUTOSUGGEST;
716 /**************************************************************************
717 * AutoComplete_QueryInterface
719 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
720 IAutoComplete2 * iface,
721 REFIID riid,
722 LPVOID *ppvObj)
724 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
726 TRACE("(%p)->(IID:%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
727 *ppvObj = NULL;
729 if (IsEqualIID(riid, &IID_IUnknown) ||
730 IsEqualIID(riid, &IID_IAutoComplete) ||
731 IsEqualIID(riid, &IID_IAutoComplete2))
733 *ppvObj = &This->IAutoComplete2_iface;
735 else if (IsEqualIID(riid, &IID_IAutoCompleteDropDown))
737 *ppvObj = &This->IAutoCompleteDropDown_iface;
740 if (*ppvObj)
742 IUnknown_AddRef((IUnknown*)*ppvObj);
743 TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
744 return S_OK;
746 WARN("unsupported interface: %s\n", debugstr_guid(riid));
747 return E_NOINTERFACE;
750 /******************************************************************************
751 * IAutoComplete2_fnAddRef
753 static ULONG WINAPI IAutoComplete2_fnAddRef(
754 IAutoComplete2 * iface)
756 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
757 ULONG refCount = InterlockedIncrement(&This->ref);
759 TRACE("(%p)->(%u)\n", This, refCount - 1);
761 return refCount;
764 /******************************************************************************
765 * IAutoComplete2_fnRelease
767 static ULONG WINAPI IAutoComplete2_fnRelease(
768 IAutoComplete2 * iface)
770 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
771 ULONG refCount = InterlockedDecrement(&This->ref);
773 TRACE("(%p)->(%u)\n", This, refCount + 1);
775 if (!refCount) {
776 TRACE("destroying IAutoComplete(%p)\n", This);
777 heap_free(This->quickComplete);
778 heap_free(This->txtbackup);
779 if (This->enumstr)
780 IEnumString_Release(This->enumstr);
781 if (This->aclist)
782 IACList_Release(This->aclist);
783 heap_free(This);
785 return refCount;
788 /******************************************************************************
789 * IAutoComplete2_fnEnable
791 static HRESULT WINAPI IAutoComplete2_fnEnable(
792 IAutoComplete2 * iface,
793 BOOL fEnable)
795 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
796 HRESULT hr = S_OK;
798 TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
800 This->enabled = fEnable;
802 return hr;
805 /******************************************************************************
806 * IAutoComplete2_fnInit
808 static HRESULT WINAPI IAutoComplete2_fnInit(
809 IAutoComplete2 * iface,
810 HWND hwndEdit,
811 IUnknown *punkACL,
812 LPCOLESTR pwzsRegKeyPath,
813 LPCOLESTR pwszQuickComplete)
815 IAutoCompleteImpl *prev, *This = impl_from_IAutoComplete2(iface);
817 TRACE("(%p)->(%p, %p, %s, %s)\n",
818 This, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
820 if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
821 if (This->options & ACO_FILTERPREFIXES) FIXME(" ACO_FILTERPREFIXES not supported\n");
822 if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
823 if (This->options & ACO_WORD_FILTER) FIXME(" ACO_WORD_FILTER not supported\n");
825 if (!hwndEdit || !punkACL)
826 return E_INVALIDARG;
828 if (This->initialized)
830 WARN("Autocompletion object is already initialized\n");
831 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
832 return This->hwndEdit ? E_FAIL : E_UNEXPECTED;
835 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
836 WARN("No IEnumString interface\n");
837 return E_NOINTERFACE;
840 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
841 if ((This->txtbackup = heap_alloc_zero(sizeof(WCHAR))) == NULL)
843 IEnumString_Release(This->enumstr);
844 This->enumstr = NULL;
845 return E_OUTOFMEMORY;
848 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist)))
849 This->aclist = NULL;
851 This->initialized = TRUE;
852 This->hwndEdit = hwndEdit;
854 /* If another AutoComplete object was previously assigned to this edit control,
855 release it but keep the same callback on the control, to avoid an infinite
856 recursive loop in ACEditSubclassProc while the property is set to this object */
857 prev = GetPropW(hwndEdit, autocomplete_propertyW);
858 SetPropW(hwndEdit, autocomplete_propertyW, This);
860 if (prev && prev->initialized) {
861 This->wpOrigEditProc = prev->wpOrigEditProc;
862 destroy_autocomplete_object(prev);
864 else
865 This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
867 /* Keep at least one reference to the object until the edit window is destroyed */
868 IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
870 if (This->options & ACO_AUTOSUGGEST)
871 create_listbox(This);
873 if (pwzsRegKeyPath)
875 static const HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
876 WCHAR *key, *value;
877 DWORD type, sz;
878 BYTE *qc;
879 HKEY hKey;
880 LSTATUS res;
881 size_t len;
882 UINT i;
884 /* pwszRegKeyPath contains the key as well as the value, so split it */
885 value = strrchrW(pwzsRegKeyPath, '\\');
886 len = value - pwzsRegKeyPath;
888 if (value && (key = heap_alloc((len+1) * sizeof(*key))) != NULL)
890 memcpy(key, pwzsRegKeyPath, len * sizeof(*key));
891 key[len] = '\0';
892 value++;
894 for (i = 0; i < ARRAY_SIZE(roots); i++)
896 if (RegOpenKeyExW(roots[i], key, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
897 continue;
898 sz = MAX_PATH * sizeof(WCHAR);
900 while ((qc = heap_alloc(sz)) != NULL)
902 res = RegQueryValueExW(hKey, value, NULL, &type, qc, &sz);
903 if (res == ERROR_SUCCESS && type == REG_SZ)
905 This->quickComplete = heap_realloc(qc, sz);
906 i = ARRAY_SIZE(roots);
907 break;
909 heap_free(qc);
910 if (res != ERROR_MORE_DATA || type != REG_SZ)
911 break;
913 RegCloseKey(hKey);
915 heap_free(key);
919 if (!This->quickComplete && pwszQuickComplete)
921 size_t len = strlenW(pwszQuickComplete)+1;
922 if ((This->quickComplete = heap_alloc(len * sizeof(WCHAR))) != NULL)
923 memcpy(This->quickComplete, pwszQuickComplete, len * sizeof(WCHAR));
926 return S_OK;
929 /**************************************************************************
930 * IAutoComplete2_fnGetOptions
932 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
933 IAutoComplete2 * iface,
934 DWORD *pdwFlag)
936 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
937 HRESULT hr = S_OK;
939 TRACE("(%p) -> (%p)\n", This, pdwFlag);
941 *pdwFlag = This->options;
943 return hr;
946 /**************************************************************************
947 * IAutoComplete2_fnSetOptions
949 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
950 IAutoComplete2 * iface,
951 DWORD dwFlag)
953 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
954 HRESULT hr = S_OK;
956 TRACE("(%p) -> (0x%x)\n", This, dwFlag);
958 This->options = dwFlag;
960 if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
961 create_listbox(This);
962 else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
963 hide_listbox(This, This->hwndListBox, TRUE);
965 return hr;
968 /**************************************************************************
969 * IAutoComplete2 VTable
971 static const IAutoComplete2Vtbl acvt =
973 IAutoComplete2_fnQueryInterface,
974 IAutoComplete2_fnAddRef,
975 IAutoComplete2_fnRelease,
976 IAutoComplete2_fnInit,
977 IAutoComplete2_fnEnable,
978 /* IAutoComplete2 */
979 IAutoComplete2_fnSetOptions,
980 IAutoComplete2_fnGetOptions,
984 static HRESULT WINAPI IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown *iface,
985 REFIID riid, LPVOID *ppvObj)
987 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
988 return IAutoComplete2_QueryInterface(&This->IAutoComplete2_iface, riid, ppvObj);
991 static ULONG WINAPI IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown *iface)
993 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
994 return IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
997 static ULONG WINAPI IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown *iface)
999 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1000 return IAutoComplete2_Release(&This->IAutoComplete2_iface);
1003 /**************************************************************************
1004 * IAutoCompleteDropDown_fnGetDropDownStatus
1006 static HRESULT WINAPI IAutoCompleteDropDown_fnGetDropDownStatus(
1007 IAutoCompleteDropDown *iface,
1008 DWORD *pdwFlags,
1009 LPWSTR *ppwszString)
1011 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1012 BOOL dropped;
1014 TRACE("(%p) -> (%p, %p)\n", This, pdwFlags, ppwszString);
1016 dropped = IsWindowVisible(This->hwndListBox);
1018 if (pdwFlags)
1019 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1021 if (ppwszString) {
1022 if (dropped) {
1023 int sel;
1025 sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
1026 if (sel >= 0)
1028 DWORD len;
1030 len = SendMessageW(This->hwndListBox, LB_GETTEXTLEN, sel, 0);
1031 *ppwszString = CoTaskMemAlloc((len+1)*sizeof(WCHAR));
1032 SendMessageW(This->hwndListBox, LB_GETTEXT, sel, (LPARAM)*ppwszString);
1034 else
1035 *ppwszString = NULL;
1037 else
1038 *ppwszString = NULL;
1041 return S_OK;
1044 /**************************************************************************
1045 * IAutoCompleteDropDown_fnResetEnumarator
1047 static HRESULT WINAPI IAutoCompleteDropDown_fnResetEnumerator(
1048 IAutoCompleteDropDown *iface)
1050 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1052 TRACE("(%p)\n", This);
1054 if (This->initialized)
1056 free_enum_strs(This);
1057 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBox))
1058 autocomplete_text(This, This->hwndEdit, autoappend_flag_displayempty);
1060 return S_OK;
1063 /**************************************************************************
1064 * IAutoCompleteDropDown VTable
1066 static const IAutoCompleteDropDownVtbl acdropdownvt =
1068 IAutoCompleteDropDown_fnQueryInterface,
1069 IAutoCompleteDropDown_fnAddRef,
1070 IAutoCompleteDropDown_fnRelease,
1071 IAutoCompleteDropDown_fnGetDropDownStatus,
1072 IAutoCompleteDropDown_fnResetEnumerator,
1075 /**************************************************************************
1076 * IAutoComplete_Constructor
1078 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
1080 IAutoCompleteImpl *lpac;
1081 HRESULT hr;
1083 if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
1084 return CLASS_E_NOAGGREGATION;
1086 lpac = heap_alloc_zero(sizeof(*lpac));
1087 if (!lpac)
1088 return E_OUTOFMEMORY;
1090 lpac->ref = 1;
1091 lpac->IAutoComplete2_iface.lpVtbl = &acvt;
1092 lpac->IAutoCompleteDropDown_iface.lpVtbl = &acdropdownvt;
1093 lpac->enabled = TRUE;
1094 lpac->options = ACO_AUTOAPPEND;
1096 hr = IAutoComplete2_QueryInterface(&lpac->IAutoComplete2_iface, riid, ppv);
1097 IAutoComplete2_Release(&lpac->IAutoComplete2_iface);
1099 TRACE("-- (%p)->\n",lpac);
1101 return hr;