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
24 - implement ACO_SEARCH style
25 - implement ACO_RTLREADING style
26 - implement ACO_WORD_FILTER style
35 #include "wine/debug.h"
47 #include "shell32_main.h"
49 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
53 IAutoComplete2 IAutoComplete2_iface
;
54 IAutoCompleteDropDown IAutoCompleteDropDown_iface
;
63 HWND hwndListBoxOwner
;
64 WNDPROC wpOrigEditProc
;
65 WNDPROC wpOrigLBoxProc
;
66 WNDPROC wpOrigLBoxOwnerProc
;
71 AUTOCOMPLETEOPTIONS options
;
79 autoappend_flag_displayempty
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))
114 str
+= (*str
== 's'); /* https */
115 if (str
[0] == ':' && str
[1] == '/' && str
[2] == '/')
121 static inline WCHAR
*filter_www(WCHAR
*str
)
123 if (!wcsncmp(str
, L
"www.", 4)) return str
+ 4;
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://")];
138 for (i
= 0; i
< ARRAY_SIZE(buf
) - 1 && text
[i
]; i
++)
139 buf
[i
] = towlower(text
[i
]);
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
)
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
;
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
,
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
;
212 if ((tmp
= realloc(strs
, array_size
* sizeof(*strs
))) == NULL
)
218 if (FAILED(IEnumString_Next(ac
->enumstr
, array_size
- cur
, &strs
[cur
], &read
)))
220 } while (read
!= 0 && (cur
+= read
) < array_size
);
225 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
226 if ((tmp
= realloc(strs
, cur
* sizeof(*strs
))))
230 sort_strs(strs
, cur
, pfx_filter
);
232 ac
->enum_strs
= strs
;
233 ac
->enum_strs_num
= cur
;
239 CoTaskMemFree(strs
[cur
]);
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
;
250 UINT i
= (a
+ b
- 1) / 2;
251 int cmp
= wcsnicmp(text
, filter_str_prefix(strs
[i
], pfx_filter
), len
);
263 static void free_enum_strs(IAutoCompleteImpl
*ac
)
265 WCHAR
**strs
= ac
->enum_strs
;
268 UINT i
= ac
->enum_strs_num
;
269 ac
->enum_strs
= NULL
;
271 CoTaskMemFree(strs
[i
]);
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
)
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
;
306 if ((hdc
= GetDCEx(ac
->hwndListBox
, 0, DCX_CACHE
)))
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
;
326 if (info
->CtlType
!= ODT_LISTBOX
|| info
->CtlID
!= id
||
327 id
!= (UINT
)GetWindowLongPtrW(ac
->hwndListBox
, GWLP_ID
))
330 if ((INT
)info
->itemID
< 0 || info
->itemAction
== ODA_FOCUS
)
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
);
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 */
363 if (args
< 1 && qc
[1] == 's')
365 memcpy(dst
, str
, str_len
* sizeof(WCHAR
));
371 qc
+= (qc
[1] == '%');
379 static BOOL
select_item_with_return_key(IAutoCompleteImpl
*ac
, HWND hwnd
)
382 HWND hwndListBox
= ac
->hwndListBox
;
383 if (!(ac
->options
& ACO_AUTOSUGGEST
))
386 if (IsWindowVisible(ac
->hwndListBoxOwner
))
388 INT sel
= SendMessageW(hwndListBox
, LB_GETCURSEL
, 0, 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 */
398 hide_listbox(ac
, hwndListBox
, TRUE
);
402 static LRESULT
change_selection(IAutoCompleteImpl
*ac
, HWND hwnd
, UINT key
)
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
)
412 sel
= (key
== VK_PRIOR
) ? count
- 1 : 0;
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);
424 if (sel
== base
) base
-= min(base
, pgsz
);
430 if (sel
== count
- 1)
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;
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
;
450 set_text_and_selection(ac
, hwnd
, msg
, len
, len
);
455 static BOOL
do_aclist_expand(IAutoCompleteImpl
*ac
, WCHAR
*txt
, WCHAR
*last_delim
)
457 WCHAR c
= last_delim
[1];
460 IEnumString_Reset(ac
->enumstr
); /* call before expand */
462 last_delim
[1] = '\0';
463 IACList_Expand(ac
->aclist
, txt
);
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
;
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
;
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
++)
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) */
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 */
507 static void autoappend_str(IAutoCompleteImpl
*ac
, WCHAR
*text
, UINT len
, WCHAR
*str
, HWND hwnd
)
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
)
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
));
529 set_text_and_selection(ac
, hwnd
, tmp
, len
, size
- 1);
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
;
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);
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
))
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;
561 if (!(ac
->options
& ACO_AUTOSUGGEST
))
564 end
= ac
->enum_strs_num
;
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);
576 SendMessageW(ac
->hwndListBox
, WM_SETREDRAW
, TRUE
, 0);
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
);
595 static void autocomplete_text(IAutoCompleteImpl
*ac
, HWND hwnd
, enum autoappend_flag flag
)
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
);
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);
617 len
= SendMessageW(hwnd
, WM_GETTEXT
, size
, (LPARAM
)text
);
619 text
= realloc(text
, (len
+ 1) * sizeof(WCHAR
));
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 */
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
)
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
)
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 */
671 /* If quickComplete is set and control is pressed, replace the string */
672 if (ac
->quickComplete
&& (GetKeyState(VK_CONTROL
) & 0x8000))
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
))))
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
);
691 if (ac
->options
& ACO_AUTOSUGGEST
)
692 hide_listbox(ac
, ac
->hwndListBox
, TRUE
);
697 if (select_item_with_return_key(ac
, hwnd
))
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
);
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
))
720 if (!IsWindowVisible(ac
->hwndListBoxOwner
))
722 if (ac
->options
& ACO_UPDOWNKEYDROPSLIST
)
724 autocomplete_text(ac
, hwnd
, autoappend_flag_displayempty
);
729 return change_selection(ac
, hwnd
, wParam
);
733 LRESULT ret
= CallWindowProcW(ac
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
734 autocomplete_text(ac
, hwnd
, autoappend_flag_no
);
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
);
750 if (!This
->enabled
) return CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
754 case CB_SHOWDROPDOWN
:
755 if (This
->options
& ACO_AUTOSUGGEST
)
756 hide_listbox(This
, This
->hwndListBox
, TRUE
);
759 if (This
->options
& ACO_AUTOSUGGEST
)
761 if (This
->hwndListBoxOwner
== (HWND
)wParam
||
762 This
->hwndListBoxOwner
== GetAncestor((HWND
)wParam
, GA_PARENT
))
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
);
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
))
780 return ACEditSubclassProc_KeyDown(This
, hwnd
, uMsg
, wParam
, lParam
);
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'))
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
);
795 if (This
->options
& ACO_AUTOSUGGEST
)
796 hide_listbox(This
, This
->hwndListBox
, TRUE
);
801 ret
= CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
802 autocomplete_text(This
, hwnd
, autoappend_flag_no
);
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
);
810 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
811 return SendMessageW(This
->hwndListBox
, WM_MOUSEWHEEL
, wParam
, lParam
);
814 if (This
->hwndListBox
)
815 set_listbox_font(This
, (HFONT
)wParam
);
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
);
837 case WM_MOUSEACTIVATE
:
838 return MA_NOACTIVATE
;
840 sel
= SendMessageW(hwnd
, LB_ITEMFROMPOINT
, 0, lParam
);
841 SendMessageW(hwnd
, LB_SETCURSEL
, sel
, 0);
844 sel
= SendMessageW(hwnd
, LB_GETCURSEL
, 0, 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
);
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
);
861 case WM_MOUSEACTIVATE
:
862 return MA_NOACTIVATE
;
864 if (draw_listbox_item(This
, (DRAWITEMSTRUCT
*)lParam
, wParam
))
868 SetWindowPos(This
->hwndListBox
, NULL
, 0, 0, LOWORD(lParam
), HIWORD(lParam
),
869 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_DEFERERASE
);
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
;
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
) {
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);
903 set_listbox_font(This
, edit_font
);
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
,
920 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
922 TRACE("(%p)->(IID:%s,%p)\n", This
, shdebugstr_guid(riid
), ppvObj
);
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
;
938 IUnknown_AddRef((IUnknown
*)*ppvObj
);
939 TRACE("-- Interface: (%p)->(%p)\n", ppvObj
, *ppvObj
);
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);
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);
972 TRACE("destroying IAutoComplete(%p)\n", This
);
973 free(This
->quickComplete
);
974 free(This
->txtbackup
);
976 IEnumString_Release(This
->enumstr
);
978 IACList_Release(This
->aclist
);
984 /******************************************************************************
985 * IAutoComplete2_fnEnable
987 static HRESULT WINAPI
IAutoComplete2_fnEnable(
988 IAutoComplete2
* iface
,
991 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
993 TRACE("(%p)->(%s)\n", This
, (fEnable
)?"true":"false");
995 This
->enabled
= fEnable
;
1000 /******************************************************************************
1001 * IAutoComplete2_fnInit
1003 static HRESULT WINAPI
IAutoComplete2_fnInit(
1004 IAutoComplete2
* iface
,
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
);
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
);
1069 static const HKEY roots
[] = { HKEY_CURRENT_USER
, HKEY_LOCAL_MACHINE
};
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
));
1088 for (i
= 0; i
< ARRAY_SIZE(roots
); i
++)
1090 if (RegOpenKeyExW(roots
[i
], key
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
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
);
1104 if (res
!= ERROR_MORE_DATA
|| type
!= REG_SZ
)
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
));
1123 /**************************************************************************
1124 * IAutoComplete2_fnGetOptions
1126 static HRESULT WINAPI
IAutoComplete2_fnGetOptions(
1127 IAutoComplete2
* iface
,
1130 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1132 TRACE("(%p) -> (%p)\n", This
, pdwFlag
);
1134 *pdwFlag
= This
->options
;
1139 /**************************************************************************
1140 * IAutoComplete2_fnSetOptions
1142 static HRESULT WINAPI
IAutoComplete2_fnSetOptions(
1143 IAutoComplete2
* iface
,
1146 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1147 DWORD changed
= This
->options
^ dwFlag
;
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
);
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
,
1210 LPWSTR
*ppwszString
)
1212 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1215 TRACE("(%p) -> (%p, %p)\n", This
, pdwFlags
, ppwszString
);
1217 dropped
= IsWindowVisible(This
->hwndListBoxOwner
);
1220 *pdwFlags
= (dropped
? ACDD_VISIBLE
: 0);
1226 sel
= SendMessageW(This
->hwndListBox
, LB_GETCURSEL
, 0, 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
);
1237 *ppwszString
= NULL
;
1240 *ppwszString
= NULL
;
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
);
1258 free_enum_strs(This
);
1259 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
1260 autocomplete_text(This
, This
->hwndEdit
, autoappend_flag_displayempty
);
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
;
1285 if (pUnkOuter
&& !IsEqualIID (riid
, &IID_IUnknown
))
1286 return CLASS_E_NOAGGREGATION
;
1288 lpac
= calloc(1, sizeof(*lpac
));
1290 return E_OUTOFMEMORY
;
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
);