Allow to add extra accelerators to wxMenuItem

These accelerators are not shown in wxMenuItem label, but still will
work.

Implement support for them in all major ports and XRC.

Co-Authored-By: Vadim Zeitlin <vadim@wxwidgets.org>
This commit is contained in:
Alexander Koshelev
2021-11-02 16:19:25 +03:00
committed by Vadim Zeitlin
parent e729791222
commit 0cd898975c
18 changed files with 375 additions and 33 deletions

View File

@@ -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 \<accel\> "sub-properties":
@beginTable
@hdr3col{property, type, description}
@row3col{accel, @ref overview_xrcformat_type_text_notrans,
wxMenuItem's accelerator (default: none).}
@endTable
Example:
@code
<extra-accels>
<accel>Ctrl-W</accel>
<accel>Shift-Ctrl-W</accel>
</extra-accels>
@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:
<object class="wxMenuItem" name="wxID_FIND">
<label>_Find...</label>
<accel>Ctrl-F</accel>
<extra-accels>
<accel>Ctrl-W</accel>
<accel>Shift-Ctrl-W</accel>
</extra-accels>
</object>
<object class="separator"/>
<object class="wxMenuItem" name="menu_fuzzy">

View File

@@ -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(

View File

@@ -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<wxAcceleratorEntry>& 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<wxAcceleratorEntry> 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,

View File

@@ -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;

View File

@@ -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<wxMenuItem*> m_hiddenMenuItems;
#endif // wxUSE_ACCEL
wxDECLARE_DYNAMIC_CLASS(wxMenuItem);
};

View File

@@ -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();
//@}
};

View File

@@ -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-<object> 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 }*
}

View File

@@ -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

View File

@@ -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);

View File

@@ -45,6 +45,15 @@
<bitmap>uncenter.xpm</bitmap>
<help>Disable autocentering of a dialog on its parent</help>
</object>
<object class="wxMenuItem" name="multiple_accels">
<label>_Multiple accelerators</label>
<help>You can open the dialog with multiple accelerators</help>
<accel>Ctrl-W</accel>
<extra-accels>
<accel>Ctrl-T</accel>
<accel>Shift-Ctrl-W</accel>
</extra-accels>
</object>
</object>
<object class="wxMenu" name="advanced_demos_menu">
<label>_Advanced</label>

View File

@@ -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)

View File

@@ -704,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)
@@ -987,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",

View File

@@ -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<wxAcceleratorEntry>& 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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
// ----------------------------------------------------------------------------

View File

@@ -10,6 +10,7 @@
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#include <wx/xml/xml.h>
#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<wxString> 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

View File

@@ -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");
@@ -607,6 +637,42 @@ void MenuTestCase::Events()
const wxCommandEvent& ev2 = handler.GetEvent();
CHECK( ev2.GetId() == static_cast<int>(MenuTestCase_SelectAll) );
// Invoke accelerator and extra accelerators, all of them should work.
sim.Char('U', wxMOD_CONTROL);
wxYield();
if ( handler.GotEvent() )
CHECK( handler.GetEvent().GetId() == MenuTestCase_ExtraAccel );
else
FAIL("No expected event for Ctrl-U");
sim.Char('V', wxMOD_CONTROL);
wxYield();
if ( handler.GotEvent() )
CHECK( handler.GetEvent().GetId() == MenuTestCase_ExtraAccel );
else
FAIL("No expected event for Ctrl-V");
sim.Char('W', wxMOD_CONTROL);
wxYield();
if ( handler.GotEvent() )
CHECK( handler.GetEvent().GetId() == MenuTestCase_ExtraAccels );
else
FAIL("No expected event for Ctrl-W");
sim.Char('T', wxMOD_CONTROL);
wxYield();
if ( handler.GotEvent() )
CHECK( handler.GetEvent().GetId() == MenuTestCase_ExtraAccels );
else
FAIL("No expected event for Ctrl-T");
sim.Char('W', wxMOD_CONTROL | wxMOD_SHIFT);
wxYield();
if ( handler.GotEvent() )
CHECK( handler.GetEvent().GetId() == MenuTestCase_ExtraAccels );
else
FAIL("No expected event for Ctrl-Shift-W");
// 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_
// work.