From 9be2c3717d0d4563c39cdb17f0c496265e7ebce8 Mon Sep 17 00:00:00 2001 From: Stefan Csomor Date: Sat, 4 Jul 2020 17:56:01 +0200 Subject: [PATCH] Improve native keyDown handling in wxOSX Provide API for dealing with m_lastKeyDownEvent instead of using it directly and extend it to avoid sending duplicate events for keys which are mapped to multiple selectors, such as Ctrl-O with the default key bindings. Closes https://github.com/wxWidgets/wxWidgets/pull/1928 --- include/wx/osx/cocoa/private.h | 40 ++++++++++++++++- src/osx/cocoa/textctrl.mm | 10 ++--- src/osx/cocoa/window.mm | 78 ++++++++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 17 deletions(-) diff --git a/include/wx/osx/cocoa/private.h b/include/wx/osx/cocoa/private.h index 0220537f64..8394ebb6ff 100644 --- a/include/wx/osx/cocoa/private.h +++ b/include/wx/osx/cocoa/private.h @@ -48,6 +48,26 @@ WXWindow WXDLLIMPEXP_CORE wxOSXGetKeyWindow(); class WXDLLIMPEXP_FWD_CORE wxDialog; +class WXDLLIMPEXP_FWD_CORE wxWidgetCocoaImpl; + +// a class which disables sending wx keydown events useful when adding text programmatically, for wx-internal use only +class wxWidgetCocoaNativeKeyDownSuspender +{ +public: + // stops sending keydown events for text inserted into this widget + explicit wxWidgetCocoaNativeKeyDownSuspender(wxWidgetCocoaImpl *target); + + // resumes sending keydown events + ~wxWidgetCocoaNativeKeyDownSuspender(); + +private: + wxWidgetCocoaImpl *m_target; + NSEvent* m_nsevent; + bool m_wxsent; + + wxDECLARE_NO_COPY_CLASS(wxWidgetCocoaNativeKeyDownSuspender); +}; + class WXDLLIMPEXP_CORE wxWidgetCocoaImpl : public wxWidgetImpl { public : @@ -203,7 +223,24 @@ public : protected: WXWidget m_osxView; + + // begins processing of native key down event, storing the native event for later wx event generation + void BeginNativeKeyDownEvent( NSEvent* event ); + // done with the current native key down event + void EndNativeKeyDownEvent(); + // allow executing text changes without triggering key down events + + // is currently processing a native key down event + bool IsInNativeKeyDown() const; + // the native key event + NSEvent* GetLastNativeKeyDownEvent(); + // did send the wx event for the current native key down event + void SetKeyDownSent(); + // was the wx event for the current native key down event sent + bool WasKeyDownSent() const; + NSEvent* m_lastKeyDownEvent; + bool m_lastKeyDownWXSent; #if !wxOSX_USE_NATIVE_FLIPPED bool m_isFlipped; #endif @@ -211,6 +248,8 @@ protected: // events, don't resend them bool m_hasEditor; + friend class wxWidgetCocoaNativeKeyDownSuspender; + wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxWidgetCocoaImpl); }; @@ -534,4 +573,3 @@ private: #endif // _WX_PRIVATE_COCOA_H_ - diff --git a/src/osx/cocoa/textctrl.mm b/src/osx/cocoa/textctrl.mm index 6eb03239b9..1e46578226 100644 --- a/src/osx/cocoa/textctrl.mm +++ b/src/osx/cocoa/textctrl.mm @@ -781,7 +781,7 @@ bool wxNSTextViewControl::CanFocus() const void wxNSTextViewControl::insertText(NSString* str, WXWidget slf, void *_cmd) { NSString *text = [str isKindOfClass:[NSAttributedString class]] ? [(id)str string] : str; - if ( m_lastKeyDownEvent ==NULL || !DoHandleCharEvent(m_lastKeyDownEvent, text) ) + if ( !IsInNativeKeyDown() || !DoHandleCharEvent(GetLastNativeKeyDownEvent(), text) ) { wxOSX_TextEventHandlerPtr superimpl = (wxOSX_TextEventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; superimpl(slf, (SEL)_cmd, str); @@ -988,10 +988,8 @@ void wxNSTextViewControl::WriteText(const wxString& str) { wxString st(wxMacConvertNewlines10To13(str)); wxMacEditHelper helper(m_textView); - NSEvent* formerEvent = m_lastKeyDownEvent; - m_lastKeyDownEvent = nil; + wxWidgetCocoaNativeKeyDownSuspender suspend(this); [m_textView insertText:wxCFStringRef( st , m_wxPeer->GetFont().GetEncoding() ).AsNSString()]; - m_lastKeyDownEvent = formerEvent; // Some text styles have to be updated manually. DoUpdateTextStyle(); } @@ -1467,8 +1465,7 @@ void wxNSTextFieldControl::ShowPosition(long pos) void wxNSTextFieldControl::WriteText(const wxString& str) { - NSEvent* formerEvent = m_lastKeyDownEvent; - m_lastKeyDownEvent = nil; + wxWidgetCocoaNativeKeyDownSuspender suspend(this); NSText* editor = [m_textField currentEditor]; if ( editor ) { @@ -1490,7 +1487,6 @@ void wxNSTextFieldControl::WriteText(const wxString& str) SetStringValue( val ) ; SetSelection( start + str.length() , start + str.length() ) ; } - m_lastKeyDownEvent = formerEvent; } void wxNSTextFieldControl::controlAction(WXWidget WXUNUSED(slf), diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 1ed6cf0ade..fc525f7a43 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -1557,7 +1557,7 @@ void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd) return; } - m_lastKeyDownEvent = event; + BeginNativeKeyDownEvent(event); } if ( GetFocusedViewInWindow([slf window]) != slf || m_hasEditor || !DoHandleKeyEvent(event) ) @@ -1565,7 +1565,11 @@ void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd) wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd]; superimpl(slf, (SEL)_cmd, event); } - m_lastKeyDownEvent = NULL; + + if ( [event type] == NSKeyDown ) + { + EndNativeKeyDownEvent(); + } } // Class containing data used for gestures support. @@ -2138,18 +2142,18 @@ void wxWidgetCocoaImpl::insertText(NSString* text, WXWidget slf, void *_cmd) bool result = false; if ( HasUserKeyHandling() && !m_hasEditor && [text length] > 0) { - if ( m_lastKeyDownEvent!=NULL && [text isEqualToString:[m_lastKeyDownEvent characters]]) + if ( IsInNativeKeyDown() && [text isEqualToString:[GetLastNativeKeyDownEvent() characters]]) { // If we have a corresponding key event, send wxEVT_KEY_DOWN now. // (see also: wxWidgetCocoaImpl::DoHandleKeyEvent) { wxKeyEvent wxevent(wxEVT_KEY_DOWN); - SetupKeyEvent( wxevent, m_lastKeyDownEvent ); + SetupKeyEvent( wxevent, GetLastNativeKeyDownEvent() ); result = GetWXPeer()->OSXHandleKeyEvent(wxevent); } // ...and wxEVT_CHAR. - result = result || DoHandleCharEvent(m_lastKeyDownEvent, text); + result = result || DoHandleCharEvent(GetLastNativeKeyDownEvent(), text); } else { @@ -2170,12 +2174,18 @@ void wxWidgetCocoaImpl::doCommandBySelector(void* sel, WXWidget slf, void* _cmd) wxLogTrace(TRACE_KEYS, "Selector %s for %s", wxDumpSelector((SEL)sel), wxDumpNSView(slf)); - if ( m_lastKeyDownEvent!=NULL ) + // keystrokes can be translated on macos to selectors, see + // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/TextDefaultsBindings/TextDefaultsBindings.html + // it is also possible to map 1 keystroke to multiple commands, eg Ctrl-O on mac is translated to the bash-equivalent of + // execute and move back in history, since this results in two commands, Ctrl-O was sent twice as a wx key down event. + // we now track the sending of the events to avoid duplicates. + + if ( IsInNativeKeyDown() && !WasKeyDownSent()) { // If we have a corresponding key event, send wxEVT_KEY_DOWN now. // (see also: wxWidgetCocoaImpl::DoHandleKeyEvent) wxKeyEvent wxevent(wxEVT_KEY_DOWN); - SetupKeyEvent( wxevent, m_lastKeyDownEvent ); + SetupKeyEvent( wxevent, GetLastNativeKeyDownEvent() ); bool result = GetWXPeer()->OSXHandleKeyEvent(wxevent); if (!result) @@ -2184,9 +2194,10 @@ void wxWidgetCocoaImpl::doCommandBySelector(void* sel, WXWidget slf, void* _cmd) wxKeyEvent wxevent2(wxevent) ; wxevent2.SetEventType(wxEVT_CHAR); - SetupKeyEvent( wxevent2, m_lastKeyDownEvent ); + SetupKeyEvent( wxevent2, GetLastNativeKeyDownEvent() ); GetWXPeer()->OSXHandleKeyEvent(wxevent2); } + SetKeyDownSent(); } else { @@ -2544,7 +2555,8 @@ void wxWidgetCocoaImpl::Init() #if !wxOSX_USE_NATIVE_FLIPPED m_isFlipped = true; #endif - m_lastKeyDownEvent = NULL; + m_lastKeyDownEvent = nil; + m_lastKeyDownWXSent = false; m_hasEditor = false; } @@ -2568,6 +2580,54 @@ wxWidgetCocoaImpl::~wxWidgetCocoaImpl() wxCocoaGestures::EraseForObject(this); } +void wxWidgetCocoaImpl::BeginNativeKeyDownEvent( NSEvent* event ) +{ + m_lastKeyDownEvent = event; + m_lastKeyDownWXSent = false; +} + +void wxWidgetCocoaImpl::EndNativeKeyDownEvent() +{ + m_lastKeyDownEvent = nil; + m_lastKeyDownWXSent = false; +} + +bool wxWidgetCocoaImpl::IsInNativeKeyDown() const +{ + return m_lastKeyDownEvent != nil; +} + +NSEvent* wxWidgetCocoaImpl::GetLastNativeKeyDownEvent() +{ + wxASSERT( m_lastKeyDownEvent != nil); + return m_lastKeyDownEvent; +} + +void wxWidgetCocoaImpl::SetKeyDownSent() +{ + wxASSERT( !m_lastKeyDownWXSent ); + m_lastKeyDownWXSent = true; +} + +bool wxWidgetCocoaImpl::WasKeyDownSent() const +{ + return m_lastKeyDownWXSent; +} + +wxWidgetCocoaNativeKeyDownSuspender::wxWidgetCocoaNativeKeyDownSuspender( wxWidgetCocoaImpl *target ) : m_target(target) +{ + m_nsevent = m_target->m_lastKeyDownEvent; + m_wxsent = m_target->m_lastKeyDownWXSent; + + m_target->m_lastKeyDownEvent = nil; +} + +wxWidgetCocoaNativeKeyDownSuspender::~wxWidgetCocoaNativeKeyDownSuspender() +{ + m_target->m_lastKeyDownEvent = m_nsevent; + m_target->m_lastKeyDownWXSent = m_wxsent; +} + bool wxWidgetCocoaImpl::IsVisible() const { return [m_osxView isHiddenOrHasHiddenAncestor] == NO;