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