cmd: Use wide-char string literals.
[wine/zf.git] / dlls / shell32 / autocomplete.c
blobf0777ff1398f7229989d98095a47939ac091b00d
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
28 #include "config.h"
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <string.h>
34 #define COBJMACROS
36 #include "wine/debug.h"
37 #include "windef.h"
38 #include "winbase.h"
39 #include "winreg.h"
40 #include "undocshell.h"
41 #include "shlwapi.h"
42 #include "winerror.h"
43 #include "objbase.h"
45 #include "pidl.h"
46 #include "shlobj.h"
47 #include "shldisp.h"
48 #include "debughlp.h"
49 #include "shell32_main.h"
51 #include "wine/unicode.h"
53 WINE_DEFAULT_DEBUG_CHANNEL(shell);
55 typedef struct
57 IAutoComplete2 IAutoComplete2_iface;
58 IAutoCompleteDropDown IAutoCompleteDropDown_iface;
59 LONG ref;
60 BOOL initialized;
61 BOOL enabled;
62 UINT enum_strs_num;
63 WCHAR **enum_strs;
64 WCHAR **listbox_strs;
65 HWND hwndEdit;
66 HWND hwndListBox;
67 HWND hwndListBoxOwner;
68 WNDPROC wpOrigEditProc;
69 WNDPROC wpOrigLBoxProc;
70 WNDPROC wpOrigLBoxOwnerProc;
71 WCHAR *txtbackup;
72 WCHAR *quickComplete;
73 IEnumString *enumstr;
74 IACList *aclist;
75 AUTOCOMPLETEOPTIONS options;
76 WCHAR no_fwd_char;
77 } IAutoCompleteImpl;
79 enum autoappend_flag
81 autoappend_flag_yes,
82 autoappend_flag_no,
83 autoappend_flag_displayempty
86 enum prefix_filtering
88 prefix_filtering_none = 0, /* no prefix filtering (raw search) */
89 prefix_filtering_protocol, /* filter common protocol (e.g. http://) */
90 prefix_filtering_all /* filter all common prefixes (protocol & www. ) */
93 static const WCHAR autocomplete_propertyW[] = {'W','i','n','e',' ','A','u','t','o',
94 'c','o','m','p','l','e','t','e',' ',
95 'c','o','n','t','r','o','l',0};
97 static inline IAutoCompleteImpl *impl_from_IAutoComplete2(IAutoComplete2 *iface)
99 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoComplete2_iface);
102 static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown *iface)
104 return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface);
107 static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end)
109 /* Send it directly to the edit control to match Windows behavior */
110 WNDPROC proc = ac->wpOrigEditProc;
111 if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text))
112 CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
115 static inline WCHAR *filter_protocol(WCHAR *str)
117 static const WCHAR http[] = {'h','t','t','p'};
119 if (!strncmpW(str, http, ARRAY_SIZE(http)))
121 str += ARRAY_SIZE(http);
122 str += (*str == 's'); /* https */
123 if (str[0] == ':' && str[1] == '/' && str[2] == '/')
124 return str + 3;
126 return NULL;
129 static inline WCHAR *filter_www(WCHAR *str)
131 static const WCHAR www[] = {'w','w','w','.'};
133 if (!strncmpW(str, www, ARRAY_SIZE(www)))
134 return str + ARRAY_SIZE(www);
135 return NULL;
139 Get the prefix filtering based on text, for example if text's prefix
140 is a protocol, then we return none because we actually filter nothing
142 static enum prefix_filtering get_text_prefix_filtering(const WCHAR *text)
144 /* Convert to lowercase to perform case insensitive filtering,
145 using the longest possible prefix as the size of the buffer */
146 WCHAR buf[sizeof("https://")];
147 UINT i;
149 for (i = 0; i < ARRAY_SIZE(buf) - 1 && text[i]; i++)
150 buf[i] = tolowerW(text[i]);
151 buf[i] = '\0';
153 if (filter_protocol(buf)) return prefix_filtering_none;
154 if (filter_www(buf)) return prefix_filtering_protocol;
155 return prefix_filtering_all;
159 Filter the prefix of str based on the value of pfx_filter
160 This is used in sorting, so it's more performance sensitive
162 static WCHAR *filter_str_prefix(WCHAR *str, enum prefix_filtering pfx_filter)
164 WCHAR *p = str;
166 if (pfx_filter == prefix_filtering_none) return str;
167 if ((p = filter_protocol(str))) str = p;
169 if (pfx_filter == prefix_filtering_protocol) return str;
170 if ((p = filter_www(str))) str = p;
172 return str;
175 static inline int sort_strs_cmpfn_impl(WCHAR *a, WCHAR *b, enum prefix_filtering pfx_filter)
177 WCHAR *str1 = filter_str_prefix(a, pfx_filter);
178 WCHAR *str2 = filter_str_prefix(b, pfx_filter);
179 return strcmpiW(str1, str2);
182 static int sort_strs_cmpfn_none(const void *a, const void *b)
184 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_none);
187 static int sort_strs_cmpfn_protocol(const void *a, const void *b)
189 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_protocol);
192 static int sort_strs_cmpfn_all(const void *a, const void *b)
194 return sort_strs_cmpfn_impl(*(WCHAR* const*)a, *(WCHAR* const*)b, prefix_filtering_all);
197 static int (*const sort_strs_cmpfn[])(const void*, const void*) =
199 sort_strs_cmpfn_none,
200 sort_strs_cmpfn_protocol,
201 sort_strs_cmpfn_all
204 static void sort_strs(WCHAR **strs, UINT numstrs, enum prefix_filtering pfx_filter)
206 qsort(strs, numstrs, sizeof(*strs), sort_strs_cmpfn[pfx_filter]);
210 Enumerate all of the strings and sort them in the internal list.
212 We don't free the enumerated strings (except on error) to avoid needless
213 copies, until the next reset (or the object itself is destroyed)
215 static void enumerate_strings(IAutoCompleteImpl *ac, enum prefix_filtering pfx_filter)
217 UINT cur = 0, array_size = 1024;
218 LPOLESTR *strs = NULL, *tmp;
219 ULONG read;
223 if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
224 goto fail;
225 strs = tmp;
229 if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
230 read = 0;
231 } while (read != 0 && (cur += read) < array_size);
233 array_size *= 2;
234 } while (read != 0);
236 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
237 if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
239 strs = tmp;
240 if (cur > 0)
241 sort_strs(strs, cur, pfx_filter);
243 ac->enum_strs = strs;
244 ac->enum_strs_num = cur;
245 return;
248 fail:
249 while (cur--)
250 CoTaskMemFree(strs[cur]);
251 heap_free(strs);
254 static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
255 UINT len, enum prefix_filtering pfx_filter, int direction)
257 WCHAR **strs = ac->enum_strs;
258 UINT index = ~0, a = start, b = ac->enum_strs_num;
259 while (a < b)
261 UINT i = (a + b - 1) / 2;
262 int cmp = strncmpiW(text, filter_str_prefix(strs[i], pfx_filter), len);
263 if (cmp == 0)
265 index = i;
266 cmp = direction;
268 if (cmp <= 0) b = i;
269 else a = i + 1;
271 return index;
274 static void free_enum_strs(IAutoCompleteImpl *ac)
276 WCHAR **strs = ac->enum_strs;
277 if (strs)
279 UINT i = ac->enum_strs_num;
280 ac->enum_strs = NULL;
281 while (i--)
282 CoTaskMemFree(strs[i]);
283 heap_free(strs);
287 static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
289 ShowWindow(ac->hwndListBoxOwner, SW_HIDE);
290 SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
291 if (reset) free_enum_strs(ac);
294 static void show_listbox(IAutoCompleteImpl *ac)
296 RECT r;
297 UINT cnt, width, height;
299 GetWindowRect(ac->hwndEdit, &r);
301 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
302 cnt = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
303 height = SendMessageW(ac->hwndListBox, LB_GETITEMHEIGHT, 0, 0) * min(cnt + 1, 7);
304 width = r.right - r.left;
306 SetWindowPos(ac->hwndListBoxOwner, HWND_TOP, r.left, r.bottom + 1, width, height,
307 SWP_SHOWWINDOW | SWP_NOACTIVATE);
310 static void set_listbox_font(IAutoCompleteImpl *ac, HFONT font)
312 /* We have to calculate the item height manually due to owner-drawn */
313 HFONT old_font = NULL;
314 UINT height = 16;
315 HDC hdc;
317 if ((hdc = GetDCEx(ac->hwndListBox, 0, DCX_CACHE)))
319 TEXTMETRICW metrics;
320 if (font) old_font = SelectObject(hdc, font);
321 if (GetTextMetricsW(hdc, &metrics))
322 height = metrics.tmHeight;
323 if (old_font) SelectObject(hdc, old_font);
324 ReleaseDC(ac->hwndListBox, hdc);
326 SendMessageW(ac->hwndListBox, WM_SETFONT, (WPARAM)font, FALSE);
327 SendMessageW(ac->hwndListBox, LB_SETITEMHEIGHT, 0, height);
330 static BOOL draw_listbox_item(IAutoCompleteImpl *ac, DRAWITEMSTRUCT *info, UINT id)
332 COLORREF old_text, old_bk;
333 HDC hdc = info->hDC;
334 UINT state;
335 WCHAR *str;
337 if (info->CtlType != ODT_LISTBOX || info->CtlID != id ||
338 id != (UINT)GetWindowLongPtrW(ac->hwndListBox, GWLP_ID))
339 return FALSE;
341 if ((INT)info->itemID < 0 || info->itemAction == ODA_FOCUS)
342 return TRUE;
344 state = info->itemState;
345 if (state & ODS_SELECTED)
347 old_bk = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
348 old_text = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
351 str = ac->listbox_strs[info->itemID];
352 ExtTextOutW(hdc, info->rcItem.left + 1, info->rcItem.top,
353 ETO_OPAQUE | ETO_CLIPPED, &info->rcItem, str,
354 strlenW(str), NULL);
356 if (state & ODS_SELECTED)
358 SetBkColor(hdc, old_bk);
359 SetTextColor(hdc, old_text);
361 return TRUE;
364 static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len)
366 /* Replace the first %s directly without using snprintf, to avoid
367 exploits since the format string can be retrieved from the registry */
368 WCHAR *base = dst;
369 UINT args = 0;
370 while (*qc != '\0')
372 if (qc[0] == '%')
374 if (args < 1 && qc[1] == 's')
376 memcpy(dst, str, str_len * sizeof(WCHAR));
377 dst += str_len;
378 qc += 2;
379 args++;
380 continue;
382 qc += (qc[1] == '%');
384 *dst++ = *qc++;
386 *dst = '\0';
387 return dst - base;
390 static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
392 WCHAR *text;
393 HWND hwndListBox = ac->hwndListBox;
394 if (!(ac->options & ACO_AUTOSUGGEST))
395 return FALSE;
397 if (IsWindowVisible(ac->hwndListBoxOwner))
399 INT sel = SendMessageW(hwndListBox, LB_GETCURSEL, 0, 0);
400 if (sel >= 0)
402 text = ac->listbox_strs[sel];
403 set_text_and_selection(ac, hwnd, text, 0, strlenW(text));
404 hide_listbox(ac, hwndListBox, TRUE);
405 ac->no_fwd_char = '\r'; /* RETURN char */
406 return TRUE;
409 hide_listbox(ac, hwndListBox, TRUE);
410 return FALSE;
413 static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
415 WCHAR *msg;
416 UINT len;
418 INT count = SendMessageW(ac->hwndListBox, LB_GETCOUNT, 0, 0);
419 INT sel = SendMessageW(ac->hwndListBox, LB_GETCURSEL, 0, 0);
420 if (key == VK_PRIOR || key == VK_NEXT)
422 if (sel < 0)
423 sel = (key == VK_PRIOR) ? count - 1 : 0;
424 else
426 INT base = SendMessageW(ac->hwndListBox, LB_GETTOPINDEX, 0, 0);
427 INT pgsz = SendMessageW(ac->hwndListBox, LB_GETLISTBOXINFO, 0, 0);
428 pgsz = max(pgsz - 1, 1);
429 if (key == VK_PRIOR)
431 if (sel == 0)
432 sel = -1;
433 else
435 if (sel == base) base -= min(base, pgsz);
436 sel = base;
439 else
441 if (sel == count - 1)
442 sel = -1;
443 else
445 base += pgsz;
446 if (sel >= base) base += pgsz;
447 sel = min(base, count - 1);
452 else if (key == VK_UP || (key == VK_TAB && (GetKeyState(VK_SHIFT) & 0x8000)))
453 sel = ((sel - 1) < -1) ? count - 1 : sel - 1;
454 else
455 sel = ((sel + 1) >= count) ? -1 : sel + 1;
457 SendMessageW(ac->hwndListBox, LB_SETCURSEL, sel, 0);
459 msg = (sel >= 0) ? ac->listbox_strs[sel] : ac->txtbackup;
460 len = strlenW(msg);
461 set_text_and_selection(ac, hwnd, msg, len, len);
463 return 0;
466 static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
468 WCHAR c = last_delim[1];
470 free_enum_strs(ac);
471 IEnumString_Reset(ac->enumstr); /* call before expand */
473 last_delim[1] = '\0';
474 IACList_Expand(ac->aclist, txt);
475 last_delim[1] = c;
476 return TRUE;
479 static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
481 /* call IACList::Expand only when needed, if the
482 new txt and old_txt require different expansions */
483 static const WCHAR empty[] = { 0 };
485 const WCHAR *old_txt = ac->txtbackup;
486 WCHAR c, *p, *last_delim;
487 size_t i = 0;
489 /* '/' is allowed as a delim for unix paths */
490 static const WCHAR delims[] = { '\\', '/', 0 };
492 /* always expand if the enumerator was reset */
493 if (!ac->enum_strs) old_txt = empty;
495 /* skip the shared prefix */
496 while ((c = tolowerW(txt[i])) == tolowerW(old_txt[i]))
498 if (c == '\0') return FALSE;
499 i++;
502 /* they differ at this point, check for a delim further in txt */
503 for (last_delim = NULL, p = &txt[i]; (p = strpbrkW(p, delims)) != NULL; p++)
504 last_delim = p;
505 if (last_delim) return do_aclist_expand(ac, txt, last_delim);
507 /* txt has no delim after i, check for a delim further in old_txt */
508 if (strpbrkW(&old_txt[i], delims))
510 /* scan backwards to find the first delim before txt[i] (if any) */
511 while (i--)
512 if (strchrW(delims, txt[i]))
513 return do_aclist_expand(ac, txt, &txt[i]);
515 /* Windows doesn't expand without a delim, but it does reset */
516 free_enum_strs(ac);
519 return FALSE;
522 static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR *str, HWND hwnd)
524 DWORD sel_start;
525 WCHAR *tmp;
526 size_t size;
528 /* Don't auto-append unless the caret is at the end */
529 SendMessageW(hwnd, EM_GETSEL, (WPARAM)&sel_start, 0);
530 if (sel_start != len)
531 return;
533 /* The character capitalization can be different,
534 so merge text and str into a new string */
535 size = len + strlenW(&str[len]) + 1;
537 if ((tmp = heap_alloc(size * sizeof(*tmp))))
539 memcpy(tmp, text, len * sizeof(*tmp));
540 memcpy(&tmp[len], &str[len], (size - len) * sizeof(*tmp));
542 else tmp = str;
544 set_text_and_selection(ac, hwnd, tmp, len, size - 1);
545 if (tmp != str)
546 heap_free(tmp);
549 static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
550 enum prefix_filtering pfx_filter, HWND hwnd,
551 enum autoappend_flag flag)
553 /* Return FALSE if we need to hide the listbox */
554 WCHAR **str = ac->enum_strs;
555 UINT start, end;
556 if (!str) return !(ac->options & ACO_AUTOSUGGEST);
558 /* Windows seems to disable autoappend if ACO_NOPREFIXFILTERING is set */
559 if (!(ac->options & ACO_NOPREFIXFILTERING) && len)
561 start = find_matching_enum_str(ac, 0, text, len, pfx_filter, -1);
562 if (start == ~0)
563 return !(ac->options & ACO_AUTOSUGGEST);
565 if (flag == autoappend_flag_yes)
566 autoappend_str(ac, text, len, filter_str_prefix(str[start], pfx_filter), hwnd);
567 if (!(ac->options & ACO_AUTOSUGGEST))
568 return TRUE;
570 /* Find the index beyond the last string that matches */
571 end = find_matching_enum_str(ac, start + 1, text, len, pfx_filter, 1);
572 end = (end == ~0 ? start : end) + 1;
574 else
576 if (!(ac->options & ACO_AUTOSUGGEST))
577 return TRUE;
578 start = 0;
579 end = ac->enum_strs_num;
580 if (end == 0)
581 return FALSE;
584 SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
585 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
587 ac->listbox_strs = str + start;
588 SendMessageW(ac->hwndListBox, LB_SETCOUNT, end - start, 0);
590 show_listbox(ac);
591 SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
592 return TRUE;
595 static enum prefix_filtering setup_prefix_filtering(IAutoCompleteImpl *ac, const WCHAR *text)
597 enum prefix_filtering pfx_filter;
598 if (!(ac->options & ACO_FILTERPREFIXES)) return prefix_filtering_none;
600 pfx_filter = get_text_prefix_filtering(text);
601 if (!ac->enum_strs) return pfx_filter;
603 /* If the prefix filtering is different, re-sort the filtered strings */
604 if (pfx_filter != get_text_prefix_filtering(ac->txtbackup))
605 sort_strs(ac->enum_strs, ac->enum_strs_num, pfx_filter);
607 return pfx_filter;
610 static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
612 WCHAR *text;
613 BOOL expanded = FALSE;
614 enum prefix_filtering pfx_filter;
615 UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
617 if (flag != autoappend_flag_displayempty && len == 0)
619 if (ac->options & ACO_AUTOSUGGEST)
620 hide_listbox(ac, ac->hwndListBox, FALSE);
621 free_enum_strs(ac);
622 return;
625 size = len + 1;
626 if (!(text = heap_alloc(size * sizeof(WCHAR))))
628 /* Reset the listbox to prevent potential crash from ResetEnumerator */
629 SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
630 return;
632 len = SendMessageW(hwnd, WM_GETTEXT, size, (LPARAM)text);
633 if (len + 1 != size)
634 text = heap_realloc(text, (len + 1) * sizeof(WCHAR));
636 if (ac->aclist)
638 if (text[len - 1] == '\\' || text[len - 1] == '/')
639 flag = autoappend_flag_no;
640 expanded = aclist_expand(ac, text);
642 pfx_filter = setup_prefix_filtering(ac, text);
644 if (expanded || !ac->enum_strs)
646 if (!expanded) IEnumString_Reset(ac->enumstr);
647 enumerate_strings(ac, pfx_filter);
650 /* Set txtbackup to point to text itself (which must not be released),
651 and it must be done here since aclist_expand uses it to track changes */
652 heap_free(ac->txtbackup);
653 ac->txtbackup = text;
655 if (!display_matching_strs(ac, text, len, pfx_filter, hwnd, flag))
656 hide_listbox(ac, ac->hwndListBox, FALSE);
659 static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
661 ac->hwndEdit = NULL;
662 free_enum_strs(ac);
663 if (ac->hwndListBoxOwner)
664 DestroyWindow(ac->hwndListBoxOwner);
665 IAutoComplete2_Release(&ac->IAutoComplete2_iface);
669 Helper for ACEditSubclassProc
671 static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT uMsg,
672 WPARAM wParam, LPARAM lParam)
674 switch (wParam)
676 case VK_ESCAPE:
677 /* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
678 if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBoxOwner))
680 hide_listbox(ac, ac->hwndListBox, FALSE);
681 ac->no_fwd_char = 0x1B; /* ESC char */
682 return 0;
684 break;
685 case VK_RETURN:
686 /* If quickComplete is set and control is pressed, replace the string */
687 if (ac->quickComplete && (GetKeyState(VK_CONTROL) & 0x8000))
689 WCHAR *text, *buf;
690 size_t sz;
691 UINT len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
692 ac->no_fwd_char = '\n'; /* CTRL+RETURN char */
694 if (!(text = heap_alloc((len + 1) * sizeof(WCHAR))))
695 return 0;
696 len = SendMessageW(hwnd, WM_GETTEXT, len + 1, (LPARAM)text);
697 sz = strlenW(ac->quickComplete) + 1 + len;
699 if ((buf = heap_alloc(sz * sizeof(WCHAR))))
701 len = format_quick_complete(buf, ac->quickComplete, text, len);
702 set_text_and_selection(ac, hwnd, buf, 0, len);
703 heap_free(buf);
706 if (ac->options & ACO_AUTOSUGGEST)
707 hide_listbox(ac, ac->hwndListBox, TRUE);
708 heap_free(text);
709 return 0;
712 if (select_item_with_return_key(ac, hwnd))
713 return 0;
714 break;
715 case VK_TAB:
716 if ((ac->options & (ACO_AUTOSUGGEST | ACO_USETAB)) == (ACO_AUTOSUGGEST | ACO_USETAB)
717 && IsWindowVisible(ac->hwndListBoxOwner) && !(GetKeyState(VK_CONTROL) & 0x8000))
719 ac->no_fwd_char = '\t';
720 return change_selection(ac, hwnd, wParam);
722 break;
723 case VK_UP:
724 case VK_DOWN:
725 case VK_PRIOR:
726 case VK_NEXT:
727 /* Two cases here:
728 - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
729 set, display it with all the entries, without selecting any
730 - if the listbox is visible, change the selection
732 if (!(ac->options & ACO_AUTOSUGGEST))
733 break;
735 if (!IsWindowVisible(ac->hwndListBoxOwner))
737 if (ac->options & ACO_UPDOWNKEYDROPSLIST)
739 autocomplete_text(ac, hwnd, autoappend_flag_displayempty);
740 return 0;
743 else
744 return change_selection(ac, hwnd, wParam);
745 break;
746 case VK_DELETE:
748 LRESULT ret = CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
749 autocomplete_text(ac, hwnd, autoappend_flag_no);
750 return ret;
753 ac->no_fwd_char = '\0';
754 return CallWindowProcW(ac->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
758 Window procedure for autocompletion
760 static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
762 IAutoCompleteImpl *This = GetPropW(hwnd, autocomplete_propertyW);
763 LRESULT ret;
765 if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
767 switch (uMsg)
769 case CB_SHOWDROPDOWN:
770 if (This->options & ACO_AUTOSUGGEST)
771 hide_listbox(This, This->hwndListBox, TRUE);
772 return 0;
773 case WM_KILLFOCUS:
774 if (This->options & ACO_AUTOSUGGEST)
776 if (This->hwndListBoxOwner == (HWND)wParam ||
777 This->hwndListBoxOwner == GetAncestor((HWND)wParam, GA_PARENT))
778 break;
779 hide_listbox(This, This->hwndListBox, FALSE);
782 /* Reset the enumerator if it's not visible anymore */
783 if (!IsWindowVisible(hwnd)) free_enum_strs(This);
784 break;
785 case WM_WINDOWPOSCHANGED:
787 WINDOWPOS *pos = (WINDOWPOS*)lParam;
789 if ((pos->flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE) &&
790 This->hwndListBoxOwner && IsWindowVisible(This->hwndListBoxOwner))
791 show_listbox(This);
792 break;
794 case WM_KEYDOWN:
795 return ACEditSubclassProc_KeyDown(This, hwnd, uMsg, wParam, lParam);
796 case WM_CHAR:
797 case WM_UNICHAR:
798 if (wParam == This->no_fwd_char) return 0;
799 This->no_fwd_char = '\0';
801 /* Don't autocomplete at all on most control characters */
802 if (iscntrlW(wParam) && !(wParam >= '\b' && wParam <= '\r'))
803 break;
805 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
806 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' '
807 ? autoappend_flag_yes : autoappend_flag_no);
808 return ret;
809 case WM_SETTEXT:
810 if (This->options & ACO_AUTOSUGGEST)
811 hide_listbox(This, This->hwndListBox, TRUE);
812 break;
813 case WM_CUT:
814 case WM_CLEAR:
815 case WM_UNDO:
816 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
817 autocomplete_text(This, hwnd, autoappend_flag_no);
818 return ret;
819 case WM_PASTE:
820 ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
821 autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND)
822 ? autoappend_flag_yes : autoappend_flag_no);
823 return ret;
824 case WM_MOUSEWHEEL:
825 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
826 return SendMessageW(This->hwndListBox, WM_MOUSEWHEEL, wParam, lParam);
827 break;
828 case WM_SETFONT:
829 if (This->hwndListBox)
830 set_listbox_font(This, (HFONT)wParam);
831 break;
832 case WM_DESTROY:
834 WNDPROC proc = This->wpOrigEditProc;
836 SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)proc);
837 RemovePropW(hwnd, autocomplete_propertyW);
838 destroy_autocomplete_object(This);
839 return CallWindowProcW(proc, hwnd, uMsg, wParam, lParam);
842 return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
845 static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
847 IAutoCompleteImpl *This = (IAutoCompleteImpl *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
848 WCHAR *msg;
849 INT sel;
851 switch (uMsg) {
852 case WM_MOUSEACTIVATE:
853 return MA_NOACTIVATE;
854 case WM_MOUSEMOVE:
855 sel = SendMessageW(hwnd, LB_ITEMFROMPOINT, 0, lParam);
856 SendMessageW(hwnd, LB_SETCURSEL, sel, 0);
857 return 0;
858 case WM_LBUTTONDOWN:
859 sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
860 if (sel < 0)
861 return 0;
862 msg = This->listbox_strs[sel];
863 set_text_and_selection(This, This->hwndEdit, msg, 0, strlenW(msg));
864 hide_listbox(This, hwnd, TRUE);
865 return 0;
867 return CallWindowProcW(This->wpOrigLBoxProc, hwnd, uMsg, wParam, lParam);
870 static LRESULT APIENTRY ACLBoxOwnerSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
872 IAutoCompleteImpl *This = (IAutoCompleteImpl*)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
874 switch (uMsg)
876 case WM_MOUSEACTIVATE:
877 return MA_NOACTIVATE;
878 case WM_DRAWITEM:
879 if (draw_listbox_item(This, (DRAWITEMSTRUCT*)lParam, wParam))
880 return TRUE;
881 break;
882 case WM_SIZE:
883 SetWindowPos(This->hwndListBox, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam),
884 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_DEFERERASE);
885 break;
887 return CallWindowProcW(This->wpOrigLBoxOwnerProc, hwnd, uMsg, wParam, lParam);
890 static void create_listbox(IAutoCompleteImpl *This)
892 This->hwndListBoxOwner = CreateWindowExW(WS_EX_NOACTIVATE, WC_STATICW, NULL,
893 WS_BORDER | WS_POPUP | WS_CLIPCHILDREN,
894 0, 0, 0, 0, NULL, NULL, shell32_hInstance, NULL);
895 if (!This->hwndListBoxOwner)
897 This->options &= ~ACO_AUTOSUGGEST;
898 return;
901 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
902 This->hwndListBox = CreateWindowExW(WS_EX_NOACTIVATE, WC_LISTBOXW, NULL,
903 WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NODATA | LBS_OWNERDRAWFIXED | LBS_NOINTEGRALHEIGHT,
904 0, 0, 0, 0, This->hwndListBoxOwner, NULL, shell32_hInstance, NULL);
906 if (This->hwndListBox) {
907 HFONT edit_font;
909 This->wpOrigLBoxProc = (WNDPROC) SetWindowLongPtrW( This->hwndListBox, GWLP_WNDPROC, (LONG_PTR) ACLBoxSubclassProc);
910 SetWindowLongPtrW( This->hwndListBox, GWLP_USERDATA, (LONG_PTR)This);
912 This->wpOrigLBoxOwnerProc = (WNDPROC)SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_WNDPROC, (LONG_PTR)ACLBoxOwnerSubclassProc);
913 SetWindowLongPtrW(This->hwndListBoxOwner, GWLP_USERDATA, (LONG_PTR)This);
915 /* Use the same font as the edit control, as it gets destroyed before it anyway */
916 edit_font = (HFONT)SendMessageW(This->hwndEdit, WM_GETFONT, 0, 0);
917 if (edit_font)
918 set_listbox_font(This, edit_font);
919 return;
922 DestroyWindow(This->hwndListBoxOwner);
923 This->hwndListBoxOwner = NULL;
924 This->options &= ~ACO_AUTOSUGGEST;
927 /**************************************************************************
928 * AutoComplete_QueryInterface
930 static HRESULT WINAPI IAutoComplete2_fnQueryInterface(
931 IAutoComplete2 * iface,
932 REFIID riid,
933 LPVOID *ppvObj)
935 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
937 TRACE("(%p)->(IID:%s,%p)\n", This, shdebugstr_guid(riid), ppvObj);
938 *ppvObj = NULL;
940 if (IsEqualIID(riid, &IID_IUnknown) ||
941 IsEqualIID(riid, &IID_IAutoComplete) ||
942 IsEqualIID(riid, &IID_IAutoComplete2))
944 *ppvObj = &This->IAutoComplete2_iface;
946 else if (IsEqualIID(riid, &IID_IAutoCompleteDropDown))
948 *ppvObj = &This->IAutoCompleteDropDown_iface;
951 if (*ppvObj)
953 IUnknown_AddRef((IUnknown*)*ppvObj);
954 TRACE("-- Interface: (%p)->(%p)\n", ppvObj, *ppvObj);
955 return S_OK;
957 WARN("unsupported interface: %s\n", debugstr_guid(riid));
958 return E_NOINTERFACE;
961 /******************************************************************************
962 * IAutoComplete2_fnAddRef
964 static ULONG WINAPI IAutoComplete2_fnAddRef(
965 IAutoComplete2 * iface)
967 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
968 ULONG refCount = InterlockedIncrement(&This->ref);
970 TRACE("(%p)->(%u)\n", This, refCount - 1);
972 return refCount;
975 /******************************************************************************
976 * IAutoComplete2_fnRelease
978 static ULONG WINAPI IAutoComplete2_fnRelease(
979 IAutoComplete2 * iface)
981 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
982 ULONG refCount = InterlockedDecrement(&This->ref);
984 TRACE("(%p)->(%u)\n", This, refCount + 1);
986 if (!refCount) {
987 TRACE("destroying IAutoComplete(%p)\n", This);
988 heap_free(This->quickComplete);
989 heap_free(This->txtbackup);
990 if (This->enumstr)
991 IEnumString_Release(This->enumstr);
992 if (This->aclist)
993 IACList_Release(This->aclist);
994 heap_free(This);
996 return refCount;
999 /******************************************************************************
1000 * IAutoComplete2_fnEnable
1002 static HRESULT WINAPI IAutoComplete2_fnEnable(
1003 IAutoComplete2 * iface,
1004 BOOL fEnable)
1006 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1008 TRACE("(%p)->(%s)\n", This, (fEnable)?"true":"false");
1010 This->enabled = fEnable;
1012 return S_OK;
1015 /******************************************************************************
1016 * IAutoComplete2_fnInit
1018 static HRESULT WINAPI IAutoComplete2_fnInit(
1019 IAutoComplete2 * iface,
1020 HWND hwndEdit,
1021 IUnknown *punkACL,
1022 LPCOLESTR pwzsRegKeyPath,
1023 LPCOLESTR pwszQuickComplete)
1025 IAutoCompleteImpl *prev, *This = impl_from_IAutoComplete2(iface);
1027 TRACE("(%p)->(%p, %p, %s, %s)\n",
1028 This, hwndEdit, punkACL, debugstr_w(pwzsRegKeyPath), debugstr_w(pwszQuickComplete));
1030 if (This->options & ACO_SEARCH) FIXME(" ACO_SEARCH not supported\n");
1031 if (This->options & ACO_RTLREADING) FIXME(" ACO_RTLREADING not supported\n");
1032 if (This->options & ACO_WORD_FILTER) FIXME(" ACO_WORD_FILTER not supported\n");
1034 if (!hwndEdit || !punkACL)
1035 return E_INVALIDARG;
1037 if (This->initialized)
1039 WARN("Autocompletion object is already initialized\n");
1040 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
1041 return This->hwndEdit ? E_FAIL : E_UNEXPECTED;
1044 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IEnumString, (LPVOID*)&This->enumstr))) {
1045 WARN("No IEnumString interface\n");
1046 return E_NOINTERFACE;
1049 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
1050 if ((This->txtbackup = heap_alloc_zero(sizeof(WCHAR))) == NULL)
1052 IEnumString_Release(This->enumstr);
1053 This->enumstr = NULL;
1054 return E_OUTOFMEMORY;
1057 if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist)))
1058 This->aclist = NULL;
1060 This->initialized = TRUE;
1061 This->hwndEdit = hwndEdit;
1063 /* If another AutoComplete object was previously assigned to this edit control,
1064 release it but keep the same callback on the control, to avoid an infinite
1065 recursive loop in ACEditSubclassProc while the property is set to this object */
1066 prev = GetPropW(hwndEdit, autocomplete_propertyW);
1067 SetPropW(hwndEdit, autocomplete_propertyW, This);
1069 if (prev && prev->initialized) {
1070 This->wpOrigEditProc = prev->wpOrigEditProc;
1071 destroy_autocomplete_object(prev);
1073 else
1074 This->wpOrigEditProc = (WNDPROC) SetWindowLongPtrW(hwndEdit, GWLP_WNDPROC, (LONG_PTR) ACEditSubclassProc);
1076 /* Keep at least one reference to the object until the edit window is destroyed */
1077 IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1079 if (This->options & ACO_AUTOSUGGEST)
1080 create_listbox(This);
1082 if (pwzsRegKeyPath)
1084 static const HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
1085 WCHAR *key, *value;
1086 DWORD type, sz;
1087 BYTE *qc;
1088 HKEY hKey;
1089 LSTATUS res;
1090 size_t len;
1091 UINT i;
1093 /* pwszRegKeyPath contains the key as well as the value, so split it */
1094 value = strrchrW(pwzsRegKeyPath, '\\');
1095 len = value - pwzsRegKeyPath;
1097 if (value && (key = heap_alloc((len+1) * sizeof(*key))) != NULL)
1099 memcpy(key, pwzsRegKeyPath, len * sizeof(*key));
1100 key[len] = '\0';
1101 value++;
1103 for (i = 0; i < ARRAY_SIZE(roots); i++)
1105 if (RegOpenKeyExW(roots[i], key, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
1106 continue;
1107 sz = MAX_PATH * sizeof(WCHAR);
1109 while ((qc = heap_alloc(sz)) != NULL)
1111 res = RegQueryValueExW(hKey, value, NULL, &type, qc, &sz);
1112 if (res == ERROR_SUCCESS && type == REG_SZ)
1114 This->quickComplete = heap_realloc(qc, sz);
1115 i = ARRAY_SIZE(roots);
1116 break;
1118 heap_free(qc);
1119 if (res != ERROR_MORE_DATA || type != REG_SZ)
1120 break;
1122 RegCloseKey(hKey);
1124 heap_free(key);
1128 if (!This->quickComplete && pwszQuickComplete)
1130 size_t len = strlenW(pwszQuickComplete)+1;
1131 if ((This->quickComplete = heap_alloc(len * sizeof(WCHAR))) != NULL)
1132 memcpy(This->quickComplete, pwszQuickComplete, len * sizeof(WCHAR));
1135 return S_OK;
1138 /**************************************************************************
1139 * IAutoComplete2_fnGetOptions
1141 static HRESULT WINAPI IAutoComplete2_fnGetOptions(
1142 IAutoComplete2 * iface,
1143 DWORD *pdwFlag)
1145 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1147 TRACE("(%p) -> (%p)\n", This, pdwFlag);
1149 *pdwFlag = This->options;
1151 return S_OK;
1154 /**************************************************************************
1155 * IAutoComplete2_fnSetOptions
1157 static HRESULT WINAPI IAutoComplete2_fnSetOptions(
1158 IAutoComplete2 * iface,
1159 DWORD dwFlag)
1161 IAutoCompleteImpl *This = impl_from_IAutoComplete2(iface);
1162 DWORD changed = This->options ^ dwFlag;
1163 HRESULT hr = S_OK;
1165 TRACE("(%p) -> (0x%x)\n", This, dwFlag);
1167 This->options = dwFlag;
1169 if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
1170 create_listbox(This);
1171 else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
1172 hide_listbox(This, This->hwndListBox, TRUE);
1174 /* If ACO_FILTERPREFIXES changed we might have to reset the enumerator */
1175 if ((changed & ACO_FILTERPREFIXES) && This->txtbackup)
1177 if (get_text_prefix_filtering(This->txtbackup) != prefix_filtering_none)
1178 IAutoCompleteDropDown_ResetEnumerator(&This->IAutoCompleteDropDown_iface);
1181 return hr;
1184 /**************************************************************************
1185 * IAutoComplete2 VTable
1187 static const IAutoComplete2Vtbl acvt =
1189 IAutoComplete2_fnQueryInterface,
1190 IAutoComplete2_fnAddRef,
1191 IAutoComplete2_fnRelease,
1192 IAutoComplete2_fnInit,
1193 IAutoComplete2_fnEnable,
1194 /* IAutoComplete2 */
1195 IAutoComplete2_fnSetOptions,
1196 IAutoComplete2_fnGetOptions,
1200 static HRESULT WINAPI IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown *iface,
1201 REFIID riid, LPVOID *ppvObj)
1203 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1204 return IAutoComplete2_QueryInterface(&This->IAutoComplete2_iface, riid, ppvObj);
1207 static ULONG WINAPI IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown *iface)
1209 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1210 return IAutoComplete2_AddRef(&This->IAutoComplete2_iface);
1213 static ULONG WINAPI IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown *iface)
1215 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1216 return IAutoComplete2_Release(&This->IAutoComplete2_iface);
1219 /**************************************************************************
1220 * IAutoCompleteDropDown_fnGetDropDownStatus
1222 static HRESULT WINAPI IAutoCompleteDropDown_fnGetDropDownStatus(
1223 IAutoCompleteDropDown *iface,
1224 DWORD *pdwFlags,
1225 LPWSTR *ppwszString)
1227 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1228 BOOL dropped;
1230 TRACE("(%p) -> (%p, %p)\n", This, pdwFlags, ppwszString);
1232 dropped = IsWindowVisible(This->hwndListBoxOwner);
1234 if (pdwFlags)
1235 *pdwFlags = (dropped ? ACDD_VISIBLE : 0);
1237 if (ppwszString) {
1238 if (dropped) {
1239 int sel;
1241 sel = SendMessageW(This->hwndListBox, LB_GETCURSEL, 0, 0);
1242 if (sel >= 0)
1244 WCHAR *str = This->listbox_strs[sel];
1245 size_t size = (strlenW(str) + 1) * sizeof(*str);
1247 if (!(*ppwszString = CoTaskMemAlloc(size)))
1248 return E_OUTOFMEMORY;
1249 memcpy(*ppwszString, str, size);
1251 else
1252 *ppwszString = NULL;
1254 else
1255 *ppwszString = NULL;
1258 return S_OK;
1261 /**************************************************************************
1262 * IAutoCompleteDropDown_fnResetEnumarator
1264 static HRESULT WINAPI IAutoCompleteDropDown_fnResetEnumerator(
1265 IAutoCompleteDropDown *iface)
1267 IAutoCompleteImpl *This = impl_from_IAutoCompleteDropDown(iface);
1269 TRACE("(%p)\n", This);
1271 if (This->hwndEdit)
1273 free_enum_strs(This);
1274 if ((This->options & ACO_AUTOSUGGEST) && IsWindowVisible(This->hwndListBoxOwner))
1275 autocomplete_text(This, This->hwndEdit, autoappend_flag_displayempty);
1277 return S_OK;
1280 /**************************************************************************
1281 * IAutoCompleteDropDown VTable
1283 static const IAutoCompleteDropDownVtbl acdropdownvt =
1285 IAutoCompleteDropDown_fnQueryInterface,
1286 IAutoCompleteDropDown_fnAddRef,
1287 IAutoCompleteDropDown_fnRelease,
1288 IAutoCompleteDropDown_fnGetDropDownStatus,
1289 IAutoCompleteDropDown_fnResetEnumerator,
1292 /**************************************************************************
1293 * IAutoComplete_Constructor
1295 HRESULT WINAPI IAutoComplete_Constructor(IUnknown * pUnkOuter, REFIID riid, LPVOID * ppv)
1297 IAutoCompleteImpl *lpac;
1298 HRESULT hr;
1300 if (pUnkOuter && !IsEqualIID (riid, &IID_IUnknown))
1301 return CLASS_E_NOAGGREGATION;
1303 lpac = heap_alloc_zero(sizeof(*lpac));
1304 if (!lpac)
1305 return E_OUTOFMEMORY;
1307 lpac->ref = 1;
1308 lpac->IAutoComplete2_iface.lpVtbl = &acvt;
1309 lpac->IAutoCompleteDropDown_iface.lpVtbl = &acdropdownvt;
1310 lpac->enabled = TRUE;
1311 lpac->options = ACO_AUTOAPPEND;
1313 hr = IAutoComplete2_QueryInterface(&lpac->IAutoComplete2_iface, riid, ppv);
1314 IAutoComplete2_Release(&lpac->IAutoComplete2_iface);
1316 TRACE("-- (%p)->\n",lpac);
1318 return hr;