Listview typing completion

François Gouget fgouget at codeweavers.com
Sun Feb 11 13:32:20 CST 2001


   When one types something in a listview, the cursor normally goes to
the first item matching what is being typed.

   The previous code did not work if you quickly typed the same
character twice: if you type "nnn" the cursor should go to the first
item matching "nnn" (e.g. "nnno") or to the third item starting with an
"n". I couldn't resist rewriting TREEVIEW_ProcessLetterKeys. I believe
the new code is simpler and AFAIK it handles everything just fine.

Changelog:

   François Gouget <fgouget at codeweavers.com>

 * dlls/comctl32/listview.c
   Fix (rewrite), and add documentation to LISTVIEW_ProcessLetterKeys
   Lowered the key repetition delay (Aric)


-- 
François Gouget
fgouget at codeweavers.com
-------------- next part --------------
@@ -122,10 +124,10 @@
     DWORD dwHoverTime;
     INT nColumnCount; /* the number of columns in this control */
 
+    DWORD lastKeyPressTimestamp; /* Added */
     WPARAM charCode; /* Added */
-    CHAR szSearchParam[ MAX_PATH ]; /* Added */
-    DWORD timeSinceLastKeyPress; /* Added */
     INT nSearchParamLength; /* Added */
+    CHAR szSearchParam[ MAX_PATH ]; /* Added */
 } LISTVIEW_INFO;
 
 /*
@@ -171,7 +173,6 @@
 /* 
  * macros
  */
-#define GETITEMCOUNT(infoPtr) ((infoPtr)->hdpaItems->nItemCount)
 #define ListView_LVNotify(hwnd,lCtrlId,plvnm) \
     (BOOL)SendMessageA((hwnd),WM_NOTIFY,(WPARAM)(INT)lCtrlId,(LPARAM)(LPNMLISTVIEW)(plvnm))
 #define ListView_Notify(hwnd,lCtrlId,pnmh) \
@@ -235,14 +236,8 @@
 static void LISTVIEW_FillBackground(HWND hwnd, HDC hdc, LPRECT rc);
 
 /******** Defines that LISTVIEW_ProcessLetterKeys uses ****************/
-#define KEY_DELAY       900
-#define LISTVIEW_InitLvItemStruct(item,idx,TEXT)  \
-                    ZeroMemory(&(item), sizeof(LVITEMA)); \
-                    (item).mask = LVIF_TEXT; \
-                    (item).iItem = (idx); \
-                    (item).iSubItem = 0; \
-                    (item).pszText = (TEXT); \
-                    (item).cchTextMax = MAX_PATH
+#define KEY_DELAY       450
+
 
 static BOOL
 LISTVIEW_SendCustomDrawNotify (HWND hwnd, DWORD dwDrawStage, HDC hdc,
@@ -339,231 +334,157 @@
 
 
 /*************************************************************************
-* DESCRIPTION:
-*       Processes keyboard messages generated by pressing the letter keys on the keyboard.
-*       Assumes the list is sorted alphabetically, without regard to case.
-*
-* PARAMETERS:
-*       [I]   HWND: handle to the window
-*       [I]   WPARAM: the character code, the actual character
-*       [I]   LPARAM: key data
-*
-*
-* RETURN:
-*       Zero.
-*
-* TODO:
-*       
-*
-*/
-static INT LISTVIEW_ProcessLetterKeys( HWND hwnd, WPARAM charCode, LPARAM keyData )
+ *		LISTVIEW_ProcessLetterKeys
+ *
+ *  Processes keyboard messages generated by pressing the letter keys 
+ *  on the keyboard.
+ *  What this does is perform a case insensitive search from the 
+ *  current position with the following quirks:
+ *  - If two chars or more are pressed in quick succession we search 
+ *    for the corresponding string (e.g. 'abc').
+ *  - If there is a delay we wipe away the current search string and 
+ *    restart with just that char.
+ *  - If the user keeps pressing the same character, whether slowly or 
+ *    fast, so that the search string is entirely composed of this 
+ *    character ('aaaaa' for instance), then we search for first item 
+ *    that starting with that character.
+ *  - If the user types the above character in quick succession, then 
+ *    we must also search for the corresponding string ('aaaaa'), and 
+ *    go to that string if there is a match.
+ *
+ * RETURNS
+ *
+ *  Zero.
+ *
+ * BUGS
+ *
+ *  - The current implementation has a list of characters it will 
+ *    accept and it ignores averything else. In particular it will 
+ *    ignore accentuated characters which seems to match what 
+ *    Windows does. But I'm not sure it makes sense to follow 
+ *    Windows there.
+ *  - We don't sound a beep when the search fails.
+ *
+ * SEE ALSO
+ *
+ *  TREEVIEW_ProcessLetterKeys
+ */
+static INT LISTVIEW_ProcessLetterKeys(
+    HWND hwnd, /* handle to the window */
+    WPARAM charCode, /* the character code, the actual character */
+    LPARAM keyData /* key data */
+    )
 {
-    LISTVIEW_INFO *infoPtr = NULL;
-    INT nItem = -1;
-    BOOL bRedraw;
-    INT nSize = 0;
-    INT idx = 0;
-    BOOL bFoundMatchingFiles = FALSE;           
+    LISTVIEW_INFO *infoPtr;
+    INT nItem;
+    INT nSize;
+    INT endidx,idx;
     LVITEMA item;
-    CHAR TEXT[ MAX_PATH ];
-    CHAR szCharCode[ 2 ];
-    DWORD timeSinceLastKeyPress = 0;
-    
-    szCharCode[0] = charCode;
-    szCharCode[1] = 0;
-   
-    /* simple parameter checking  */
-    if ( !hwnd || !charCode || !keyData )
+    CHAR buffer[MAX_PATH];
+    DWORD timestamp,elapsed;
+
+    /* simple parameter checking */
+    if (!hwnd || !charCode || !keyData)
         return 0;
-                                             
-    infoPtr = (LISTVIEW_INFO *)GetWindowLongA(hwnd, 0);
-    
-    if ( !infoPtr )
+
+    infoPtr=(LISTVIEW_INFO*)GetWindowLongA(hwnd, 0);
+    if (!infoPtr)
         return 0;
-    
+
     /* only allow the valid WM_CHARs through */
-    if ( isalnum( charCode ) || charCode == '.' || charCode == '`' || charCode == '!' 
-          || charCode == '@' || charCode == '#' || charCode == '$' || charCode == '%'
-          || charCode == '^' || charCode == '&' || charCode == '*' || charCode == '('
-          || charCode == ')' || charCode == '-' || charCode == '_' || charCode == '+'
-          || charCode == '=' || charCode == '\\'|| charCode == ']' || charCode == '}'
-          || charCode == '[' || charCode == '{' || charCode == '/' || charCode == '?'
-          || charCode == '>' || charCode == '<' || charCode == ',' || charCode == '~')
-    {
-        timeSinceLastKeyPress = GetTickCount();
-        
-        nSize = GETITEMCOUNT( infoPtr );
-        /* if there are 0 items, there is no where to go */
-        if ( nSize == 0 )
-            return 0;
-        /* 
-         * If the last charCode equals the current charCode then look
-         * to the next element in list to see if it matches the previous 
-         * charCode.
+    if (!isalnum(charCode) &&
+        charCode != '.' && charCode != '`' && charCode != '!' &&
+        charCode != '@' && charCode != '#' && charCode != '$' &&
+        charCode != '%' && charCode != '^' && charCode != '&' &&
+        charCode != '*' && charCode != '(' && charCode != ')' &&
+        charCode != '-' && charCode != '_' && charCode != '+' &&
+        charCode != '=' && charCode != '\\'&& charCode != ']' &&
+        charCode != '}' && charCode != '[' && charCode != '{' &&
+        charCode != '/' && charCode != '?' && charCode != '>' &&
+        charCode != '<' && charCode != ',' && charCode != '~')
+        return 0;
+
+    nSize=GETITEMCOUNT(infoPtr);
+    /* if there's one item or less, there is no where to go */
+    if (nSize <= 1)
+        return 0;
+
+    /* compute how much time elapsed since last keypress */
+    timestamp=GetTickCount();
+    if (timestamp > infoPtr->lastKeyPressTimestamp) {
+        elapsed=timestamp-infoPtr->lastKeyPressTimestamp;
+    } else {
+        elapsed=infoPtr->lastKeyPressTimestamp-timestamp;
+    }
+
+    /* update the search parameters */
+    infoPtr->lastKeyPressTimestamp=timestamp;
+    if (elapsed < KEY_DELAY) {
+        if (infoPtr->nSearchParamLength < sizeof(infoPtr->szSearchParam)) {
+            infoPtr->szSearchParam[infoPtr->nSearchParamLength++]=charCode;
+        }
+        if (infoPtr->charCode != charCode) {
+            infoPtr->charCode=charCode=0;
+        }
+    } else {
+        infoPtr->charCode=charCode;
+        infoPtr->szSearchParam[0]=charCode;
+        infoPtr->nSearchParamLength=1;
+        /* Redundant with the 1 char string */
+        charCode=0;
+    }
+
+    /* and search from the current position */
+    nItem=-1;
+    if (infoPtr->nFocusedItem >= 0) {
+        endidx=infoPtr->nFocusedItem;
+        idx=endidx;
+        /* if looking for single character match,
+         * then we must always move forward
          */
-        if ( infoPtr->charCode == charCode )
-        { 
-            if ( timeSinceLastKeyPress - infoPtr->timeSinceLastKeyPress < KEY_DELAY )
-            {    /* append new character to search string */
-                strcat( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength++;
-
-                /* loop from start of list view */
-                for( idx = infoPtr->nFocusedItem; idx < nSize; idx++ )
-                {   /* get item */
-                    LISTVIEW_InitLvItemStruct( item, idx, TEXT );
-                    ListView_GetItemA( hwnd, &item );
-
-                    /* compare items */
-                    if ( strncasecmp( item.pszText, infoPtr->szSearchParam, 
-                                      infoPtr->nSearchParamLength ) == 0 )
-                    {
-                        nItem = idx;
-                        break;
-                    }
-                }
-            }
-            else if ( infoPtr->timeSinceLastKeyPress > timeSinceLastKeyPress )
-            { /* The DWORD went over it's boundary?? Ergo assuming too slow??. */
-                for ( idx = 0; idx < nSize; idx++ )
-                {
-                    LISTVIEW_InitLvItemStruct( item, idx, TEXT );
-                    ListView_GetItemA( hwnd, &item );
-    
-                    if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                    {
-                        nItem = idx;
-                        break;
-                    }
-                }
-                strcpy( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength = 1;
-            }
-            else
-            {   /* Save szCharCode for use in later searches */
-                strcpy( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength = 1;
-    
-                LISTVIEW_InitLvItemStruct( item, infoPtr->nFocusedItem + 1, TEXT );
-                ListView_GetItemA( hwnd, &item );
-    
-                if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                    nItem = infoPtr->nFocusedItem + 1;
-                else
-                {   /*
-                     * Ok so there are no more folders that match
-                     * now we look for files.
-                     */   
-                    for ( idx = infoPtr->nFocusedItem + 1; idx < nSize; idx ++ )
-                    {
-                        LISTVIEW_InitLvItemStruct( item, idx, TEXT );
-                        ListView_GetItemA( hwnd, &item );
-                                
-                        if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                        {
-                            nItem = idx;
-                            bFoundMatchingFiles = TRUE;
-                            break;
-                        }
-                    }
-                    if ( !bFoundMatchingFiles ) 
-                    {  /* go back to first instance */
-                        for ( idx = 0; idx < nSize; idx ++ )
-                        {
-                            LISTVIEW_InitLvItemStruct( item,idx, TEXT );
-                            ListView_GetItemA( hwnd, &item );
-                                    
-                            if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                            {
-                                nItem = idx;
-                                break;
-                            }
-                        }
-                    }
-                }
-            }
-        } /*END: if ( infoPtr->charCode == charCode )*/
-        
-        else /* different keypressed */
-        {
-            /* could be that they are spelling the file/directory for us */
-            if ( timeSinceLastKeyPress - infoPtr->timeSinceLastKeyPress > KEY_DELAY )
-            {   /* 
-                 * Too slow, move to the first instance of the
-                 * charCode. 
-                 */
-                for ( idx = 0; idx < nSize; idx++ )
-                {
-                    LISTVIEW_InitLvItemStruct( item,idx, TEXT );
-                    ListView_GetItemA( hwnd, &item );
-    
-                    if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                    {   
-                        nItem = idx;
-                        break;
-                    }
-                }
-                strcpy( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength = 1;
-            }
-            else if ( infoPtr->timeSinceLastKeyPress > timeSinceLastKeyPress )
-            {  /* The DWORD went over it's boundery?? Ergo assuming too slow??. */
-                for ( idx = 0; idx < nSize; idx++ )
-                {
-                    LISTVIEW_InitLvItemStruct( item,idx, TEXT );
-                    ListView_GetItemA( hwnd, &item );
-    
-                    if ( strncasecmp( &( item.pszText[ 0 ] ), szCharCode, 1 ) == 0 )
-                    {
-                        nItem = idx;
-                        break;
-                    }
-                }
-                strcpy( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength = 1;
-            }
-            else /* Search for the string the user is typing */
-            {   
-                /* append new character to search string */
-                strcat( infoPtr->szSearchParam, szCharCode );
-                infoPtr->nSearchParamLength++;
-
-                /* loop from start of list view */
-                for( idx = 0; idx < nSize; idx++ )
-                {   /* get item */
-                    LISTVIEW_InitLvItemStruct( item, idx, TEXT );
-                    ListView_GetItemA( hwnd, &item );
-
-                    /* compare items */
-                    if ( strncasecmp( item.pszText, infoPtr->szSearchParam, 
-                                      infoPtr->nSearchParamLength ) == 0 )
-                    {
-                        nItem = idx;
-                        break;
-                    }
-                }
-            }
-        }/*END: else */
+        if (infoPtr->nSearchParamLength == 1)
+            idx++;
+    } else {
+        endidx=nSize;
+        idx=0;
     }
-    else
-        return 0;
-    
-    bRedraw = LISTVIEW_KeySelection(hwnd, nItem );
-    if (bRedraw != FALSE)
-    {
-        /* refresh client area */
-        InvalidateRect(hwnd, NULL, TRUE);
-        UpdateWindow(hwnd);
+    do {
+        if (idx == nSize) {
+            if (endidx == nSize)
+                break;
+            idx=0;
+        }
+
+        /* get item */
+        ZeroMemory(&item, sizeof(item));
+        item.mask = LVIF_TEXT;
+        item.iItem = idx;
+        item.iSubItem = 0;
+        item.pszText = buffer;
+        item.cchTextMax = sizeof(buffer);
+        ListView_GetItemA( hwnd, &item );
+
+        /* check for a match */
+        if (strncasecmp(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) {
+            nItem=idx;
+            break;
+        } else if ( (charCode != 0) && (nItem == -1) && (nItem != infoPtr->nFocusedItem) &&
+                    (strncasecmp(item.pszText,infoPtr->szSearchParam,1) == 0) ) {
+            /* This would work but we must keep looking for a longer match */
+            nItem=idx;
+        }
+        idx++;
+    } while (idx != endidx);
+
+    if (nItem != -1) {
+        if (LISTVIEW_KeySelection(hwnd, nItem) != FALSE) {
+            /* refresh client area */
+            InvalidateRect(hwnd, NULL, TRUE);
+            UpdateWindow(hwnd);
+        }
     }
 
-    /* Store the WM_CHAR for next time */
-    infoPtr->charCode = charCode;
-    
-    /* Store time */
-    infoPtr->timeSinceLastKeyPress = timeSinceLastKeyPress;
-    
     return 0;
-
 }
 
 /*************************************************************************
@@ -9276,10 +9197,9 @@
   case LVM_UPDATE: 
     return LISTVIEW_Update(hwnd, (INT)wParam);
 
-/*	case WM_CHAR: */
   case WM_CHAR:
     return LISTVIEW_ProcessLetterKeys( hwnd, wParam, lParam );
-  
+
   case WM_COMMAND:
     return LISTVIEW_Command(hwnd, wParam, lParam);
 


More information about the wine-patches mailing list