shell32: Move SHCreateLinks() flags to shlfolder.c.
[wine.git] / dlls / shell32 / autocomplete.c
blob0718f782460a435bb92f3d7abc2db9494e0a42ca
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_RTLREADING style
26 - implement ACO_WORD_FILTER style
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <string.h>
33 #define COBJMACROS
35 #include "wine/debug.h"
36 #include "windef.h"
37 #include "winbase.h"
38 #include "winreg.h"
39 #include "undocshell.h"
40 #include "shlwapi.h"
41 #include "winerror.h"
42 #include "objbase.h"
44 #include "pidl.h"
45 #include "shlobj.h"
46 #include "shldisp.h"
47 #include "debughlp.h"
48 #include "shell32_main.h"
50 WINE_DEFAULT_DEBUG_CHANNEL(shell);
52 typedef struct
54 IAutoComplete2 IAutoComplete2_iface;
55 IAutoCompleteDropDown IAutoCompleteDropDown_iface;
56 LONG ref;
57 BOOL initialized;
58 BOOL enabled;
59 UINT enum_strs_num;
60 WCHAR **enum_strs;
61 WCHAR **listbox_strs;
62 HWND hwndEdit;
63 HWND hwndListBox;
64 HWND hwndListBoxOwner;
65 WNDPROC wpOrigEditProc;
66 WNDPROC wpOrigLBoxProc;
67 WNDPROC wpOrigLBoxOwnerProc;
68 WCHAR *txtbackup;
69 WCHAR *quickComplete;
70 IEnumString *enumstr;
71 IACList *aclist;
72 AUTOCOMPLETEOPTIONS options;
73 WCHAR no_fwd_char;
74 } IAutoCompleteImpl;
76 enum autoappend_flag
78 autoappend_flag_yes,
79 autoappend_flag_no,
80 autoappend_flag_displayempty
83 enum prefix_filtering
85 prefix_filtering_none = 0, /* no prefix filtering (raw search) */
86 prefix_filtering_protocol, /* filter common protocol (e.g. http://) */
87 prefix_filtering_all /* filter all common prefixes (protocol & www. ) */
90 static const WCHAR autocomplete_propertyW[] = L"Wine Autocomplete control";
92 static inline IAutoCompleteImpl *impl_from_IAutoComplete2(IAutoComplete2 *iface)
94 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoComplete2_iface);
97 static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown *iface)
99 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface);
102 static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end)
104 /* Send it directly to the edit control to match Windows behavior */
105 WNDPROC proc = ac->wpOrigEditProc;
106 if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text))
107 CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
110 static inline WCHAR *filter_protocol(WCHAR *str)
112 if (!wcsncmp(str, L"http", 4))
114 str += 4;
115 str += (*str == 's'); /* https */
116 if (str[0] == ':' && str[1] == '/' && str[2] == '/')
117 return str + 3;
119 return NULL;
122 static inline WCHAR *filter_www(WCHAR *str)
124 if (!wcsncmp(str, L"www.", 4)) return str + 4;
125 return NULL;
129 Get the prefix filtering based on text, for example if text's prefix
130 is a protocol, then we return none because we actually filter nothing
132 static enum prefix_filtering get_text_prefix_filtering(const WCHAR *text)
134 /* Convert to lowercase to perform case insensitive filtering,
135 using the longest possible prefix as the size of the buffer */
136 WCHAR buf[sizeof("https://")];
137 UINT i;
139 for (i = 0; i < ARRAY_SIZE(buf) - 1 && text[i]; i++)
140 buf[i] = towlower(text[i]);
141 buf[i] = '\0';
143 if (filter_protocol(buf)) return prefix_filtering_none;
144 if (filter_www(buf)) return prefix_filtering_protocol;
145 return prefix_filtering_all;
149 Filter the prefix of str based on the value of pfx_filter
150 This is used in sorting, so it's more performance sensitive
152 static WCHAR *filter_str_prefix(WCHAR *str, enum prefix_filtering pfx_filter)
154 WCHAR *p = str;
156 if (pfx_filter == prefix_filtering_none) return str;
157 if ((p = filter_protocol(str))) str = p;
159 if (pfx_filter == prefix_filtering_protocol) return str;
160 if ((p = filter_www(str))) str = p;
162 return str;
165 static inline int sort_strs_cmpfn_impl(WCHAR *a, WCHAR *b, enum prefix_filtering pfx_filter)
167 WCHAR *str1 = filter_str_prefix(a, pfx_filter);
168 WCHAR *str2 = filter_str_prefix(b, pfx_filter);
169 return wcsicmp(str1, str2);
172 static int __cdecl sort_strs_cmpfn_none(const void *a, const void *b)
174 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_none);
177 static int __cdecl sort_strs_cmpfn_protocol(const void *a, const void *b)
179 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_protocol);
182 static int __cdecl sort_strs_cmpfn_all(const void *a, const void *b)
184 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_all);
187 static int (* __cdecl sort_strs_cmpfn[])(const void*, const void*) =
189 sort_strs_cmpfn_none,
190 sort_strs_cmpfn_protocol,
191 sort_strs_cmpfn_all
194 static void sort_strs(WCHAR **strs, UINT numstrs, enum prefix_filtering pfx_filter)
196 qsort(strs, numstrs, sizeof(*strs), sort_strs_cmpfn[pfx_filter]);
200 Enumerate all of the strings and sort them in the internal list.
202 We don't free the enumerated strings (except on error) to avoid needless
203 copies, until the next reset (or the object itself is destroyed)
205 static void enumerate_strings(IAutoCompleteImpl *ac, enum prefix_filtering pfx_filter)
207 UINT cur = 0, array_size = 1024;
208 LPOLESTR *strs = NULL, *tmp;
209 ULONG read;
213 if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
214 goto fail;
215 strs = tmp;
219 if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
220 read = 0;
221 } while (read != 0 && (cur += read) < array_size);
223 array_size *= 2;
224 } while (read != 0);
226 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
227 if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
229 strs = tmp;
230 if (cur > 0)
231 sort_strs(strs, cur, pfx_filter);
233 ac->enum_strs = strs;
234 ac->enum_strs_num = cur;
235 return;
238 fail:
239 while (cur--)
240 CoTaskMemFree(strs[cur]);
241 heap_free(strs);
244 static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
245 UINT len, enum prefix_filtering pfx_filter, int direction)
247 WCHAR **strs = ac->enum_strs;
248 UINT index = ~0, a = start, b = ac->enum_strs_num;
249 while (a < b)
251 UINT i = (a + b - 1) / 2;
252 int cmp = wcsnicmp(text, filter_str_prefix(strs[i], pfx_filter), len);
253 if (cmp == 0)
255 index = i;
256 cmp = direction;
258 if (cmp <= 0) b = i;
259 else a = i + 1;
261 return index;
264 static void free_enum_strs(IAutoCompleteImpl *ac)
266 WCHAR **strs = ac->enum_strs;
267 if (strs)
269 UINT i = ac->enum_strs_num;
270 ac->enum_strs = NULL;
271 while (i--)
272 CoTaskMemFree(strs[i]);
273 heap_free(strs);
277 static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
279 ShowWindow(ac->hwndListBoxOwner, SW_HIDE);
280 SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
281 if (reset) free_enum_strs(ac);
284 static void show_listbox(IAutoCompleteImpl *ac)
286 RECT r;
287 UINT cnt, width, height;
289 GetWindowRect(ac->hwndEdit, &r);
291 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
292 cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
293 height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7);
294 width = r.right - r.left;
296 SetWindowPos(ac->hwndListBoxOwner, HWND_TOP, r.left, r.bottom + 1, width, height,
297 SWP_SHOWWINDOW | SWP_NOACTIVATE);
300 static void set_listbox_font(IAutoCompleteImpl *ac, HFONT font)
302 /* We have to calculate the item height manually due to owner-drawn */
303 HFONT old_font = NULL;
304 UINT height = 16;
305 HDC hdc;
307 if ((hdc = GetDCEx(ac->hwndListBox, 0, DCX_CACHE)))
309 TEXTMETRICW metrics;
310 if (font) old_font = SelectObject(hdc, font);
311 if (GetTextMetricsW(hdc, &metrics))
312 height = metrics.tmHeight;
313 if (old_font) SelectObject(hdc, old_font);
314 ReleaseDC(ac->hwndListBox, hdc);
316 SendMessageW(ac->hwndListBox, WM_SETFONT, (WPARAM)font, FALSE);
317 SendMessageW(ac->hwndListBox, LB_SETITEMHEIGHT, 0, height);
320 static BOOL draw_listbox_item(IAutoCompleteImpl *ac, DRAWITEMSTRUCT *info, UINT id)
322 COLORREF old_text, old_bk;
323 HDC hdc = info->hDC;
324 UINT state;
325 WCHAR *str;
327 if (info->CtlType != ODT_LISTBOX || info->CtlID != id ||
328 id != (UINT)GetWindowLongPtrW(ac->hwndListBox, GWLP_ID))
329 return FALSE;
331 if ((INT)info->itemID < 0 || info->itemAction == ODA_FOCUS)
332 return TRUE;
334 state = info->itemState;
335 if (state & ODS_SELECTED)
337 old_bk = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
338 old_text = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
341 str = ac->listbox_strs[info->itemID];
342 ExtTextOutW(hdc, info->rcItem.left + 1, info->rcItem.top,
343 ETO_OPAQUE | ETO_CLIPPED, &info->rcItem, str,
344 lstrlenW(str), NULL);
346 if (state & ODS_SELECTED)
348 SetBkColor(hdc, old_bk);
349 SetTextColor(hdc, old_text);
351 return TRUE;
354 static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len)
356 /* Replace the first %s directly without using snprintf, to avoid
357 exploits since the format string can be retrieved from the registry */
358 WCHAR *base = dst;
359 UINT args = 0;
360 while (*qc != '\0')
362 if (qc[0] == '%')
364 if (args < 1 && qc[1] == 's')
366 memcpy(dst, str, str_len * sizeof(WCHAR));
367 dst += str_len;
368 qc += 2;
369 args++;
370 continue;
372 qc += (qc[1] == '%');
374 *dst++ = *qc++;
376 *dst = '\0';
377 return dst - base;
380 static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
382 WCHAR *text;
383 HWND hwndListBox = ac->hwndListBox;
384 if (!(ac->options & ACO_AUTOSUGGEST))
385 return FALSE;
387 if (IsWindowVisible(ac->hwndListBoxOwner))
389 INT sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0);
390 if (sel >= 0)
392 text = ac->listbox_strs[sel];
393 set_text_and_selection(ac, hwnd, text, 0, lstrlenW(text));
394 hide_listbox(ac, hwndListBox, TRUE);
395 ac->no_fwd_char = '\r'; /* RETURN char */
396 return TRUE;
399 hide_listbox(ac, hwndListBox, TRUE);
400 return FALSE;
403 static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
405 WCHAR *msg;
406 UINT len;
408 INT count = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
409 INT sel = SendMessageW(ac->hwndListBox, LB_GETCURSEL, 0, 0);
410 if (key == VK_PRIOR || key == VK_NEXT)
412 if (sel < 0)
413 sel = (key == VK_PRIOR) ? count - 1 : 0;
414 else
416 INT base = SendMessageW(ac->hwndListBox, LB_GETTOPINDEX, 0, 0);
417 INT pgsz = SendMessageW(ac->hwndListBox, LB_GETLISTBOXINFO, 0, 0);
418 pgsz = max(pgsz - 1, 1);
419 if (key == VK_PRIOR)
421 if (sel == 0)
422 sel = -1;
423 else
425 if (sel == base) base -= min(base, pgsz);
426 sel = base;
429 else
431 if (sel == count - 1)
432 sel = -1;
433 else
435 base += pgsz;
436 if (sel >= base) base += pgsz;
437 sel = min(base, count - 1);
442 else if (key == VK_UP || (key == VK_TAB && (GetKeyState(VK_SHIFT) & 0x8000)))
443 sel = ((sel - 1) < -1) ? count - 1 : sel - 1;
444 else
445 sel = ((sel + 1) >= count) ? -1 : sel + 1;
447 SendMessageW(ac->hwndListBox, LB_SETCURSEL, sel, 0);
449 msg = (sel >= 0) ? ac->listbox_strs[sel] : ac->txtbackup;
450 len = lstrlenW(msg);
451 set_text_and_selection(ac, hwnd, msg, len, len);
453 return 0;
456 static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
458 WCHAR c = last_delim[1];
460 free_enum_strs(ac);
461 IEnumString_Reset(ac->enumstr); /* call before expand */
463 last_delim[1] = '\0';
464 IACList_Expand(ac->aclist, txt);
465 last_delim[1] = c;
466 return TRUE;
469 static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
471 /* call IACList::Expand only when needed, if the
472 new txt and old_txt require different expansions */
474 const WCHAR *old_txt = ac->txtbackup;
475 WCHAR c, *p, *last_delim;
476 size_t i = 0;
478 /* always expand if the enumerator was reset */
479 if (!ac->enum_strs) old_txt = L"";
481 /* skip the shared prefix */
482 while ((c = towlower(txt[i])) == towlower(old_txt[i]))
484 if (c == '\0') return FALSE;
485 i++;
488 /* they differ at this point, check for a delim further in txt */
489 for (last_delim = NULL, p = &txt[i]; (p = wcspbrk(p, L"\\/")) != NULL; p++)
490 last_delim = p;
491 if (last_delim) return do_aclist_expand(ac, txt, last_delim);
493 /* txt has no delim after i, check for a delim further in old_txt */
494 if (wcspbrk(&old_txt[i], L"\\/"))
496 /* scan backwards to find the first delim before txt[i] (if any) */
497 while (i--)
498 if (wcschr(L"\\/", txt[i]))
499 return do_aclist_expand(ac, txt, &txt[i]);
501 /* Windows doesn't expand without a delim, but it does reset */
502 free_enum_strs(ac);
505 return FALSE;
508 static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR *str, HWND hwnd)
510 DWORD sel_start;
511 WCHAR *tmp;
512 size_t size;
514 /* Don't auto-append unless the caret is at the end */
515 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&sel_start, 0);
516 if (sel_start != len)
517 return;
519 /* The character capitalization can be different,
520 so merge text and str into a new string */
521 size = len + lstrlenW(&str[len]) + 1;
523 if ((tmp = heap_alloc(size * sizeof(*tmp))))
525 memcpy(tmp, text, len * sizeof(*tmp));
526 memcpy(&tmp[len], &str[len], (size - len) * sizeof(*tmp));
528 else tmp = str;
530 set_text_and_selection(ac, hwnd, tmp, len, size - 1);
531 if (tmp != str)
532 heap_free(tmp);
535 static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
536 enum prefix_filtering pfx_filter, HWND hwnd,
537 enum autoappend_flag flag)
539 /* Return FALSE if we need to hide the listbox */
540 WCHAR **str = ac->enum_strs;
541 UINT start, end;
542 if (!str) return !(ac->options & ACO_AUTOSUGGEST);
544 /* Windows seems to disable autoappend if ACO_NOPREFIXFILTERING is set */
545 if (!(ac->options & ACO_NOPREFIXFILTERING) && len)
547 start = find_matching_enum_str(ac, 0, text, len, pfx_filter, -1);
548 if (start == ~0)
549 return !(ac->options & ACO_AUTOSUGGEST);
551 if (flag == autoappend_flag_yes)
552 autoappend_str(ac, text, len, filter_str_prefix(str[start], pfx_filter), hwnd);
553 if (!(ac->options & ACO_AUTOSUGGEST))
554 return TRUE;
556 /* Find the index beyond the last string that matches */
557 end = find_matching_enum_str(ac, start + 1, text, len, pfx_filter, 1);
558 end = (end == ~0 ? start : end) + 1;
560 else
562 if (!(ac->options & ACO_AUTOSUGGEST))
563 return TRUE;
564 start = 0;
565 end = ac->enum_strs_num;
566 if (end == 0)
567 return FALSE;
570 SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
571 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
573 ac->listbox_strs = str + start;
574 SendMessageW(ac->hwndListBox, LB_SETCOUNT, end - start, 0);
576 show_listbox(ac);
577 SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
578 return TRUE;
581 static enum prefix_filtering setup_prefix_filtering(IAutoCompleteImpl *ac, const WCHAR *text)
583 enum prefix_filtering pfx_filter;
584 if (!(ac->options & ACO_FILTERPREFIXES)) return prefix_filtering_none;
586 pfx_filter = get_text_prefix_filtering(text);
587 if (!ac->enum_strs) return pfx_filter;
589 /* If the prefix filtering is different, re-sort the filtered strings */
590 if (pfx_filter != get_text_prefix_filtering(ac->txtbackup))
591 sort_strs(ac->enum_strs, ac->enum_strs_num, pfx_filter);
593 return pfx_filter;
596 static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
598 WCHAR *text;
599 BOOL expanded = FALSE;
600 enum prefix_filtering pfx_filter;
601 UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
603 if (flag != autoappend_flag_displayempty && len == 0)
605 if (ac->options & ACO_AUTOSUGGEST)
606 hide_listbox(ac, ac->hwndListBox, FALSE);
607 free_enum_strs(ac);
608 return;
611 size = len + 1;
612 if (!(text = heap_alloc(size * sizeof(WCHAR))))
614 /* Reset the listbox to prevent potential crash from ResetEnumerator */
615 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
616 return;
618 len = SendMessageW(hwnd, WM_GETTEXT, size, (LPARAM)text);
619 if (len + 1 != size)
620 text = heap_realloc(text, (len + 1) * sizeof(WCHAR));
622 if (ac->aclist)
624 if (text[len - 1] == '\\' || text[len - 1] == '/')
625 flag = autoappend_flag_no;
626 expanded = aclist_expand(ac, text);
628 pfx_filter = setup_prefix_filtering(ac, text);
630 if (expanded || !ac->enum_strs)
632 if (!expanded) IEnumString_Reset(ac->enumstr);
633 enumerate_strings(ac, pfx_filter);
636 /* Set txtbackup to point to text itself (which must not be released),
637 and it must be done here since aclist_expand uses it to track changes */
638 heap_free(ac->txtbackup);
639 ac->txtbackup = text;
641 if (!display_matching_strs(ac, text, len, pfx_filter, hwnd, flag))
642 hide_listbox(ac, ac->hwndListBox, FALSE);
645 static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
647 ac->hwndEdit = NULL;
648 free_enum_strs(ac);
649 if (ac->hwndListBoxOwner)
650 DestroyWindow(ac->hwndListBoxOwner);
651 IAutoComplete2_Release(&ac->IAutoComplete2_iface);
655 Helper for ACEditSubclassProc
657 static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT uMsg,
658 WPARAM wParam, LPARAM lParam)
660 switch (wParam)
662 case VK_ESCAPE:
663 /* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
664 if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBoxOwner))
666 hide_listbox(ac, ac->hwndListBox, FALSE);
667 ac->no_fwd_char = 0x1B; /* ESC char */
668 return 0;
670 break;
671 case VK_RETURN:
672 /* If quickComplete is set and control is pressed, replace the string */
673 if (ac->quickComplete && (GetKeyState(VK_CONTROL) & 0x8000))
675 WCHAR *text, *buf;
676 size_t sz;
677 UINT len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
678 ac->no_fwd_char = '\n'; /* CTRL+RETURN char */
680 if (!(text = heap_alloc((len + 1) * sizeof(WCHAR))))
681 return 0;
682 len = SendMessageW(hwnd, WM_GETTEXT, len + 1, (LPARAM)text);
683 sz = lstrlenW(ac->quickComplete) + 1 + len;
685 if ((buf = heap_alloc(sz * sizeof(WCHAR))))
687 len = format_quick_complete(buf, ac->quickComplete, text, len);
688 set_text_and_selection(ac, hwnd, buf, 0, len);
689 heap_free(buf);
692 if (ac->options & ACO_AUTOSUGGEST)
693 hide_listbox(ac, ac->hwndListBox, TRUE);
694 heap_free(text);
695 return 0;
698 if (select_item_with_return_key(ac, hwnd))
699 return 0;
700 break;
701 case VK_TAB:
702 if ((ac->options & (ACO_AUTOSUGGEST | ACO_USETAB)) == (ACO_AUTOSUGGEST | ACO_USETAB)
703 && IsWindowVisible(ac->hwndListBoxOwner) && !(GetKeyState(VK_CONTROL) & 0x8000))
705 ac->no_fwd_char = '\t';
706 return change_selection(ac, hwnd, wParam);
708 break;
709 case VK_UP:
710 case VK_DOWN:
711 case VK_PRIOR:
712 case VK_NEXT:
713 /* Two cases here:
714 - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
715 set, display it with all the entries, without selecting any
716 - if the listbox is visible, change the selection
718 if (!(ac->options & ACO_AUTOSUGGEST))
719 break;
721 if (!IsWindowVisible(ac->hwndListBoxOwner))
723 if (ac->options & ACO_UPDOWNKEYDROPSLIST)
725 autocomplete_text(ac, hwnd, autoappend_flag_displayempty);
726 return 0;
729 else
730 return change_selection(ac, hwnd, wParam);
731 break;
732 case VK_DELETE:
734 LRESULT ret = CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
735 autocomplete_text(ac, hwnd, autoappend_flag_no);
736 return ret;
739 ac->no_fwd_char = '\0';
740 return CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
744 Window procedure for autocompletion
746 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
748 IAutoCompleteImpl *This = GetPropW(hwnd, autocomplete_propertyW);
749 LRESULT ret;
751 if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
753 switch (uMsg)
755 case CB_SHOWDROPDOWN:
756 if (This->options & ACO_AUTOSUGGEST)
757 hide_listbox(This, This->hwndListBox, TRUE);
758 return 0;
759 case WM_KILLFOCUS:
760 if (This->options & ACO_AUTOSUGGEST)
762 if (This->hwndListBoxOwner == (HWND)wParam ||
763 This->hwndListBoxOwner == GetAncestor((HWND)wParam, GA_PARENT))
764 break;
765 hide_listbox(This, This->hwndListBox, FALSE);
768 /* Reset the enumerator if it's not visible anymore */
769 if (!IsWindowVisible(hwnd)) free_enum_strs(This);
770 break;
771 case WM_WINDOWPOSCHANGED:
773 WINDOWPOS *pos = (WINDOWPOS*)lParam;
775 if ((pos->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE) &&
776 This->hwndListBoxOwner && IsWindowVisible(This->hwndListBoxOwner))
777 show_listbox(This);
778 break;
780 case WM_KEYDOWN:
781 return ACEditSubclassProc_KeyDown(This, hwnd, uMsg, wParam, lParam);
782 case WM_CHAR:
783 case WM_UNICHAR:
784 if (wParam == This->no_fwd_char) return 0;
785 This->no_fwd_char = '\0';
787 /* Don't autocomplete at all on most control characters */
788 if (wParam < 32 && !(wParam >= '\b' && wParam <= '\r'))
789 break;
791 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
792 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' '
793 ? autoappend_flag_yes : autoappend_flag_no);
794 return ret;
795 case WM_SETTEXT:
796 if (This->options & ACO_AUTOSUGGEST)
797 hide_listbox(This, This->hwndListBox, TRUE);
798 break;
799 case WM_CUT:
800 case WM_CLEAR:
801 case WM_UNDO:
802 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
803 autocomplete_text(This, hwnd, autoappend_flag_no);
804 return ret;
805 case WM_PASTE:
806 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
807 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND)
808 ? autoappend_flag_yes : autoappend_flag_no);
809 return ret;
810 case WM_MOUSEWHEEL:
811 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
812 return SendMessageW(This->hwndListBox, WM_MOUSEWHEEL, wParam, lParam);
813 break;
814 case WM_SETFONT:
815 if (This->hwndListBox)
816 set_listbox_font(This, (HFONT)wParam);
817 break;
818 case WM_DESTROY:
820 WNDPROC proc = This->wpOrigEditProc;
822 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)proc);
823 RemovePropW(hwnd, autocomplete_propertyW);
824 destroy_autocomplete_object(This);
825 return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
828 return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
831 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
833 IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
834 WCHAR *msg;
835 INT sel;
837 switch (uMsg) {
838 case WM_MOUSEACTIVATE:
839 return MA_NOACTIVATE;
840 case WM_MOUSEMOVE:
841 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
842 SendMessageW(hwnd, LB_SETCURSEL, sel, 0);
843 return 0;
844 case WM_LBUTTONDOWN:
845 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
846 if (sel < 0)
847 return 0;
848 msg = This->listbox_strs[sel];
849 set_text_and_selection(This, This->hwndEdit, msg, 0, lstrlenW(msg));
850 hide_listbox(This, hwnd, TRUE);
851 return 0;
853 return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
856 static LRESULT APIENTRY ACLBoxOwnerSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
858 IAutoCompleteImpl *This = (IAutoCompleteImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
860 switch (uMsg)
862 case WM_MOUSEACTIVATE:
863 return MA_NOACTIVATE;
864 case WM_DRAWITEM:
865 if (draw_listbox_item(This, (DRAWITEMSTRUCT*)lParam, wParam))
866 return TRUE;
867 break;
868 case WM_SIZE:
869 SetWindowPos(This->hwndListBox, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam),
870 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_DEFERERASE);
871 break;
873 return CallWindowProcW(This->wpOrigLBoxOwnerProc, hwnd, uMsg, wParam, lParam);
876 static void create_listbox(IAutoCompleteImpl *This)
878 This->hwndListBoxOwner = CreateWindowExW(WS_EX_NOACTIVATE, WC_STATICW, NULL,
879 WS_BORDER | WS_POPUP | WS_CLIPCHILDREN,
880 0, 0, 0, 0, NULL, NULL, shell32_hInstance, NULL);
881 if (!This->hwndListBoxOwner)
883 This->options &= ~ACO_AUTOSUGGEST;
884 return;
887 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
888 This->hwndListBox = CreateWindowExW(WS_EX_NOACTIVATE, WC_LISTBOXW, NULL,
889 WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NODATA | LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT,
890 0, 0, 0, 0, This->hwndListBoxOwner, NULL, shell32_hInstance, NULL);
892 if (This->hwndListBox) {
893 HFONT edit_font;
895 This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
896 SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
898 This->wpOrigLBoxOwnerProc = (WNDPROC)SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_WNDPROC, (LONG_PTR)ACLBoxOwnerSubclassProc);
899 SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_USERDATA, (LONG_PTR)This);
901 /* Use the same font as the edit control, as it gets destroyed before it anyway */
902 edit_font = (HFONT)SendMessageW(This->hwndEdit, WM_GETFONT, 0, 0);
903 if (edit_font)
904 set_listbox_font(This, edit_font);
905 return;
908 DestroyWindow(This->hwndListBoxOwner);
909 This->hwndListBoxOwner = NULL;
910 This->options &= ~ACO_AUTOSUGGEST;
913 /**************************************************************************
914 * AutoComplete_QueryInterface
916 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
917 IAutoComplete2 * iface,
918 REFIID riid,
919 LPVOID *ppvObj)
921 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
923 TRACE("(%p)->(IID:%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
924 *ppvObj = NULL;
926 if (IsEqualIID(riid, &IID_IUnknown) ||
927 IsEqualIID(riid, &IID_IAutoComplete) ||
928 IsEqualIID(riid, &IID_IAutoComplete2))
930 *ppvObj = &This->IAutoComplete2_iface;
932 else if (IsEqualIID(riid, &IID_IAutoCompleteDropDown))
934 *ppvObj = &This->IAutoCompleteDropDown_iface;
937 if (*ppvObj)
939 IUnknown_AddRef((IUnknown*)*ppvObj);
940 TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
941 return S_OK;
943 WARN("unsupported interface: %s\n", debugstr_guid(riid));
944 return E_NOINTERFACE;
947 /******************************************************************************
948 * IAutoComplete2_fnAddRef
950 static ULONG WINAPI IAutoComplete2_fnAddRef(
951 IAutoComplete2 * iface)
953 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
954 ULONG refCount = InterlockedIncrement(&This->ref);
956 TRACE("(%p)->(%u)\n", This, refCount - 1);
958 return refCount;
961 /******************************************************************************
962 * IAutoComplete2_fnRelease
964 static ULONG WINAPI IAutoComplete2_fnRelease(
965 IAutoComplete2 * iface)
967 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
968 ULONG refCount = InterlockedDecrement(&This->ref);
970 TRACE("(%p)->(%u)\n", This, refCount + 1);
972 if (!refCount) {
973 TRACE("destroying IAutoComplete(%p)\n", This);
974 heap_free(This->quickComplete);
975 heap_free(This->txtbackup);
976 if (This->enumstr)
977 IEnumString_Release(This->enumstr);
978 if (This->aclist)
979 IACList_Release(This->aclist);
980 heap_free(This);
982 return refCount;
985 /******************************************************************************
986 * IAutoComplete2_fnEnable
988 static HRESULT WINAPI IAutoComplete2_fnEnable(
989 IAutoComplete2 * iface,
990 BOOL fEnable)
992 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
994 TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
996 This->enabled = fEnable;
998 return S_OK;
1001 /******************************************************************************
1002 * IAutoComplete2_fnInit
1004 static HRESULT WINAPI IAutoComplete2_fnInit(
1005 IAutoComplete2 * iface,
1006 HWND hwndEdit,
1007 IUnknown *punkACL,
1008 LPCOLESTR pwzsRegKeyPath,
1009 LPCOLESTR pwszQuickComplete)
1011 IAutoCompleteImpl *prev, *This = impl_from_IAutoComplete2(iface);
1013 TRACE("(%p)->(%p, %p, %s, %s)\n",
1014 This, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
1016 if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
1017 if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
1018 if (This->options & ACO_WORD_FILTER) FIXME(" ACO_WORD_FILTER not supported\n");
1020 if (!hwndEdit || !punkACL)
1021 return E_INVALIDARG;
1023 if (This->initialized)
1025 WARN("Autocompletion object is already initialized\n");
1026 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
1027 return This->hwndEdit ? E_FAIL : E_UNEXPECTED;
1030 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
1031 WARN("No IEnumString interface\n");
1032 return E_NOINTERFACE;
1035 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
1036 if ((This->txtbackup = heap_alloc_zero(sizeof(WCHAR))) == NULL)
1038 IEnumString_Release(This->enumstr);
1039 This->enumstr = NULL;
1040 return E_OUTOFMEMORY;
1043 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist)))
1044 This->aclist = NULL;
1046 This->initialized = TRUE;
1047 This->hwndEdit = hwndEdit;
1049 /* If another AutoComplete object was previously assigned to this edit control,
1050 release it but keep the same callback on the control, to avoid an infinite
1051 recursive loop in ACEditSubclassProc while the property is set to this object */
1052 prev = GetPropW(hwndEdit, autocomplete_propertyW);
1053 SetPropW(hwndEdit, autocomplete_propertyW, This);
1055 if (prev && prev->initialized) {
1056 This->wpOrigEditProc = prev->wpOrigEditProc;
1057 destroy_autocomplete_object(prev);
1059 else
1060 This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
1062 /* Keep at least one reference to the object until the edit window is destroyed */
1063 IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1065 if (This->options & ACO_AUTOSUGGEST)
1066 create_listbox(This);
1068 if (pwzsRegKeyPath)
1070 static const HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
1071 WCHAR *key, *value;
1072 DWORD type, sz;
1073 BYTE *qc;
1074 HKEY hKey;
1075 LSTATUS res;
1076 size_t len;
1077 UINT i;
1079 /* pwszRegKeyPath contains the key as well as the value, so split it */
1080 value = wcsrchr(pwzsRegKeyPath, '\\');
1081 len = value - pwzsRegKeyPath;
1083 if (value && (key = heap_alloc((len+1) * sizeof(*key))) != NULL)
1085 memcpy(key, pwzsRegKeyPath, len * sizeof(*key));
1086 key[len] = '\0';
1087 value++;
1089 for (i = 0; i < ARRAY_SIZE(roots); i++)
1091 if (RegOpenKeyExW(roots[i], key, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
1092 continue;
1093 sz = MAX_PATH * sizeof(WCHAR);
1095 while ((qc = heap_alloc(sz)) != NULL)
1097 res = RegQueryValueExW(hKey, value, NULL, &type, qc, &sz);
1098 if (res == ERROR_SUCCESS && type == REG_SZ)
1100 This->quickComplete = heap_realloc(qc, sz);
1101 i = ARRAY_SIZE(roots);
1102 break;
1104 heap_free(qc);
1105 if (res != ERROR_MORE_DATA || type != REG_SZ)
1106 break;
1108 RegCloseKey(hKey);
1110 heap_free(key);
1114 if (!This->quickComplete && pwszQuickComplete)
1116 size_t len = lstrlenW(pwszQuickComplete)+1;
1117 if ((This->quickComplete = heap_alloc(len * sizeof(WCHAR))) != NULL)
1118 memcpy(This->quickComplete, pwszQuickComplete, len * sizeof(WCHAR));
1121 return S_OK;
1124 /**************************************************************************
1125 * IAutoComplete2_fnGetOptions
1127 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
1128 IAutoComplete2 * iface,
1129 DWORD *pdwFlag)
1131 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1133 TRACE("(%p) -> (%p)\n", This, pdwFlag);
1135 *pdwFlag = This->options;
1137 return S_OK;
1140 /**************************************************************************
1141 * IAutoComplete2_fnSetOptions
1143 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
1144 IAutoComplete2 * iface,
1145 DWORD dwFlag)
1147 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1148 DWORD changed = This->options ^ dwFlag;
1149 HRESULT hr = S_OK;
1151 TRACE("(%p) -> (0x%x)\n", This, dwFlag);
1153 This->options = dwFlag;
1155 if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
1156 create_listbox(This);
1157 else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
1158 hide_listbox(This, This->hwndListBox, TRUE);
1160 /* If ACO_FILTERPREFIXES changed we might have to reset the enumerator */
1161 if ((changed & ACO_FILTERPREFIXES) && This->txtbackup)
1163 if (get_text_prefix_filtering(This->txtbackup) != prefix_filtering_none)
1164 IAutoCompleteDropDown_ResetEnumerator(&This->IAutoCompleteDropDown_iface);
1167 return hr;
1170 /**************************************************************************
1171 * IAutoComplete2 VTable
1173 static const IAutoComplete2Vtbl acvt =
1175 IAutoComplete2_fnQueryInterface,
1176 IAutoComplete2_fnAddRef,
1177 IAutoComplete2_fnRelease,
1178 IAutoComplete2_fnInit,
1179 IAutoComplete2_fnEnable,
1180 /* IAutoComplete2 */
1181 IAutoComplete2_fnSetOptions,
1182 IAutoComplete2_fnGetOptions,
1186 static HRESULT WINAPI IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown *iface,
1187 REFIID riid, LPVOID *ppvObj)
1189 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1190 return IAutoComplete2_QueryInterface(&This->IAutoComplete2_iface, riid, ppvObj);
1193 static ULONG WINAPI IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown *iface)
1195 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1196 return IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1199 static ULONG WINAPI IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown *iface)
1201 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1202 return IAutoComplete2_Release(&This->IAutoComplete2_iface);
1205 /**************************************************************************
1206 * IAutoCompleteDropDown_fnGetDropDownStatus
1208 static HRESULT WINAPI IAutoCompleteDropDown_fnGetDropDownStatus(
1209 IAutoCompleteDropDown *iface,
1210 DWORD *pdwFlags,
1211 LPWSTR *ppwszString)
1213 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1214 BOOL dropped;
1216 TRACE("(%p) -> (%p, %p)\n", This, pdwFlags, ppwszString);
1218 dropped = IsWindowVisible(This->hwndListBoxOwner);
1220 if (pdwFlags)
1221 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1223 if (ppwszString) {
1224 if (dropped) {
1225 int sel;
1227 sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
1228 if (sel >= 0)
1230 WCHAR *str = This->listbox_strs[sel];
1231 size_t size = (lstrlenW(str) + 1) * sizeof(*str);
1233 if (!(*ppwszString = CoTaskMemAlloc(size)))
1234 return E_OUTOFMEMORY;
1235 memcpy(*ppwszString, str, size);
1237 else
1238 *ppwszString = NULL;
1240 else
1241 *ppwszString = NULL;
1244 return S_OK;
1247 /**************************************************************************
1248 * IAutoCompleteDropDown_fnResetEnumarator
1250 static HRESULT WINAPI IAutoCompleteDropDown_fnResetEnumerator(
1251 IAutoCompleteDropDown *iface)
1253 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1255 TRACE("(%p)\n", This);
1257 if (This->hwndEdit)
1259 free_enum_strs(This);
1260 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
1261 autocomplete_text(This, This->hwndEdit, autoappend_flag_displayempty);
1263 return S_OK;
1266 /**************************************************************************
1267 * IAutoCompleteDropDown VTable
1269 static const IAutoCompleteDropDownVtbl acdropdownvt =
1271 IAutoCompleteDropDown_fnQueryInterface,
1272 IAutoCompleteDropDown_fnAddRef,
1273 IAutoCompleteDropDown_fnRelease,
1274 IAutoCompleteDropDown_fnGetDropDownStatus,
1275 IAutoCompleteDropDown_fnResetEnumerator,
1278 /**************************************************************************
1279 * IAutoComplete_Constructor
1281 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
1283 IAutoCompleteImpl *lpac;
1284 HRESULT hr;
1286 if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
1287 return CLASS_E_NOAGGREGATION;
1289 lpac = heap_alloc_zero(sizeof(*lpac));
1290 if (!lpac)
1291 return E_OUTOFMEMORY;
1293 lpac->ref = 1;
1294 lpac->IAutoComplete2_iface.lpVtbl = &acvt;
1295 lpac->IAutoCompleteDropDown_iface.lpVtbl = &acdropdownvt;
1296 lpac->enabled = TRUE;
1297 lpac->options = ACO_AUTOAPPEND;
1299 hr = IAutoComplete2_QueryInterface(&lpac->IAutoComplete2_iface, riid, ppv);
1300 IAutoComplete2_Release(&lpac->IAutoComplete2_iface);
1302 TRACE("-- (%p)->\n",lpac);
1304 return hr;