diff --git a/include/wx/osx/cocoa/private.h b/include/wx/osx/cocoa/private.h index c2611295ab..7900204a31 100644 --- a/include/wx/osx/cocoa/private.h +++ b/include/wx/osx/cocoa/private.h @@ -129,6 +129,8 @@ public : void InstallEventHandler( WXWidget control = NULL ); + virtual bool ShouldHandleKeyNavigation(const wxKeyEvent &event) const; + bool DoHandleKeyNavigation(const wxKeyEvent &event); virtual bool DoHandleMouseEvent(NSEvent *event); virtual bool DoHandleKeyEvent(NSEvent *event); virtual bool DoHandleCharEvent(NSEvent *event, NSString *text); diff --git a/include/wx/osx/cocoa/private/textimpl.h b/include/wx/osx/cocoa/private/textimpl.h index de74297139..b1c0d3d0a2 100644 --- a/include/wx/osx/cocoa/private/textimpl.h +++ b/include/wx/osx/cocoa/private/textimpl.h @@ -16,9 +16,27 @@ @class wxTextEntryFormatter; +class wxNSTextBase : public wxWidgetCocoaImpl, public wxTextWidgetImpl +{ +public : + wxNSTextBase( wxTextCtrl *text, WXWidget w ) + : wxWidgetCocoaImpl(text, w), + wxTextWidgetImpl(text) + { + } + wxNSTextBase( wxWindow *wxPeer, wxTextEntry *entry, WXWidget w ) + : wxWidgetCocoaImpl(wxPeer, w), + wxTextWidgetImpl(entry) + { + } + virtual ~wxNSTextBase() { } + + virtual bool ShouldHandleKeyNavigation(const wxKeyEvent &event) const wxOVERRIDE; +}; + // implementation exposed, so that search control can pull it -class wxNSTextFieldControl : public wxWidgetCocoaImpl, public wxTextWidgetImpl +class wxNSTextFieldControl : public wxNSTextBase { public : // wxNSTextFieldControl must always be associated with a wxTextEntry. If @@ -67,7 +85,7 @@ private: wxTextEntryFormatter* GetFormatter(); }; -class wxNSTextViewControl : public wxWidgetCocoaImpl, public wxTextWidgetImpl +class wxNSTextViewControl : public wxNSTextBase { public: wxNSTextViewControl( wxTextCtrl *wxPeer, WXWidget w, long style ); diff --git a/include/wx/osx/radiobox.h b/include/wx/osx/radiobox.h index bbf61a7b37..d12539f343 100644 --- a/include/wx/osx/radiobox.h +++ b/include/wx/osx/radiobox.h @@ -16,7 +16,7 @@ class WXDLLIMPEXP_FWD_CORE wxBitmap ; class WXDLLIMPEXP_FWD_CORE wxRadioButton ; -class WXDLLIMPEXP_CORE wxRadioBox: public wxControl, public wxRadioBoxBase +class WXDLLIMPEXP_CORE wxRadioBox: public wxNavigationEnabled, public wxRadioBoxBase { wxDECLARE_DYNAMIC_CLASS(wxRadioBox); public: diff --git a/src/common/containr.cpp b/src/common/containr.cpp index 9904c6761a..380a13571a 100644 --- a/src/common/containr.cpp +++ b/src/common/containr.cpp @@ -38,6 +38,10 @@ // trace mask for focus messages #define TRACE_FOCUS wxT("focus") +#if (defined(__WXMSW__) || defined(__WXMAC__)) && wxUSE_RADIOBTN +#define USE_RADIOBTN_NAV +#endif + // ============================================================================ // implementation // ============================================================================ @@ -235,7 +239,7 @@ void wxControlContainer::SetLastFocus(wxWindow *win) // -------------------------------------------------------------------- // The following four functions are used to find other radio buttons -// within the same group. Used by wxSetFocusToChild on wxMSW +// within the same group. Used by wxSetFocusToChild // -------------------------------------------------------------------- #if wxUSE_RADIOBTN @@ -348,7 +352,7 @@ wxRadioButton* wxGetSelectedButtonInGroup(wxRadioButton *btn) return NULL; } -#endif // __WXMSW__ +#endif // wxUSE_RADIOBTN // ---------------------------------------------------------------------------- // Keyboard handling - this is the place where the TAB traversal logic is @@ -464,12 +468,12 @@ void wxControlContainer::HandleOnNavigationKey( wxNavigationKeyEvent& event ) if ( winFocus ) { -#if defined(__WXMSW__) && wxUSE_RADIOBTN +#if defined(USE_RADIOBTN_NAV) // If we are in a radio button group, start from the first item in the // group if ( event.IsFromTab() && wxIsKindOf(winFocus, wxRadioButton ) ) winFocus = wxGetFirstButtonInGroup((wxRadioButton*)winFocus); -#endif // __WXMSW__ +#endif // USE_RADIOBTN_NAV // ok, we found the focus - now is it our child? start_node = children.Find( winFocus ); } @@ -554,7 +558,7 @@ void wxControlContainer::HandleOnNavigationKey( wxNavigationKeyEvent& event ) continue; } -#if defined(__WXMSW__) && wxUSE_RADIOBTN +#if defined(USE_RADIOBTN_NAV) if ( event.IsFromTab() ) { if ( wxIsKindOf(child, wxRadioButton) ) @@ -611,7 +615,7 @@ void wxControlContainer::HandleOnNavigationKey( wxNavigationKeyEvent& event ) return; } } -#endif // __WXMSW__ +#endif // USE_RADIOBTN_NAV if ( child->CanAcceptFocusFromKeyboard() ) { @@ -748,7 +752,7 @@ bool wxSetFocusToChild(wxWindow *win, wxWindow **childLastFocused) if ( child->CanAcceptFocusFromKeyboard() && !child->IsTopLevel() ) { -#if defined(__WXMSW__) && wxUSE_RADIOBTN +#if defined(USE_RADIOBTN_NAV) // If a radiobutton is the first focusable child, search for the // selected radiobutton in the same group wxRadioButton* btn = wxDynamicCast(child, wxRadioButton); @@ -758,7 +762,7 @@ bool wxSetFocusToChild(wxWindow *win, wxWindow **childLastFocused) if (selected) child = selected; } -#endif // __WXMSW__ +#endif // USE_RADIOBTN_NAV wxLogTrace(TRACE_FOCUS, wxT("SetFocusToChild() => first child (0x%p)."), diff --git a/src/osx/cocoa/textctrl.mm b/src/osx/cocoa/textctrl.mm index ab72396964..f9610c7ff2 100644 --- a/src/osx/cocoa/textctrl.mm +++ b/src/osx/cocoa/textctrl.mm @@ -279,6 +279,45 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; handled = YES; } } + else if (wxpeer->GetWindowStyle() & wxTE_PASSWORD) + { + // Make TAB and ESCAPE work on password fields. The general fix done in wxWidgetCocoaImpl::DoHandleKeyEvent() + // does not help for password fields because wxWidgetCocoaImpl::keyEvent() is not executed + // when TAB is pressed, only when it is released. + wxKeyEvent wxevent(wxEVT_KEY_DOWN); + if (commandSelector == @selector(insertTab:)) + { + wxevent.m_keyCode = WXK_TAB; + } + else if (commandSelector == @selector(insertBacktab:)) + { + wxevent.m_keyCode = WXK_TAB; + wxevent.SetShiftDown(true); + } + else if (commandSelector == @selector(selectNextKeyView:)) + { + wxevent.m_keyCode = WXK_TAB; + wxevent.SetRawControlDown(true); + } + else if (commandSelector == @selector(selectPreviousKeyView:)) + { + wxevent.m_keyCode = WXK_TAB; + wxevent.SetShiftDown(true); + wxevent.SetRawControlDown(true); + } + else if (commandSelector == @selector(cancelOperation:)) + { + wxevent.m_keyCode = WXK_ESCAPE; + } + if (wxevent.GetKeyCode() != WXK_NONE) + { + wxKeyEvent eventHook(wxEVT_CHAR_HOOK, wxevent); + if (wxpeer->OSXHandleKeyEvent(eventHook) && !eventHook.IsNextEventAllowed()) + handled = YES; + else if (impl->DoHandleKeyNavigation(wxevent)) + handled = YES; + } + } } } @@ -649,6 +688,15 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; } @end + +bool wxNSTextBase::ShouldHandleKeyNavigation(const wxKeyEvent &event) const +{ + // Text controls must be allowed to handle the key even if wxWANTS_CHARS is not set, provided wxTE_PROCESS_TAB + // is set. To make Shift+TAB work with text controls we must process it here regardless of wxTE_PROCESS_TAB. + // For Ctrl(+Shift)+TAB to work as navigation key consistently in all types of text fields we must process it here as well. + return (!m_wxPeer->HasFlag(wxTE_PROCESS_TAB) || event.HasAnyModifiers()); +} + // wxNSTextViewControl // Official Apple docs suggest to use FLT_MAX when embedding an NSTextView @@ -659,8 +707,7 @@ NSView* wxMacEditHelper::ms_viewCurrentlyEdited = nil; #define MAX_WIDTH 1000000 wxNSTextViewControl::wxNSTextViewControl( wxTextCtrl *wxPeer, WXWidget w, long style ) - : wxWidgetCocoaImpl(wxPeer, w), - wxTextWidgetImpl(wxPeer) + : wxNSTextBase(wxPeer, w) { wxNSTextScrollView* sv = (wxNSTextScrollView*) w; m_scrollView = sv; @@ -966,8 +1013,7 @@ wxSize wxNSTextViewControl::GetBestSize() const // wxNSTextFieldControl wxNSTextFieldControl::wxNSTextFieldControl( wxTextCtrl *text, WXWidget w ) - : wxWidgetCocoaImpl(text, w), - wxTextWidgetImpl(text) + : wxNSTextBase(text, w) { Init(w); } @@ -975,8 +1021,7 @@ wxNSTextFieldControl::wxNSTextFieldControl( wxTextCtrl *text, WXWidget w ) wxNSTextFieldControl::wxNSTextFieldControl(wxWindow *wxPeer, wxTextEntry *entry, WXWidget w) - : wxWidgetCocoaImpl(wxPeer, w), - wxTextWidgetImpl(entry) + : wxNSTextBase(wxPeer, entry, w) { Init(w); } diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 805b04b530..ff4c1d3780 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -2833,6 +2833,41 @@ bool wxWidgetCocoaImpl::DoHandleCharEvent(NSEvent *event, NSString *text) return result; } +bool wxWidgetCocoaImpl::ShouldHandleKeyNavigation(const wxKeyEvent &WXUNUSED(event)) const +{ + // Only controls that intercept tabs for different behavior should return false (ie wxTE_PROCESS_TAB) + return true; +} + +bool wxWidgetCocoaImpl::DoHandleKeyNavigation(const wxKeyEvent &event) +{ + bool handled = false; + wxWindow *focus = GetWXPeer(); + if (focus && event.GetKeyCode() == WXK_TAB) + { + if (ShouldHandleKeyNavigation(event)) + { + wxWindow* iter = focus->GetParent() ; + while (iter && !handled) + { + if (iter->HasFlag(wxTAB_TRAVERSAL)) + { + wxNavigationKeyEvent new_event; + new_event.SetEventObject( focus ); + new_event.SetDirection( !event.ShiftDown() ); + /* CTRL-TAB changes the (parent) window, i.e. switch notebook page */ + new_event.SetWindowChange( event.ControlDown() ); + new_event.SetCurrentFocus( focus ); + handled = iter->HandleWindowEvent( new_event ) && !new_event.GetSkipped(); + } + + iter = iter->GetParent() ; + } + } + } + return handled; +} + bool wxWidgetCocoaImpl::DoHandleKeyEvent(NSEvent *event) { wxKeyEvent wxevent(wxEVT_KEY_DOWN); @@ -2847,6 +2882,9 @@ bool wxWidgetCocoaImpl::DoHandleKeyEvent(NSEvent *event) if ( GetWXPeer()->OSXHandleKeyEvent(eventHook) && !eventHook.IsNextEventAllowed() ) return true; + + if (DoHandleKeyNavigation(wxevent)) + return true; } if ( IsUserPane() && [event type] == NSKeyDown)