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/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index a5f2305125..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,9 +103,13 @@ 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 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(); // 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/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 diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index fcdae4beec..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,27 +854,43 @@ void wxTextEntry::MSWProcessSpecialKey(wxKeyEvent& WXUNUSED(event)) wxFAIL_MSG(wxS("Must be overridden if can be called")); } -wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() +bool wxTextEntry::MSWUsesStandardAutoComplete() const { - if ( !m_autoCompleteData ) + 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 ( !MSWHasAutoCompleteData() ) { 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; } @@ -876,14 +900,13 @@ 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. } 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 +915,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; }