From a6e4cc7eb003a2930b4a443325047041b415a2b6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 7 Apr 2021 22:20:05 +0100 Subject: [PATCH 1/3] Refactor wxTextEntry to use MSWEnsureHasAutoCompleteData() The new function has more clear semantics than GetOrCreateCompleter() which both returned the completer value and set m_autoCompleteData to it as a side effect. MSWEnsureHasAutoCompleteData() still does the latter, but returns just a boolean indicating whether it succeeded or failed, making using it more straightforward. No real changes. --- include/wx/msw/textentry.h | 6 +++--- src/msw/textentry.cpp | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index a5f2305125..434ca9c3b8 100644 --- a/include/wx/msw/textentry.h +++ b/include/wx/msw/textentry.h @@ -100,9 +100,9 @@ private: // be called and the default implementation asserts if this is not the case. virtual void MSWProcessSpecialKey(wxKeyEvent& event); - // Get the auto-complete object creating it if necessary. Returns NULL if - // creating it failed. - wxTextAutoCompleteData *GetOrCreateCompleter(); + // Check that we have auto-complete data, creating it if necessary. Returns + // false if creating it failed. + bool MSWEnsureHasAutoCompleteData(); // Various auto-completion-related stuff, only used if any of AutoComplete() // methods are called. Use the function above to access it. diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index fcdae4beec..771d842d83 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -846,27 +846,29 @@ void wxTextEntry::MSWProcessSpecialKey(wxKeyEvent& WXUNUSED(event)) wxFAIL_MSG(wxS("Must be overridden if can be called")); } -wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() +bool wxTextEntry::MSWEnsureHasAutoCompleteData() { if ( !m_autoCompleteData ) { wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); - if ( ac->IsOk() ) - m_autoCompleteData = ac; - else + if ( !ac->IsOk() ) + { delete ac; + return false; + } + + m_autoCompleteData = ac; } - return m_autoCompleteData; + return true; } bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) { - wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); - if ( !ac ) + if ( !MSWEnsureHasAutoCompleteData() ) return false; - ac->ChangeStrings(choices); + m_autoCompleteData->ChangeStrings(choices); return true; } @@ -882,8 +884,7 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) } else // Have a valid completer. { - wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); - if ( !ac ) + if ( !MSWEnsureHasAutoCompleteData() ) { // Delete the custom completer for consistency with the case when // we succeed to avoid memory leaks in user code. @@ -892,7 +893,7 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) } // This gives ownership of the custom completer to m_autoCompleteData. - if ( !ac->ChangeCustomCompleter(completer) ) + if ( !m_autoCompleteData->ChangeCustomCompleter(completer) ) return false; } From 10072595045e9a28e472577eb278d946ce3381b5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 7 Apr 2021 22:22:14 +0100 Subject: [PATCH 2/3] Add wxTextEntry::MSWUsesStandardAutoComplete() Implementation is a hack, using a magic pointer value because just storing this in wxTextAutoCompleteData is not simple, as any flag added to it would need to be reset in several different places. This is not used yet, but will be in the upcoming commits. --- include/wx/msw/textentry.h | 7 +++++++ src/msw/textentry.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index 434ca9c3b8..b21078b15a 100644 --- a/include/wx/msw/textentry.h +++ b/include/wx/msw/textentry.h @@ -81,6 +81,9 @@ protected: virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE; #endif // wxUSE_OLE + // Returns true if this control uses standard file names completion. + bool MSWUsesStandardAutoComplete() const; + // Helper for wxTE_PROCESS_ENTER handling: activates the default button in // the dialog containing this control if any. bool ClickDefaultButtonIfPossible(); @@ -100,6 +103,10 @@ private: // be called and the default implementation asserts if this is not the case. virtual void MSWProcessSpecialKey(wxKeyEvent& event); + // Check if we really have auto-complete data. This is not the same as just + // checking if m_autoCompleteData is NULL, see the code for more details. + bool MSWHasAutoCompleteData() const; + // Check that we have auto-complete data, creating it if necessary. Returns // false if creating it failed. bool MSWEnsureHasAutoCompleteData(); diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index 771d842d83..4b009bd325 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -659,6 +659,10 @@ private: wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); }; +// Special pointer value which indicates that we're using SHAutoComplete(). +static wxTextAutoCompleteData* const wxDUMMY_SHAUTOCOMPLETE_DATA = + reinterpret_cast(-1); + #endif // HAS_AUTOCOMPLETE // ============================================================================ @@ -679,7 +683,8 @@ wxTextEntry::wxTextEntry() wxTextEntry::~wxTextEntry() { #ifdef HAS_AUTOCOMPLETE - delete m_autoCompleteData; + if ( MSWHasAutoCompleteData() ) + delete m_autoCompleteData; #endif // HAS_AUTOCOMPLETE } @@ -833,8 +838,11 @@ bool wxTextEntry::DoAutoCompleteFileNames(int flags) // Disable the other kinds of completion now that we use the built-in file // names completion. - if ( m_autoCompleteData ) - m_autoCompleteData->DisableCompletion(); + if ( MSWHasAutoCompleteData() ) + delete m_autoCompleteData; + + // Set it to the special value indicating that we're using SHAutoComplete(). + m_autoCompleteData = wxDUMMY_SHAUTOCOMPLETE_DATA; return true; } @@ -846,9 +854,23 @@ void wxTextEntry::MSWProcessSpecialKey(wxKeyEvent& WXUNUSED(event)) wxFAIL_MSG(wxS("Must be overridden if can be called")); } +bool wxTextEntry::MSWUsesStandardAutoComplete() const +{ + return m_autoCompleteData == wxDUMMY_SHAUTOCOMPLETE_DATA; +} + +bool wxTextEntry::MSWHasAutoCompleteData() const +{ + // We use special wxDUMMY_SHAUTOCOMPLETE_DATA for the pointer to indicate + // that we're using SHAutoComplete(), so we need to check for it too, and + // not just whether the pointer is non-NULL. + return m_autoCompleteData != NULL + && m_autoCompleteData != wxDUMMY_SHAUTOCOMPLETE_DATA; +} + bool wxTextEntry::MSWEnsureHasAutoCompleteData() { - if ( !m_autoCompleteData ) + if ( !MSWHasAutoCompleteData() ) { wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); if ( !ac->IsOk() ) @@ -878,7 +900,7 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) // First deal with the case when we just want to disable auto-completion. if ( !completer ) { - if ( m_autoCompleteData ) + if ( MSWHasAutoCompleteData() ) m_autoCompleteData->DisableCompletion(); //else: Nothing to do, we hadn't used auto-completion even before. } From 12488298024c0b546395abc258eec0ba61283806 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 8 Apr 2021 00:18:58 +0100 Subject: [PATCH 3/3] 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