diff --git a/docs/doxygen/overviews/xrc_format.h b/docs/doxygen/overviews/xrc_format.h index 015c8a182c..0c676c36fe 100644 --- a/docs/doxygen/overviews/xrc_format.h +++ b/docs/doxygen/overviews/xrc_format.h @@ -518,6 +518,28 @@ Example: @endcode +@subsection overview_xrcformat_type_extra_accels Accelerators List + +Defines a list of wxMenuItem's extra accelerators. + +The extra-accels property element is a "composite" element: +it contains one or more @c \ "sub-properties": + +@beginTable +@hdr3col{property, type, description} +@row3col{accel, @ref overview_xrcformat_type_text_notrans, + wxMenuItem's accelerator (default: none).} +@endTable + +Example: +@code + + Ctrl-W + Shift-Ctrl-W + +@endcode + + @section overview_xrcformat_windows Controls and Windows This section describes support wxWindow-derived classes in XRC format. @@ -1496,6 +1518,10 @@ wxMenuItem objects support the following properties: Item's label (may be omitted if stock ID is used).} @row3col{accel, @ref overview_xrcformat_type_text_notrans, Item's accelerator (default: none).} +@row3col{extra-accels, @ref overview_xrcformat_type_extra_accels, + List of item's extra accelerators. Such accelerators will not be shown + in item's label, but still will work. (default: none). + This property is only supported since wxWidgets 3.1.6.} @row3col{radio, @ref overview_xrcformat_type_bool, Item's kind is wxITEM_RADIO (default: 0)?} @row3col{checkable, @ref overview_xrcformat_type_bool, @@ -1520,6 +1546,10 @@ Example: Ctrl-F + + Ctrl-W + Shift-Ctrl-W + diff --git a/include/wx/gtk/menuitem.h b/include/wx/gtk/menuitem.h index 376959b32e..0156cbc727 100644 --- a/include/wx/gtk/menuitem.h +++ b/include/wx/gtk/menuitem.h @@ -34,11 +34,20 @@ public: virtual void SetBitmap(const wxBitmap& bitmap); virtual const wxBitmap& GetBitmap() const { return m_bitmap; } +#if wxUSE_ACCEL + virtual void AddExtraAccel(const wxAcceleratorEntry& accel) wxOVERRIDE; + virtual void ClearExtraAccels() wxOVERRIDE; +#endif // wxUSE_ACCEL + // implementation void SetMenuItem(GtkWidget *menuItem); GtkWidget *GetMenuItem() const { return m_menuItem; } void SetGtkLabel(); +#if wxUSE_ACCEL + void GTKSetExtraAccels(); +#endif // wxUSE_ACCEL + #if WXWIN_COMPATIBILITY_2_8 // compatibility only, don't use in new code wxDEPRECATED_CONSTRUCTOR( diff --git a/include/wx/menuitem.h b/include/wx/menuitem.h index 8c1d2c68e2..93a739d595 100644 --- a/include/wx/menuitem.h +++ b/include/wx/menuitem.h @@ -23,6 +23,8 @@ #include "wx/windowid.h" +#include "wx/vector.h" + // ---------------------------------------------------------------------------- // forward declarations // ---------------------------------------------------------------------------- @@ -122,6 +124,14 @@ public: // set the accel for this item - this may also be done indirectly with // SetText() virtual void SetAccel(wxAcceleratorEntry *accel); + + // add the accel to extra accels list + virtual void AddExtraAccel(const wxAcceleratorEntry& accel); + + // return vector of extra accels. Implementation only. + const wxVector& GetExtraAccels() const { return m_extraAccels; } + + virtual void ClearExtraAccels() { m_extraAccels.clear(); } #endif // wxUSE_ACCEL #if WXWIN_COMPATIBILITY_2_8 @@ -163,6 +173,10 @@ protected: bool m_isChecked; // is checked? bool m_isEnabled; // is enabled? +#if wxUSE_ACCEL + wxVector m_extraAccels; // extra accels will work, but won't be shown in wxMenuItem title +#endif // wxUSE_ACCEL + // this ctor is for the derived classes only, we're never created directly wxMenuItemBase(wxMenu *parentMenu = NULL, int itemid = wxID_SEPARATOR, diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index 3be6ab906f..ffbcb1312f 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -165,6 +165,7 @@ public : virtual void Check( bool check ) = 0; virtual void SetLabel( const wxString& text, wxAcceleratorEntry *entry ) = 0; virtual void Hide( bool hide = true ) = 0; + virtual void SetAllowsKeyEquivalentWhenHidden( bool ) {} virtual void * GetHMenuItem() = 0; diff --git a/include/wx/osx/menuitem.h b/include/wx/osx/menuitem.h index 803739b647..44f22ff15e 100644 --- a/include/wx/osx/menuitem.h +++ b/include/wx/osx/menuitem.h @@ -17,6 +17,7 @@ #include "wx/defs.h" #include "wx/bitmap.h" +#include "wx/vector.h" // ---------------------------------------------------------------------------- // wxMenuItem: an item in the menu, optionally implements owner-drawn behaviour @@ -42,6 +43,12 @@ public: virtual void Enable(bool bDoEnable = true) wxOVERRIDE; virtual void Check(bool bDoCheck = true) wxOVERRIDE; +#if wxUSE_ACCEL + virtual void AddExtraAccel(const wxAcceleratorEntry& accel) wxOVERRIDE; + virtual void ClearExtraAccels() wxOVERRIDE; + void RemoveHiddenItems(); +#endif // wxUSE_ACCEL + virtual void SetBitmap(const wxBitmap& bitmap) ; virtual const wxBitmap& GetBitmap() const { return m_bitmap; } @@ -61,6 +68,10 @@ private: wxMenuItemImpl* m_peer; +#if wxUSE_ACCEL + wxVector m_hiddenMenuItems; +#endif // wxUSE_ACCEL + wxDECLARE_DYNAMIC_CLASS(wxMenuItem); }; diff --git a/interface/wx/menuitem.h b/interface/wx/menuitem.h index f421836c65..fc5de12019 100644 --- a/interface/wx/menuitem.h +++ b/interface/wx/menuitem.h @@ -567,6 +567,30 @@ public: */ virtual void SetAccel(wxAcceleratorEntry *accel); + /** + Add an extra accelerator for this menu item. + + Additional accelerators are not shown in the item's label, + but still will trigger the menu command when pressed. + + They can be useful to let multiple keys be used as accelerators + for the same command, e.g. @c WXK_ADD and @c WXK_NUMPAD_ADD. + + @onlyfor{wxmsw,wxgtk} + + @since 3.1.6 + */ + void AddExtraAccel(wxAcceleratorEntry *accel); + + /** + Clear the extra accelerators list. + + This doesn't affect the main item accelerator (if any). + + @since 3.1.6 + */ + void ClearExtraAccels(); + //@} }; diff --git a/misc/schema/xrc_schema.rnc b/misc/schema/xrc_schema.rnc index b601d54e36..742190bc0c 100644 --- a/misc/schema/xrc_schema.rnc +++ b/misc/schema/xrc_schema.rnc @@ -501,6 +501,10 @@ t_list_of_numbers = xsd:string { pattern = "\d+(,\d+)*" } t_list_of_numbers_with_weights = xsd:string { pattern = "\d+(:\d+)?(,\d+(:\d+)?)*" } +t_extra_accels = ( + [xrc:p="o"] element accel {_, t_text }+ + ) + # # Handlers for non- content: @@ -1193,15 +1197,16 @@ wxMenuItem = element object { attribute class { "wxMenuItem" } & stdObjectNodeAttributes & - [xrc:p="o"] element label {_, t_text }* & - [xrc:p="o"] element accel {_, t_text }* & - [xrc:p="o"] element radio {_, t_bool }* & - [xrc:p="o"] element checkable {_, t_bool }* & - [xrc:p="o"] element bitmap {_, t_bitmap }* & - [xrc:p="o"] element bitmap2 {_, t_bitmap }* & - [xrc:p="o"] element help {_, t_text }* & - [xrc:p="o"] element enabled {_, t_bool }* & - [xrc:p="o"] element checked {_, t_bool }* + [xrc:p="o"] element label {_, t_text }* & + [xrc:p="o"] element accel {_, t_text }* & + [xrc:p="o"] element extra-accels {_, t_extra_accels }* & + [xrc:p="o"] element radio {_, t_bool }* & + [xrc:p="o"] element checkable {_, t_bool }* & + [xrc:p="o"] element bitmap {_, t_bitmap }* & + [xrc:p="o"] element bitmap2 {_, t_bitmap }* & + [xrc:p="o"] element help {_, t_text }* & + [xrc:p="o"] element enabled {_, t_bool }* & + [xrc:p="o"] element checked {_, t_bool }* } diff --git a/samples/xrc/myframe.cpp b/samples/xrc/myframe.cpp index e897500454..6ec36104aa 100644 --- a/samples/xrc/myframe.cpp +++ b/samples/xrc/myframe.cpp @@ -84,6 +84,7 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(XRCID("derived_tool_or_menuitem"), MyFrame::OnDerivedDialogToolOrMenuCommand) EVT_MENU(XRCID("controls_tool_or_menuitem"), MyFrame::OnControlsToolOrMenuCommand) EVT_MENU(XRCID("uncentered_tool_or_menuitem"), MyFrame::OnUncenteredToolOrMenuCommand) + EVT_MENU(XRCID("multiple_accels"), MyFrame::OnMultipleAccels) EVT_MENU(XRCID("aui_demo_tool_or_menuitem"), MyFrame::OnAuiDemoToolOrMenuCommand) EVT_MENU(XRCID("obj_ref_tool_or_menuitem"), MyFrame::OnObjRefToolOrMenuCommand) EVT_MENU(XRCID("custom_class_tool_or_menuitem"), MyFrame::OnCustomClassToolOrMenuCommand) @@ -285,6 +286,26 @@ void MyFrame::OnUncenteredToolOrMenuCommand(wxCommandEvent& WXUNUSED(event)) dlg.ShowModal(); } +void MyFrame::OnMultipleAccels(wxCommandEvent& WXUNUSED(event)) +{ + wxString msg; +#if defined(__WXOSX_COCOA__) + wxString main = "Cmd-W"; + wxString extra1 = "Cmd-T"; + wxString extra2 = "Shift-Cmd-W"; +#else + wxString main = "Ctrl-W"; + wxString extra1 = "Ctrl-T"; + wxString extra2 = "Shift-Ctrl-W"; +#endif + msg.Printf( + "You can open this dialog with any of '%s' (main), '%s' or '%s' (extra) accelerators.", + main, extra1, extra2 + ); + + wxMessageBox(msg, _("Multiple accelerators demo"), wxOK | wxICON_INFORMATION, this); +} + void MyFrame::OnAuiDemoToolOrMenuCommand(wxCommandEvent& WXUNUSED(event)) { #if wxUSE_AUI diff --git a/samples/xrc/myframe.h b/samples/xrc/myframe.h index 5315e0bd0d..17ab0f43c1 100644 --- a/samples/xrc/myframe.h +++ b/samples/xrc/myframe.h @@ -43,6 +43,7 @@ private: void OnDerivedDialogToolOrMenuCommand(wxCommandEvent& event); void OnControlsToolOrMenuCommand(wxCommandEvent& event); void OnUncenteredToolOrMenuCommand(wxCommandEvent& event); + void OnMultipleAccels(wxCommandEvent& event); void OnAuiDemoToolOrMenuCommand(wxCommandEvent& event); void OnObjRefToolOrMenuCommand(wxCommandEvent& event); void OnCustomClassToolOrMenuCommand(wxCommandEvent& event); diff --git a/samples/xrc/rc/menu.xrc b/samples/xrc/rc/menu.xrc index e154d358f0..0d568f2bbc 100644 --- a/samples/xrc/rc/menu.xrc +++ b/samples/xrc/rc/menu.xrc @@ -45,6 +45,15 @@ uncenter.xpm Disable autocentering of a dialog on its parent + + + You can open the dialog with multiple accelerators + Ctrl-W + + Ctrl-T + Shift-Ctrl-W + + diff --git a/src/common/menucmn.cpp b/src/common/menucmn.cpp index fb6a04b6d2..03dbe8fd02 100644 --- a/src/common/menucmn.cpp +++ b/src/common/menucmn.cpp @@ -296,6 +296,11 @@ void wxMenuItemBase::SetAccel(wxAcceleratorEntry *accel) SetItemLabel(text); } +void wxMenuItemBase::AddExtraAccel(const wxAcceleratorEntry& accel) +{ + m_extraAccels.push_back(accel); +} + #endif // wxUSE_ACCEL void wxMenuItemBase::SetItemLabel(const wxString& str) diff --git a/src/gtk/menu.cpp b/src/gtk/menu.cpp index 03093f41b6..77e64cb1be 100644 --- a/src/gtk/menu.cpp +++ b/src/gtk/menu.cpp @@ -36,8 +36,38 @@ extern int wxOpenModalDialogsCount; static const int wxGTK_TITLE_ID = -3; #if wxUSE_ACCEL -static bool wxGetGtkAccel(const wxMenuItem*, guint*, GdkModifierType*); -#endif +namespace +{ + +// Simple struct encapsulating a GTK accelerator with wrappers for just the +// operations that we need. +class GtkAccel +{ +public: + // Can be constructed from any menu item (must be valid, but doesn't have + // to be an accelerator) or an already existing accelerator entry. + explicit GtkAccel(const wxMenuItem* item); + explicit GtkAccel(const wxAcceleratorEntry& entry) { Init(&entry); } + + // Default copy ctor, assignment operator and dtor are all OK. + + bool IsOk() const { return m_key != 0; } + + void Add(GtkWidget* widget, GtkAccelGroup* accelGroup, GtkAccelFlags accelFlags); + void Remove(GtkWidget* widget, GtkAccelGroup* accelGroup); + +private: + static wxString GetGtkHotKey(const wxAcceleratorEntry*); + + void Init(const wxAcceleratorEntry* entry); + + guint m_key; + GdkModifierType m_mods; +}; + +} // anonymous namespace + +#endif // wxUSE_ACCEL // Unity hack: under Ubuntu Unity the global menu bar is not affected by a // modal dialog being shown, so the user can select a menu item before hiding @@ -642,12 +672,10 @@ void wxMenuItem::SetItemLabel( const wxString& str ) if (m_menuItem) { // remove old accelerator - guint accel_key; - GdkModifierType accel_mods; - if ( wxGetGtkAccel(this, &accel_key, &accel_mods) ) + GtkAccel gtkAccel(this); + if ( gtkAccel.IsOk() ) { - gtk_widget_remove_accelerator( - m_menuItem, GetRootParentMenu(m_parentMenu)->m_accel, accel_key, accel_mods); + gtkAccel.Remove(m_menuItem, GetRootParentMenu(m_parentMenu)->m_accel); } } #endif // wxUSE_ACCEL @@ -662,13 +690,11 @@ void wxMenuItem::SetGtkLabel() GtkLabel* label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(m_menuItem))); gtk_label_set_text_with_mnemonic(label, wxGTK_CONV_SYS(text)); #if wxUSE_ACCEL - guint accel_key; - GdkModifierType accel_mods; - if ( wxGetGtkAccel(this, &accel_key, &accel_mods) ) + GtkAccel gtkAccel(this); + if ( gtkAccel.IsOk() ) { - gtk_widget_add_accelerator( - m_menuItem, "activate", GetRootParentMenu(m_parentMenu)->m_accel, - accel_key, accel_mods, GTK_ACCEL_VISIBLE); + gtkAccel.Add(m_menuItem, GetRootParentMenu(m_parentMenu)->m_accel, + GTK_ACCEL_VISIBLE); } else { @@ -678,6 +704,54 @@ void wxMenuItem::SetGtkLabel() #endif // wxUSE_ACCEL } +#if wxUSE_ACCEL +void wxMenuItem::GTKSetExtraAccels() +{ + GtkAccelGroup* const accelGroup = GetRootParentMenu(m_parentMenu)->m_accel; + + const size_t extraAccelsSize = m_extraAccels.size(); + for (size_t i = 0; i < extraAccelsSize; ++i) + { + GtkAccel(m_extraAccels[i]).Add(m_menuItem, accelGroup, GTK_ACCEL_MASK); + } +} + +void wxMenuItem::AddExtraAccel(const wxAcceleratorEntry& accel) +{ + wxMenuItemBase::AddExtraAccel(accel); + + // If the item is not yet part of the menu, all its extra accelerators will + // be registered with GTK in GTKSetExtraAccels() once it is added to the + // menu, but if it had already been added to it, we need to let GTK know + // about the new extra accelerator. + if (m_menuItem) + { + GtkAccelGroup* const accelGroup = GetRootParentMenu(m_parentMenu)->m_accel; + + GtkAccel(accel).Add(m_menuItem, accelGroup, GTK_ACCEL_MASK); + } +} + +void wxMenuItem::ClearExtraAccels() +{ + // Tell GTK not to use the existing accelerators any longer if they're + // already in use. + if (m_menuItem) + { + GtkAccelGroup* const accelGroup = GetRootParentMenu(m_parentMenu)->m_accel; + + const size_t extraAccelsSize = m_extraAccels.size(); + for (size_t i = 0; i < extraAccelsSize; ++i) + { + GtkAccel(m_extraAccels[i]).Remove(m_menuItem, accelGroup); + } + } + + wxMenuItemBase::ClearExtraAccels(); +} + +#endif // wxUSE_ACCEL + void wxMenuItem::SetBitmap(const wxBitmap& bitmap) { if (m_kind == wxITEM_NORMAL) @@ -961,6 +1035,10 @@ void wxMenu::GtkAppend(wxMenuItem* mitem, int pos) if ( mitem->IsSubMenu() ) UpdateSubMenuItemLabels(mitem); +#if wxUSE_ACCEL + mitem->GTKSetExtraAccels(); +#endif + g_signal_connect (menuItem, "select", G_CALLBACK(menuitem_select), mitem); g_signal_connect (menuItem, "deselect", @@ -1038,11 +1116,11 @@ void wxMenu::Attach(wxMenuBarBase *menubar) #if wxUSE_ACCEL -static wxString GetGtkHotKey( const wxMenuItem& item ) +/* static */ +wxString GtkAccel::GetGtkHotKey(const wxAcceleratorEntry *accel) { wxString hotkey; - wxAcceleratorEntry *accel = item.GetAccel(); if ( accel ) { int flags = accel->GetFlags(); @@ -1313,33 +1391,44 @@ static wxString GetGtkHotKey( const wxMenuItem& item ) } break; } - - delete accel; } return hotkey; } -static bool -wxGetGtkAccel(const wxMenuItem* item, guint* accel_key, GdkModifierType* accel_mods) +void GtkAccel::Init(const wxAcceleratorEntry* entry) { - const wxString string = GetGtkHotKey(*item); + const wxString string = GetGtkHotKey(entry); if (!string.empty()) { - gtk_accelerator_parse(wxGTK_CONV_SYS(string), accel_key, accel_mods); + gtk_accelerator_parse(wxGTK_CONV_SYS(string), &m_key, &m_mods); // Normally, we detect all the keys considered invalid by GTK in // GetGtkHotKey(), but just in case GTK decides to add more invalid // keys in the future versions, recheck once again using its function. - if ( gtk_accelerator_valid(*accel_key, *accel_mods) ) - return true; - - wxLogDebug("\"%s\" is not a valid keyboard accelerator " - "for this GTK version", - string); + if ( !gtk_accelerator_valid(m_key, m_mods) ) + { + wxLogDebug("\"%s\" is not a valid keyboard accelerator " + "for this GTK version", + string); + m_key = 0; + } } -#ifndef __WXGTK4__ else + { + // Key with this code can never be a valid accelerator, so we use + // it to indicate that it's invalid. + m_key = 0; + } +} + +GtkAccel::GtkAccel(const wxMenuItem* item) +{ + wxScopedPtr accel(item->GetAccel()); + Init(accel.get()); + +#ifndef __WXGTK4__ + if ( !IsOk() ) { wxGCC_WARNING_SUPPRESS(deprecated-declarations) @@ -1351,17 +1440,33 @@ wxGetGtkAccel(const wxMenuItem* item, guint* accel_key, GdkModifierType* accel_m gtk_stock_lookup(stockid, &stock_item) && stock_item.keyval ) { - *accel_key = stock_item.keyval; - *accel_mods = stock_item.modifier; - - return true; + m_key = stock_item.keyval; + m_mods = stock_item.modifier; } wxGCC_WARNING_RESTORE() } -#endif - - return false; +#endif // !__WXGTK4__ } + +void +GtkAccel::Add(GtkWidget* widget, GtkAccelGroup* accelGroup, GtkAccelFlags accelFlags) +{ + if ( IsOk() ) + { + gtk_widget_add_accelerator( + widget, "activate", accelGroup, + m_key, m_mods, accelFlags); + } +} + +void GtkAccel::Remove(GtkWidget* widget, GtkAccelGroup* accelGroup) +{ + if ( IsOk() ) + { + gtk_widget_remove_accelerator(widget, accelGroup, m_key, m_mods); + } +} + #endif // wxUSE_ACCEL #ifndef __WXGTK4__ diff --git a/src/msw/menu.cpp b/src/msw/menu.cpp index 52deb6ce88..4ac1468572 100644 --- a/src/msw/menu.cpp +++ b/src/msw/menu.cpp @@ -225,29 +225,30 @@ void wxMenu::UpdateAccel(wxMenuItem *item) return; } - // find the (new) accel for this item + // remove old accels + int n = wxNOT_FOUND; + while ( (n = FindAccel(item->GetId())) != wxNOT_FOUND ) + { + delete m_accels[n]; + m_accels.RemoveAt(n); + } + + // find the (new) accel for this item and add it if any wxAcceleratorEntry *accel = wxAcceleratorEntry::Create(item->GetItemLabel()); if ( accel ) + { accel->m_command = item->GetId(); - - // find the old one - int n = FindAccel(item->GetId()); - if ( n == wxNOT_FOUND ) - { - // no old, add new if any - if ( accel ) - m_accels.Add(accel); - else - return; // skipping RebuildAccelTable() below + m_accels.Add(accel); } - else + + // add extra accels + const wxVector& extraAccelsVector = item->GetExtraAccels(); + const int extraAccelsSize = extraAccelsVector.size(); + for (int i = 0; i < extraAccelsSize; ++i) { - // replace old with new or just remove the old one if no new - delete m_accels[n]; - if ( accel ) - m_accels[n] = accel; - else - m_accels.RemoveAt(n); + wxAcceleratorEntry *extraAccel = new wxAcceleratorEntry(extraAccelsVector[i]); + extraAccel->m_command = item->GetId(); + m_accels.Add(extraAccel); } if ( IsAttached() ) @@ -274,19 +275,22 @@ void wxMenu::RemoveAccel(wxMenuItem *item) return; } - // remove the corresponding accel from the accel table - int n = FindAccel(item->GetId()); - if ( n != wxNOT_FOUND ) + // remove the corresponding accels from the accel table + int n = wxNOT_FOUND; + bool accels_found = false; + while ( (n = FindAccel(item->GetId())) != wxNOT_FOUND ) { delete m_accels[n]; - m_accels.RemoveAt(n); + accels_found = true; + } #if wxUSE_OWNER_DRAWN + if ( accels_found ) + { ResetMaxAccelWidth(); -#endif } - //else: this item doesn't have an accel, nothing to do +#endif } #endif // wxUSE_ACCEL diff --git a/src/osx/cocoa/menuitem.mm b/src/osx/cocoa/menuitem.mm index 85e9abe3e1..9aa2f4d5a1 100644 --- a/src/osx/cocoa/menuitem.mm +++ b/src/osx/cocoa/menuitem.mm @@ -20,6 +20,7 @@ #endif // WX_PRECOMP #include "wx/osx/private.h" +#include "wx/osx/private/available.h" // a mapping from wx ids to standard osx actions in order to support the native menu item handling // if a new mapping is added, make sure the wxNonOwnedWindowController has a handler for this action as well @@ -270,6 +271,15 @@ public : wxLogDebug("wxMenuItemCocoaImpl::Hide not yet supported under OS X < 10.5"); } + void SetAllowsKeyEquivalentWhenHidden( bool allow ) wxOVERRIDE + { + // setAllowsKeyEquivalentWhenHidden is available since macOS 10.13 + if (WX_IS_MACOS_AVAILABLE(10, 13)) + [m_osxMenuItem setAllowsKeyEquivalentWhenHidden:allow ]; + else + wxLogDebug("wxMenuItemCocoaImpl::setAllowsKeyEquivalentWhenHidden not supported under OS X < 10.13"); + } + void SetLabel( const wxString& text, wxAcceleratorEntry *entry ) wxOVERRIDE { wxCFStringRef cfText(text); diff --git a/src/osx/menu_osx.cpp b/src/osx/menu_osx.cpp index e70899b881..8ff64ee79d 100644 --- a/src/osx/menu_osx.cpp +++ b/src/osx/menu_osx.cpp @@ -211,6 +211,10 @@ wxMenuItem *wxMenu::DoRemove(wxMenuItem *item) wxOSXMenuRemoveItem(m_hMenu , pos ); */ +#if wxUSE_ACCEL + // we need to remove all hidden menu items related to this one + item->RemoveHiddenItems(); +#endif GetPeer()->Remove( item ); // and from internal data structures return wxMenuBase::DoRemove(item); diff --git a/src/osx/menuitem_osx.cpp b/src/osx/menuitem_osx.cpp index 80dc0a8f9a..2d6487d358 100644 --- a/src/osx/menuitem_osx.cpp +++ b/src/osx/menuitem_osx.cpp @@ -21,6 +21,7 @@ #endif // WX_PRECOMP #include "wx/osx/private.h" +#include "wx/osx/private/available.h" wxIMPLEMENT_ABSTRACT_CLASS(wxMenuItemImpl, wxObject); @@ -207,6 +208,45 @@ void wxMenuItem::UpdateItemText() #endif // wxUSE_ACCEL/!wxUSE_ACCEL } +#if wxUSE_ACCEL + +void wxMenuItem::AddExtraAccel(const wxAcceleratorEntry& accel) +{ + if (WX_IS_MACOS_AVAILABLE(10, 13)) + { + wxMenuItemBase::AddExtraAccel(accel); + + // create the same wxMenuItem but hidden and with different accelerator. + wxMenuItem* hiddenMenuItem = new wxMenuItem(m_parentMenu, GetId(), m_text, m_help, GetKind(), m_subMenu); + hiddenMenuItem->SetAccel(&(m_extraAccels.back())); + hiddenMenuItem->GetPeer()->Hide(true); + hiddenMenuItem->GetPeer()->SetAllowsKeyEquivalentWhenHidden(true); + m_parentMenu->GetPeer()->InsertOrAppend( hiddenMenuItem, -1 ); + hiddenMenuItem->SetMenu(m_parentMenu); + m_hiddenMenuItems.push_back(hiddenMenuItem); + } + else + { + wxLogDebug("Extra accelerators not being supported under macOS < 10.13"); + } +} + +void wxMenuItem::ClearExtraAccels() +{ + wxMenuItemBase::ClearExtraAccels(); + RemoveHiddenItems(); +} + +void wxMenuItem::RemoveHiddenItems() +{ + for (size_t i = 0; i < m_hiddenMenuItems.size(); ++i) + { + m_parentMenu->GetPeer()->Remove( m_hiddenMenuItems[i] ); + } +} + +#endif // wxUSE_ACCEL + // ---------------------------------------------------------------------------- // wxMenuItemBase // ---------------------------------------------------------------------------- diff --git a/src/xrc/xh_menu.cpp b/src/xrc/xh_menu.cpp index 9e073cb2b4..14ff78317b 100644 --- a/src/xrc/xh_menu.cpp +++ b/src/xrc/xh_menu.cpp @@ -10,6 +10,7 @@ // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" +#include #if wxUSE_XRC && wxUSE_MENUS @@ -79,6 +80,18 @@ wxObject *wxMenuXmlHandler::DoCreateResource() wxString label = GetText(wxT("label")); #if wxUSE_ACCEL wxString accel = GetText(wxT("accel"), false); + wxVector extraAccels; + if (HasParam(wxT("extra-accels"))) + { + wxXmlNode* const extraAccelsNode = GetParamNode(wxT("extra-accels")); + wxXmlNode* node = extraAccelsNode->GetChildren(); + while (node) + { + if (node->GetName() == wxT("accel")) + extraAccels.push_back(node->GetChildren()->GetContent()); + node = node->GetNext(); + } + } #endif // wxUSE_ACCEL wxItemKind kind = wxITEM_NORMAL; @@ -101,11 +114,34 @@ wxObject *wxMenuXmlHandler::DoCreateResource() wxMenuItem *mitem = new wxMenuItem(p_menu, id, label, GetText(wxT("help")), kind); #if wxUSE_ACCEL + if (!extraAccels.empty()) + { + const int entriesSize = extraAccels.size(); + for (int i = 0; i < entriesSize; ++i) + { + wxAcceleratorEntry entry; + if (entry.FromString(extraAccels[i])) + mitem->AddExtraAccel(entry); + else + ReportParamError + ( + "extra-accels", + wxString::Format("cannot create accel from '%s\'", extraAccels[i]) + ); + } + } + if (!accel.empty()) { wxAcceleratorEntry entry; if (entry.FromString(accel)) mitem->SetAccel(&entry); + else + ReportParamError + ( + "accel", + wxString::Format("cannot create accel from '%s'", accel) + ); } #endif // wxUSE_ACCEL diff --git a/tests/menu/menu.cpp b/tests/menu/menu.cpp index 6ce4a58664..7003674c6d 100644 --- a/tests/menu/menu.cpp +++ b/tests/menu/menu.cpp @@ -38,6 +38,8 @@ enum MenuTestCase_Foo = 10000, MenuTestCase_SelectAll, MenuTestCase_Bar, + MenuTestCase_ExtraAccel, + MenuTestCase_ExtraAccels, MenuTestCase_First }; @@ -173,6 +175,34 @@ void MenuTestCase::CreateFrame() "Accelerator conflicting with wxTextCtrl"); m_itemCount++; + // Test adding an extra accelerator to the item before adding it to the menu. + wxAcceleratorEntry entry; + + wxMenuItem* const + extraAccel = new wxMenuItem(fileMenu, MenuTestCase_ExtraAccel, "Extra accels"); + + CHECK( entry.FromString("Ctrl-U") ); + extraAccel->SetAccel(&entry); + + CHECK( entry.FromString("Ctrl-V") ); + extraAccel->AddExtraAccel(entry); + + fileMenu->Append(extraAccel); + m_itemCount++; + + // And now also test adding 2 of them after creating the initial menu item. + wxMenuItem* const + extraAccels = fileMenu->Append(MenuTestCase_ExtraAccels, "Extra accels 2"); + m_itemCount++; + + CHECK( entry.FromString("Ctrl-T") ); + extraAccels->AddExtraAccel(entry); + + CHECK(entry.FromString("Shift-Ctrl-W")); + extraAccels->AddExtraAccel(entry); + + CHECK(entry.FromString("Ctrl-W")); + extraAccels->SetAccel(&entry); PopulateMenu(helpMenu, "Helpmenu item ", m_itemCount); helpMenu->Append(MenuTestCase_Bar, "Bar\tF1"); @@ -532,7 +562,6 @@ public: { m_win->Bind(wxEVT_MENU, &MenuEventHandler::OnMenu, this); - m_gotEvent = false; m_event = NULL; } @@ -543,33 +572,41 @@ public: delete m_event; } - const wxCommandEvent& GetEvent() + // Check that we received an event with the given ID and return the event + // object if we did (otherwise fail the test and return NULL). + const wxObject* CheckGot(int expectedId) { - CPPUNIT_ASSERT( m_gotEvent ); + if ( !m_event ) + { + FAIL("Event not generated"); + return NULL; + } - m_gotEvent = false; + CHECK( m_event->GetId() == expectedId ); - return *m_event; + const wxObject* const src = m_event->GetEventObject(); + + delete m_event; + m_event = NULL; + + return src; } bool GotEvent() const { - return m_gotEvent; + return m_event != NULL; } private: void OnMenu(wxCommandEvent& event) { - CPPUNIT_ASSERT( !m_gotEvent ); + CHECK( !m_event ); - delete m_event; m_event = static_cast(event.Clone()); - m_gotEvent = true; } wxWindow* const m_win; wxCommandEvent* m_event; - bool m_gotEvent; }; #endif // wxUSE_UIACTIONSIMULATOR @@ -589,23 +626,45 @@ void MenuTestCase::Events() sim.KeyUp(WXK_F1); wxYield(); - const wxCommandEvent& ev = handler.GetEvent(); - CPPUNIT_ASSERT_EQUAL( static_cast(MenuTestCase_Bar), ev.GetId() ); - - wxObject* const src = ev.GetEventObject(); - CPPUNIT_ASSERT( src ); - - CPPUNIT_ASSERT_EQUAL( "wxMenu", - wxString(src->GetClassInfo()->GetClassName()) ); - CPPUNIT_ASSERT_EQUAL( static_cast(m_menuWithBar), - src ); + INFO("Expecting event for F1"); + if ( const wxObject* const src = handler.CheckGot(MenuTestCase_Bar) ) + { + CHECK( wxString(src->GetClassInfo()->GetClassName()) == "wxMenu" ); + CHECK( src == m_menuWithBar ); + } // Invoke another accelerator, it should also work. sim.Char('A', wxMOD_CONTROL); wxYield(); - const wxCommandEvent& ev2 = handler.GetEvent(); - CHECK( ev2.GetId() == static_cast(MenuTestCase_SelectAll) ); + INFO("Expecting event for Ctrl-A"); + handler.CheckGot(MenuTestCase_SelectAll); + + // Invoke accelerator and extra accelerators, all of them should work. + sim.Char('U', wxMOD_CONTROL); + wxYield(); + INFO("Expecting event for Ctrl-U"); + handler.CheckGot(MenuTestCase_ExtraAccel); + + sim.Char('V', wxMOD_CONTROL); + wxYield(); + INFO("Expecting event for Ctrl-V"); + handler.CheckGot(MenuTestCase_ExtraAccel); + + sim.Char('W', wxMOD_CONTROL); + wxYield(); + INFO("Expecting event for Ctrl-W"); + handler.CheckGot(MenuTestCase_ExtraAccels); + + sim.Char('T', wxMOD_CONTROL); + wxYield(); + INFO("Expecting event for Ctrl-T"); + handler.CheckGot(MenuTestCase_ExtraAccels); + + sim.Char('W', wxMOD_CONTROL | wxMOD_SHIFT); + wxYield(); + INFO("Expecting event for Ctrl-Shift-W"); + handler.CheckGot(MenuTestCase_ExtraAccels); // Now create a text control which uses the same accelerator for itself and // check that when the text control has focus, the accelerator does _not_