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"
39 #include "undocshell.h"
48 #include "shell32_main.h"
50 WINE_DEFAULT_DEBUG_CHANNEL(shell
);
54 IAutoComplete2 IAutoComplete2_iface
;
55 IAutoCompleteDropDown IAutoCompleteDropDown_iface
;
64 HWND hwndListBoxOwner
;
65 WNDPROC wpOrigEditProc
;
66 WNDPROC wpOrigLBoxProc
;
67 WNDPROC wpOrigLBoxOwnerProc
;
72 AUTOCOMPLETEOPTIONS options
;
80 autoappend_flag_displayempty
85 prefix_filtering_none
= 0, /* no prefix filtering (raw search) */
86 prefix_filtering_protocol
, /* filter common protocol (e.g. http://) */
87 prefix_filtering_all
/* filter all common prefixes (protocol & www. ) */
90 static const WCHAR autocomplete_propertyW
[] = L
"Wine Autocomplete control";
92 static inline IAutoCompleteImpl
*impl_from_IAutoComplete2(IAutoComplete2
*iface
)
94 return CONTAINING_RECORD(iface
, IAutoCompleteImpl
, IAutoComplete2_iface
);
97 static inline IAutoCompleteImpl
*impl_from_IAutoCompleteDropDown(IAutoCompleteDropDown
*iface
)
99 return CONTAINING_RECORD(iface
, IAutoCompleteImpl
, IAutoCompleteDropDown_iface
);
102 static void set_text_and_selection(IAutoCompleteImpl
*ac
, HWND hwnd
, WCHAR
*text
, WPARAM start
, LPARAM end
)
104 /* Send it directly to the edit control to match Windows behavior */
105 WNDPROC proc
= ac
->wpOrigEditProc
;
106 if (CallWindowProcW(proc
, hwnd
, WM_SETTEXT
, 0, (LPARAM
)text
))
107 CallWindowProcW(proc
, hwnd
, EM_SETSEL
, start
, end
);
110 static inline WCHAR
*filter_protocol(WCHAR
*str
)
112 if (!wcsncmp(str
, L
"http", 4))
115 str
+= (*str
== 's'); /* https */
116 if (str
[0] == ':' && str
[1] == '/' && str
[2] == '/')
122 static inline WCHAR
*filter_www(WCHAR
*str
)
124 if (!wcsncmp(str
, L
"www.", 4)) return str
+ 4;
129 Get the prefix filtering based on text, for example if text's prefix
130 is a protocol, then we return none because we actually filter nothing
132 static enum prefix_filtering
get_text_prefix_filtering(const WCHAR
*text
)
134 /* Convert to lowercase to perform case insensitive filtering,
135 using the longest possible prefix as the size of the buffer */
136 WCHAR buf
[sizeof("https://")];
139 for (i
= 0; i
< ARRAY_SIZE(buf
) - 1 && text
[i
]; i
++)
140 buf
[i
] = towlower(text
[i
]);
143 if (filter_protocol(buf
)) return prefix_filtering_none
;
144 if (filter_www(buf
)) return prefix_filtering_protocol
;
145 return prefix_filtering_all
;
149 Filter the prefix of str based on the value of pfx_filter
150 This is used in sorting, so it's more performance sensitive
152 static WCHAR
*filter_str_prefix(WCHAR
*str
, enum prefix_filtering pfx_filter
)
156 if (pfx_filter
== prefix_filtering_none
) return str
;
157 if ((p
= filter_protocol(str
))) str
= p
;
159 if (pfx_filter
== prefix_filtering_protocol
) return str
;
160 if ((p
= filter_www(str
))) str
= p
;
165 static inline int sort_strs_cmpfn_impl(WCHAR
*a
, WCHAR
*b
, enum prefix_filtering pfx_filter
)
167 WCHAR
*str1
= filter_str_prefix(a
, pfx_filter
);
168 WCHAR
*str2
= filter_str_prefix(b
, pfx_filter
);
169 return wcsicmp(str1
, str2
);
172 static int __cdecl
sort_strs_cmpfn_none(const void *a
, const void *b
)
174 return sort_strs_cmpfn_impl(*(WCHAR
* const*)a
, *(WCHAR
* const*)b
, prefix_filtering_none
);
177 static int __cdecl
sort_strs_cmpfn_protocol(const void *a
, const void *b
)
179 return sort_strs_cmpfn_impl(*(WCHAR
* const*)a
, *(WCHAR
* const*)b
, prefix_filtering_protocol
);
182 static int __cdecl
sort_strs_cmpfn_all(const void *a
, const void *b
)
184 return sort_strs_cmpfn_impl(*(WCHAR
* const*)a
, *(WCHAR
* const*)b
, prefix_filtering_all
);
187 static int (* __cdecl sort_strs_cmpfn
[])(const void*, const void*) =
189 sort_strs_cmpfn_none
,
190 sort_strs_cmpfn_protocol
,
194 static void sort_strs(WCHAR
**strs
, UINT numstrs
, enum prefix_filtering pfx_filter
)
196 qsort(strs
, numstrs
, sizeof(*strs
), sort_strs_cmpfn
[pfx_filter
]);
200 Enumerate all of the strings and sort them in the internal list.
202 We don't free the enumerated strings (except on error) to avoid needless
203 copies, until the next reset (or the object itself is destroyed)
205 static void enumerate_strings(IAutoCompleteImpl
*ac
, enum prefix_filtering pfx_filter
)
207 UINT cur
= 0, array_size
= 1024;
208 LPOLESTR
*strs
= NULL
, *tmp
;
213 if ((tmp
= heap_realloc(strs
, array_size
* sizeof(*strs
))) == NULL
)
219 if (FAILED(IEnumString_Next(ac
->enumstr
, array_size
- cur
, &strs
[cur
], &read
)))
221 } while (read
!= 0 && (cur
+= read
) < array_size
);
226 /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
227 if ((tmp
= heap_realloc(strs
, cur
* sizeof(*strs
))))
231 sort_strs(strs
, cur
, pfx_filter
);
233 ac
->enum_strs
= strs
;
234 ac
->enum_strs_num
= cur
;
240 CoTaskMemFree(strs
[cur
]);
244 static UINT
find_matching_enum_str(IAutoCompleteImpl
*ac
, UINT start
, WCHAR
*text
,
245 UINT len
, enum prefix_filtering pfx_filter
, int direction
)
247 WCHAR
**strs
= ac
->enum_strs
;
248 UINT index
= ~0, a
= start
, b
= ac
->enum_strs_num
;
251 UINT i
= (a
+ b
- 1) / 2;
252 int cmp
= wcsnicmp(text
, filter_str_prefix(strs
[i
], pfx_filter
), len
);
264 static void free_enum_strs(IAutoCompleteImpl
*ac
)
266 WCHAR
**strs
= ac
->enum_strs
;
269 UINT i
= ac
->enum_strs_num
;
270 ac
->enum_strs
= NULL
;
272 CoTaskMemFree(strs
[i
]);
277 static void hide_listbox(IAutoCompleteImpl
*ac
, HWND hwnd
, BOOL reset
)
279 ShowWindow(ac
->hwndListBoxOwner
, SW_HIDE
);
280 SendMessageW(hwnd
, LB_RESETCONTENT
, 0, 0);
281 if (reset
) free_enum_strs(ac
);
284 static void show_listbox(IAutoCompleteImpl
*ac
)
287 UINT cnt
, width
, height
;
289 GetWindowRect(ac
->hwndEdit
, &r
);
291 /* Windows XP displays 7 lines at most, then it uses a scroll bar */
292 cnt
= SendMessageW(ac
->hwndListBox
, LB_GETCOUNT
, 0, 0);
293 height
= SendMessageW(ac
->hwndListBox
, LB_GETITEMHEIGHT
, 0, 0) * min(cnt
+ 1, 7);
294 width
= r
.right
- r
.left
;
296 SetWindowPos(ac
->hwndListBoxOwner
, HWND_TOP
, r
.left
, r
.bottom
+ 1, width
, height
,
297 SWP_SHOWWINDOW
| SWP_NOACTIVATE
);
300 static void set_listbox_font(IAutoCompleteImpl
*ac
, HFONT font
)
302 /* We have to calculate the item height manually due to owner-drawn */
303 HFONT old_font
= NULL
;
307 if ((hdc
= GetDCEx(ac
->hwndListBox
, 0, DCX_CACHE
)))
310 if (font
) old_font
= SelectObject(hdc
, font
);
311 if (GetTextMetricsW(hdc
, &metrics
))
312 height
= metrics
.tmHeight
;
313 if (old_font
) SelectObject(hdc
, old_font
);
314 ReleaseDC(ac
->hwndListBox
, hdc
);
316 SendMessageW(ac
->hwndListBox
, WM_SETFONT
, (WPARAM
)font
, FALSE
);
317 SendMessageW(ac
->hwndListBox
, LB_SETITEMHEIGHT
, 0, height
);
320 static BOOL
draw_listbox_item(IAutoCompleteImpl
*ac
, DRAWITEMSTRUCT
*info
, UINT id
)
322 COLORREF old_text
, old_bk
;
327 if (info
->CtlType
!= ODT_LISTBOX
|| info
->CtlID
!= id
||
328 id
!= (UINT
)GetWindowLongPtrW(ac
->hwndListBox
, GWLP_ID
))
331 if ((INT
)info
->itemID
< 0 || info
->itemAction
== ODA_FOCUS
)
334 state
= info
->itemState
;
335 if (state
& ODS_SELECTED
)
337 old_bk
= SetBkColor(hdc
, GetSysColor(COLOR_HIGHLIGHT
));
338 old_text
= SetTextColor(hdc
, GetSysColor(COLOR_HIGHLIGHTTEXT
));
341 str
= ac
->listbox_strs
[info
->itemID
];
342 ExtTextOutW(hdc
, info
->rcItem
.left
+ 1, info
->rcItem
.top
,
343 ETO_OPAQUE
| ETO_CLIPPED
, &info
->rcItem
, str
,
344 lstrlenW(str
), NULL
);
346 if (state
& ODS_SELECTED
)
348 SetBkColor(hdc
, old_bk
);
349 SetTextColor(hdc
, old_text
);
354 static size_t format_quick_complete(WCHAR
*dst
, const WCHAR
*qc
, const WCHAR
*str
, size_t str_len
)
356 /* Replace the first %s directly without using snprintf, to avoid
357 exploits since the format string can be retrieved from the registry */
364 if (args
< 1 && qc
[1] == 's')
366 memcpy(dst
, str
, str_len
* sizeof(WCHAR
));
372 qc
+= (qc
[1] == '%');
380 static BOOL
select_item_with_return_key(IAutoCompleteImpl
*ac
, HWND hwnd
)
383 HWND hwndListBox
= ac
->hwndListBox
;
384 if (!(ac
->options
& ACO_AUTOSUGGEST
))
387 if (IsWindowVisible(ac
->hwndListBoxOwner
))
389 INT sel
= SendMessageW(hwndListBox
, LB_GETCURSEL
, 0, 0);
392 text
= ac
->listbox_strs
[sel
];
393 set_text_and_selection(ac
, hwnd
, text
, 0, lstrlenW(text
));
394 hide_listbox(ac
, hwndListBox
, TRUE
);
395 ac
->no_fwd_char
= '\r'; /* RETURN char */
399 hide_listbox(ac
, hwndListBox
, TRUE
);
403 static LRESULT
change_selection(IAutoCompleteImpl
*ac
, HWND hwnd
, UINT key
)
408 INT count
= SendMessageW(ac
->hwndListBox
, LB_GETCOUNT
, 0, 0);
409 INT sel
= SendMessageW(ac
->hwndListBox
, LB_GETCURSEL
, 0, 0);
410 if (key
== VK_PRIOR
|| key
== VK_NEXT
)
413 sel
= (key
== VK_PRIOR
) ? count
- 1 : 0;
416 INT base
= SendMessageW(ac
->hwndListBox
, LB_GETTOPINDEX
, 0, 0);
417 INT pgsz
= SendMessageW(ac
->hwndListBox
, LB_GETLISTBOXINFO
, 0, 0);
418 pgsz
= max(pgsz
- 1, 1);
425 if (sel
== base
) base
-= min(base
, pgsz
);
431 if (sel
== count
- 1)
436 if (sel
>= base
) base
+= pgsz
;
437 sel
= min(base
, count
- 1);
442 else if (key
== VK_UP
|| (key
== VK_TAB
&& (GetKeyState(VK_SHIFT
) & 0x8000)))
443 sel
= ((sel
- 1) < -1) ? count
- 1 : sel
- 1;
445 sel
= ((sel
+ 1) >= count
) ? -1 : sel
+ 1;
447 SendMessageW(ac
->hwndListBox
, LB_SETCURSEL
, sel
, 0);
449 msg
= (sel
>= 0) ? ac
->listbox_strs
[sel
] : ac
->txtbackup
;
451 set_text_and_selection(ac
, hwnd
, msg
, len
, len
);
456 static BOOL
do_aclist_expand(IAutoCompleteImpl
*ac
, WCHAR
*txt
, WCHAR
*last_delim
)
458 WCHAR c
= last_delim
[1];
461 IEnumString_Reset(ac
->enumstr
); /* call before expand */
463 last_delim
[1] = '\0';
464 IACList_Expand(ac
->aclist
, txt
);
469 static BOOL
aclist_expand(IAutoCompleteImpl
*ac
, WCHAR
*txt
)
471 /* call IACList::Expand only when needed, if the
472 new txt and old_txt require different expansions */
474 const WCHAR
*old_txt
= ac
->txtbackup
;
475 WCHAR c
, *p
, *last_delim
;
478 /* always expand if the enumerator was reset */
479 if (!ac
->enum_strs
) old_txt
= L
"";
481 /* skip the shared prefix */
482 while ((c
= towlower(txt
[i
])) == towlower(old_txt
[i
]))
484 if (c
== '\0') return FALSE
;
488 /* they differ at this point, check for a delim further in txt */
489 for (last_delim
= NULL
, p
= &txt
[i
]; (p
= wcspbrk(p
, L
"\\/")) != NULL
; p
++)
491 if (last_delim
) return do_aclist_expand(ac
, txt
, last_delim
);
493 /* txt has no delim after i, check for a delim further in old_txt */
494 if (wcspbrk(&old_txt
[i
], L
"\\/"))
496 /* scan backwards to find the first delim before txt[i] (if any) */
498 if (wcschr(L
"\\/", txt
[i
]))
499 return do_aclist_expand(ac
, txt
, &txt
[i
]);
501 /* Windows doesn't expand without a delim, but it does reset */
508 static void autoappend_str(IAutoCompleteImpl
*ac
, WCHAR
*text
, UINT len
, WCHAR
*str
, HWND hwnd
)
514 /* Don't auto-append unless the caret is at the end */
515 SendMessageW(hwnd
, EM_GETSEL
, (WPARAM
)&sel_start
, 0);
516 if (sel_start
!= len
)
519 /* The character capitalization can be different,
520 so merge text and str into a new string */
521 size
= len
+ lstrlenW(&str
[len
]) + 1;
523 if ((tmp
= heap_alloc(size
* sizeof(*tmp
))))
525 memcpy(tmp
, text
, len
* sizeof(*tmp
));
526 memcpy(&tmp
[len
], &str
[len
], (size
- len
) * sizeof(*tmp
));
530 set_text_and_selection(ac
, hwnd
, tmp
, len
, size
- 1);
535 static BOOL
display_matching_strs(IAutoCompleteImpl
*ac
, WCHAR
*text
, UINT len
,
536 enum prefix_filtering pfx_filter
, HWND hwnd
,
537 enum autoappend_flag flag
)
539 /* Return FALSE if we need to hide the listbox */
540 WCHAR
**str
= ac
->enum_strs
;
542 if (!str
) return !(ac
->options
& ACO_AUTOSUGGEST
);
544 /* Windows seems to disable autoappend if ACO_NOPREFIXFILTERING is set */
545 if (!(ac
->options
& ACO_NOPREFIXFILTERING
) && len
)
547 start
= find_matching_enum_str(ac
, 0, text
, len
, pfx_filter
, -1);
549 return !(ac
->options
& ACO_AUTOSUGGEST
);
551 if (flag
== autoappend_flag_yes
)
552 autoappend_str(ac
, text
, len
, filter_str_prefix(str
[start
], pfx_filter
), hwnd
);
553 if (!(ac
->options
& ACO_AUTOSUGGEST
))
556 /* Find the index beyond the last string that matches */
557 end
= find_matching_enum_str(ac
, start
+ 1, text
, len
, pfx_filter
, 1);
558 end
= (end
== ~0 ? start
: end
) + 1;
562 if (!(ac
->options
& ACO_AUTOSUGGEST
))
565 end
= ac
->enum_strs_num
;
570 SendMessageW(ac
->hwndListBox
, WM_SETREDRAW
, FALSE
, 0);
571 SendMessageW(ac
->hwndListBox
, LB_RESETCONTENT
, 0, 0);
573 ac
->listbox_strs
= str
+ start
;
574 SendMessageW(ac
->hwndListBox
, LB_SETCOUNT
, end
- start
, 0);
577 SendMessageW(ac
->hwndListBox
, WM_SETREDRAW
, TRUE
, 0);
581 static enum prefix_filtering
setup_prefix_filtering(IAutoCompleteImpl
*ac
, const WCHAR
*text
)
583 enum prefix_filtering pfx_filter
;
584 if (!(ac
->options
& ACO_FILTERPREFIXES
)) return prefix_filtering_none
;
586 pfx_filter
= get_text_prefix_filtering(text
);
587 if (!ac
->enum_strs
) return pfx_filter
;
589 /* If the prefix filtering is different, re-sort the filtered strings */
590 if (pfx_filter
!= get_text_prefix_filtering(ac
->txtbackup
))
591 sort_strs(ac
->enum_strs
, ac
->enum_strs_num
, pfx_filter
);
596 static void autocomplete_text(IAutoCompleteImpl
*ac
, HWND hwnd
, enum autoappend_flag flag
)
599 BOOL expanded
= FALSE
;
600 enum prefix_filtering pfx_filter
;
601 UINT size
, len
= SendMessageW(hwnd
, WM_GETTEXTLENGTH
, 0, 0);
603 if (flag
!= autoappend_flag_displayempty
&& len
== 0)
605 if (ac
->options
& ACO_AUTOSUGGEST
)
606 hide_listbox(ac
, ac
->hwndListBox
, FALSE
);
612 if (!(text
= heap_alloc(size
* sizeof(WCHAR
))))
614 /* Reset the listbox to prevent potential crash from ResetEnumerator */
615 SendMessageW(ac
->hwndListBox
, LB_RESETCONTENT
, 0, 0);
618 len
= SendMessageW(hwnd
, WM_GETTEXT
, size
, (LPARAM
)text
);
620 text
= heap_realloc(text
, (len
+ 1) * sizeof(WCHAR
));
624 if (text
[len
- 1] == '\\' || text
[len
- 1] == '/')
625 flag
= autoappend_flag_no
;
626 expanded
= aclist_expand(ac
, text
);
628 pfx_filter
= setup_prefix_filtering(ac
, text
);
630 if (expanded
|| !ac
->enum_strs
)
632 if (!expanded
) IEnumString_Reset(ac
->enumstr
);
633 enumerate_strings(ac
, pfx_filter
);
636 /* Set txtbackup to point to text itself (which must not be released),
637 and it must be done here since aclist_expand uses it to track changes */
638 heap_free(ac
->txtbackup
);
639 ac
->txtbackup
= text
;
641 if (!display_matching_strs(ac
, text
, len
, pfx_filter
, hwnd
, flag
))
642 hide_listbox(ac
, ac
->hwndListBox
, FALSE
);
645 static void destroy_autocomplete_object(IAutoCompleteImpl
*ac
)
649 if (ac
->hwndListBoxOwner
)
650 DestroyWindow(ac
->hwndListBoxOwner
);
651 IAutoComplete2_Release(&ac
->IAutoComplete2_iface
);
655 Helper for ACEditSubclassProc
657 static LRESULT
ACEditSubclassProc_KeyDown(IAutoCompleteImpl
*ac
, HWND hwnd
, UINT uMsg
,
658 WPARAM wParam
, LPARAM lParam
)
663 /* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
664 if ((ac
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(ac
->hwndListBoxOwner
))
666 hide_listbox(ac
, ac
->hwndListBox
, FALSE
);
667 ac
->no_fwd_char
= 0x1B; /* ESC char */
672 /* If quickComplete is set and control is pressed, replace the string */
673 if (ac
->quickComplete
&& (GetKeyState(VK_CONTROL
) & 0x8000))
677 UINT len
= SendMessageW(hwnd
, WM_GETTEXTLENGTH
, 0, 0);
678 ac
->no_fwd_char
= '\n'; /* CTRL+RETURN char */
680 if (!(text
= heap_alloc((len
+ 1) * sizeof(WCHAR
))))
682 len
= SendMessageW(hwnd
, WM_GETTEXT
, len
+ 1, (LPARAM
)text
);
683 sz
= lstrlenW(ac
->quickComplete
) + 1 + len
;
685 if ((buf
= heap_alloc(sz
* sizeof(WCHAR
))))
687 len
= format_quick_complete(buf
, ac
->quickComplete
, text
, len
);
688 set_text_and_selection(ac
, hwnd
, buf
, 0, len
);
692 if (ac
->options
& ACO_AUTOSUGGEST
)
693 hide_listbox(ac
, ac
->hwndListBox
, TRUE
);
698 if (select_item_with_return_key(ac
, hwnd
))
702 if ((ac
->options
& (ACO_AUTOSUGGEST
| ACO_USETAB
)) == (ACO_AUTOSUGGEST
| ACO_USETAB
)
703 && IsWindowVisible(ac
->hwndListBoxOwner
) && !(GetKeyState(VK_CONTROL
) & 0x8000))
705 ac
->no_fwd_char
= '\t';
706 return change_selection(ac
, hwnd
, wParam
);
714 - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
715 set, display it with all the entries, without selecting any
716 - if the listbox is visible, change the selection
718 if (!(ac
->options
& ACO_AUTOSUGGEST
))
721 if (!IsWindowVisible(ac
->hwndListBoxOwner
))
723 if (ac
->options
& ACO_UPDOWNKEYDROPSLIST
)
725 autocomplete_text(ac
, hwnd
, autoappend_flag_displayempty
);
730 return change_selection(ac
, hwnd
, wParam
);
734 LRESULT ret
= CallWindowProcW(ac
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
735 autocomplete_text(ac
, hwnd
, autoappend_flag_no
);
739 ac
->no_fwd_char
= '\0';
740 return CallWindowProcW(ac
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
744 Window procedure for autocompletion
746 static LRESULT APIENTRY
ACEditSubclassProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
748 IAutoCompleteImpl
*This
= GetPropW(hwnd
, autocomplete_propertyW
);
751 if (!This
->enabled
) return CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
755 case CB_SHOWDROPDOWN
:
756 if (This
->options
& ACO_AUTOSUGGEST
)
757 hide_listbox(This
, This
->hwndListBox
, TRUE
);
760 if (This
->options
& ACO_AUTOSUGGEST
)
762 if (This
->hwndListBoxOwner
== (HWND
)wParam
||
763 This
->hwndListBoxOwner
== GetAncestor((HWND
)wParam
, GA_PARENT
))
765 hide_listbox(This
, This
->hwndListBox
, FALSE
);
768 /* Reset the enumerator if it's not visible anymore */
769 if (!IsWindowVisible(hwnd
)) free_enum_strs(This
);
771 case WM_WINDOWPOSCHANGED
:
773 WINDOWPOS
*pos
= (WINDOWPOS
*)lParam
;
775 if ((pos
->flags
& (SWP_NOMOVE
| SWP_NOSIZE
)) != (SWP_NOMOVE
| SWP_NOSIZE
) &&
776 This
->hwndListBoxOwner
&& IsWindowVisible(This
->hwndListBoxOwner
))
781 return ACEditSubclassProc_KeyDown(This
, hwnd
, uMsg
, wParam
, lParam
);
784 if (wParam
== This
->no_fwd_char
) return 0;
785 This
->no_fwd_char
= '\0';
787 /* Don't autocomplete at all on most control characters */
788 if (wParam
< 32 && !(wParam
>= '\b' && wParam
<= '\r'))
791 ret
= CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
792 autocomplete_text(This
, hwnd
, (This
->options
& ACO_AUTOAPPEND
) && wParam
>= ' '
793 ? autoappend_flag_yes
: autoappend_flag_no
);
796 if (This
->options
& ACO_AUTOSUGGEST
)
797 hide_listbox(This
, This
->hwndListBox
, TRUE
);
802 ret
= CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
803 autocomplete_text(This
, hwnd
, autoappend_flag_no
);
806 ret
= CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
807 autocomplete_text(This
, hwnd
, (This
->options
& ACO_AUTOAPPEND
)
808 ? autoappend_flag_yes
: autoappend_flag_no
);
811 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
812 return SendMessageW(This
->hwndListBox
, WM_MOUSEWHEEL
, wParam
, lParam
);
815 if (This
->hwndListBox
)
816 set_listbox_font(This
, (HFONT
)wParam
);
820 WNDPROC proc
= This
->wpOrigEditProc
;
822 SetWindowLongPtrW(hwnd
, GWLP_WNDPROC
, (LONG_PTR
)proc
);
823 RemovePropW(hwnd
, autocomplete_propertyW
);
824 destroy_autocomplete_object(This
);
825 return CallWindowProcW(proc
, hwnd
, uMsg
, wParam
, lParam
);
828 return CallWindowProcW(This
->wpOrigEditProc
, hwnd
, uMsg
, wParam
, lParam
);
831 static LRESULT APIENTRY
ACLBoxSubclassProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
833 IAutoCompleteImpl
*This
= (IAutoCompleteImpl
*)GetWindowLongPtrW(hwnd
, GWLP_USERDATA
);
838 case WM_MOUSEACTIVATE
:
839 return MA_NOACTIVATE
;
841 sel
= SendMessageW(hwnd
, LB_ITEMFROMPOINT
, 0, lParam
);
842 SendMessageW(hwnd
, LB_SETCURSEL
, sel
, 0);
845 sel
= SendMessageW(hwnd
, LB_GETCURSEL
, 0, 0);
848 msg
= This
->listbox_strs
[sel
];
849 set_text_and_selection(This
, This
->hwndEdit
, msg
, 0, lstrlenW(msg
));
850 hide_listbox(This
, hwnd
, TRUE
);
853 return CallWindowProcW(This
->wpOrigLBoxProc
, hwnd
, uMsg
, wParam
, lParam
);
856 static LRESULT APIENTRY
ACLBoxOwnerSubclassProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
858 IAutoCompleteImpl
*This
= (IAutoCompleteImpl
*)GetWindowLongPtrW(hwnd
, GWLP_USERDATA
);
862 case WM_MOUSEACTIVATE
:
863 return MA_NOACTIVATE
;
865 if (draw_listbox_item(This
, (DRAWITEMSTRUCT
*)lParam
, wParam
))
869 SetWindowPos(This
->hwndListBox
, NULL
, 0, 0, LOWORD(lParam
), HIWORD(lParam
),
870 SWP_NOACTIVATE
| SWP_NOMOVE
| SWP_NOZORDER
| SWP_DEFERERASE
);
873 return CallWindowProcW(This
->wpOrigLBoxOwnerProc
, hwnd
, uMsg
, wParam
, lParam
);
876 static void create_listbox(IAutoCompleteImpl
*This
)
878 This
->hwndListBoxOwner
= CreateWindowExW(WS_EX_NOACTIVATE
, WC_STATICW
, NULL
,
879 WS_BORDER
| WS_POPUP
| WS_CLIPCHILDREN
,
880 0, 0, 0, 0, NULL
, NULL
, shell32_hInstance
, NULL
);
881 if (!This
->hwndListBoxOwner
)
883 This
->options
&= ~ACO_AUTOSUGGEST
;
887 /* FIXME : The listbox should be resizable with the mouse. WS_THICKFRAME looks ugly */
888 This
->hwndListBox
= CreateWindowExW(WS_EX_NOACTIVATE
, WC_LISTBOXW
, NULL
,
889 WS_CHILD
| WS_VISIBLE
| WS_VSCROLL
| LBS_NODATA
| LBS_OWNERDRAWFIXED
| LBS_NOINTEGRALHEIGHT
,
890 0, 0, 0, 0, This
->hwndListBoxOwner
, NULL
, shell32_hInstance
, NULL
);
892 if (This
->hwndListBox
) {
895 This
->wpOrigLBoxProc
= (WNDPROC
) SetWindowLongPtrW( This
->hwndListBox
, GWLP_WNDPROC
, (LONG_PTR
) ACLBoxSubclassProc
);
896 SetWindowLongPtrW( This
->hwndListBox
, GWLP_USERDATA
, (LONG_PTR
)This
);
898 This
->wpOrigLBoxOwnerProc
= (WNDPROC
)SetWindowLongPtrW(This
->hwndListBoxOwner
, GWLP_WNDPROC
, (LONG_PTR
)ACLBoxOwnerSubclassProc
);
899 SetWindowLongPtrW(This
->hwndListBoxOwner
, GWLP_USERDATA
, (LONG_PTR
)This
);
901 /* Use the same font as the edit control, as it gets destroyed before it anyway */
902 edit_font
= (HFONT
)SendMessageW(This
->hwndEdit
, WM_GETFONT
, 0, 0);
904 set_listbox_font(This
, edit_font
);
908 DestroyWindow(This
->hwndListBoxOwner
);
909 This
->hwndListBoxOwner
= NULL
;
910 This
->options
&= ~ACO_AUTOSUGGEST
;
913 /**************************************************************************
914 * AutoComplete_QueryInterface
916 static HRESULT WINAPI
IAutoComplete2_fnQueryInterface(
917 IAutoComplete2
* iface
,
921 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
923 TRACE("(%p)->(IID:%s,%p)\n", This
, shdebugstr_guid(riid
), ppvObj
);
926 if (IsEqualIID(riid
, &IID_IUnknown
) ||
927 IsEqualIID(riid
, &IID_IAutoComplete
) ||
928 IsEqualIID(riid
, &IID_IAutoComplete2
))
930 *ppvObj
= &This
->IAutoComplete2_iface
;
932 else if (IsEqualIID(riid
, &IID_IAutoCompleteDropDown
))
934 *ppvObj
= &This
->IAutoCompleteDropDown_iface
;
939 IUnknown_AddRef((IUnknown
*)*ppvObj
);
940 TRACE("-- Interface: (%p)->(%p)\n", ppvObj
, *ppvObj
);
943 WARN("unsupported interface: %s\n", debugstr_guid(riid
));
944 return E_NOINTERFACE
;
947 /******************************************************************************
948 * IAutoComplete2_fnAddRef
950 static ULONG WINAPI
IAutoComplete2_fnAddRef(
951 IAutoComplete2
* iface
)
953 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
954 ULONG refCount
= InterlockedIncrement(&This
->ref
);
956 TRACE("(%p)->(%u)\n", This
, refCount
- 1);
961 /******************************************************************************
962 * IAutoComplete2_fnRelease
964 static ULONG WINAPI
IAutoComplete2_fnRelease(
965 IAutoComplete2
* iface
)
967 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
968 ULONG refCount
= InterlockedDecrement(&This
->ref
);
970 TRACE("(%p)->(%u)\n", This
, refCount
+ 1);
973 TRACE("destroying IAutoComplete(%p)\n", This
);
974 heap_free(This
->quickComplete
);
975 heap_free(This
->txtbackup
);
977 IEnumString_Release(This
->enumstr
);
979 IACList_Release(This
->aclist
);
985 /******************************************************************************
986 * IAutoComplete2_fnEnable
988 static HRESULT WINAPI
IAutoComplete2_fnEnable(
989 IAutoComplete2
* iface
,
992 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
994 TRACE("(%p)->(%s)\n", This
, (fEnable
)?"true":"false");
996 This
->enabled
= fEnable
;
1001 /******************************************************************************
1002 * IAutoComplete2_fnInit
1004 static HRESULT WINAPI
IAutoComplete2_fnInit(
1005 IAutoComplete2
* iface
,
1008 LPCOLESTR pwzsRegKeyPath
,
1009 LPCOLESTR pwszQuickComplete
)
1011 IAutoCompleteImpl
*prev
, *This
= impl_from_IAutoComplete2(iface
);
1013 TRACE("(%p)->(%p, %p, %s, %s)\n",
1014 This
, hwndEdit
, punkACL
, debugstr_w(pwzsRegKeyPath
), debugstr_w(pwszQuickComplete
));
1016 if (This
->options
& ACO_SEARCH
) FIXME(" ACO_SEARCH not supported\n");
1017 if (This
->options
& ACO_RTLREADING
) FIXME(" ACO_RTLREADING not supported\n");
1018 if (This
->options
& ACO_WORD_FILTER
) FIXME(" ACO_WORD_FILTER not supported\n");
1020 if (!hwndEdit
|| !punkACL
)
1021 return E_INVALIDARG
;
1023 if (This
->initialized
)
1025 WARN("Autocompletion object is already initialized\n");
1026 /* This->hwndEdit is set to NULL when the edit window is destroyed. */
1027 return This
->hwndEdit
? E_FAIL
: E_UNEXPECTED
;
1030 if (FAILED (IUnknown_QueryInterface (punkACL
, &IID_IEnumString
, (LPVOID
*)&This
->enumstr
))) {
1031 WARN("No IEnumString interface\n");
1032 return E_NOINTERFACE
;
1035 /* Prevent txtbackup from ever being NULL to simplify aclist_expand */
1036 if ((This
->txtbackup
= heap_alloc_zero(sizeof(WCHAR
))) == NULL
)
1038 IEnumString_Release(This
->enumstr
);
1039 This
->enumstr
= NULL
;
1040 return E_OUTOFMEMORY
;
1043 if (FAILED (IUnknown_QueryInterface (punkACL
, &IID_IACList
, (LPVOID
*)&This
->aclist
)))
1044 This
->aclist
= NULL
;
1046 This
->initialized
= TRUE
;
1047 This
->hwndEdit
= hwndEdit
;
1049 /* If another AutoComplete object was previously assigned to this edit control,
1050 release it but keep the same callback on the control, to avoid an infinite
1051 recursive loop in ACEditSubclassProc while the property is set to this object */
1052 prev
= GetPropW(hwndEdit
, autocomplete_propertyW
);
1053 SetPropW(hwndEdit
, autocomplete_propertyW
, This
);
1055 if (prev
&& prev
->initialized
) {
1056 This
->wpOrigEditProc
= prev
->wpOrigEditProc
;
1057 destroy_autocomplete_object(prev
);
1060 This
->wpOrigEditProc
= (WNDPROC
) SetWindowLongPtrW(hwndEdit
, GWLP_WNDPROC
, (LONG_PTR
) ACEditSubclassProc
);
1062 /* Keep at least one reference to the object until the edit window is destroyed */
1063 IAutoComplete2_AddRef(&This
->IAutoComplete2_iface
);
1065 if (This
->options
& ACO_AUTOSUGGEST
)
1066 create_listbox(This
);
1070 static const HKEY roots
[] = { HKEY_CURRENT_USER
, HKEY_LOCAL_MACHINE
};
1079 /* pwszRegKeyPath contains the key as well as the value, so split it */
1080 value
= wcsrchr(pwzsRegKeyPath
, '\\');
1081 len
= value
- pwzsRegKeyPath
;
1083 if (value
&& (key
= heap_alloc((len
+1) * sizeof(*key
))) != NULL
)
1085 memcpy(key
, pwzsRegKeyPath
, len
* sizeof(*key
));
1089 for (i
= 0; i
< ARRAY_SIZE(roots
); i
++)
1091 if (RegOpenKeyExW(roots
[i
], key
, 0, KEY_READ
, &hKey
) != ERROR_SUCCESS
)
1093 sz
= MAX_PATH
* sizeof(WCHAR
);
1095 while ((qc
= heap_alloc(sz
)) != NULL
)
1097 res
= RegQueryValueExW(hKey
, value
, NULL
, &type
, qc
, &sz
);
1098 if (res
== ERROR_SUCCESS
&& type
== REG_SZ
)
1100 This
->quickComplete
= heap_realloc(qc
, sz
);
1101 i
= ARRAY_SIZE(roots
);
1105 if (res
!= ERROR_MORE_DATA
|| type
!= REG_SZ
)
1114 if (!This
->quickComplete
&& pwszQuickComplete
)
1116 size_t len
= lstrlenW(pwszQuickComplete
)+1;
1117 if ((This
->quickComplete
= heap_alloc(len
* sizeof(WCHAR
))) != NULL
)
1118 memcpy(This
->quickComplete
, pwszQuickComplete
, len
* sizeof(WCHAR
));
1124 /**************************************************************************
1125 * IAutoComplete2_fnGetOptions
1127 static HRESULT WINAPI
IAutoComplete2_fnGetOptions(
1128 IAutoComplete2
* iface
,
1131 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1133 TRACE("(%p) -> (%p)\n", This
, pdwFlag
);
1135 *pdwFlag
= This
->options
;
1140 /**************************************************************************
1141 * IAutoComplete2_fnSetOptions
1143 static HRESULT WINAPI
IAutoComplete2_fnSetOptions(
1144 IAutoComplete2
* iface
,
1147 IAutoCompleteImpl
*This
= impl_from_IAutoComplete2(iface
);
1148 DWORD changed
= This
->options
^ dwFlag
;
1151 TRACE("(%p) -> (0x%x)\n", This
, dwFlag
);
1153 This
->options
= dwFlag
;
1155 if ((This
->options
& ACO_AUTOSUGGEST
) && This
->hwndEdit
&& !This
->hwndListBox
)
1156 create_listbox(This
);
1157 else if (!(This
->options
& ACO_AUTOSUGGEST
) && This
->hwndListBox
)
1158 hide_listbox(This
, This
->hwndListBox
, TRUE
);
1160 /* If ACO_FILTERPREFIXES changed we might have to reset the enumerator */
1161 if ((changed
& ACO_FILTERPREFIXES
) && This
->txtbackup
)
1163 if (get_text_prefix_filtering(This
->txtbackup
) != prefix_filtering_none
)
1164 IAutoCompleteDropDown_ResetEnumerator(&This
->IAutoCompleteDropDown_iface
);
1170 /**************************************************************************
1171 * IAutoComplete2 VTable
1173 static const IAutoComplete2Vtbl acvt
=
1175 IAutoComplete2_fnQueryInterface
,
1176 IAutoComplete2_fnAddRef
,
1177 IAutoComplete2_fnRelease
,
1178 IAutoComplete2_fnInit
,
1179 IAutoComplete2_fnEnable
,
1180 /* IAutoComplete2 */
1181 IAutoComplete2_fnSetOptions
,
1182 IAutoComplete2_fnGetOptions
,
1186 static HRESULT WINAPI
IAutoCompleteDropDown_fnQueryInterface(IAutoCompleteDropDown
*iface
,
1187 REFIID riid
, LPVOID
*ppvObj
)
1189 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1190 return IAutoComplete2_QueryInterface(&This
->IAutoComplete2_iface
, riid
, ppvObj
);
1193 static ULONG WINAPI
IAutoCompleteDropDown_fnAddRef(IAutoCompleteDropDown
*iface
)
1195 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1196 return IAutoComplete2_AddRef(&This
->IAutoComplete2_iface
);
1199 static ULONG WINAPI
IAutoCompleteDropDown_fnRelease(IAutoCompleteDropDown
*iface
)
1201 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1202 return IAutoComplete2_Release(&This
->IAutoComplete2_iface
);
1205 /**************************************************************************
1206 * IAutoCompleteDropDown_fnGetDropDownStatus
1208 static HRESULT WINAPI
IAutoCompleteDropDown_fnGetDropDownStatus(
1209 IAutoCompleteDropDown
*iface
,
1211 LPWSTR
*ppwszString
)
1213 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1216 TRACE("(%p) -> (%p, %p)\n", This
, pdwFlags
, ppwszString
);
1218 dropped
= IsWindowVisible(This
->hwndListBoxOwner
);
1221 *pdwFlags
= (dropped
? ACDD_VISIBLE
: 0);
1227 sel
= SendMessageW(This
->hwndListBox
, LB_GETCURSEL
, 0, 0);
1230 WCHAR
*str
= This
->listbox_strs
[sel
];
1231 size_t size
= (lstrlenW(str
) + 1) * sizeof(*str
);
1233 if (!(*ppwszString
= CoTaskMemAlloc(size
)))
1234 return E_OUTOFMEMORY
;
1235 memcpy(*ppwszString
, str
, size
);
1238 *ppwszString
= NULL
;
1241 *ppwszString
= NULL
;
1247 /**************************************************************************
1248 * IAutoCompleteDropDown_fnResetEnumarator
1250 static HRESULT WINAPI
IAutoCompleteDropDown_fnResetEnumerator(
1251 IAutoCompleteDropDown
*iface
)
1253 IAutoCompleteImpl
*This
= impl_from_IAutoCompleteDropDown(iface
);
1255 TRACE("(%p)\n", This
);
1259 free_enum_strs(This
);
1260 if ((This
->options
& ACO_AUTOSUGGEST
) && IsWindowVisible(This
->hwndListBoxOwner
))
1261 autocomplete_text(This
, This
->hwndEdit
, autoappend_flag_displayempty
);
1266 /**************************************************************************
1267 * IAutoCompleteDropDown VTable
1269 static const IAutoCompleteDropDownVtbl acdropdownvt
=
1271 IAutoCompleteDropDown_fnQueryInterface
,
1272 IAutoCompleteDropDown_fnAddRef
,
1273 IAutoCompleteDropDown_fnRelease
,
1274 IAutoCompleteDropDown_fnGetDropDownStatus
,
1275 IAutoCompleteDropDown_fnResetEnumerator
,
1278 /**************************************************************************
1279 * IAutoComplete_Constructor
1281 HRESULT WINAPI
IAutoComplete_Constructor(IUnknown
* pUnkOuter
, REFIID riid
, LPVOID
* ppv
)
1283 IAutoCompleteImpl
*lpac
;
1286 if (pUnkOuter
&& !IsEqualIID (riid
, &IID_IUnknown
))
1287 return CLASS_E_NOAGGREGATION
;
1289 lpac
= heap_alloc_zero(sizeof(*lpac
));
1291 return E_OUTOFMEMORY
;
1294 lpac
->IAutoComplete2_iface
.lpVtbl
= &acvt
;
1295 lpac
->IAutoCompleteDropDown_iface
.lpVtbl
= &acdropdownvt
;
1296 lpac
->enabled
= TRUE
;
1297 lpac
->options
= ACO_AUTOAPPEND
;
1299 hr
= IAutoComplete2_QueryInterface(&lpac
->IAutoComplete2_iface
, riid
, ppv
);
1300 IAutoComplete2_Release(&lpac
->IAutoComplete2_iface
);
1302 TRACE("-- (%p)->\n",lpac
);