Merge branch 'msw-richedit-paste'

Fix pasting long strings into wxTextCtrl in wxMSW.
This commit is contained in:
Vadim Zeitlin
2019-10-10 13:56:00 +02:00
4 changed files with 188 additions and 18 deletions

View File

@@ -60,6 +60,8 @@ public:
virtual void GetSelection(long *from, long *to) const wxOVERRIDE; virtual void GetSelection(long *from, long *to) const wxOVERRIDE;
virtual void Paste() wxOVERRIDE;
virtual void Redo() wxOVERRIDE; virtual void Redo() wxOVERRIDE;
virtual bool CanRedo() const 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. // false if we hit the limit set by SetMaxLength() and so didn't change it.
bool AdjustSpaceLimit(); 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; wxMenu* m_privateContextMenu;
bool m_isNativeCaretShown; bool m_isNativeCaretShown;

View File

@@ -995,6 +995,16 @@ void MyTextCtrl::OnKeyUp(wxKeyEvent& event)
void MyTextCtrl::OnKeyDown(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() ) switch ( event.GetKeyCode() )
{ {
case WXK_F1: case WXK_F1:
@@ -1088,11 +1098,6 @@ void MyTextCtrl::OnKeyDown(wxKeyEvent& event)
wxLogMessage("Control marked as non modified"); wxLogMessage("Control marked as non modified");
break; break;
} }
if ( ms_logKey )
LogKeyEvent( "Key down", event);
event.Skip();
} }
//---------------------------------------------------------------------- //----------------------------------------------------------------------

View File

@@ -47,6 +47,7 @@
#if wxUSE_CLIPBOARD #if wxUSE_CLIPBOARD
#include "wx/clipbrd.h" #include "wx/clipbrd.h"
#include "wx/dataobj.h"
#endif #endif
#include "wx/textfile.h" #include "wx/textfile.h"
@@ -2154,21 +2155,37 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
// from working. So we work around it by intercepting these shortcuts // from working. So we work around it by intercepting these shortcuts
// ourselves and emitting clipboard events (which richedit will handle, // ourselves and emitting clipboard events (which richedit will handle,
// so everything works as before, including pasting of rich text): // 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': switch ( event.GetKeyCode() )
Copy(); {
return; case 'C':
case 'X': case WXK_INSERT:
Cut(); Copy();
return; return;
case 'V': case 'X':
Paste(); Cut();
return; return;
default: case 'V':
break; Paste();
return;
default:
break;
}
}
else if ( event.GetModifiers() == wxMOD_SHIFT )
{
switch ( event.GetKeyCode() )
{
case WXK_INSERT:
Paste();
return;
case WXK_DELETE:
Cut();
return;
}
} }
} }
@@ -2197,6 +2214,24 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
event.Skip(); 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 bool
wxTextCtrl::MSWHandleMessage(WXLRESULT *rc, wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
WXUINT nMsg, WXUINT nMsg,
@@ -2301,6 +2336,12 @@ wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
::SetCursor(GetHcursorOf(*wxSTANDARD_CURSOR)); ::SetCursor(GetHcursorOf(*wxSTANDARD_CURSOR));
} }
#endif // wxUSE_MENUS #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; return processed;
@@ -2439,6 +2480,48 @@ bool wxTextCtrl::AdjustSpaceLimit()
return true; 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 bool wxTextCtrl::AcceptsFocusFromKeyboard() const
{ {
// we don't want focus if we can't be edited unless we're a multiline // we don't want focus if we can't be edited unless we're a multiline

View File

@@ -27,6 +27,11 @@
#include "wx/scopeguard.h" #include "wx/scopeguard.h"
#include "wx/uiaction.h" #include "wx/uiaction.h"
#if wxUSE_CLIPBOARD
#include "wx/clipbrd.h"
#include "wx/dataobj.h"
#endif // wxUSE_CLIPBOARD
#ifdef __WXGTK__ #ifdef __WXGTK__
#include "wx/stopwatch.h" #include "wx/stopwatch.h"
#endif #endif
@@ -1341,4 +1346,74 @@ TEST_CASE("wxTextCtrl::GetBestSize", "[wxTextCtrl][best-size]")
CHECK( sizeVeryLong.y == sizeLong.y ); 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<wxTextCtrl>
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 #endif //wxUSE_TEXTCTRL