Merge branch 'osx-text-undo'

Implement undo/redo for (multiline) wxTextCtrl in wxOSX.

See https://github.com/wxWidgets/wxWidgets/pull/2474
This commit is contained in:
Vadim Zeitlin 2021-08-20 22:10:48 +02:00
commit 6689feb648
16 changed files with 220 additions and 25 deletions

View file

@ -64,6 +64,9 @@ public:
virtual void Redo() wxOVERRIDE;
virtual bool CanRedo() const wxOVERRIDE;
#if wxUSE_RICHEDIT
virtual void EmptyUndoBuffer() wxOVERRIDE;
#endif // wxUSE_RICHEDIT
virtual void SetInsertionPointEnd() wxOVERRIDE;
virtual long GetInsertionPoint() const wxOVERRIDE;

View file

@ -436,6 +436,8 @@ public:
- (void)textDidChange:(NSNotification *)aNotification;
- (void)changeColor:(id)sender;
@property (retain) NSUndoManager* undoManager;
@end
@interface wxNSComboBox : NSComboBox

View file

@ -136,12 +136,19 @@ public:
virtual void controlTextDidChange() wxOVERRIDE;
virtual bool CanUndo() const wxOVERRIDE;
virtual void Undo() wxOVERRIDE;
virtual bool CanRedo() const wxOVERRIDE;
virtual void Redo() wxOVERRIDE;
virtual void EmptyUndoBuffer() wxOVERRIDE;
protected:
void DoUpdateTextStyle();
NSScrollView* m_scrollView;
NSTextView* m_textView;
bool m_useCharWrapping;
NSUndoManager* m_undoManager;
};
class wxNSComboBoxControl : public wxNSTextFieldControl, public wxComboWidgetImpl

View file

@ -730,6 +730,7 @@ public :
virtual void Undo() ;
virtual bool CanRedo() const;
virtual void Redo() ;
virtual void EmptyUndoBuffer() ;
virtual int GetNumberOfLines() const ;
virtual long XYToPosition(long x, long y) const;
virtual bool PositionToXY(long pos, long *x, long *y) const ;

View file

@ -74,6 +74,8 @@ public:
virtual void MarkDirty() wxOVERRIDE;
virtual void DiscardEdits() wxOVERRIDE;
virtual void EmptyUndoBuffer() wxOVERRIDE;
// text control under some platforms supports the text styles: these
// methods apply the given text style to the given selection or to
// set/get the style which will be used for all appended text

View file

@ -429,6 +429,11 @@ protected:
wxString m_cpuArch;
};
// Returns true only for MSW programs running under Wine.
#ifdef __WINDOWS__
WXDLLIMPEXP_BASE bool wxIsRunningUnderWine();
#else // !__WINDOWS__
inline bool wxIsRunningUnderWine() { return false; }
#endif // __WINDOWS__/!__WINDOWS__
#endif // _WX_PLATINFO_H_

View file

@ -3642,7 +3642,7 @@ public:
bool CanUndo() const wxOVERRIDE;
// Delete the undo history.
void EmptyUndoBuffer();
void EmptyUndoBuffer() wxOVERRIDE;
// Undo one action in the undo history.
void Undo() wxOVERRIDE;

View file

@ -574,6 +574,8 @@ public:
DiscardEdits();
}
virtual void EmptyUndoBuffer() { }
// styles handling
// ---------------

View file

@ -629,3 +629,16 @@ public:
//@}
};
/**
Returns true only for MSW programs running under Wine.
This function can be used to check for some functionality not implemented
when using Wine.
@since 3.1.6
@library{wxbase}
@category{cfg}
*/
bool wxIsRunningUnderWine();

View file

@ -1312,6 +1312,17 @@ public:
*/
virtual void DiscardEdits();
/**
Delete the undo history.
Currently only implemented in wxMSW (for controls using wxTE_RICH2
style only) and wxOSX (for multiline text controls only), does nothing
in the other ports or for the controls not using the appropriate styles.
@since 3.1.6
*/
virtual void EmptyUndoBuffer();
/**
This function inserts into the control the character which would have
been inserted if the given key event had occurred in the text control.

View file

@ -29,6 +29,10 @@
#include "wx/apptrait.h"
#ifdef __WINDOWS__
#include "wx/dynlib.h"
#endif
// global object
// VERY IMPORTANT: do not use the default constructor since it would
// try to init the wxPlatformInfo instance using
@ -373,3 +377,12 @@ wxEndianness wxPlatformInfo::GetEndianness(const wxString& end)
return wxENDIAN_INVALID;
}
#ifdef __WINDOWS__
bool wxIsRunningUnderWine()
{
return wxLoadedDLL("ntdll.dll").HasSymbol(wxS("wine_get_version"));
}
#endif // __WINDOWS__

View file

@ -690,33 +690,16 @@ bool wxTextCtrl::MSWCreateText(const wxString& value,
::SendMessage(GetHwnd(), EM_SETMARGINS, wParam, lParam);
}
#if wxUSE_RICHEDIT && wxUSE_OLE && defined(wxHAS_TOM_H)
#if wxUSE_RICHEDIT
// For RichEdit >= 4, SetFont(), called above from MSWCreateControl(), uses
// EM_SETCHARFORMAT which affects the undo buffer, meaning that CanUndo()
// for a newly created control returns true, which is unexpected. To avoid
// this, we explicitly use Undo(tomFalse) here to clear the undo buffer.
// And since Undo(tomFalse) also disables the undo buffer, we need to
// enable it again immediately after clearing by calling Undo(tomTrue).
// for a newly created control returns true, which is unexpected, so clear
// the undo buffer.
if ( GetRichVersion() >= 4 )
{
wxCOMPtr<IRichEditOle> pRichEditOle;
if ( SendMessage(GetHwnd(), EM_GETOLEINTERFACE,
0, (LPARAM)&pRichEditOle) && pRichEditOle )
{
wxCOMPtr<ITextDocument> pDoc;
HRESULT hr = pRichEditOle->QueryInterface
(
wxIID_PPV_ARGS(ITextDocument, &pDoc)
);
if ( SUCCEEDED(hr) )
{
hr = pDoc->Undo(tomFalse, NULL);
if ( SUCCEEDED(hr) )
pDoc->Undo(tomTrue, NULL);
}
}
EmptyUndoBuffer();
}
#endif // wxUSE_RICHEDIT && wxHAS_TOM_H
#endif // wxUSE_RICHEDIT
return true;
}
@ -2012,6 +1995,37 @@ bool wxTextCtrl::CanRedo() const
return wxTextEntry::CanRedo();
}
#if wxUSE_RICHEDIT
void wxTextCtrl::EmptyUndoBuffer()
{
#if wxUSE_OLE && defined(wxHAS_TOM_H)
// We need to use Undo(tomFalse) to clear the undo buffer, but calling it
// also disables the undo buffer, so we need to enable it again immediately
// after clearing by calling Undo(tomTrue).
if ( GetRichVersion() >= 4 )
{
wxCOMPtr<IRichEditOle> pRichEditOle;
if ( SendMessage(GetHwnd(), EM_GETOLEINTERFACE,
0, (LPARAM)&pRichEditOle) && pRichEditOle )
{
wxCOMPtr<ITextDocument> pDoc;
HRESULT hr = pRichEditOle->QueryInterface
(
wxIID_PPV_ARGS(ITextDocument, &pDoc)
);
if ( SUCCEEDED(hr) )
{
hr = pDoc->Undo(tomFalse, NULL);
if ( SUCCEEDED(hr) )
pDoc->Undo(tomTrue, NULL);
}
}
}
#endif // wxUSE_OLE && wxHAS_TOM_H
}
#endif // wxUSE_RICHEDIT
// ----------------------------------------------------------------------------
// caret handling (Windows only)
// ----------------------------------------------------------------------------

View file

@ -426,6 +426,16 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil;
}
}
- (instancetype)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if ( self )
{
self.undoManager = [[[NSUndoManager alloc] init] autorelease];
}
return self;
}
- (void)textDidChange:(NSNotification *)aNotification
{
wxUnusedVar(aNotification);
@ -434,6 +444,11 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil;
impl->controlTextDidChange();
}
- (nullable NSUndoManager *)undoManagerForTextView:(NSTextView *)view
{
return self.undoManager;
}
- (void)changeColor:(id)sender
{
@ -773,6 +788,10 @@ wxNSTextViewControl::wxNSTextViewControl( wxTextCtrl *wxPeer, WXWidget w, long s
[tv setDelegate: tv];
m_undoManager = tv.undoManager;
[tv setAllowsUndo:YES];
InstallEventHandler(tv);
}
@ -1019,6 +1038,46 @@ void wxNSTextViewControl::controlTextDidChange()
DoUpdateTextStyle();
}
bool wxNSTextViewControl::CanUndo() const
{
if ( !m_undoManager )
return false;
return [m_undoManager canUndo];
}
void wxNSTextViewControl::Undo()
{
if ( !m_undoManager )
return;
[m_undoManager undo];
}
bool wxNSTextViewControl::CanRedo() const
{
if ( !m_undoManager )
return false;
return [m_undoManager canRedo];
}
void wxNSTextViewControl::Redo()
{
if ( !m_undoManager )
return;
[m_undoManager redo];
}
void wxNSTextViewControl::EmptyUndoBuffer()
{
if ( !m_undoManager )
return;
[m_undoManager removeAllActions];
}
void wxNSTextViewControl::DoUpdateTextStyle()
{
if ( m_useCharWrapping )

View file

@ -259,6 +259,13 @@ void wxTextCtrl::DiscardEdits()
m_dirty = false;
}
void wxTextCtrl::EmptyUndoBuffer()
{
wxCHECK_RET( GetTextPeer(), "Must create the control first" );
GetTextPeer()->EmptyUndoBuffer() ;
}
int wxTextCtrl::GetNumberOfLines() const
{
return GetTextPeer()->GetNumberOfLines() ;
@ -342,7 +349,7 @@ void wxTextCtrl::OnDropFiles(wxDropFilesEvent& event)
void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
{
if ( event.GetModifiers() == wxMOD_CONTROL )
if ( event.ControlDown() )
{
switch( event.GetKeyCode() )
{
@ -361,6 +368,18 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
if ( CanCut() )
Cut() ;
return;
case 'Z':
if ( !event.ShiftDown() )
{
if ( CanUndo() )
Undo() ;
return;
}
// else fall through to Redo
case 'Y':
if ( CanRedo() )
Redo() ;
return;
default:
break;
}
@ -688,6 +707,10 @@ void wxTextWidgetImpl::Redo()
{
}
void wxTextWidgetImpl::EmptyUndoBuffer()
{
}
long wxTextWidgetImpl::XYToPosition(long WXUNUSED(x), long WXUNUSED(y)) const
{
return 0 ;

View file

@ -1094,6 +1094,7 @@ overrideNeeded = (
'CanPaste',
'CanRedo',
'CanUndo',
'EmptyUndoBuffer',
'Clear',
'AppendText',
)

View file

@ -20,6 +20,7 @@
#include "wx/textctrl.h"
#endif // WX_PRECOMP
#include "wx/platinfo.h"
#include "wx/scopedptr.h"
#include "wx/uiaction.h"
@ -1472,4 +1473,42 @@ TEST_CASE("wxTextCtrl::InitialCanUndo", "[wxTextCtrl][undo]")
}
}
// This test would always fail with MinGW-32 for the same reason as described
// above.
#ifndef __MINGW32_TOOLCHAIN__
TEST_CASE("wxTextCtrl::EmptyUndoBuffer", "[wxTextCtrl][undo]")
{
if ( wxIsRunningUnderWine() )
{
// Wine doesn't implement EM_GETOLEINTERFACE and related stuff currently
WARN("Skipping test known to fail under Wine.");
return;
}
wxScopedPtr<wxTextCtrl> text(new wxTextCtrl(wxTheApp->GetTopWindow(),
wxID_ANY, "",
wxDefaultPosition,
wxDefaultSize,
wxTE_MULTILINE | wxTE_RICH2));
text->AppendText("foo");
if ( !text->CanUndo() )
{
WARN("Skipping test as Undo() is not supported on this platform.");
return;
}
text->EmptyUndoBuffer();
CHECK_FALSE( text->CanUndo() );
CHECK_NOTHROW( text->Undo() );
CHECK( text->GetValue() == "foo" );
}
#endif // __MINGW32_TOOLCHAIN__
#endif //wxUSE_TEXTCTRL