From 2f6cb20d2ca3512d60cb15bca17fe4712d7818e5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 9 Oct 2019 00:26:26 +0200 Subject: [PATCH 1/3] Stop intercepting Alt-Fn keys in the text sample This was annoying, especially under MSW, as Alt-F4 couldn't be used to close the application quickly. --- samples/text/text.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/samples/text/text.cpp b/samples/text/text.cpp index f4ee6f9038..309c505a95 100644 --- a/samples/text/text.cpp +++ b/samples/text/text.cpp @@ -995,6 +995,16 @@ void MyTextCtrl::OnKeyUp(wxKeyEvent& event) void MyTextCtrl::OnKeyDown(wxKeyEvent& event) { + if ( ms_logKey ) + LogKeyEvent( "Key down", event); + + event.Skip(); + + // Only handle bare function keys below, notably let Alt-Fn perform their + // usual default functions as intercepting them is annoying. + if ( event.GetModifiers() != 0 ) + return; + switch ( event.GetKeyCode() ) { case WXK_F1: @@ -1088,11 +1098,6 @@ void MyTextCtrl::OnKeyDown(wxKeyEvent& event) wxLogMessage("Control marked as non modified"); break; } - - if ( ms_logKey ) - LogKeyEvent( "Key down", event); - - event.Skip(); } //---------------------------------------------------------------------- From 7a9e969dca3b6526757fa95cae98ecc822613570 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 9 Oct 2019 00:42:13 +0200 Subject: [PATCH 2/3] Send clipboard events for Ctrl/Shift-{Ins,Del} in rich edit too The corresponding wxClipboardTextEvent was generated for Ctrl-{C,V,X} key combinations, but not Shift-{Ins,Del} or Ctrl-Ins ones, which are also handled by the native control by default. --- src/msw/textctrl.cpp | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index 2196918b93..126c69931c 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -2154,21 +2154,37 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event) // from working. So we work around it by intercepting these shortcuts // ourselves and emitting clipboard events (which richedit will handle, // so everything works as before, including pasting of rich text): - if ( event.GetModifiers() == wxMOD_CONTROL && IsRich() ) + if ( IsRich() ) { - switch ( event.GetKeyCode() ) + if ( event.GetModifiers() == wxMOD_CONTROL ) { - case 'C': - Copy(); - return; - case 'X': - Cut(); - return; - case 'V': - Paste(); - return; - default: - break; + switch ( event.GetKeyCode() ) + { + case 'C': + case WXK_INSERT: + Copy(); + return; + case 'X': + Cut(); + return; + case 'V': + Paste(); + return; + default: + break; + } + } + else if ( event.GetModifiers() == wxMOD_SHIFT ) + { + switch ( event.GetKeyCode() ) + { + case WXK_INSERT: + Paste(); + return; + case WXK_DELETE: + Cut(); + return; + } } } From efc2a9da2d87117604ec8856162a9e19d6b19ef7 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 9 Oct 2019 01:01:21 +0200 Subject: [PATCH 3/3] Fix pasting long strings in wxTextCtrl under MSW Adjust the length limit before pasting to ensure that all text on clipboard will be successfully pasted, instead of only pasting the part of it which fits. Add a unit test checking that this works. Closes #4646. --- include/wx/msw/textctrl.h | 7 +++ src/msw/textctrl.cpp | 67 +++++++++++++++++++++++++++++ tests/controls/textctrltest.cpp | 75 +++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/include/wx/msw/textctrl.h b/include/wx/msw/textctrl.h index f7be3725be..f5f88658cb 100644 --- a/include/wx/msw/textctrl.h +++ b/include/wx/msw/textctrl.h @@ -60,6 +60,8 @@ public: virtual void GetSelection(long *from, long *to) const wxOVERRIDE; + virtual void Paste() wxOVERRIDE; + virtual void Redo() wxOVERRIDE; virtual bool CanRedo() const wxOVERRIDE; @@ -292,6 +294,11 @@ private: // false if we hit the limit set by SetMaxLength() and so didn't change it. bool AdjustSpaceLimit(); + // Called before pasting to ensure that the limit is at big enough to allow + // pasting the entire text on the clipboard. + void AdjustMaxLengthBeforePaste(); + + wxMenu* m_privateContextMenu; bool m_isNativeCaretShown; diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index 126c69931c..53c0d8c3a0 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -47,6 +47,7 @@ #if wxUSE_CLIPBOARD #include "wx/clipbrd.h" + #include "wx/dataobj.h" #endif #include "wx/textfile.h" @@ -2213,6 +2214,24 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event) event.Skip(); } +void wxTextCtrl::Paste() +{ + // Before pasting, check that the pasted text will fit, unless an explicit + // maximum length was set, to avoid only pasting some part of it. + // + // Note that rich text controls do not send WM_PASTE, so we can't do it in + // response to it, but we could handle EN_PROTECTED (after requesting it by + // specifying ENM_PROTECTED in EM_SETEVENTMASK argument) and check for the + // message being WM_PASTE there, but this doesn't seem to be better than + // the simpler approach used here. + if ( IsRich() ) + { + AdjustMaxLengthBeforePaste(); + } + + wxTextCtrlBase::Paste(); +} + bool wxTextCtrl::MSWHandleMessage(WXLRESULT *rc, WXUINT nMsg, @@ -2317,6 +2336,12 @@ wxTextCtrl::MSWHandleMessage(WXLRESULT *rc, ::SetCursor(GetHcursorOf(*wxSTANDARD_CURSOR)); } #endif // wxUSE_MENUS + + case WM_PASTE: + // Note that we get this message for plain EDIT controls only, rich + // controls are dealt with in our own Paste(). + AdjustMaxLengthBeforePaste(); + break; } return processed; @@ -2455,6 +2480,48 @@ bool wxTextCtrl::AdjustSpaceLimit() return true; } +void wxTextCtrl::AdjustMaxLengthBeforePaste() +{ +#if wxUSE_CLIPBOARD + // We only need to do this for multi line controls, single lines should + // never receive more text than fits into them by default anyhow. + if ( IsSingleLine() ) + return; + + // Also don't override an explicitly set limit. + unsigned int limit; + if ( HasSpaceLimit(&limit) ) + return; + + // Otherwise check if we have enough space for clipboard data. We only do + // it for plain text because this is all we know how to handle here. + wxClipboardLocker lock; + wxTextDataObject textData; + if ( !wxTheClipboard->GetData(textData) ) + return; + + // Unfortunately we can't just get the length directly because we need to + // convert EOLs, otherwise our calculation of the required length could be + // way off when there are many lines. + const unsigned long lenPasted = + wxTextFile::Translate(textData.GetText(), wxTextFileType_Dos).length(); + + long from, to; + GetSelection(&from, &to); + const unsigned long lenSel = to - from; + + const unsigned long lenCurrent = GetLastPosition(); + + // We need enough space for all the current text and all the new + // text, but the selection will be replaced. + const unsigned long lenNeeded = lenCurrent - lenSel + lenPasted; + if ( lenNeeded >= limit ) + { + SetMaxLength(lenNeeded); + } +#endif // wxUSE_CLIPBOARD +} + bool wxTextCtrl::AcceptsFocusFromKeyboard() const { // we don't want focus if we can't be edited unless we're a multiline diff --git a/tests/controls/textctrltest.cpp b/tests/controls/textctrltest.cpp index b25f280d5e..ef70a2fdc4 100644 --- a/tests/controls/textctrltest.cpp +++ b/tests/controls/textctrltest.cpp @@ -27,6 +27,11 @@ #include "wx/scopeguard.h" #include "wx/uiaction.h" +#if wxUSE_CLIPBOARD + #include "wx/clipbrd.h" + #include "wx/dataobj.h" +#endif // wxUSE_CLIPBOARD + #ifdef __WXGTK__ #include "wx/stopwatch.h" #endif @@ -1341,4 +1346,74 @@ TEST_CASE("wxTextCtrl::GetBestSize", "[wxTextCtrl][best-size]") CHECK( sizeVeryLong.y == sizeLong.y ); } +#if wxUSE_CLIPBOARD + +TEST_CASE("wxTextCtrl::LongPaste", "[wxTextCtrl][clipboard][paste]") +{ + long style = 0; + + SECTION("Plain") + { + style = wxTE_MULTILINE; + } + + // wxTE_RICH[2] style only makes any different under MSW, so don't bother + // testing it under the other platforms. +#ifdef __WXMSW__ + SECTION("Rich") + { + style = wxTE_MULTILINE | wxTE_RICH; + } + + SECTION("Rich v2") + { + style = wxTE_MULTILINE | wxTE_RICH2; + } +#endif // __WXMSW__ + + if ( !style ) + { + // This can happen when explicitly selecting just a single section to + // execute -- this code still runs even if the corresponding section is + // skipped, so we have to explicitly skip it too in this case. + return; + } + + wxScopedPtr + text(new wxTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY, wxString(), + wxDefaultPosition, wxDefaultSize, style)); + + // This could actually be much higher, but it makes the test proportionally + // slower, so use a relatively small (but still requiring more space than + // the default maximum length under MSW) number here. + const int NUM_LINES = 10000; + + { + wxClipboardLocker lock; + + // Build a longish string. + wxString s; + s.reserve(NUM_LINES*5 + 10); + for ( int n = 0; n < NUM_LINES; ++n ) + { + s += wxString::Format("%04d\n", n); + } + + s += "THE END"; + + wxTheClipboard->AddData(new wxTextDataObject(s)); + } + + text->ChangeValue("THE BEGINNING\n"); + text->SetInsertionPointEnd(); + text->Paste(); + + const int numLines = text->GetNumberOfLines(); + + CHECK( numLines == NUM_LINES + 2 ); + CHECK( text->GetLineText(numLines - 1) == "THE END" ); +} + +#endif // wxUSE_CLIPBOARD + #endif //wxUSE_TEXTCTRL