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:
@@ -294,6 +294,13 @@ private:
|
|||||||
virtual void MSWProcessSpecialKey(wxKeyEvent& event) wxOVERRIDE;
|
virtual void MSWProcessSpecialKey(wxKeyEvent& event) wxOVERRIDE;
|
||||||
#endif // wxUSE_OLE
|
#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);
|
void OnKeyDown(wxKeyEvent& event);
|
||||||
|
|
||||||
// Used by EN_MAXTEXT handler to increase the size limit (will do nothing
|
// Used by EN_MAXTEXT handler to increase the size limit (will do nothing
|
||||||
|
@@ -81,6 +81,9 @@ protected:
|
|||||||
virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE;
|
virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE;
|
||||||
#endif // wxUSE_OLE
|
#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
|
// Helper for wxTE_PROCESS_ENTER handling: activates the default button in
|
||||||
// the dialog containing this control if any.
|
// the dialog containing this control if any.
|
||||||
bool ClickDefaultButtonIfPossible();
|
bool ClickDefaultButtonIfPossible();
|
||||||
@@ -100,9 +103,13 @@ private:
|
|||||||
// be called and the default implementation asserts if this is not the case.
|
// be called and the default implementation asserts if this is not the case.
|
||||||
virtual void MSWProcessSpecialKey(wxKeyEvent& event);
|
virtual void MSWProcessSpecialKey(wxKeyEvent& event);
|
||||||
|
|
||||||
// Get the auto-complete object creating it if necessary. Returns NULL if
|
// Check if we really have auto-complete data. This is not the same as just
|
||||||
// creating it failed.
|
// checking if m_autoCompleteData is NULL, see the code for more details.
|
||||||
wxTextAutoCompleteData *GetOrCreateCompleter();
|
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()
|
// Various auto-completion-related stuff, only used if any of AutoComplete()
|
||||||
// methods are called. Use the function above to access it.
|
// methods are called. Use the function above to access it.
|
||||||
|
@@ -2116,6 +2116,10 @@ bool wxTextCtrl::MSWShouldPreProcessMessage(WXMSG* msg)
|
|||||||
case VK_HOME:
|
case VK_HOME:
|
||||||
case VK_END:
|
case VK_END:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case VK_BACK:
|
||||||
|
if ( MSWNeedsToHandleCtrlBackspace() )
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // Shift is pressed
|
else // Shift is pressed
|
||||||
@@ -2206,8 +2210,119 @@ void wxTextCtrl::MSWProcessSpecialKey(wxKeyEvent& event)
|
|||||||
|
|
||||||
#endif // wxUSE_OLE
|
#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)
|
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
|
// 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
|
// when Ctrl-V, X or C is pressed and this prevents wxClipboardTextEvent
|
||||||
// from working. So we work around it by intercepting these shortcuts
|
// from working. So we work around it by intercepting these shortcuts
|
||||||
|
@@ -659,6 +659,10 @@ private:
|
|||||||
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData);
|
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
|
#endif // HAS_AUTOCOMPLETE
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -679,6 +683,7 @@ wxTextEntry::wxTextEntry()
|
|||||||
wxTextEntry::~wxTextEntry()
|
wxTextEntry::~wxTextEntry()
|
||||||
{
|
{
|
||||||
#ifdef HAS_AUTOCOMPLETE
|
#ifdef HAS_AUTOCOMPLETE
|
||||||
|
if ( MSWHasAutoCompleteData() )
|
||||||
delete m_autoCompleteData;
|
delete m_autoCompleteData;
|
||||||
#endif // HAS_AUTOCOMPLETE
|
#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
|
// Disable the other kinds of completion now that we use the built-in file
|
||||||
// names completion.
|
// names completion.
|
||||||
if ( m_autoCompleteData )
|
if ( MSWHasAutoCompleteData() )
|
||||||
m_autoCompleteData->DisableCompletion();
|
delete m_autoCompleteData;
|
||||||
|
|
||||||
|
// Set it to the special value indicating that we're using SHAutoComplete().
|
||||||
|
m_autoCompleteData = wxDUMMY_SHAUTOCOMPLETE_DATA;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -846,27 +854,43 @@ void wxTextEntry::MSWProcessSpecialKey(wxKeyEvent& WXUNUSED(event))
|
|||||||
wxFAIL_MSG(wxS("Must be overridden if can be called"));
|
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;
|
||||||
{
|
|
||||||
wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this);
|
|
||||||
if ( ac->IsOk() )
|
|
||||||
m_autoCompleteData = ac;
|
|
||||||
else
|
|
||||||
delete ac;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_autoCompleteData;
|
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() )
|
||||||
|
{
|
||||||
|
delete ac;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_autoCompleteData = ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
|
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
|
||||||
{
|
{
|
||||||
wxTextAutoCompleteData * const ac = GetOrCreateCompleter();
|
if ( !MSWEnsureHasAutoCompleteData() )
|
||||||
if ( !ac )
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ac->ChangeStrings(choices);
|
m_autoCompleteData->ChangeStrings(choices);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -876,14 +900,13 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer)
|
|||||||
// First deal with the case when we just want to disable auto-completion.
|
// First deal with the case when we just want to disable auto-completion.
|
||||||
if ( !completer )
|
if ( !completer )
|
||||||
{
|
{
|
||||||
if ( m_autoCompleteData )
|
if ( MSWHasAutoCompleteData() )
|
||||||
m_autoCompleteData->DisableCompletion();
|
m_autoCompleteData->DisableCompletion();
|
||||||
//else: Nothing to do, we hadn't used auto-completion even before.
|
//else: Nothing to do, we hadn't used auto-completion even before.
|
||||||
}
|
}
|
||||||
else // Have a valid completer.
|
else // Have a valid completer.
|
||||||
{
|
{
|
||||||
wxTextAutoCompleteData * const ac = GetOrCreateCompleter();
|
if ( !MSWEnsureHasAutoCompleteData() )
|
||||||
if ( !ac )
|
|
||||||
{
|
{
|
||||||
// Delete the custom completer for consistency with the case when
|
// Delete the custom completer for consistency with the case when
|
||||||
// we succeed to avoid memory leaks in user code.
|
// 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.
|
// This gives ownership of the custom completer to m_autoCompleteData.
|
||||||
if ( !ac->ChangeCustomCompleter(completer) )
|
if ( !m_autoCompleteData->ChangeCustomCompleter(completer) )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user