Qt: open context menu on mouse press - c++

I'm trying to change the default behavior of context menus: Instead of opening on the release event of the right mouse button, I want it to open on the press event, and it's actions to be triggered on the release event). On one widget I could overload the mousePressEvent() and fire a custom contextmenu event, but I want it to be global to all the context menus of my program...
Any ideas?
Thanks.

I was trying to implement a widget base on top of QWidget with a custom way to handle context menu to suit your needs when I realize that using ActionsContextMenu policy with actions directly own by the widget does exactly the behavior you are expecting. (Qt 4.6.2 & 4.7 on linux, didn't try on windows yet but I don't know why the behavior should be different).
Is that a policy you can use ? If you don't really need external menus, I'll suggest to go with this solution.
Otherwise you will have to create your own widget base with a custom QMenu member. You should use the Qt::PreventContextMenu policy to guarantee the right click to end in the void mousePressEvent(QMouseEvent *event) of your widget. In this event handler make sure to show your menu. In your menu re-implement the void mouseReleaseEvent( QMouseEvent *event) if it don't trigger the current action do it your-self with the mouse position (in the event) and the QAction* actionAt( const QPoint & pt) const. But be careful the void mouseReleaseEvent( QMouseEvent *event) of the QMenu is already re-implemented from QWidget and may do some stuff you want to preserve !
EDIT
It's kind of sad but this behavior seems to be different by design on windows the void QMenu::mouseReleaseEvent(QMouseEvent *e) does the following:
Extracted form qmenu.cpp, Qt 4.6.2 sdk
#if defined(Q_WS_WIN)
//On Windows only context menus can be activated with the right button
if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0)
#endif
d->activateAction(action, QAction::Trigger);
I don't know what the topCausedWidget() does in life but it's kind of explicit that only left button release will trigger the current action ...
One simple solution for you will be to re-implement your QMenu with this line commented ...

Sounds like you need to create your own class based on QMenu, and use it for every context menu in your program.
Check here for a reference.

Related

Is there a way to distinguish, whether a Qt widget got focus from mouse clicking or from table key pressing?

I'm using Qt5 on windows.
Is there a way to distinguish whether a Qt widget got focus from mouse clicking or from table key pressing?
Yes, there is. Override QWidget::focusInEvent and use the QFocusEvent::reason method of the focus event to get the reason!
Simple sample:
void MyWidget::focusInEvent(QFocusEvent *event) {
qDebug() << event->reason();
QWidget::focusInEvent(event);
}
Note: In case you want to get this information from an already existing widget, you can always install an event filter instead. See https://doc.qt.io/qt-5/qobject.html#installEventFilter for an example on how do do that.

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.
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:
Create dedicated QObject subclass(es) for QAction widgets event filtering.
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.
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.
triggered() cannot have this argument by design because it by itself is not necessarily result of a mouse event:
This signal is emitted when an action is activated by the user; for example, when the user clicks a menu option, toolbar button, or presses an action's shortcut key combination, or when trigger() was called
You need to connect to mouse events if you need QMouseEvent as parameter. In fact Qt itself emits triggered() when (but not only as I highlighted in doc quote) framework receives mouse event from menu. So it looks like you'll need to do a similar thing in your code and add your own logic.
P.S. This discussion might be interesting for you

How to handle right button clicks in QTreeWidgetItem?

I'm implementing something similar to Eclipse's Package Explorer, using QTreeWidget, but I don't know how to handle right mouse button clicks.
How do I use Qt creator so I can handle right clicks on a QTreeWidgetItem?
You can set the context menu policy on the tree view item and then create a signal/slot event handlers as per usual.
For an example you can refer to this:
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeView, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(onCustomContextMenu(const QPoint &)));
Then just implement the onContextMenu function above

Always have focus policy on hidden widget? (Qt C++)

I am learning to process keypress and keyrelease events in Qt (C++). I have a class Keyboard with which I want to process all of these events. It inherits QObject. It doesn't need to process any mouse events. I am trying to figure out how I can direct all of the keyboard input when my application is open to that class.
I've tried adding it as a widget in a layout of my MainWindow class and hiding it (the widget, not the layout). Currently, that is not responding.
I've also tried this in my MainWindow class:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
keys->keyPressEvent(event);
//Keys is a Keyboard object with this public method:
//void keyPressEvent(QKeyEvent *event);
}
But that's not working either. In my Keyboard::Keyboard() constructor, I have:
this->setFocusPolicy(Qt::StrongFocus);
I'm not sure if there's anything else I need to do to make sure the keyboard input gets there.
If someone knows a way to send all keyboard events to this class for my Qt application, it would be very helpful!
Thanks,
John
For anyone who wants to know, I found the answer to my question.
In the constructor for the class that is handling my keyboard events, I added this line:
QWidget::grabKeyboard();
and now all the keyboard input when that application is active goes straight to that widget.
You can check the reference for more information:
QWidget::grabKeyboard.
Note: nothing else (i.e., no other widgets) will get keyboard input until you call QWidget::releaseKeyboard().

Context Menu works for child but not parent Widget

I'm developing a desktop program that displays data in several QWidget windows, and I'm attempting to use a context menu to allow the user to copy/save an image of the window for use elsewhere. I encounter a pretty strange error when trying to get the context menu to appear in the window. I initially used the Qt Design mode to create the on_Plot_customContextMenuRequested(const QPoint &pos) slot for the entire window (entire Qwidget?), which did not work. When I create the on_SignalPlot_customContextMenuRequested slot, it works perfectly, but only on that specific widget, which is a subset of the entire window. I use identical code for each slot, and the debug output shows that the individual widget context menu request signal is emitted but the signal for the whole window is not. Is there a way to get it to work for the whole window?
Could the fact that the two child widgets take up the entire window cause the issue? I use a grid layout to ensure that the plots resize with the window.
(I'd show an image of the designer layout, but I don't have enough reputation.)
Does not work (code for whole window):
void Plot::on_Plot_customContextMenuRequested(const QPoint &pos)
{
qDebug()<<"plot context menu requested";
qDebug()<<pos;
QMenu* menu=new QMenu();
menu->addAction(copyWinAct);
menu->addAction(saveWinAct);
menu->exec(QCursor::pos());
}
Works Perfectly (code for individual plot/widget):
void Plot::on_SignalPlot_customContextMenuRequested(const QPoint &pos)
{
qDebug()<<"plot context menu requested";
qDebug()<<pos;
QMenu* menu=new QMenu();
menu->addAction(copyWinAct);
menu->addAction(saveWinAct);
menu->exec(QCursor::pos());
}
Thanks for your help.
If anyone else has this problem, I've found a solution. By connecting the customContextMenuRequested signal to one slot, each sub-widget will display the same context menu. Setting the overall widget to the same slot will make the whole window behave in the same manner. I added the following code to the class default constructor and created the corresponding slot to get everything to behave properly.
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(contextMenuSlot(QPoint)));
QList<QWidget *> windowChildren=this->findChildren<QWidget *>();
foreach (QWidget *child, windowChildren)
{
child->setContextMenuPolicy(Qt::CustomContextMenu);
connect(child,SIGNAL(customContextMenuRequested(QPoint)),this,SLOT(contextMenuSlot(QPoint)));
}
Good luck to anyone else fighting this problem.