简体   繁体   中英

QT - QAction::eventFilter: Ambiguous shortcut overload

Searching here and other placed like qtcentre I've seen this problem has come up but can't seem to get it working. I've got a MainWindow widget with a QSplitter which contains two Pane widgets (subclassed from QFrame ). Each Pane has a menubar with identical associated QActions/Shortcuts .

I've tried all combinations of ShortcutContexts with setShortcutContext() .

WindowShortcut and ApplicationShortcut contexts give the expected "Ambiguous shortcut overload".

While WidgetShortcut and WidgetWithChildrenShortcut both do nothing.

If I activate the menus manually they of course work fine. I've also tried forcing focus on the parent widget with an overloaded enterEvent() .

Any ideas?

thanks.

main.h

#include <QMainWindow>
#include <QFrame>

QT_BEGIN_NAMESPACE
class QAction;
class QMenu;
class QHBoxLayout;
class QSplitter;
class QWidget;
QT_END_NAMESPACE

class Pane: public QFrame
{
  Q_OBJECT

  public:
    Pane(QWidget* parent = 0);

  protected:
    void            enterEvent(QEvent *event);
    void            leaveEvent(QEvent *event);

  private:
    void            createMenus();

    QMenuBar *      m_menuBar;

  private Q_SLOTS:
    void            split();
};

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

private:
    void createActions();
    void createMenus();
    void setupUi(QMainWindow *MainWindow);

    QMenu *fileMenu;
    QAction *exitAct;

    QWidget *centralwidget;
    QHBoxLayout *horizontalLayout;
    QSplitter *splitter;
    QFrame *frame;
    QFrame *frame_2;
};

main.cpp

#include <iostream>
#include <QApplication>
#include <QMainWindow>
#include <QSplitter>
#include <QFrame>
#include <QMenuBar>
#include <QBoxLayout>
#include "main.h"

Pane::Pane(QWidget* parent) :
    QFrame(parent)
{
    setFrameShape(QFrame::StyledPanel);
    setFrameShadow(QFrame::Raised);

    QVBoxLayout *layout = new QVBoxLayout;
    QFrame::setLayout(layout);

    m_menuBar = new QMenuBar;
    QWidget *m_widget = new QWidget;

    layout->addWidget(m_menuBar);
    layout->addWidget(m_widget);
    layout->setContentsMargins(2, 2, 2, 2);

    show();

    createMenus();
}

void
Pane::enterEvent(QEvent *event)
{   
    std::cout << "enter" << std::endl;
    setFocus();
    setStyleSheet("QFrame { border: 1px solid rgb(127, 127, 0); }");
    if (focusWidget())
        std::cout << "focuswidget = " << focusWidget()->objectName().toUtf8().constData() << std::endl;
}

void
Pane::leaveEvent(QEvent *event)
{   
    std::cout << "leave" << std::endl;
    clearFocus();
    setStyleSheet("QFrame { border: 1px solid rgb(64, 64, 64); }");
}

void
Pane::split()
{
    std::cout << "split pane" << std::endl;
}

void
Pane::createMenus()
{
    QMenu *paneMenu = m_menuBar->addMenu(tr("&Pane"));

    QAction *paneSplitAct = new QAction(tr("Split"), this);
    paneSplitAct->setShortcut(Qt::Key_S);
    paneSplitAct->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    paneSplitAct->setStatusTip(tr("Split Pane"));
    connect(paneSplitAct, SIGNAL(triggered()), this, SLOT(split()));
    paneMenu->addAction(paneSplitAct);
}

MainWindow::MainWindow()
{
    setupUi(this);

    createActions();
    createMenus();
}

void MainWindow::createActions()
{
    exitAct = new QAction(tr("E&xit"), this);
    exitAct->setShortcuts(QKeySequence::Quit);
    exitAct->setStatusTip(tr("Exit the application"));
    connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
}

void MainWindow::createMenus()
{
    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(exitAct);
}


void MainWindow::setupUi(QMainWindow *MainWindow)
{
    if (MainWindow->objectName().isEmpty())
        MainWindow->setObjectName(QString::fromUtf8("MainWindow"));

    MainWindow->resize(800, 600);
    centralwidget = new QWidget(MainWindow);
    centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
    horizontalLayout = new QHBoxLayout(centralwidget);
    horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
    splitter = new QSplitter(centralwidget);
    splitter->setObjectName(QString::fromUtf8("splitter"));
    splitter->setOrientation(Qt::Horizontal);

    frame = new Pane(splitter);
    frame->setObjectName(QString::fromUtf8("frame"));
    splitter->addWidget(frame);

    frame_2 = new Pane(splitter);
    frame_2->setObjectName(QString::fromUtf8("frame_2"));
    splitter->addWidget(frame_2);

    horizontalLayout->addWidget(splitter);

    MainWindow->setCentralWidget(centralwidget);

    QMetaObject::connectSlotsByName(MainWindow);
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    app.setOrganizationName("Trolltech");
    app.setApplicationName("Application Example");
    MainWindow mainWin;
    mainWin.show();
    return app.exec();
}

main.pro

HEADERS       = main.h
SOURCES       = main.cpp
CONFIG       += no_keywords

UPDATE: Adding an addAction(paneSplitAct) call at the end of Pane::createMenus() in conjunction with using Qt::WidgetShortcut context seems to give me what I want.

From what I understand of the docs this is supposed to create a context menu in the widget. I don't appear to be getting one (right mouse click I assume) but that's ok since I don't want one. The eventEvent() and leaveEvent() overrides are still needed to set the focus correctly.

I've had similar problems: 2 different widgets had the same shortcut for an action (different action between the widgets). As long as only one of the widgets was visible in the application, everything worked. As soon as both were visible, I got this "ambiguous shortcut overload" message.

The solution was: both action's contexts have to be set correctly to Qt::WidgetWithChildrenShortcut, then it worked fine - just according to current focus.

If only one action has the correct context, the one with the default context got triggered and the message was displayed.

If both actions had no context set (default), the Action created last was triggered and the message displayed.

So if you add an Action with a Shortcut anywhere, remember to think about the correct context and setting it, be restrictive!

AFAIK for most scenarios like this setting the shortcut context to WidgetShortcut is the correct thing to do. The problem though is that your duplicate actions are in menu bars which cannot have focus (in the traditional widget sense), which is why it doesn't do anything.

It may make more sense to put the shared actions into the main window and make them application shortcuts. Then in the main window slots that the actions trigger, work out which Pane object has focus and push the commands onto it.

This solution worked well for me to trigger action with a shortcut when the menu bar was disabled or not used.

Routine to add the shortcut:

void StingrayEditor::add_shortcut(const QJsonObject& item_json)
{
    QString item_path = item_json["path"].toString();
    QString shortcut = item_json["shortcut"].toString();
    if (!shortcut.isEmpty()) {
        QKeySequence key_sequence = QKeySequence::fromString(shortcut);
        QAction* shortcut_action = new QAction(item_path, this);
        if (!key_sequence.isEmpty()) {
            shortcut_action->setShortcut(key_sequence);
            shortcut_action->setShortcutContext(Qt::ApplicationShortcut);
        }
        connect(shortcut_action, &QAction::triggered, this, [item_path]()
        {
            // Action to be executed
        });

        // Add the action to the main window.
        addAction(shortcut_action);
    }
}

It is important that you use shortcut_action->setShortcutContext(Qt::ApplicationShortcut);

Then you'll need to filter/listen for events to catch the QEvent::Shortcut :

bool StingrayEditor::eventFilter(QObject* obj, QEvent* e)
{
    switch (e->type()) {
    case QEvent::Shortcut: {
        QShortcutEvent* sev = static_cast<QShortcutEvent*>(e);
        if (sev->isAmbiguous()) {
            foreach(const auto& action, actions()) {
                if (action->shortcut() == sev->key()) {
                    action->trigger(); // Trigger the action that matches the ambigous shortcut event.
                    return true;
                }
            }
        }
    }
    // ...
    default: break;
    }

    return false;
}

Do not forget to register for events like so:

qApp->installEventFilter(this);

"overload" means you have used this shortcut for multiple actions. Each action shortcut must be unique.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM