From 12488298024c0b546395abc258eec0ba61283806 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 8 Apr 2021 00:18:58 +0100 Subject: [PATCH] Implement support for Ctrl+Backspace in plain EDIT controls This shortcut is undocumented, but works in rich edit controls and even in plain ones if they use SHAutoComplete(), so support it in all controls because this is what people expect. --- include/wx/msw/textctrl.h | 7 +++ src/msw/textctrl.cpp | 115 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/include/wx/msw/textctrl.h b/include/wx/msw/textctrl.h index 21f5a79c76..feeef94ff3 100644 --- a/include/wx/msw/textctrl.h +++ b/include/wx/msw/textctrl.h @@ -294,6 +294,13 @@ private: virtual void MSWProcessSpecialKey(wxKeyEvent& event) wxOVERRIDE; #endif // wxUSE_OLE + // Do we need to handle Ctrl+Backspace ourselves? + bool MSWNeedsToHandleCtrlBackspace() const; + + // Delete backwards until the start of the previous word before caret in a + // way compatible with the standard MSW Ctrl+Backspace shortcut. + void MSWDeleteWordBack(); + void OnKeyDown(wxKeyEvent& event); // Used by EN_MAXTEXT handler to increase the size limit (will do nothing diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index 7156658d38..408c610d2c 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -2116,6 +2116,10 @@ bool wxTextCtrl::MSWShouldPreProcessMessage(WXMSG* msg) case VK_HOME: case VK_END: return false; + + case VK_BACK: + if ( MSWNeedsToHandleCtrlBackspace() ) + return false; } } else // Shift is pressed @@ -2206,8 +2210,119 @@ void wxTextCtrl::MSWProcessSpecialKey(wxKeyEvent& event) #endif // wxUSE_OLE +void wxTextCtrl::MSWDeleteWordBack() +{ + // Surprisingly the behaviour of Ctrl+Backspace is different in all three + // cases where it's supported by MSW itself: + // + // 1. Rich edit controls simply ignore selection and handle it as usual. + // 2. Plain edit controls don't do anything when there is selection. + // 3. Notepad in Windows 10 1809 and later deletes just the selection. + // + // The latter behaviour seems the most useful, so do it like this here too. + if ( HasSelection() ) + { + RemoveSelection(); + return; + } + + // This variable contains one end of the range to delete, the rest of this + // function is concerned with finding the starting end of this range. + const long end = GetInsertionPoint(); + + long col, line; + if ( !PositionToXY(end, &col, &line) ) + return; + + // We stop at the start of line, so remember it. + const long start = XYToPosition(0, line); + + const wxString& text = GetLineText(line); + + // The way it works in rich text controls or when SHAutoComplete() is used + // is that it deletes everything until the first span of alphanumeric + // characters it finds (moving backwards). But the implementation of the + // same shortcut in in notepad in Windows 10 versions 1809 and later + // doesn't behave in quite the same way and doesn't handle alphanumeric + // characters specially, i.e. it just stops on space. This seems more + // useful and simpler to implement, and it probably will become standard in + // the future, so do it like this here too. + + // First skip all space starting from the character to the left of the + // current one. + long current = end; + for ( ;; ) + { + if ( current == start ) + { + // When there is nothing but spaces to the left until the start of + // line, we need to delete these spaces (if any) as well as the new + // line separating this line from the previous one (if any). + if ( line > 0 ) + { + // This function is only used with plain EDITs which use "\r\n" + // and so we need to subtract 2 to account for the new line. + current -= 2; + } + + break; + } + + // We start from the previous character. + --current; + + // Did we find the end of the previous "word"? + if ( text[current - start] != ' ' ) + { + for ( ;; ) + { + if ( current == start ) + { + // We don't delete the new line in this case, as we're going to + // delete some non-spaces in this line. + break; + } + + --current; + + if ( text[current - start] == ' ' ) + { + // Don't delete the space itself. + ++current; + break; + } + } + + break; + } + } + + Remove(current, end); +} + +bool wxTextCtrl::MSWNeedsToHandleCtrlBackspace() const +{ + // We want to handle the undocumented Ctrl+Backspace shortcut only if it's + // not handled by the control itself, which is a bit tricky because it's + // normally only handled by rich edit controls, but plain EDIT ones may + // also handle it if they use SHAutoComplete(). + return !HasFlag(wxTE_READONLY) && + !IsRich() && + !MSWUsesStandardAutoComplete(); +} + void wxTextCtrl::OnKeyDown(wxKeyEvent& event) { + // Handle Ctrl+Backspace if necessary: this is not a documented standard + // shortcut, but it's a de facto standard and people expect it to work. + if ( MSWNeedsToHandleCtrlBackspace() && + event.GetModifiers() == wxMOD_CONTROL && + event.GetKeyCode() == WXK_BACK ) + { + MSWDeleteWordBack(); + return; + } + // richedit control doesn't send WM_PASTE, WM_CUT and WM_COPY messages // when Ctrl-V, X or C is pressed and this prevents wxClipboardTextEvent // from working. So we work around it by intercepting these shortcuts