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
This commit is contained in:
Stefan Csomor
2020-07-04 17:56:01 +02:00
committed by Vadim Zeitlin
parent de56f99c5a
commit 9be2c3717d
3 changed files with 111 additions and 17 deletions

View File

@@ -48,6 +48,26 @@ WXWindow WXDLLIMPEXP_CORE wxOSXGetKeyWindow();
class WXDLLIMPEXP_FWD_CORE wxDialog; 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 class WXDLLIMPEXP_CORE wxWidgetCocoaImpl : public wxWidgetImpl
{ {
public : public :
@@ -203,7 +223,24 @@ public :
protected: protected:
WXWidget m_osxView; 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; NSEvent* m_lastKeyDownEvent;
bool m_lastKeyDownWXSent;
#if !wxOSX_USE_NATIVE_FLIPPED #if !wxOSX_USE_NATIVE_FLIPPED
bool m_isFlipped; bool m_isFlipped;
#endif #endif
@@ -211,6 +248,8 @@ protected:
// events, don't resend them // events, don't resend them
bool m_hasEditor; bool m_hasEditor;
friend class wxWidgetCocoaNativeKeyDownSuspender;
wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxWidgetCocoaImpl); wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxWidgetCocoaImpl);
}; };
@@ -534,4 +573,3 @@ private:
#endif #endif
// _WX_PRIVATE_COCOA_H_ // _WX_PRIVATE_COCOA_H_

View File

@@ -781,7 +781,7 @@ bool wxNSTextViewControl::CanFocus() const
void wxNSTextViewControl::insertText(NSString* str, WXWidget slf, void *_cmd) void wxNSTextViewControl::insertText(NSString* str, WXWidget slf, void *_cmd)
{ {
NSString *text = [str isKindOfClass:[NSAttributedString class]] ? [(id)str string] : str; 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]; wxOSX_TextEventHandlerPtr superimpl = (wxOSX_TextEventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd];
superimpl(slf, (SEL)_cmd, str); superimpl(slf, (SEL)_cmd, str);
@@ -988,10 +988,8 @@ void wxNSTextViewControl::WriteText(const wxString& str)
{ {
wxString st(wxMacConvertNewlines10To13(str)); wxString st(wxMacConvertNewlines10To13(str));
wxMacEditHelper helper(m_textView); wxMacEditHelper helper(m_textView);
NSEvent* formerEvent = m_lastKeyDownEvent; wxWidgetCocoaNativeKeyDownSuspender suspend(this);
m_lastKeyDownEvent = nil;
[m_textView insertText:wxCFStringRef( st , m_wxPeer->GetFont().GetEncoding() ).AsNSString()]; [m_textView insertText:wxCFStringRef( st , m_wxPeer->GetFont().GetEncoding() ).AsNSString()];
m_lastKeyDownEvent = formerEvent;
// Some text styles have to be updated manually. // Some text styles have to be updated manually.
DoUpdateTextStyle(); DoUpdateTextStyle();
} }
@@ -1467,8 +1465,7 @@ void wxNSTextFieldControl::ShowPosition(long pos)
void wxNSTextFieldControl::WriteText(const wxString& str) void wxNSTextFieldControl::WriteText(const wxString& str)
{ {
NSEvent* formerEvent = m_lastKeyDownEvent; wxWidgetCocoaNativeKeyDownSuspender suspend(this);
m_lastKeyDownEvent = nil;
NSText* editor = [m_textField currentEditor]; NSText* editor = [m_textField currentEditor];
if ( editor ) if ( editor )
{ {
@@ -1490,7 +1487,6 @@ void wxNSTextFieldControl::WriteText(const wxString& str)
SetStringValue( val ) ; SetStringValue( val ) ;
SetSelection( start + str.length() , start + str.length() ) ; SetSelection( start + str.length() , start + str.length() ) ;
} }
m_lastKeyDownEvent = formerEvent;
} }
void wxNSTextFieldControl::controlAction(WXWidget WXUNUSED(slf), void wxNSTextFieldControl::controlAction(WXWidget WXUNUSED(slf),

View File

@@ -1557,7 +1557,7 @@ void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd)
return; return;
} }
m_lastKeyDownEvent = event; BeginNativeKeyDownEvent(event);
} }
if ( GetFocusedViewInWindow([slf window]) != slf || m_hasEditor || !DoHandleKeyEvent(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]; wxOSX_EventHandlerPtr superimpl = (wxOSX_EventHandlerPtr) [[slf superclass] instanceMethodForSelector:(SEL)_cmd];
superimpl(slf, (SEL)_cmd, event); superimpl(slf, (SEL)_cmd, event);
} }
m_lastKeyDownEvent = NULL;
if ( [event type] == NSKeyDown )
{
EndNativeKeyDownEvent();
}
} }
// Class containing data used for gestures support. // Class containing data used for gestures support.
@@ -2138,18 +2142,18 @@ void wxWidgetCocoaImpl::insertText(NSString* text, WXWidget slf, void *_cmd)
bool result = false; bool result = false;
if ( HasUserKeyHandling() && !m_hasEditor && [text length] > 0) 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. // If we have a corresponding key event, send wxEVT_KEY_DOWN now.
// (see also: wxWidgetCocoaImpl::DoHandleKeyEvent) // (see also: wxWidgetCocoaImpl::DoHandleKeyEvent)
{ {
wxKeyEvent wxevent(wxEVT_KEY_DOWN); wxKeyEvent wxevent(wxEVT_KEY_DOWN);
SetupKeyEvent( wxevent, m_lastKeyDownEvent ); SetupKeyEvent( wxevent, GetLastNativeKeyDownEvent() );
result = GetWXPeer()->OSXHandleKeyEvent(wxevent); result = GetWXPeer()->OSXHandleKeyEvent(wxevent);
} }
// ...and wxEVT_CHAR. // ...and wxEVT_CHAR.
result = result || DoHandleCharEvent(m_lastKeyDownEvent, text); result = result || DoHandleCharEvent(GetLastNativeKeyDownEvent(), text);
} }
else else
{ {
@@ -2170,12 +2174,18 @@ void wxWidgetCocoaImpl::doCommandBySelector(void* sel, WXWidget slf, void* _cmd)
wxLogTrace(TRACE_KEYS, "Selector %s for %s", wxLogTrace(TRACE_KEYS, "Selector %s for %s",
wxDumpSelector((SEL)sel), wxDumpNSView(slf)); 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. // If we have a corresponding key event, send wxEVT_KEY_DOWN now.
// (see also: wxWidgetCocoaImpl::DoHandleKeyEvent) // (see also: wxWidgetCocoaImpl::DoHandleKeyEvent)
wxKeyEvent wxevent(wxEVT_KEY_DOWN); wxKeyEvent wxevent(wxEVT_KEY_DOWN);
SetupKeyEvent( wxevent, m_lastKeyDownEvent ); SetupKeyEvent( wxevent, GetLastNativeKeyDownEvent() );
bool result = GetWXPeer()->OSXHandleKeyEvent(wxevent); bool result = GetWXPeer()->OSXHandleKeyEvent(wxevent);
if (!result) if (!result)
@@ -2184,9 +2194,10 @@ void wxWidgetCocoaImpl::doCommandBySelector(void* sel, WXWidget slf, void* _cmd)
wxKeyEvent wxevent2(wxevent) ; wxKeyEvent wxevent2(wxevent) ;
wxevent2.SetEventType(wxEVT_CHAR); wxevent2.SetEventType(wxEVT_CHAR);
SetupKeyEvent( wxevent2, m_lastKeyDownEvent ); SetupKeyEvent( wxevent2, GetLastNativeKeyDownEvent() );
GetWXPeer()->OSXHandleKeyEvent(wxevent2); GetWXPeer()->OSXHandleKeyEvent(wxevent2);
} }
SetKeyDownSent();
} }
else else
{ {
@@ -2544,7 +2555,8 @@ void wxWidgetCocoaImpl::Init()
#if !wxOSX_USE_NATIVE_FLIPPED #if !wxOSX_USE_NATIVE_FLIPPED
m_isFlipped = true; m_isFlipped = true;
#endif #endif
m_lastKeyDownEvent = NULL; m_lastKeyDownEvent = nil;
m_lastKeyDownWXSent = false;
m_hasEditor = false; m_hasEditor = false;
} }
@@ -2568,6 +2580,54 @@ wxWidgetCocoaImpl::~wxWidgetCocoaImpl()
wxCocoaGestures::EraseForObject(this); 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 bool wxWidgetCocoaImpl::IsVisible() const
{ {
return [m_osxView isHiddenOrHasHiddenAncestor] == NO; return [m_osxView isHiddenOrHasHiddenAncestor] == NO;