diff --git a/dlls/comctl32/treeview.c b/dlls/comctl32/treeview.c index f13d04ab6b2..ed2b241a354 100644 --- a/dlls/comctl32/treeview.c +++ b/dlls/comctl32/treeview.c @@ -25,6 +25,7 @@ */ #include +#include #include #include #include @@ -119,9 +120,16 @@ typedef struct tagTREEVIEW_INFO int stateImageHeight; int stateImageWidth; HDPA items; + + DWORD lastKeyPressTimestamp; /* Added */ + WPARAM charCode; /* Added */ + INT nSearchParamLength; /* Added */ + CHAR szSearchParam[ MAX_PATH ]; /* Added */ } TREEVIEW_INFO; +/******** Defines that TREEVIEW_ProcessLetterKeys uses ****************/ +#define KEY_DELAY 450 /* bitflags for infoPtr->uInternalStatus */ @@ -3976,6 +3984,156 @@ TREEVIEW_SelectItem(TREEVIEW_INFO *infoPtr, INT wParam, HTREEITEM item) return TRUE; } +/************************************************************************* + * TREEVIEW_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. + * - The search should start from the focused item, not from the selected + * item. One reason for this is to allow for multiple selections in trees. + * But currently infoPtr->focusedItem does not seem very usable. + * + * SEE ALSO + * + * TREEVIEW_ProcessLetterKeys + */ +static INT TREEVIEW_ProcessLetterKeys( + HWND hwnd, /* handle to the window */ + WPARAM charCode, /* the character code, the actual character */ + LPARAM keyData /* key data */ + ) +{ + TREEVIEW_INFO *infoPtr; + HTREEITEM nItem; + HTREEITEM endidx,idx; + TVITEMEXA item; + CHAR buffer[MAX_PATH]; + DWORD timestamp,elapsed; + + /* simple parameter checking */ + if (!hwnd || !charCode || !keyData) + return 0; + + infoPtr=(TREEVIEW_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 != '~') + 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=NULL; + if (infoPtr->selectedItem != NULL) { + endidx=infoPtr->selectedItem; + /* if looking for single character match, + * then we must always move forward + */ + if (infoPtr->nSearchParamLength == 1) + idx=TREEVIEW_GetNextListItem(infoPtr,endidx); + else + idx=endidx; + } else { + endidx=NULL; + idx=infoPtr->root->firstChild; + } + do { + if (idx == NULL) { + if (endidx == NULL) + break; + idx=infoPtr->root->firstChild; + } + + /* get item */ + ZeroMemory(&item, sizeof(item)); + item.mask = TVIF_TEXT; + item.hItem = idx; + item.pszText = buffer; + item.cchTextMax = sizeof(buffer); + TREEVIEW_GetItemA( infoPtr, &item ); + + /* check for a match */ + if (strncasecmp(item.pszText,infoPtr->szSearchParam,infoPtr->nSearchParamLength) == 0) { + nItem=idx; + break; + } else if ( (charCode != 0) && (nItem == NULL) && + (nItem != infoPtr->selectedItem) && + (strncasecmp(item.pszText,infoPtr->szSearchParam,1) == 0) ) { + /* This would work but we must keep looking for a longer match */ + nItem=idx; + } + idx=TREEVIEW_GetNextListItem(infoPtr,idx); + } while (idx != endidx); + + if (nItem != NULL) { + if (TREEVIEW_DoSelectItem(infoPtr, TVGN_CARET, nItem, TVC_BYKEYBOARD)) { + TREEVIEW_EnsureVisible(infoPtr, nItem, FALSE); + } + } + + return 0; +} + /* Scrolling ************************************************************/ static LRESULT @@ -4874,7 +5032,8 @@ TREEVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case TVM_SORTCHILDRENCB: return TREEVIEW_SortChildrenCB(infoPtr, wParam, (LPTVSORTCB)lParam); - /* WM_CHAR */ + case WM_CHAR: + return TREEVIEW_ProcessLetterKeys( hwnd, wParam, lParam ); case WM_COMMAND: return TREEVIEW_Command(infoPtr, wParam, lParam);