Make accelerators and radio items work in wxQt menus.

See #22985.
This commit is contained in:
Vadim Zeitlin 2022-11-26 16:57:00 +01:00
commit d458af3ca7
5 changed files with 128 additions and 26 deletions

View file

@ -40,6 +40,12 @@ public:
virtual QAction *GetHandle() const;
virtual void SetFont(const wxFont& font);
#if wxUSE_ACCEL
virtual void AddExtraAccel(const wxAcceleratorEntry& accel) override;
virtual void ClearExtraAccels() override;
#endif // wxUSE_ACCEL
private:
// Qt is using an action instead of a menu item.
wxQtAction *m_qtAction;

View file

@ -439,7 +439,10 @@ public:
<tt>\\t</tt> followed by a valid key combination (e.g. <tt>CTRL+V</tt>).
Its general syntax is any combination of @c "CTRL", @c "RAWCTRL", @c
"ALT" and @c "SHIFT" strings (case doesn't matter) separated by either
@c '-' or @c '+' characters and followed by the accelerator itself.
@c '-' or @c '+' characters and followed by the accelerator itself. Note that
the displayed accelerator can differ from the string specified here though,
e.g. <tt>Ctrl+X</tt> could be actually shown on the screen when <tt>Ctrl-X</tt>
is used.
Notice that @c CTRL corresponds to the "Ctrl" key on most platforms but
not under macOS where it is mapped to "Cmd" key on Mac keyboard.
Usually this is exactly what you want in portable code but if you

View file

@ -11,6 +11,7 @@
#include "wx/menu.h"
#include "wx/qt/private/utils.h"
#include "wx/qt/private/converter.h"
#include "wx/qt/private/winevent.h"
#include "wx/stockitem.h"
#include <QtWidgets/QMenu>
@ -22,6 +23,36 @@ static void ApplyStyle( QMenu *qtMenu, long style )
qtMenu->setTearOffEnabled( true );
}
// wxQtActionGroup: an exclusive group which synchronizes QActions in
// QActionGroup with their wx wrappers.
class wxQtActionGroup : public QActionGroup, public wxQtSignalHandler
{
public:
explicit wxQtActionGroup( wxMenu* handler )
: QActionGroup( handler->GetHandle() ),
wxQtSignalHandler( handler )
{
setExclusive(true);
connect( this, &QActionGroup::triggered, this, &wxQtActionGroup::triggered );
}
void AddAction( QAction* action )
{
m_activeAction = QActionGroup::addAction(action);
}
private:
void triggered ( QAction* action );
QAction* m_activeAction;
};
//-----------------------------------------------------------------------------
// wxMenu implementation
//-----------------------------------------------------------------------------
wxMenu::wxMenu(long style)
: wxMenuBase( style )
{
@ -80,8 +111,8 @@ static void InsertMenuItemAction( const wxMenu *menu, const wxMenuItem *previous
}
else
{
QActionGroup *actionGroup = new QActionGroup( qtMenu );
actionGroup->addAction( itemAction );
auto actionGroup = new wxQtActionGroup( const_cast<wxMenu*>(menu) );
actionGroup->AddAction( itemAction );
item->Check();
wxASSERT_MSG( itemAction->actionGroup() == actionGroup, "Must be the same action group" );
}
@ -309,3 +340,14 @@ QWidget *wxMenuBar::GetHandle() const
{
return m_qtMenuBar;
}
void wxQtActionGroup::triggered( QAction* action )
{
if ( action != m_activeAction )
{
if ( m_activeAction->isCheckable() )
m_activeAction->setChecked(false);
m_activeAction = action;
}
}

View file

@ -26,7 +26,10 @@ public:
wxItemKind kind, wxMenu *subMenu, wxMenuItem *menuItem );
// Set the action shortcut to correspond to the accelerator specified by
// the given label.
// the given label. They set the primary shortcut the first time they are
// called, and set additional shortcuts/accels for subsequent calls. if
// text is empty, any axtra accelerators will be removed.
void UpdateShortcuts(const wxString& text);
void UpdateShortcutsFromLabel(const wxString& text);
wxMenu* GetMenu() const
@ -34,7 +37,16 @@ public:
return static_cast<wxMenu*>(wxQtSignalHandler::GetHandler());
}
// Convert hyphenated shortcuts to use the plus sign (+) which Qt understands.
static wxString Normalize(const wxString& text)
{
QString normalized = wxQtConvertString( text );
normalized.replace(QRegExp("([^+-])[-]"), "\\1+");
return wxQtConvertString( normalized );
}
private:
void onActionToggled( bool checked );
void onActionTriggered( bool checked );
const wxWindowID m_mitemId;
@ -54,18 +66,22 @@ wxMenuItem::wxMenuItem(wxMenu *parentMenu, int id, const wxString& text,
const wxString& help, wxItemKind kind, wxMenu *subMenu)
: wxMenuItemBase( parentMenu, id, text, help, kind, subMenu )
{
m_qtAction = new wxQtAction( parentMenu, id, text, help, kind, subMenu, this );
m_qtAction = new wxQtAction( parentMenu, id,
wxQtAction::Normalize( text ),
help, kind, subMenu, this );
}
void wxMenuItem::SetItemLabel( const wxString &label )
{
wxMenuItemBase::SetItemLabel( label );
const wxString qtlabel = wxQtAction::Normalize( label );
m_qtAction->UpdateShortcutsFromLabel( label );
wxMenuItemBase::SetItemLabel( qtlabel );
m_qtAction->setText( wxQtConvertString( label ));
m_qtAction->UpdateShortcutsFromLabel( qtlabel );
m_qtAction->setText( wxQtConvertString( qtlabel ));
}
@ -146,6 +162,22 @@ QAction *wxMenuItem::GetHandle() const
return m_qtAction;
}
#if wxUSE_ACCEL
void wxMenuItem::AddExtraAccel(const wxAcceleratorEntry& accel)
{
wxMenuItemBase::AddExtraAccel(accel);
m_qtAction->UpdateShortcuts( accel.ToRawString() );
}
void wxMenuItem::ClearExtraAccels()
{
wxMenuItemBase::ClearExtraAccels();
m_qtAction->UpdateShortcuts( wxString() );
}
#endif // wxUSE_ACCEL
//=============================================================================
wxQtAction::wxQtAction( wxMenu *handler, int id, const wxString &text, const wxString &help,
@ -180,6 +212,7 @@ wxQtAction::wxQtAction( wxMenu *handler, int id, const wxString &text, const wxS
break;
}
connect( this, &QAction::toggled, this, &wxQtAction::onActionToggled );
connect( this, &QAction::triggered, this, &wxQtAction::onActionTriggered );
UpdateShortcutsFromLabel( text );
@ -191,13 +224,47 @@ void wxQtAction::UpdateShortcutsFromLabel(const wxString& text)
const wxString accelStr = text.AfterFirst('\t');
if ( !accelStr.empty() )
{
setShortcut( QKeySequence( wxQtConvertString(accelStr) ) );
UpdateShortcuts(accelStr);
}
#else
wxUnusedVar(text);
#endif // wxUSE_ACCEL
}
void wxQtAction::UpdateShortcuts(const wxString& text)
{
#if wxUSE_ACCEL
QList<QKeySequence> shortcuts = this->shortcuts();
if ( text.empty() )
{
if ( shortcuts.size() > 1 )
{
// Keep the primary shortcut only and get rid of the rest
setShortcut( shortcuts.first() );
}
}
else
{
QKeySequence keySequence = QKeySequence( wxQtConvertString( text ) );
if ( !shortcuts.contains(keySequence) )
{
shortcuts.push_back(keySequence);
setShortcuts( shortcuts );
}
}
#else
wxUnusedVar(text);
#endif // wxUSE_ACCEL
}
void wxQtAction::onActionToggled( bool checked )
{
GetMenu()->Check(m_mitemId, checked);
}
void wxQtAction::onActionTriggered( bool checked )
{
GetMenu()->Check(m_mitemId, checked);
GetMenu()->SendEvent(m_mitemId, m_isCheckable ? checked : -1 );
}

View file

@ -444,15 +444,11 @@ void MenuTestCase::RadioItems()
// Subsequent items in a group are not checked.
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 1) );
#ifdef __WXQT__
WARN("Radio check test does not work under Qt");
#else
// Checking the second one make the first one unchecked however.
menu->Check(MenuTestCase_First + 1, true);
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First) );
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First + 1) );
menu->Check(MenuTestCase_First, true);
#endif
// Adding more radio items after a separator creates another radio group...
menu->AppendSeparator();
@ -464,30 +460,22 @@ void MenuTestCase::RadioItems()
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First) );
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First + 2) );
#ifdef __WXQT__
WARN("Radio check test does not work under Qt");
#else
menu->Check(MenuTestCase_First + 3, true);
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First + 3) );
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 2) );
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First) );
menu->Check(MenuTestCase_First + 2, true);
#endif
// Insert an item in the middle of an existing radio group.
menu->InsertRadioItem(4, MenuTestCase_First + 5, "Radio 5");
CPPUNIT_ASSERT( menu->IsChecked(MenuTestCase_First + 2) );
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 5) );
#ifdef __WXQT__
WARN("Radio check test does not work under Qt");
#else
menu->Check( MenuTestCase_First + 5, true );
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 3) );
menu->Check( MenuTestCase_First + 3, true );
#endif
// Prepend a couple of items before the first group.
menu->PrependRadioItem(MenuTestCase_First + 6, "Radio 6");
@ -495,9 +483,6 @@ void MenuTestCase::RadioItems()
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 6) );
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 7) );
#ifdef __WXQT__
WARN("Radio check test does not work under Qt");
#else
menu->Check(MenuTestCase_First + 7, true);
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 1) );
@ -505,7 +490,6 @@ void MenuTestCase::RadioItems()
// Check that the last radio group still works as expected.
menu->Check(MenuTestCase_First + 4, true);
CPPUNIT_ASSERT( !menu->IsChecked(MenuTestCase_First + 5) );
#endif
}
void MenuTestCase::RemoveAdd()