S.llous S.llous - 4 months ago 25
C++ Question

I want to know if QAction is clicked by left or right mouse button

I have a

QAction
in
QMenu
. When
QAction
is
triggered()
I would like to know which button did it.

connect(YourAction, SIGNAL(triggered()), this, SLOT(actionclicked()));

void MainWindow::actionclicked(QMouseEvent *e)
{
if (e->buttons() == Qt::RightButton)
}


I can't do something like this because
triggered()
does not have such argument.

Answer

As @mvidelgauz noticed, QAction is abstracted from input devices which may triggered the action. Nevertheless, if the action is used in your GUI, it has one or more associated widgets: tool buttons in a toolbar, entries in menu bar and so on. These widgets act like any other widgets, so they receive events which may be filtered with the use of installEventFilter and eventFilter. These two methods are inherited from QObject, so they are present in almost any Qt class. For example, let's create an application with QMainWindow and QAction called actionTest. Then let's turn the main window itself into an action filter for actionTest's associated widgets by overriding main window's eventFilter method:

bool eventFilter(QObject *obj, QEvent *ev) {
    //Catch only mouse press events.
    if(ev->type() == QEvent::MouseButtonPress) {
        // Cast general event to mouse event.
        QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
        // Show which button was clicked.
        if(mev->button() == Qt::LeftButton) {
            qDebug() << "Left button!";
        }
        if(mev->button() == Qt::RightButton) {
            qDebug() << "Right button!";
        }
    }
    // In this example we just showed the clicked button. Pass the event
    // for further processing to make QAction slots work.
    return QMainWindow::eventFilter(obj, ev);
}

Then we need to install event filter object for all watched objects, which are widgets in our case. Let's do it in main window constructor:

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    for(auto wgtPtr : ui->actionTest->associatedWidgets()) {
        wgtPtr->installEventFilter(this);
    }
}

Finally, add a slot for triggered() signal handling:

void on_actionTest_triggered() {
    qDebug() << "Action triggered!";
}

Now if you click the action menu entry with left mouse button, it will print

Left button!
Action triggered!

while for right mouse button the result will be

Right button!
Action triggered!

Note that widget event filtering is always performed before triggered() signal emission.

The above code is just an example, and MainWindow class is not the best place to host eventFilter method. In real code you may either:

  1. Create dedicated QObject subclass(es) for QAction widgets event filtering.
  2. Subclass QAction and override it's eventFilter method. In this case you may just save the result of QMouseEvent::button() in the QAction subclass object and later use it in triggered() signal handler. There is a minor inconvenience that Qt creator (at least up to v3.2.1) does not allow you to "promote" QActions in it's form designer, so you'll need to add actions to menus manually in window constructor.
  3. Subclass QMenu, QToolBar, etc.., and make them action filters? I don't know how can it be better than two former variants.

See also documentation about Qt event system.

Let's clarify case 2. Assume the class inherited from QAction is called MyAction. In order to make it work you need to install MyAction objects as filters for themselves (their widgets, to be more specific). You need to do it after widgets were created, so installing filter in MyAction constructor may be premature and lead to crashes. Better place for filter installation is a constructor of a class which owns MyAction object. Typically it's a widget or window class. So just add

for(auto wgtPtr : ui->myActionObject->associatedWidgets()) {
    wgtPtr->installEventFilter(ui->myActionObject);
}

to your window constructor after ui->setupUi(this) call. This code is like in the above example, but we use ui->myActionObject instead of this object as filter.