widl: Properly align name table entries.
[wine.git] / dlls / shell32 / autocomplete.c
blobe8b064c6dafb1e89a444b8a3479f30a9d8877c26
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 "shlwapi.h"
40 #include "winerror.h"
41 #include "objbase.h"
43 #include "pidl.h"
44 #include "shlobj.h"
45 #include "shldisp.h"
46 #include "debughlp.h"
47 #include "shell32_main.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(shell);
51 typedef struct
53 IAutoComplete2 IAutoComplete2_iface;
54 IAutoCompleteDropDown IAutoCompleteDropDown_iface;
55 LONG ref;
56 BOOL initialized;
57 BOOL enabled;
58 UINT enum_strs_num;
59 WCHAR **enum_strs;
60 WCHAR **listbox_strs;
61 HWND hwndEdit;
62 HWND hwndListBox;
63 HWND hwndListBoxOwner;
64 WNDPROC wpOrigEditProc;
65 WNDPROC wpOrigLBoxProc;
66 WNDPROC wpOrigLBoxOwnerProc;
67 WCHAR *txtbackup;
68 WCHAR *quickComplete;
69 IEnumString *enumstr;
70 IACList *aclist;
71 AUTOCOMPLETEOPTIONS options;
72 WCHAR no_fwd_char;
73 } IAutoCompleteImpl;
75 enum autoappend_flag
77 autoappend_flag_yes,
78 autoappend_flag_no,
79 autoappend_flag_displayempty
82 enum prefix_filtering
84 prefix_filtering_none = 0, /* no prefix filtering (raw search) */
85 prefix_filtering_protocol, /* filter common protocol (e.g. http://) */
86 prefix_filtering_all /* filter all common prefixes (protocol & www. ) */
89 static const WCHAR autocomplete_propertyW[] = L"Wine Autocomplete control";
91 static inline IAutoCompleteImpl *impl_from_IAutoComplete2(IAutoComplete2 *iface)
93 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoComplete2_iface);
96 static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown *iface)
98 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface);
101 static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end)
103 /* Send it directly to the edit control to match Windows behavior */
104 WNDPROC proc = ac->wpOrigEditProc;
105 if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text))
106 CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
109 static inline WCHAR *filter_protocol(WCHAR *str)
111 if (!wcsncmp(str, L"http", 4))
113 str += 4;
114 str += (*str == 's'); /* https */
115 if (str[0] == ':' && str[1] == '/' && str[2] == '/')
116 return str + 3;
118 return NULL;
121 static inline WCHAR *filter_www(WCHAR *str)
123 if (!wcsncmp(str, L"www.", 4)) return str + 4;
124 return NULL;
128 Get the prefix filtering based on text, for example if text's prefix
129 is a protocol, then we return none because we actually filter nothing
131 static enum prefix_filtering get_text_prefix_filtering(const WCHAR *text)
133 /* Convert to lowercase to perform case insensitive filtering,
134 using the longest possible prefix as the size of the buffer */
135 WCHAR buf[sizeof("https://")];
136 UINT i;
138 for (i = 0; i < ARRAY_SIZE(buf) - 1 && text[i]; i++)
139 buf[i] = towlower(text[i]);
140 buf[i] = '\0';
142 if (filter_protocol(buf)) return prefix_filtering_none;
143 if (filter_www(buf)) return prefix_filtering_protocol;
144 return prefix_filtering_all;
148 Filter the prefix of str based on the value of pfx_filter
149 This is used in sorting, so it's more performance sensitive
151 static WCHAR *filter_str_prefix(WCHAR *str, enum prefix_filtering pfx_filter)
153 WCHAR *p = str;
155 if (pfx_filter == prefix_filtering_none) return str;
156 if ((p = filter_protocol(str))) str = p;
158 if (pfx_filter == prefix_filtering_protocol) return str;
159 if ((p = filter_www(str))) str = p;
161 return str;
164 static inline int sort_strs_cmpfn_impl(WCHAR *a, WCHAR *b, enum prefix_filtering pfx_filter)
166 WCHAR *str1 = filter_str_prefix(a, pfx_filter);
167 WCHAR *str2 = filter_str_prefix(b, pfx_filter);
168 return wcsicmp(str1, str2);
171 static int __cdecl sort_strs_cmpfn_none(const void *a, const void *b)
173 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_none);
176 static int __cdecl sort_strs_cmpfn_protocol(const void *a, const void *b)
178 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_protocol);
181 static int __cdecl sort_strs_cmpfn_all(const void *a, const void *b)
183 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_all);
186 static int (* __cdecl sort_strs_cmpfn[])(const void*, const void*) =
188 sort_strs_cmpfn_none,
189 sort_strs_cmpfn_protocol,
190 sort_strs_cmpfn_all
193 static void sort_strs(WCHAR **strs, UINT numstrs, enum prefix_filtering pfx_filter)
195 qsort(strs, numstrs, sizeof(*strs), sort_strs_cmpfn[pfx_filter]);
199 Enumerate all of the strings and sort them in the internal list.
201 We don't free the enumerated strings (except on error) to avoid needless
202 copies, until the next reset (or the object itself is destroyed)
204 static void enumerate_strings(IAutoCompleteImpl *ac, enum prefix_filtering pfx_filter)
206 UINT cur = 0, array_size = 1024;
207 LPOLESTR *strs = NULL, *tmp;
208 ULONG read;
212 if ((tmp = realloc(strs, array_size * sizeof(*strs))) == NULL)
213 goto fail;
214 strs = tmp;
218 if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
219 read = 0;
220 } while (read != 0 && (cur += read) < array_size);
222 array_size *= 2;
223 } while (read != 0);
225 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
226 if ((tmp = realloc(strs, cur * sizeof(*strs))))
228 strs = tmp;
229 if (cur > 0)
230 sort_strs(strs, cur, pfx_filter);
232 ac->enum_strs = strs;
233 ac->enum_strs_num = cur;
234 return;
237 fail:
238 while (cur--)
239 CoTaskMemFree(strs[cur]);
240 free(strs);
243 static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
244 UINT len, enum prefix_filtering pfx_filter, int direction)
246 WCHAR **strs = ac->enum_strs;
247 UINT index = ~0, a = start, b = ac->enum_strs_num;
248 while (a < b)
250 UINT i = (a + b - 1) / 2;
251 int cmp = wcsnicmp(text, filter_str_prefix(strs[i], pfx_filter), len);
252 if (cmp == 0)
254 index = i;
255 cmp = direction;
257 if (cmp <= 0) b = i;
258 else a = i + 1;
260 return index;
263 static void free_enum_strs(IAutoCompleteImpl *ac)
265 WCHAR **strs = ac->enum_strs;
266 if (strs)
268 UINT i = ac->enum_strs_num;
269 ac->enum_strs = NULL;
270 while (i--)
271 CoTaskMemFree(strs[i]);
272 free(strs);
276 static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
278 ShowWindow(ac->hwndListBoxOwner, SW_HIDE);
279 SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
280 if (reset) free_enum_strs(ac);
283 static void show_listbox(IAutoCompleteImpl *ac)
285 RECT r;
286 UINT cnt, width, height;
288 GetWindowRect(ac->hwndEdit, &r);
290 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
291 cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
292 height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7);
293 width = r.right - r.left;
295 SetWindowPos(ac->hwndListBoxOwner, HWND_TOP, r.left, r.bottom + 1, width, height,
296 SWP_SHOWWINDOW | SWP_NOACTIVATE);
299 static void set_listbox_font(IAutoCompleteImpl *ac, HFONT font)
301 /* We have to calculate the item height manually due to owner-drawn */
302 HFONT old_font = NULL;
303 UINT height = 16;
304 HDC hdc;
306 if ((hdc = GetDCEx(ac->hwndListBox, 0, DCX_CACHE)))
308 TEXTMETRICW metrics;
309 if (font) old_font = SelectObject(hdc, font);
310 if (GetTextMetricsW(hdc, &metrics))
311 height = metrics.tmHeight;
312 if (old_font) SelectObject(hdc, old_font);
313 ReleaseDC(ac->hwndListBox, hdc);
315 SendMessageW(ac->hwndListBox, WM_SETFONT, (WPARAM)font, FALSE);
316 SendMessageW(ac->hwndListBox, LB_SETITEMHEIGHT, 0, height);
319 static BOOL draw_listbox_item(IAutoCompleteImpl *ac, DRAWITEMSTRUCT *info, UINT id)
321 COLORREF old_text, old_bk;
322 HDC hdc = info->hDC;
323 UINT state;
324 WCHAR *str;
326 if (info->CtlType != ODT_LISTBOX || info->CtlID != id ||
327 id != (UINT)GetWindowLongPtrW(ac->hwndListBox, GWLP_ID))
328 return FALSE;
330 if ((INT)info->itemID < 0 || info->itemAction == ODA_FOCUS)
331 return TRUE;
333 state = info->itemState;
334 if (state & ODS_SELECTED)
336 old_bk = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
337 old_text = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
340 str = ac->listbox_strs[info->itemID];
341 ExtTextOutW(hdc, info->rcItem.left + 1, info->rcItem.top,
342 ETO_OPAQUE | ETO_CLIPPED, &info->rcItem, str,
343 lstrlenW(str), NULL);
345 if (state & ODS_SELECTED)
347 SetBkColor(hdc, old_bk);
348 SetTextColor(hdc, old_text);
350 return TRUE;
353 static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len)
355 /* Replace the first %s directly without using snprintf, to avoid
356 exploits since the format string can be retrieved from the registry */
357 WCHAR *base = dst;
358 UINT args = 0;
359 while (*qc != '\0')
361 if (qc[0] == '%')
363 if (args < 1 && qc[1] == 's')
365 memcpy(dst, str, str_len * sizeof(WCHAR));
366 dst += str_len;
367 qc += 2;
368 args++;
369 continue;
371 qc += (qc[1] == '%');
373 *dst++ = *qc++;
375 *dst = '\0';
376 return dst - base;
379 static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
381 WCHAR *text;
382 HWND hwndListBox = ac->hwndListBox;
383 if (!(ac->options & ACO_AUTOSUGGEST))
384 return FALSE;
386 if (IsWindowVisible(ac->hwndListBoxOwner))
388 INT sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0);
389 if (sel >= 0)
391 text = ac->listbox_strs[sel];
392 set_text_and_selection(ac, hwnd, text, 0, lstrlenW(text));
393 hide_listbox(ac, hwndListBox, TRUE);
394 ac->no_fwd_char = '\r'; /* RETURN char */
395 return TRUE;
398 hide_listbox(ac, hwndListBox, TRUE);
399 return FALSE;
402 static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
404 WCHAR *msg;
405 UINT len;
407 INT count = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
408 INT sel = SendMessageW(ac->hwndListBox, LB_GETCURSEL, 0, 0);
409 if (key == VK_PRIOR || key == VK_NEXT)
411 if (sel < 0)
412 sel = (key == VK_PRIOR) ? count - 1 : 0;
413 else
415 INT base = SendMessageW(ac->hwndListBox, LB_GETTOPINDEX, 0, 0);
416 INT pgsz = SendMessageW(ac->hwndListBox, LB_GETLISTBOXINFO, 0, 0);
417 pgsz = max(pgsz - 1, 1);
418 if (key == VK_PRIOR)
420 if (sel == 0)
421 sel = -1;
422 else
424 if (sel == base) base -= min(base, pgsz);
425 sel = base;
428 else
430 if (sel == count - 1)
431 sel = -1;
432 else
434 base += pgsz;
435 if (sel >= base) base += pgsz;
436 sel = min(base, count - 1);
441 else if (key == VK_UP || (key == VK_TAB && (GetKeyState(VK_SHIFT) & 0x8000)))
442 sel = ((sel - 1) < -1) ? count - 1 : sel - 1;
443 else
444 sel = ((sel + 1) >= count) ? -1 : sel + 1;
446 SendMessageW(ac->hwndListBox, LB_SETCURSEL, sel, 0);
448 msg = (sel >= 0) ? ac->listbox_strs[sel] : ac->txtbackup;
449 len = lstrlenW(msg);
450 set_text_and_selection(ac, hwnd, msg, len, len);
452 return 0;
455 static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
457 WCHAR c = last_delim[1];
459 free_enum_strs(ac);
460 IEnumString_Reset(ac->enumstr); /* call before expand */
462 last_delim[1] = '\0';
463 IACList_Expand(ac->aclist, txt);
464 last_delim[1] = c;
465 return TRUE;
468 static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
470 /* call IACList::Expand only when needed, if the
471 new txt and old_txt require different expansions */
473 const WCHAR *old_txt = ac->txtbackup;
474 WCHAR c, *p, *last_delim;
475 size_t i = 0;
477 /* always expand if the enumerator was reset */
478 if (!ac->enum_strs) old_txt = L"";
480 /* skip the shared prefix */
481 while ((c = towlower(txt[i])) == towlower(old_txt[i]))
483 if (c == '\0') return FALSE;
484 i++;
487 /* they differ at this point, check for a delim further in txt */
488 for (last_delim = NULL, p = &txt[i]; (p = wcspbrk(p, L"\\/")) != NULL; p++)
489 last_delim = p;
490 if (last_delim) return do_aclist_expand(ac, txt, last_delim);
492 /* txt has no delim after i, check for a delim further in old_txt */
493 if (wcspbrk(&old_txt[i], L"\\/"))
495 /* scan backwards to find the first delim before txt[i] (if any) */
496 while (i--)
497 if (wcschr(L"\\/", txt[i]))
498 return do_aclist_expand(ac, txt, &txt[i]);
500 /* Windows doesn't expand without a delim, but it does reset */
501 free_enum_strs(ac);
504 return FALSE;
507 static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR *str, HWND hwnd)
509 DWORD sel_start;
510 WCHAR *tmp;
511 size_t size;
513 /* Don't auto-append unless the caret is at the end */
514 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&sel_start, 0);
515 if (sel_start != len)
516 return;
518 /* The character capitalization can be different,
519 so merge text and str into a new string */
520 size = len + lstrlenW(&str[len]) + 1;
522 if ((tmp = malloc(size * sizeof(*tmp))))
524 memcpy(tmp, text, len * sizeof(*tmp));
525 memcpy(&tmp[len], &str[len], (size - len) * sizeof(*tmp));
527 else tmp = str;
529 set_text_and_selection(ac, hwnd, tmp, len, size - 1);
530 if (tmp != str)
531 free(tmp);
534 static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
535 enum prefix_filtering pfx_filter, HWND hwnd,
536 enum autoappend_flag flag)
538 /* Return FALSE if we need to hide the listbox */
539 WCHAR **str = ac->enum_strs;
540 UINT start, end;
541 if (!str) return !(ac->options & ACO_AUTOSUGGEST);
543 /* Windows seems to disable autoappend if ACO_NOPREFIXFILTERING is set */
544 if (!(ac->options & ACO_NOPREFIXFILTERING) && len)
546 start = find_matching_enum_str(ac, 0, text, len, pfx_filter, -1);
547 if (start == ~0)
548 return !(ac->options & ACO_AUTOSUGGEST);
550 if (flag == autoappend_flag_yes)
551 autoappend_str(ac, text, len, filter_str_prefix(str[start], pfx_filter), hwnd);
552 if (!(ac->options & ACO_AUTOSUGGEST))
553 return TRUE;
555 /* Find the index beyond the last string that matches */
556 end = find_matching_enum_str(ac, start + 1, text, len, pfx_filter, 1);
557 end = (end == ~0 ? start : end) + 1;
559 else
561 if (!(ac->options & ACO_AUTOSUGGEST))
562 return TRUE;
563 start = 0;
564 end = ac->enum_strs_num;
565 if (end == 0)
566 return FALSE;
569 SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
570 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
572 ac->listbox_strs = str + start;
573 SendMessageW(ac->hwndListBox, LB_SETCOUNT, end - start, 0);
575 show_listbox(ac);
576 SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
577 return TRUE;
580 static enum prefix_filtering setup_prefix_filtering(IAutoCompleteImpl *ac, const WCHAR *text)
582 enum prefix_filtering pfx_filter;
583 if (!(ac->options & ACO_FILTERPREFIXES)) return prefix_filtering_none;
585 pfx_filter = get_text_prefix_filtering(text);
586 if (!ac->enum_strs) return pfx_filter;
588 /* If the prefix filtering is different, re-sort the filtered strings */
589 if (pfx_filter != get_text_prefix_filtering(ac->txtbackup))
590 sort_strs(ac->enum_strs, ac->enum_strs_num, pfx_filter);
592 return pfx_filter;
595 static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
597 WCHAR *text;
598 BOOL expanded = FALSE;
599 enum prefix_filtering pfx_filter;
600 UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
602 if (flag != autoappend_flag_displayempty && len == 0)
604 if (ac->options & ACO_AUTOSUGGEST)
605 hide_listbox(ac, ac->hwndListBox, FALSE);
606 free_enum_strs(ac);
607 return;
610 size = len + 1;
611 if (!(text = malloc(size * sizeof(WCHAR))))
613 /* Reset the listbox to prevent potential crash from ResetEnumerator */
614 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
615 return;
617 len = SendMessageW(hwnd, WM_GETTEXT, size, (LPARAM)text);
618 if (len + 1 != size)
619 text = realloc(text, (len + 1) * sizeof(WCHAR));
621 if (ac->aclist)
623 if (text[len - 1] == '\\' || text[len - 1] == '/')
624 flag = autoappend_flag_no;
625 expanded = aclist_expand(ac, text);
627 pfx_filter = setup_prefix_filtering(ac, text);
629 if (expanded || !ac->enum_strs)
631 if (!expanded) IEnumString_Reset(ac->enumstr);
632 enumerate_strings(ac, pfx_filter);
635 /* Set txtbackup to point to text itself (which must not be released),
636 and it must be done here since aclist_expand uses it to track changes */
637 free(ac->txtbackup);
638 ac->txtbackup = text;
640 if (!display_matching_strs(ac, text, len, pfx_filter, hwnd, flag))
641 hide_listbox(ac, ac->hwndListBox, FALSE);
644 static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
646 ac->hwndEdit = NULL;
647 free_enum_strs(ac);
648 if (ac->hwndListBoxOwner)
649 DestroyWindow(ac->hwndListBoxOwner);
650 IAutoComplete2_Release(&ac->IAutoComplete2_iface);
654 Helper for ACEditSubclassProc
656 static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT uMsg,
657 WPARAM wParam, LPARAM lParam)
659 switch (wParam)
661 case VK_ESCAPE:
662 /* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
663 if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBoxOwner))
665 hide_listbox(ac, ac->hwndListBox, FALSE);
666 ac->no_fwd_char = 0x1B; /* ESC char */
667 return 0;
669 break;
670 case VK_RETURN:
671 /* If quickComplete is set and control is pressed, replace the string */
672 if (ac->quickComplete && (GetKeyState(VK_CONTROL) & 0x8000))
674 WCHAR *text, *buf;
675 size_t sz;
676 UINT len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
677 ac->no_fwd_char = '\n'; /* CTRL+RETURN char */
679 if (!(text = malloc((len + 1) * sizeof(WCHAR))))
680 return 0;
681 len = SendMessageW(hwnd, WM_GETTEXT, len + 1, (LPARAM)text);
682 sz = lstrlenW(ac->quickComplete) + 1 + len;
684 if ((buf = malloc(sz * sizeof(WCHAR))))
686 len = format_quick_complete(buf, ac->quickComplete, text, len);
687 set_text_and_selection(ac, hwnd, buf, 0, len);
688 free(buf);
691 if (ac->options & ACO_AUTOSUGGEST)
692 hide_listbox(ac, ac->hwndListBox, TRUE);
693 free(text);
694 return 0;
697 if (select_item_with_return_key(ac, hwnd))
698 return 0;
699 break;
700 case VK_TAB:
701 if ((ac->options & (ACO_AUTOSUGGEST | ACO_USETAB)) == (ACO_AUTOSUGGEST | ACO_USETAB)
702 && IsWindowVisible(ac->hwndListBoxOwner) && !(GetKeyState(VK_CONTROL) & 0x8000))
704 ac->no_fwd_char = '\t';
705 return change_selection(ac, hwnd, wParam);
707 break;
708 case VK_UP:
709 case VK_DOWN:
710 case VK_PRIOR:
711 case VK_NEXT:
712 /* Two cases here:
713 - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
714 set, display it with all the entries, without selecting any
715 - if the listbox is visible, change the selection
717 if (!(ac->options & ACO_AUTOSUGGEST))
718 break;
720 if (!IsWindowVisible(ac->hwndListBoxOwner))
722 if (ac->options & ACO_UPDOWNKEYDROPSLIST)
724 autocomplete_text(ac, hwnd, autoappend_flag_displayempty);
725 return 0;
728 else
729 return change_selection(ac, hwnd, wParam);
730 break;
731 case VK_DELETE:
733 LRESULT ret = CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
734 autocomplete_text(ac, hwnd, autoappend_flag_no);
735 return ret;
738 ac->no_fwd_char = '\0';
739 return CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
743 Window procedure for autocompletion
745 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
747 IAutoCompleteImpl *This = GetPropW(hwnd, autocomplete_propertyW);
748 LRESULT ret;
750 if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
752 switch (uMsg)
754 case CB_SHOWDROPDOWN:
755 if (This->options & ACO_AUTOSUGGEST)
756 hide_listbox(This, This->hwndListBox, TRUE);
757 return 0;
758 case WM_KILLFOCUS:
759 if (This->options & ACO_AUTOSUGGEST)
761 if (This->hwndListBoxOwner == (HWND)wParam ||
762 This->hwndListBoxOwner == GetAncestor((HWND)wParam, GA_PARENT))
763 break;
764 hide_listbox(This, This->hwndListBox, FALSE);
767 /* Reset the enumerator if it's not visible anymore */
768 if (!IsWindowVisible(hwnd)) free_enum_strs(This);
769 break;
770 case WM_WINDOWPOSCHANGED:
772 WINDOWPOS *pos = (WINDOWPOS*)lParam;
774 if ((pos->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE) &&
775 This->hwndListBoxOwner && IsWindowVisible(This->hwndListBoxOwner))
776 show_listbox(This);
777 break;
779 case WM_KEYDOWN:
780 return ACEditSubclassProc_KeyDown(This, hwnd, uMsg, wParam, lParam);
781 case WM_CHAR:
782 case WM_UNICHAR:
783 if (wParam == This->no_fwd_char) return 0;
784 This->no_fwd_char = '\0';
786 /* Don't autocomplete at all on most control characters */
787 if (wParam < 32 && !(wParam >= '\b' && wParam <= '\r'))
788 break;
790 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
791 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' '
792 ? autoappend_flag_yes : autoappend_flag_no);
793 return ret;
794 case WM_SETTEXT:
795 if (This->options & ACO_AUTOSUGGEST)
796 hide_listbox(This, This->hwndListBox, TRUE);
797 break;
798 case WM_CUT:
799 case WM_CLEAR:
800 case WM_UNDO:
801 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
802 autocomplete_text(This, hwnd, autoappend_flag_no);
803 return ret;
804 case WM_PASTE:
805 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
806 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND)
807 ? autoappend_flag_yes : autoappend_flag_no);
808 return ret;
809 case WM_MOUSEWHEEL:
810 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
811 return SendMessageW(This->hwndListBox, WM_MOUSEWHEEL, wParam, lParam);
812 break;
813 case WM_SETFONT:
814 if (This->hwndListBox)
815 set_listbox_font(This, (HFONT)wParam);
816 break;
817 case WM_DESTROY:
819 WNDPROC proc = This->wpOrigEditProc;
821 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)proc);
822 RemovePropW(hwnd, autocomplete_propertyW);
823 destroy_autocomplete_object(This);
824 return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
827 return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
830 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
832 IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
833 WCHAR *msg;
834 INT sel;
836 switch (uMsg) {
837 case WM_MOUSEACTIVATE:
838 return MA_NOACTIVATE;
839 case WM_MOUSEMOVE:
840 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
841 SendMessageW(hwnd, LB_SETCURSEL, sel, 0);
842 return 0;
843 case WM_LBUTTONDOWN:
844 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
845 if (sel < 0)
846 return 0;
847 msg = This->listbox_strs[sel];
848 set_text_and_selection(This, This->hwndEdit, msg, 0, lstrlenW(msg));
849 hide_listbox(This, hwnd, TRUE);
850 return 0;
852 return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
855 static LRESULT APIENTRY ACLBoxOwnerSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
857 IAutoCompleteImpl *This = (IAutoCompleteImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
859 switch (uMsg)
861 case WM_MOUSEACTIVATE:
862 return MA_NOACTIVATE;
863 case WM_DRAWITEM:
864 if (draw_listbox_item(This, (DRAWITEMSTRUCT*)lParam, wParam))
865 return TRUE;
866 break;
867 case WM_SIZE:
868 SetWindowPos(This->hwndListBox, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam),
869 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_DEFERERASE);
870 break;
872 return CallWindowProcW(This->wpOrigLBoxOwnerProc, hwnd, uMsg, wParam, lParam);
875 static void create_listbox(IAutoCompleteImpl *This)
877 This->hwndListBoxOwner = CreateWindowExW(WS_EX_NOACTIVATE, WC_STATICW, NULL,
878 WS_BORDER | WS_POPUP | WS_CLIPCHILDREN,
879 0, 0, 0, 0, NULL, NULL, shell32_hInstance, NULL);
880 if (!This->hwndListBoxOwner)
882 This->options &= ~ACO_AUTOSUGGEST;
883 return;
886 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
887 This->hwndListBox = CreateWindowExW(WS_EX_NOACTIVATE, WC_LISTBOXW, NULL,
888 WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NODATA | LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT,
889 0, 0, 0, 0, This->hwndListBoxOwner, NULL, shell32_hInstance, NULL);
891 if (This->hwndListBox) {
892 HFONT edit_font;
894 This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
895 SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
897 This->wpOrigLBoxOwnerProc = (WNDPROC)SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_WNDPROC, (LONG_PTR)ACLBoxOwnerSubclassProc);
898 SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_USERDATA, (LONG_PTR)This);
900 /* Use the same font as the edit control, as it gets destroyed before it anyway */
901 edit_font = (HFONT)SendMessageW(This->hwndEdit, WM_GETFONT, 0, 0);
902 if (edit_font)
903 set_listbox_font(This, edit_font);
904 return;
907 DestroyWindow(This->hwndListBoxOwner);
908 This->hwndListBoxOwner = NULL;
909 This->options &= ~ACO_AUTOSUGGEST;
912 /**************************************************************************
913 * AutoComplete_QueryInterface
915 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
916 IAutoComplete2 * iface,
917 REFIID riid,
918 LPVOID *ppvObj)
920 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
922 TRACE("(%p)->(IID:%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
923 *ppvObj = NULL;
925 if (IsEqualIID(riid, &IID_IUnknown) ||
926 IsEqualIID(riid, &IID_IAutoComplete) ||
927 IsEqualIID(riid, &IID_IAutoComplete2))
929 *ppvObj = &This->IAutoComplete2_iface;
931 else if (IsEqualIID(riid, &IID_IAutoCompleteDropDown))
933 *ppvObj = &This->IAutoCompleteDropDown_iface;
936 if (*ppvObj)
938 IUnknown_AddRef((IUnknown*)*ppvObj);
939 TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
940 return S_OK;
942 WARN("unsupported interface: %s\n", debugstr_guid(riid));
943 return E_NOINTERFACE;
946 /******************************************************************************
947 * IAutoComplete2_fnAddRef
949 static ULONG WINAPI IAutoComplete2_fnAddRef(
950 IAutoComplete2 * iface)
952 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
953 ULONG refCount = InterlockedIncrement(&This->ref);
955 TRACE("(%p)->(%lu)\n", This, refCount - 1);
957 return refCount;
960 /******************************************************************************
961 * IAutoComplete2_fnRelease
963 static ULONG WINAPI IAutoComplete2_fnRelease(
964 IAutoComplete2 * iface)
966 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
967 ULONG refCount = InterlockedDecrement(&This->ref);
969 TRACE("(%p)->(%lu)\n", This, refCount + 1);
971 if (!refCount) {
972 TRACE("destroying IAutoComplete(%p)\n", This);
973 free(This->quickComplete);
974 free(This->txtbackup);
975 if (This->enumstr)
976 IEnumString_Release(This->enumstr);
977 if (This->aclist)
978 IACList_Release(This->aclist);
979 free(This);
981 return refCount;
984 /******************************************************************************
985 * IAutoComplete2_fnEnable
987 static HRESULT WINAPI IAutoComplete2_fnEnable(
988 IAutoComplete2 * iface,
989 BOOL fEnable)
991 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
993 TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
995 This->enabled = fEnable;
997 return S_OK;
1000 /******************************************************************************
1001 * IAutoComplete2_fnInit
1003 static HRESULT WINAPI IAutoComplete2_fnInit(
1004 IAutoComplete2 * iface,
1005 HWND hwndEdit,
1006 IUnknown *punkACL,
1007 LPCOLESTR pwzsRegKeyPath,
1008 LPCOLESTR pwszQuickComplete)
1010 IAutoCompleteImpl *prev, *This = impl_from_IAutoComplete2(iface);
1012 TRACE("(%p)->(%p, %p, %s, %s)\n",
1013 This, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
1015 if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
1016 if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
1017 if (This->options & ACO_WORD_FILTER) FIXME(" ACO_WORD_FILTER not supported\n");
1019 if (!hwndEdit || !punkACL)
1020 return E_INVALIDARG;
1022 if (This->initialized)
1024 WARN("Autocompletion object is already initialized\n");
1025 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
1026 return This->hwndEdit ? E_FAIL : E_UNEXPECTED;
1029 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
1030 WARN("No IEnumString interface\n");
1031 return E_NOINTERFACE;
1034 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
1035 if ((This->txtbackup = calloc(1, sizeof(WCHAR))) == NULL)
1037 IEnumString_Release(This->enumstr);
1038 This->enumstr = NULL;
1039 return E_OUTOFMEMORY;
1042 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist)))
1043 This->aclist = NULL;
1045 This->initialized = TRUE;
1046 This->hwndEdit = hwndEdit;
1048 /* If another AutoComplete object was previously assigned to this edit control,
1049 release it but keep the same callback on the control, to avoid an infinite
1050 recursive loop in ACEditSubclassProc while the property is set to this object */
1051 prev = GetPropW(hwndEdit, autocomplete_propertyW);
1052 SetPropW(hwndEdit, autocomplete_propertyW, This);
1054 if (prev && prev->initialized) {
1055 This->wpOrigEditProc = prev->wpOrigEditProc;
1056 destroy_autocomplete_object(prev);
1058 else
1059 This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
1061 /* Keep at least one reference to the object until the edit window is destroyed */
1062 IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1064 if (This->options & ACO_AUTOSUGGEST)
1065 create_listbox(This);
1067 if (pwzsRegKeyPath)
1069 static const HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
1070 WCHAR *key, *value;
1071 DWORD type, sz;
1072 BYTE *qc;
1073 HKEY hKey;
1074 LSTATUS res;
1075 size_t len;
1076 UINT i;
1078 /* pwszRegKeyPath contains the key as well as the value, so split it */
1079 value = wcsrchr(pwzsRegKeyPath, '\\');
1080 len = value - pwzsRegKeyPath;
1082 if (value && (key = malloc((len + 1) * sizeof(*key))) != NULL)
1084 memcpy(key, pwzsRegKeyPath, len * sizeof(*key));
1085 key[len] = '\0';
1086 value++;
1088 for (i = 0; i < ARRAY_SIZE(roots); i++)
1090 if (RegOpenKeyExW(roots[i], key, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
1091 continue;
1092 sz = MAX_PATH * sizeof(WCHAR);
1094 while ((qc = malloc(sz)) != NULL)
1096 res = RegQueryValueExW(hKey, value, NULL, &type, qc, &sz);
1097 if (res == ERROR_SUCCESS && type == REG_SZ)
1099 This->quickComplete = realloc(qc, sz);
1100 i = ARRAY_SIZE(roots);
1101 break;
1103 free(qc);
1104 if (res != ERROR_MORE_DATA || type != REG_SZ)
1105 break;
1107 RegCloseKey(hKey);
1109 free(key);
1113 if (!This->quickComplete && pwszQuickComplete)
1115 size_t len = lstrlenW(pwszQuickComplete)+1;
1116 if ((This->quickComplete = malloc(len * sizeof(WCHAR))) != NULL)
1117 memcpy(This->quickComplete, pwszQuickComplete, len * sizeof(WCHAR));
1120 return S_OK;
1123 /**************************************************************************
1124 * IAutoComplete2_fnGetOptions
1126 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
1127 IAutoComplete2 * iface,
1128 DWORD *pdwFlag)
1130 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1132 TRACE("(%p) -> (%p)\n", This, pdwFlag);
1134 *pdwFlag = This->options;
1136 return S_OK;
1139 /**************************************************************************
1140 * IAutoComplete2_fnSetOptions
1142 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
1143 IAutoComplete2 * iface,
1144 DWORD dwFlag)
1146 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1147 DWORD changed = This->options ^ dwFlag;
1148 HRESULT hr = S_OK;
1150 TRACE("(%p) -> (0x%lx)\n", This, dwFlag);
1152 This->options = dwFlag;
1154 if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
1155 create_listbox(This);
1156 else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
1157 hide_listbox(This, This->hwndListBox, TRUE);
1159 /* If ACO_FILTERPREFIXES changed we might have to reset the enumerator */
1160 if ((changed & ACO_FILTERPREFIXES) && This->txtbackup)
1162 if (get_text_prefix_filtering(This->txtbackup) != prefix_filtering_none)
1163 IAutoCompleteDropDown_ResetEnumerator(&This->IAutoCompleteDropDown_iface);
1166 return hr;
1169 /**************************************************************************
1170 * IAutoComplete2 VTable
1172 static const IAutoComplete2Vtbl acvt =
1174 IAutoComplete2_fnQueryInterface,
1175 IAutoComplete2_fnAddRef,
1176 IAutoComplete2_fnRelease,
1177 IAutoComplete2_fnInit,
1178 IAutoComplete2_fnEnable,
1179 /* IAutoComplete2 */
1180 IAutoComplete2_fnSetOptions,
1181 IAutoComplete2_fnGetOptions,
1185 static HRESULT WINAPI IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown *iface,
1186 REFIID riid, LPVOID *ppvObj)
1188 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1189 return IAutoComplete2_QueryInterface(&This->IAutoComplete2_iface, riid, ppvObj);
1192 static ULONG WINAPI IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown *iface)
1194 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1195 return IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1198 static ULONG WINAPI IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown *iface)
1200 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1201 return IAutoComplete2_Release(&This->IAutoComplete2_iface);
1204 /**************************************************************************
1205 * IAutoCompleteDropDown_fnGetDropDownStatus
1207 static HRESULT WINAPI IAutoCompleteDropDown_fnGetDropDownStatus(
1208 IAutoCompleteDropDown *iface,
1209 DWORD *pdwFlags,
1210 LPWSTR *ppwszString)
1212 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1213 BOOL dropped;
1215 TRACE("(%p) -> (%p, %p)\n", This, pdwFlags, ppwszString);
1217 dropped = IsWindowVisible(This->hwndListBoxOwner);
1219 if (pdwFlags)
1220 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1222 if (ppwszString) {
1223 if (dropped) {
1224 int sel;
1226 sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
1227 if (sel >= 0)
1229 WCHAR *str = This->listbox_strs[sel];
1230 size_t size = (lstrlenW(str) + 1) * sizeof(*str);
1232 if (!(*ppwszString = CoTaskMemAlloc(size)))
1233 return E_OUTOFMEMORY;
1234 memcpy(*ppwszString, str, size);
1236 else
1237 *ppwszString = NULL;
1239 else
1240 *ppwszString = NULL;
1243 return S_OK;
1246 /**************************************************************************
1247 * IAutoCompleteDropDown_fnResetEnumarator
1249 static HRESULT WINAPI IAutoCompleteDropDown_fnResetEnumerator(
1250 IAutoCompleteDropDown *iface)
1252 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1254 TRACE("(%p)\n", This);
1256 if (This->hwndEdit)
1258 free_enum_strs(This);
1259 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
1260 autocomplete_text(This, This->hwndEdit, autoappend_flag_displayempty);
1262 return S_OK;
1265 /**************************************************************************
1266 * IAutoCompleteDropDown VTable
1268 static const IAutoCompleteDropDownVtbl acdropdownvt =
1270 IAutoCompleteDropDown_fnQueryInterface,
1271 IAutoCompleteDropDown_fnAddRef,
1272 IAutoCompleteDropDown_fnRelease,
1273 IAutoCompleteDropDown_fnGetDropDownStatus,
1274 IAutoCompleteDropDown_fnResetEnumerator,
1277 /**************************************************************************
1278 * IAutoComplete_Constructor
1280 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
1282 IAutoCompleteImpl *lpac;
1283 HRESULT hr;
1285 if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
1286 return CLASS_E_NOAGGREGATION;
1288 lpac = calloc(1, sizeof(*lpac));
1289 if (!lpac)
1290 return E_OUTOFMEMORY;
1292 lpac->ref = 1;
1293 lpac->IAutoComplete2_iface.lpVtbl = &acvt;
1294 lpac->IAutoCompleteDropDown_iface.lpVtbl = &acdropdownvt;
1295 lpac->enabled = TRUE;
1296 lpac->options = ACO_AUTOAPPEND;
1298 hr = IAutoComplete2_QueryInterface(&lpac->IAutoComplete2_iface, riid, ppv);
1299 IAutoComplete2_Release(&lpac->IAutoComplete2_iface);
1301 TRACE("-- (%p)->\n",lpac);
1303 return hr;