[PATCH v2 08/11] shell32/autocomplete: Redesign the window proc to trigger on key presses instead of key release

Gabriel Ivăncescu gabrielopcode at gmail.com
Thu Sep 6 13:26:18 CDT 2018


AutoComplete currently shows up when the user releases a key, which is
wrong. Windows does it when the user presses a key, so use both WM_KEYDOWN
and WM_CHAR and redesign it so that it matches Windows behavior.

Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

v2: Most of it has been split into multiple patches, and I split it as much
as I could without introducing extra code that would just get removed on
the very next patch in the series.

A difficulty here stems from the fact that we need to handle not just
WM_KEYDOWN but also WM_CHAR, which is harder than WM_KEYUP especially when
trying to share code paths because control characters have to be handled
separately (for auto-append purposes).

For example VK_BACK is not processed in WM_KEYDOWN because it's done during
WM_CHAR as the escape '\b' along the rest. That's not the case with VK_DELETE
which has no WM_CHAR escape. This is important since the edit control *must*
receive the message before us, otherwise the behavior will be wrong, so
they have to be deferred to WM_CHAR and we can't do it in WM_KEYDOWN. That's
also why we always make sure to pass the corresponding message to the edit
control first.

Signed-off-by: Gabriel Ivăncescu <gabrielopcode at gmail.com>
---

 dlls/shell32/autocomplete.c | 111 ++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 44 deletions(-)

diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c
index 4485167..fa965c0 100644
--- a/dlls/shell32/autocomplete.c
+++ b/dlls/shell32/autocomplete.c
@@ -108,10 +108,12 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
 {
     IAutoCompleteImpl *This = GetPropW(hwnd, autocomplete_propertyW);
     HRESULT hr;
-    WCHAR hwndText[255];
+    LRESULT ret;
+    WCHAR *hwndText;
+    UINT len, size, cpt;
     RECT r;
-    BOOL displayall = FALSE;
-    int cpt, height, sel;
+    BOOLEAN displayall = FALSE, noautoappend = !(This->options & ACO_AUTOAPPEND);
+    int height, sel;
 
     if (!This->enabled) return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
 
@@ -127,21 +129,20 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                 ShowWindow(This->hwndListBox, SW_HIDE);
             }
             return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
-        case WM_KEYUP:
-        {
-            int len;
-
-            GetWindowTextW(hwnd, hwndText, ARRAY_SIZE(hwndText));
-
+        case WM_KEYDOWN:
             switch(wParam) {
                 case VK_RETURN:
                     /* If quickComplete is set and control is pressed, replace the string */
                     if (This->quickComplete && (GetKeyState(VK_CONTROL) & 0x8000))
                     {
                         WCHAR *buf;
-                        size_t len = strlenW(hwndText);
-                        size_t sz = strlenW(This->quickComplete) + 1;
-                        sz += max(len, 2);  /* %s is 2 chars */
+                        size_t sz;
+                        len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0) + 1;  /* include NUL */
+                        if (!(hwndText = heap_alloc(len * sizeof(WCHAR))))
+                            return 0;
+                        len = SendMessageW(hwnd, WM_GETTEXT, len, (LPARAM)hwndText);
+
+                        sz = strlenW(This->quickComplete)+1 + max(len, 2);  /* %s is 2 chars */
 
                         /* Replace the first %s directly without using snprintf, to avoid
                            exploits since the format string can be retrieved from the registry */
@@ -162,29 +163,31 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                             SendMessageW(hwnd, EM_SETSEL, 0, sz-1);
                             heap_free(buf);
                         }
+                        if (This->options & ACO_AUTOSUGGEST)
+                            ShowWindow(This->hwndListBox, SW_HIDE);
+                        heap_free(hwndText);
+                        return 0;
                     }
 
                     if (This->options & ACO_AUTOSUGGEST)
                         ShowWindow(This->hwndListBox, SW_HIDE);
-                    return 0;
-                case VK_LEFT:
-                case VK_RIGHT:
-                    return 0;
+                    break;
                 case VK_UP:
                 case VK_DOWN:
-                    /* Two cases here :
-                       - if the listbox is not visible, displays it
-                       with all the entries if the style ACO_UPDOWNKEYDROPSLIST
-                       is present but does not select anything.
+                    /* Two cases here:
+                       - if the listbox is not visible and ACO_UPDOWNKEYDROPSLIST is
+                         set, display it with all the entries, without selecting any
                        - if the listbox is visible, change the selection
                     */
-                    if ( (This->options & (ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST))
-                         && (!IsWindowVisible(This->hwndListBox) && (! *hwndText)) )
-                    {
-                         /* We must display all the entries */
-                         displayall = TRUE;
-                    } else {
-                        if (IsWindowVisible(This->hwndListBox)) {
+                    if (This->options & ACO_AUTOSUGGEST) {
+                        if (!IsWindowVisible(This->hwndListBox)) {
+                            if (This->options & ACO_UPDOWNKEYDROPSLIST) {
+                                ret = 0;
+                                displayall = TRUE;
+                                noautoappend = TRUE;
+                                goto autocomplete_text;
+                            }
+                        } else {
                             int count;
 
                             count = SendMessageW(This->hwndListBox, LB_GETCOUNT, 0, 0);
@@ -211,29 +214,51 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                                 len = strlenW(This->txtbackup);
                                 SendMessageW(hwnd, EM_SETSEL, len, len);
                             }
+                            return 0;
                         }
-                        return 0;
                     }
                     break;
-                case VK_BACK:
                 case VK_DELETE:
-                    if ((! *hwndText) && (This->options & ACO_AUTOSUGGEST)) {
-                        ShowWindow(This->hwndListBox, SW_HIDE);
-                        return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
-                    }
-                    break;
+                    ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
+                    goto handle_control_char;
+            }
+            return CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
+        case WM_CHAR:
+        case WM_UNICHAR:
+            ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam);
+            if (wParam < ' ')
+            {
+              handle_control_char:
+                len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
+                if ((This->options & ACO_AUTOSUGGEST) && len == 0)
+                {
+                    ShowWindow(This->hwndListBox, SW_HIDE);
+                    return ret;
+                }
+                if (wParam != 0x16 /* ^V (paste) */)
+                    noautoappend = TRUE;
             }
+            else
+            {
+              autocomplete_text:
+                len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
+            }
+
+            size = len+1;
+            if (!(hwndText = heap_alloc(size * sizeof(WCHAR))))
+                return ret;
+            len = SendMessageW(hwnd, WM_GETTEXT, size, (LPARAM)hwndText);
+            if (len+1 != size)
+                hwndText = heap_realloc(hwndText, len+1);
 
             SendMessageW(This->hwndListBox, LB_RESETCONTENT, 0, 0);
 
+            /* Set txtbackup to point to hwndText itself (which must not be released) */
             heap_free(This->txtbackup);
-            len = strlenW(hwndText);
-            This->txtbackup = heap_alloc((len + 1)*sizeof(WCHAR));
-            lstrcpyW(This->txtbackup, hwndText);
+            This->txtbackup = hwndText;
 
-            /* Returns if there is no text to search and we doesn't want to display all the entries */
-            if ((!displayall) && (! *hwndText) )
-                break;
+            if (!displayall && !len)
+                return ret;
 
             IEnumString_Reset(This->enumstr);
             for(cpt = 0;;) {
@@ -245,7 +270,7 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                     break;
 
                 if (!strncmpiW(hwndText, strs, len)) {
-                    if (cpt == 0 && (This->options & ACO_AUTOAPPEND)) {
+                    if (cpt == 0 && noautoappend == FALSE) {
                         WCHAR buffW[255];
 
                         strcpyW(buffW, hwndText);
@@ -282,9 +307,7 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
                     ShowWindow(This->hwndListBox, SW_HIDE);
                 }
             }
-
-            break;
-        }
+            return ret;
         case WM_DESTROY:
         {
             WNDPROC proc = This->wpOrigEditProc;
-- 
1.9.1




More information about the wine-devel mailing list