Merge branch 'msw-text-ctrl-backspace'

Implement support for Ctrl+Backspace for all MSW text controls.

See https://github.com/wxWidgets/wxWidgets/pull/2317
This commit is contained in:
Vadim Zeitlin
2021-04-09 17:29:14 +01:00
4 changed files with 171 additions and 19 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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<wxTextAutoCompleteData*>(-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;
}