Commit e1a7d6ee authored by Gabriel Ivăncescu's avatar Gabriel Ivăncescu Committed by Alexandre Julliard

shell32/autocomplete: Implement a cache and sort the enumerated strings for proper behavior.

Windows doesn't reset and re-enumerate it everytime autocompletion happens, and it also sorts the strings. This matches it more closely and makes it more useable on large lists as well. Signed-off-by: 's avatarGabriel Ivăncescu <gabrielopcode@gmail.com> Signed-off-by: 's avatarHuw Davies <huw@codeweavers.com> Signed-off-by: 's avatarAlexandre Julliard <julliard@winehq.org>
parent 6d9268a6
......@@ -25,7 +25,6 @@
- implement ACO_FILTERPREFIXES style
- implement ACO_RTLREADING style
- implement ResetEnumerator
- string compares should be case-insensitive, the content of the list should be sorted
*/
#include "config.h"
......@@ -62,6 +61,8 @@ typedef struct
LONG ref;
BOOL initialized;
BOOL enabled;
UINT enum_strs_num;
WCHAR **enum_strs;
HWND hwndEdit;
HWND hwndListBox;
WNDPROC wpOrigEditProc;
......@@ -103,10 +104,94 @@ static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text
CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
}
static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd)
static int enumerate_strings_cmpfn(const void *a, const void *b)
{
return strcmpiW(*(WCHAR* const*)a, *(WCHAR* const*)b);
}
/*
Enumerate all of the strings and sort them in the internal list.
We don't free the enumerated strings (except on error) to avoid needless
copies, until the next reset (or the object itself is destroyed)
*/
static void enumerate_strings(IAutoCompleteImpl *ac)
{
UINT cur = 0, array_size = 1024;
LPOLESTR *strs = NULL, *tmp;
ULONG read;
do
{
if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
goto fail;
strs = tmp;
do
{
if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
read = 0;
} while (read != 0 && (cur += read) < array_size);
array_size *= 2;
} while (read != 0);
/* Allocate even if there were zero strings enumerated, to mark it non-NULL */
if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
{
strs = tmp;
if (cur > 0)
qsort(strs, cur, sizeof(*strs), enumerate_strings_cmpfn);
ac->enum_strs = strs;
ac->enum_strs_num = cur;
return;
}
fail:
while (cur--)
CoTaskMemFree(strs[cur]);
heap_free(strs);
}
static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
UINT len, int direction)
{
WCHAR **strs = ac->enum_strs;
UINT index = ~0, a = start, b = ac->enum_strs_num;
while (a < b)
{
UINT i = (a + b - 1) / 2;
int cmp = strncmpiW(text, strs[i], len);
if (cmp == 0)
{
index = i;
cmp = direction;
}
if (cmp <= 0) b = i;
else a = i + 1;
}
return index;
}
static void free_enum_strs(IAutoCompleteImpl *ac)
{
WCHAR **strs = ac->enum_strs;
if (strs)
{
UINT i = ac->enum_strs_num;
ac->enum_strs = NULL;
while (i--)
CoTaskMemFree(strs[i]);
heap_free(strs);
}
}
static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
{
ShowWindow(hwnd, SW_HIDE);
SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
if (reset) free_enum_strs(ac);
}
static void show_listbox(IAutoCompleteImpl *ac, UINT cnt)
......@@ -167,14 +252,14 @@ static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
{
len = SendMessageW(hwndListBox, LB_GETTEXT, sel, (LPARAM)text);
set_text_and_selection(ac, hwnd, text, 0, len);
hide_listbox(ac, hwndListBox);
hide_listbox(ac, hwndListBox, TRUE);
ac->no_fwd_char = '\r'; /* RETURN char */
heap_free(text);
return TRUE;
}
}
}
hide_listbox(ac, hwndListBox);
hide_listbox(ac, hwndListBox, TRUE);
return FALSE;
}
......@@ -241,6 +326,10 @@ static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
{
WCHAR c = last_delim[1];
free_enum_strs(ac);
IEnumString_Reset(ac->enumstr); /* call before expand */
last_delim[1] = '\0';
IACList_Expand(ac->aclist, txt);
last_delim[1] = c;
......@@ -276,6 +365,9 @@ static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
while (i--)
if (strchrW(delims, txt[i]))
return do_aclist_expand(ac, txt, &txt[i]);
/* Windows doesn't expand without a delim, but it does reset */
free_enum_strs(ac);
}
return FALSE;
......@@ -312,66 +404,57 @@ static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
HWND hwnd, enum autoappend_flag flag)
{
/* Return FALSE if we need to hide the listbox */
UINT cpt;
WCHAR **str = ac->enum_strs;
UINT cnt, start, end;
if (!str) return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
if (ac->options & ACO_AUTOSUGGEST)
if (len)
{
SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
start = find_matching_enum_str(ac, 0, text, len, -1);
if (start == ~0)
return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
if (flag == autoappend_flag_yes)
autoappend_str(ac, text, len, str[start], hwnd);
if (!(ac->options & ACO_AUTOSUGGEST))
return TRUE;
/* Find the index beyond the last string that matches */
end = find_matching_enum_str(ac, start + 1, text, len, 1);
end = (end == ~0 ? start : end) + 1;
}
for (cpt = 0;;)
{
HRESULT hr;
LPOLESTR strs = NULL;
ULONG fetched;
hr = IEnumString_Next(ac->enumstr, 1, &strs, &fetched);
if (hr != S_OK)
break;
if (!strncmpiW(text, strs, len))
{
if (cpt == 0 && flag == autoappend_flag_yes)
{
autoappend_str(ac, text, len, strs, hwnd);
if (!(ac->options & ACO_AUTOSUGGEST))
{
CoTaskMemFree(strs);
break;
}
}
if (ac->options & ACO_AUTOSUGGEST)
SendMessageW(ac->hwndListBox, LB_ADDSTRING, 0, (LPARAM)strs);
cpt++;
}
CoTaskMemFree(strs);
}
if (ac->options & ACO_AUTOSUGGEST)
else
{
if (cpt)
{
show_listbox(ac, cpt);
SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
}
else
if (!(ac->options & ACO_AUTOSUGGEST))
return TRUE;
start = 0;
end = ac->enum_strs_num;
if (end == 0)
return FALSE;
}
cnt = end - start;
SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
SendMessageW(ac->hwndListBox, LB_INITSTORAGE, cnt, 0);
for (; start < end; start++)
SendMessageW(ac->hwndListBox, LB_INSERTSTRING, -1, (LPARAM)str[start]);
show_listbox(ac, cnt);
SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
return TRUE;
}
static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
{
WCHAR *text;
BOOL expanded = FALSE;
UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
if (flag != autoappend_flag_displayempty && len == 0)
{
if (ac->options & ACO_AUTOSUGGEST)
hide_listbox(ac, ac->hwndListBox);
hide_listbox(ac, ac->hwndListBox, TRUE);
return;
}
......@@ -382,28 +465,31 @@ static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_
if (len + 1 != size)
text = heap_realloc(text, (len + 1) * sizeof(WCHAR));
/* Reset it here to simplify the logic in aclist_expand for
empty strings, since it tracks changes using txtbackup,
and Reset needs to be called before IACList::Expand */
IEnumString_Reset(ac->enumstr);
if (ac->aclist)
{
aclist_expand(ac, text);
if (text[len - 1] == '\\' || text[len - 1] == '/')
flag = autoappend_flag_no;
expanded = aclist_expand(ac, text);
}
if (expanded || !ac->enum_strs)
{
if (!expanded) IEnumString_Reset(ac->enumstr);
enumerate_strings(ac);
}
/* Set txtbackup to point to text itself (which must not be released) */
/* Set txtbackup to point to text itself (which must not be released),
and it must be done here since aclist_expand uses it to track changes */
heap_free(ac->txtbackup);
ac->txtbackup = text;
if (!display_matching_strs(ac, text, len, hwnd, flag))
hide_listbox(ac, ac->hwndListBox);
hide_listbox(ac, ac->hwndListBox, FALSE);
}
static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
{
ac->hwndEdit = NULL;
free_enum_strs(ac);
if (ac->hwndListBox)
DestroyWindow(ac->hwndListBox);
IAutoComplete2_Release(&ac->IAutoComplete2_iface);
......@@ -421,7 +507,7 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT
/* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBox))
{
hide_listbox(ac, ac->hwndListBox);
hide_listbox(ac, ac->hwndListBox, FALSE);
ac->no_fwd_char = 0x1B; /* ESC char */
return 0;
}
......@@ -448,7 +534,7 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT
}
if (ac->options & ACO_AUTOSUGGEST)
hide_listbox(ac, ac->hwndListBox);
hide_listbox(ac, ac->hwndListBox, TRUE);
heap_free(text);
return 0;
}
......@@ -512,12 +598,12 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
{
case CB_SHOWDROPDOWN:
if (This->options & ACO_AUTOSUGGEST)
hide_listbox(This, This->hwndListBox);
hide_listbox(This, This->hwndListBox, TRUE);
return 0;
case WM_KILLFOCUS:
if ((This->options & ACO_AUTOSUGGEST) && ((HWND)wParam != This->hwndListBox))
{
hide_listbox(This, This->hwndListBox);
hide_listbox(This, This->hwndListBox, FALSE);
}
break;
case WM_KEYDOWN:
......@@ -583,7 +669,7 @@ static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
break;
len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
set_text_and_selection(This, This->hwndEdit, msg, 0, len);
hide_listbox(This, hwnd);
hide_listbox(This, hwnd, TRUE);
heap_free(msg);
break;
default:
......@@ -861,7 +947,7 @@ static HRESULT WINAPI IAutoComplete2_fnSetOptions(
if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
create_listbox(This);
else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
hide_listbox(This, This->hwndListBox);
hide_listbox(This, This->hwndListBox, TRUE);
return hr;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment