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
36 #include "wine/debug.h"
40 #include "undocshell.h"
49 #include "shell32_main.h"
51 #include "wine/unicode.h"
53 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
57 IAutoComplete2 IAutoComplete2_iface
;
58 IAutoCompleteDropDown IAutoCompleteDropDown_iface
;
67 HWND hwndListBoxOwner
;
68 WNDPROC wpOrigEditProc
;
69 WNDPROC wpOrigLBoxProc
;
70 WNDPROC wpOrigLBoxOwnerProc
;
75 AUTOCOMPLETEOPTIONS options
;
83 autoappend_flag_displayempty
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] == '/')
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
);
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://")];
149 for (i
= 0; i
< ARRAY_SIZE(buf
) - 1 && text
[i
]; i
++)
150 buf
[i
] = tolowerW(text
[i
]);
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
)
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
;
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
,
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
;
223 if ((tmp
= heap_realloc(strs
, array_size
* sizeof(*strs
))) == NULL
)
229 if (FAILED(IEnumString_Next(ac
->enumstr
, array_size
- cur
, &strs
[cur
], &read
)))
231 } while (read
!= 0 && (cur
+= read
) < array_size
);
236 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
237 if ((tmp
= heap_realloc(strs
, cur
* sizeof(*strs
))))
241 sort_strs(strs
, cur
, pfx_filter
);
243 ac
->enum_strs
= strs
;
244 ac
->enum_strs_num
= cur
;
250 CoTaskMemFree(strs
[cur
]);
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
;
261 UINT i
= (a
+ b
- 1) / 2;
262 int cmp
= strncmpiW(text
, filter_str_prefix(strs
[i
], pfx_filter
), len
);
274 static void free_enum_strs(IAutoCompleteImpl
*ac
)
276 WCHAR
**strs
= ac
->enum_strs
;
279 UINT i
= ac
->enum_strs_num
;
280 ac
->enum_strs
= NULL
;
282 CoTaskMemFree(strs
[i
]);
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
)
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
;
317 if ((hdc
= GetDCEx(ac
->hwndListBox
, 0, DCX_CACHE
)))
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
;
337 if (info
->CtlType
!= ODT_LISTBOX
|| info
->CtlID
!= id
||
338 id
!= (UINT
)GetWindowLongPtrW(ac
->hwndListBox
, GWLP_ID
))
341 if ((INT
)info
->itemID
< 0 || info
->itemAction
== ODA_FOCUS
)
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
,
356 if (state
& ODS_SELECTED
)
358 SetBkColor(hdc
, old_bk
);
359 SetTextColor(hdc
, old_text
);
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 */
374 if (args
< 1 && qc
[1] == 's')
376 memcpy(dst
, str
, str_len
* sizeof(WCHAR
));
382 qc
+= (qc
[1] == '%');
390 static BOOL
select_item_with_return_key(IAutoCompleteImpl
*ac
, HWND hwnd
)
393 HWND hwndListBox
= ac
->hwndListBox
;
394 if (!(ac
->options
& ACO_AUTOSUGGEST
))
397 if (IsWindowVisible(ac
->hwndListBoxOwner
))
399 INT sel
= SendMessageW(hwndListBox
, LB_GETCURSEL
, 0, 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 */
409 hide_listbox(ac
, hwndListBox
, TRUE
);
413 static LRESULT
change_selection(IAutoCompleteImpl
*ac
, HWND hwnd
, UINT key
)
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
)
423 sel
= (key
== VK_PRIOR
) ? count
- 1 : 0;
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);
435 if (sel
== base
) base
-= min(base
, pgsz
);
441 if (sel
== count
- 1)
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;
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
;
461 set_text_and_selection(ac
, hwnd
, msg
, len
, len
);
466 static BOOL
do_aclist_expand(IAutoCompleteImpl
*ac
, WCHAR
*txt
, WCHAR
*last_delim
)
468 WCHAR c
= last_delim
[1];
471 IEnumString_Reset(ac
->enumstr
); /* call before expand */
473 last_delim
[1] = '\0';
474 IACList_Expand(ac
->aclist
, txt
);
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
;
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
;
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
++)
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) */
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 */
522 static void autoappend_str(IAutoCompleteImpl
*ac
, WCHAR
*text
, UINT len
, WCHAR
*str
, HWND hwnd
)
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
)
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
));
544 set_text_and_selection(ac
, hwnd
, tmp
, len
, size
- 1);
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
;
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);
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
))
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;
576 if (!(ac
->options
& ACO_AUTOSUGGEST
))
579 end
= ac
->enum_strs_num
;
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);
591 SendMessageW(ac
->hwndListBox
, WM_SETREDRAW
, TRUE
, 0);
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
);
610 static void autocomplete_text(IAutoCompleteImpl
*ac
, HWND hwnd
, enum autoappend_flag flag
)
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
);
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);
632 len
= SendMessageW(hwnd
, WM_GETTEXT
, size
, (LPARAM
)text
);
634 text
= heap_realloc(text
, (len
+ 1) * sizeof(WCHAR
));
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
)
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
)
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 */
686 /* If quickComplete is set and control is pressed, replace the string */
687 if (ac
->quickComplete
&& (GetKeyState(VK_CONTROL
) & 0x8000))
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
))))
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
);
706 if (ac
->options
& ACO_AUTOSUGGEST
)
707 hide_listbox(ac
, ac
->hwndListBox
, TRUE
);
712 if (select_item_with_return_key(ac
, hwnd
))
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
);
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
))
735 if (!IsWindowVisible(ac
->hwndListBoxOwner
))
737 if (ac
->options
& ACO_UPDOWNKEYDROPSLIST
)
739 autocomplete_text(ac
, hwnd
, autoappend_flag_displayempty
);
744 return change_selection(ac
, hwnd
, wParam
);
748 LRESULT ret
= CallWindowProcW(ac
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
749 autocomplete_text(ac
, hwnd
, autoappend_flag_no
);
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
);
765 if (!This
->enabled
) return CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
769 case CB_SHOWDROPDOWN
:
770 if (This
->options
& ACO_AUTOSUGGEST
)
771 hide_listbox(This
, This
->hwndListBox
, TRUE
);
774 if (This
->options
& ACO_AUTOSUGGEST
)
776 if (This
->hwndListBoxOwner
== (HWND
)wParam
||
777 This
->hwndListBoxOwner
== GetAncestor((HWND
)wParam
, GA_PARENT
))
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
);
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
))
795 return ACEditSubclassProc_KeyDown(This
, hwnd
, uMsg
, wParam
, lParam
);
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'))
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
);
810 if (This
->options
& ACO_AUTOSUGGEST
)
811 hide_listbox(This
, This
->hwndListBox
, TRUE
);
816 ret
= CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
817 autocomplete_text(This
, hwnd
, autoappend_flag_no
);
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
);
825 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
826 return SendMessageW(This
->hwndListBox
, WM_MOUSEWHEEL
, wParam
, lParam
);
829 if (This
->hwndListBox
)
830 set_listbox_font(This
, (HFONT
)wParam
);
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
);
852 case WM_MOUSEACTIVATE
:
853 return MA_NOACTIVATE
;
855 sel
= SendMessageW(hwnd
, LB_ITEMFROMPOINT
, 0, lParam
);
856 SendMessageW(hwnd
, LB_SETCURSEL
, sel
, 0);
859 sel
= SendMessageW(hwnd
, LB_GETCURSEL
, 0, 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
);
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
);
876 case WM_MOUSEACTIVATE
:
877 return MA_NOACTIVATE
;
879 if (draw_listbox_item(This
, (DRAWITEMSTRUCT
*)lParam
, wParam
))
883 SetWindowPos(This
->hwndListBox
, NULL
, 0, 0, LOWORD(lParam
), HIWORD(lParam
),
884 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_DEFERERASE
);
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
;
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
) {
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);
918 set_listbox_font(This
, edit_font
);
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
,
935 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
937 TRACE("(%p)->(IID:%s,%p)\n", This
, shdebugstr_guid(riid
), ppvObj
);
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
;
953 IUnknown_AddRef((IUnknown
*)*ppvObj
);
954 TRACE("-- Interface: (%p)->(%p)\n", ppvObj
, *ppvObj
);
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);
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);
987 TRACE("destroying IAutoComplete(%p)\n", This
);
988 heap_free(This
->quickComplete
);
989 heap_free(This
->txtbackup
);
991 IEnumString_Release(This
->enumstr
);
993 IACList_Release(This
->aclist
);
999 /******************************************************************************
1000 * IAutoComplete2_fnEnable
1002 static HRESULT WINAPI
IAutoComplete2_fnEnable(
1003 IAutoComplete2
* iface
,
1006 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1008 TRACE("(%p)->(%s)\n", This
, (fEnable
)?"true":"false");
1010 This
->enabled
= fEnable
;
1015 /******************************************************************************
1016 * IAutoComplete2_fnInit
1018 static HRESULT WINAPI
IAutoComplete2_fnInit(
1019 IAutoComplete2
* iface
,
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
);
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
);
1084 static const HKEY roots
[] = { HKEY_CURRENT_USER
, HKEY_LOCAL_MACHINE
};
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
));
1103 for (i
= 0; i
< ARRAY_SIZE(roots
); i
++)
1105 if (RegOpenKeyExW(roots
[i
], key
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
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
);
1119 if (res
!= ERROR_MORE_DATA
|| type
!= REG_SZ
)
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
));
1138 /**************************************************************************
1139 * IAutoComplete2_fnGetOptions
1141 static HRESULT WINAPI
IAutoComplete2_fnGetOptions(
1142 IAutoComplete2
* iface
,
1145 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1147 TRACE("(%p) -> (%p)\n", This
, pdwFlag
);
1149 *pdwFlag
= This
->options
;
1154 /**************************************************************************
1155 * IAutoComplete2_fnSetOptions
1157 static HRESULT WINAPI
IAutoComplete2_fnSetOptions(
1158 IAutoComplete2
* iface
,
1161 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1162 DWORD changed
= This
->options
^ dwFlag
;
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
);
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
,
1225 LPWSTR
*ppwszString
)
1227 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1230 TRACE("(%p) -> (%p, %p)\n", This
, pdwFlags
, ppwszString
);
1232 dropped
= IsWindowVisible(This
->hwndListBoxOwner
);
1235 *pdwFlags
= (dropped
? ACDD_VISIBLE
: 0);
1241 sel
= SendMessageW(This
->hwndListBox
, LB_GETCURSEL
, 0, 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
);
1252 *ppwszString
= NULL
;
1255 *ppwszString
= NULL
;
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
);
1273 free_enum_strs(This
);
1274 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
1275 autocomplete_text(This
, This
->hwndEdit
, autoappend_flag_displayempty
);
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
;
1300 if (pUnkOuter
&& !IsEqualIID (riid
, &IID_IUnknown
))
1301 return CLASS_E_NOAGGREGATION
;
1303 lpac
= heap_alloc_zero(sizeof(*lpac
));
1305 return E_OUTOFMEMORY
;
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
);