Is there a way to know what activated QAction? - c++

I have created instance of QAction inside QGraphicsView child class and connected it to my slot in the same class.
QAction *action = new QAction(tr("New"), this);
action->setObjectName("addStopAction");
action->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_N));
connect(action, SIGNAL(triggered()), this, SLOT(addNew()));
addAction(action);
Slot is a function creating new instance of QGraphicsItem on scene assigned to QGraphicsView.
void MyGraphicsView::addNew() {
// Insert new item at cursor position
}
I also add this action to a QMenu which serves as my class context menu.
QMenu *contextMenu = new QMenu(this);
contextMenu->addAction(action);
Everything works fine. When I press Command/Ctrl + N new item is created at cursor position. But when I right-click and select action from context menu I want new item to be created at menu positon.
I can, of course, do some little hack to flag if SLOT was called after contextMenuEvent or something like that, but what I would like to know is:
Is there any way to find out what made QAction emit its triggered() signal inside connected SLOT? That way I could handle when I should place new item at cursor position and when at context menu position inside SLOT implementation.

Of course, you can find out what signal emit inside connected SLOT.
Just use QObject::sender(). In you case:
void MyGraphicsView::addNew() {
QAction* pAction = qobject_cast<QAction*>(sender());
Q_ASSERT(pAction);
// do something with pAction
}

I think you can use custom data that a QAction object can contain.
You can set it when you create a context menu:
void showContextMenu(const QPoint &pos)
{
...
action->setData(pos);
...
}
And in the addNew() function you check if data exists and reset it in the end:
void addNew()
{
QPoint pos;
QPoint posFromAction = action->data()->toPoint();
if (posFromAction.isNull())
{
pos = QCursor::pos(); ///< pos will be current cursor's position
}
else
{
pos = posFromAction; ///< pos will be menu's position
}
doYourStuffAt(pos)
action->setData(QPoint()); ///< reset action's data
}

i managed something similar by connecting the menu to a function like connect (menu, SIGNAL( triggered(QAction*) ), this, SLOT( menuAction_triggered(QAction*) ));
when you execute you context menu, the QMenu::exec(QPoint) will return you the pointer to the action, so you may not need a extra function/slot for it.
you can check for the name of the action with its text QAction::text() or if you have stored your pointers somewhere by comparing the address.
soo long zai

You can reference self.sender() in the function that is called when by customContextMenuRequested signal.
self.tree1.customContextMenuRequested.connect(self.menu1pop) # rightclick menu signal
...
def menu1pop(self, pos):
widget = self.sender()
item = widget.itemAt(pos)
if item is None: return # only show contextmenu if on an item
self.setProperty("mywidget", widget) # pass current widget
self.menu1.popup(QCursor.pos()) # show menu at right click cursor position
self.sender() will return your widget object and then you can set the widget object inside a property.
Then when your action function is called you can recall the widget object by reading the property.
widget = self.property("mywidget")
It's a bit of a hack, but a simple and reliable way to know which widget your action was called from.

You MIGHT be able to use QObject::sender() in the slot that receives the call. Not tried this for actions though. It's probably a bit uglier than your proposed 'hack' (where you could actually implement that quite nicely with a scoped class).

Related

Do you need to delete widget after removeItemWidget from QTreeWidget?

I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:
QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1);
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);
When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use
ui.treeWidget->removeItemWidget(treeItem, 1);
Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.
Do I need to delete it what would be the right order?
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;
or
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.
So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.
The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:
delete comboBox;
comboBox = nullptr;
or:
comboBox.deleteLater();
The second one is preferred.
EDIT:
I don't change the answer since it could be a dishonest to change what was already accepted, ...
But as #Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.
Anyway, the documentation says it is safe to call deleteLater() more than once:
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.
You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().
From the documentation of setItemWidget():
Note: The tree takes ownership of the widget.
EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.
Explaination
You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by
destructing its parent tree widget
This is a direct consequence of the Qt's parent-child mechanism.
calling QTreeWidget::removeItemWidget anytime the tree widget still lives.
This one is not so obvious, since the documentation simply sais:
Removes the widget set in the given item in the given column.
However, looking at the source code it becomes pretty clear what is indeed happening, i.e.
QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)
inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
{ setItemWidget(item, column, nullptr); }
QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->removeEventFilter(this);
oldWidget->deleteLater();
}
Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.
Example
Here is a simple example I have prepared for you in order to demonstrate the described behaviour:
#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
struct MainWindow : public QWidget
{
MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
auto *item = new QTreeWidgetItem(treeWidget);
auto *button = new QPushButton(tr("Remove combo box"), this);
auto *comboBox = new QComboBox();
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
treeWidget->setItemWidget(item, 0, comboBox);
l->addWidget(button);
l->addWidget(treeWidget);
connect(comboBox, &QComboBox::destroyed, [](){
qDebug("The combo box is gone.");
});
connect(button, &QPushButton::clicked, [treeWidget, item](){
treeWidget->removeItemWidget(item, 0);
});
resize(400, 300);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result
The described ways of destroyng the widget could be tested with the application
Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints
The combo box is gone.
After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints
The combo box is gone.

Qt & Prepending one QMenu to another QMenu

Using Qt5, say I have a control implementing its own context menu. And suppose that under some conditions I want to prepend some items to the standard context menu. So, to do this I create a temporary QMenu, add some stuff to it and the append the standard menu. Something like:
// MyControl is derived from QPlainTextEdit
void MyControl::showContextMenu(const QPoint& pos)
{
// This is QPlainTextEdit::createStandardContextMenu()
QMenu* contextMenu = createStandardContextMenu();
if (someCondition)
{
QMenu* tempMenu = new QMenu(this);
/* add several actions to tempMenu */
tempMenu->addSeperator();
for (auto a : contextMenu->actions)
{
tempMenu->addAction(a);
}
// Feel like I should delete the original QMenu here but doing this
// will delete the QActions it created
// delete contextMenu;
contextMenu = tempMenu;
}
contextMenu->exec(mapToGlobal(pos));
delete contextMenu;
}
My question is, isn't this introducing a memory leak? And if so, what is the correct way to go about this? I can't delete contextMenu before I do contextMenu = newMenu; because that apparently deletes the actions I want.
EDIT:
Ultimately what I want to do is use createStandardContextMenu() which returns a allocated QMenu, and then add some QActions to the top of the menu, and make sure nothing leaks.
QWidget::addAction(QAction*) does not take ownership of the action. Whether deleting contextMenu will result in deletion of actions depends on how createStandardContextMenu is implemented (e. g. QMenu::addAction(QString) does take ownership of the action it creates).
So the actions that are owned by the original menu should be re-parented:
for (auto a : contextMenu->actions)
{
tempMenu->addAction(a);
if (a->parent() == contextMenu){
a->setParent(tempMenu);
}
}
delete contextMenu;
contextMenu = tempMenu;
Use insertAction on the QMenu to insert custom actions before the first standard action, like this:
QMenu* contextMenu = createStandardContextMenu();
QAction* first = contextMenu->actions().at(0);
QAction* customAction = /* Create some custom action */
contextMenu->insertAction(first, customAction);
EDIT: You can then use insertSeparator to separate your custom actions from the first standard action.
Context menu should be modal dialog, so dont use dynamic allocation, and pass "this" to contructor, and build menu depending on passed context
(condition)
{
context.add(...);
}
CustomMenu menu(this,context);
menu.exec(mapToGlobal(point));
no rep to add comment, so
QMenu* tempMenu = new QMenu(this);
no, this wont leak, since you pass pointer of parent object.

How to add a list of QActions to a QMenu and handle them with a single slot?

First, I have a list of QWidgets that I won't know the length of until runtime. I then create a QListWidget where I show them and when someone clicks them I use the signal currentItemChanged(QListWidgetItem*, QListWidgetItem*) to catch it and get the clicked item's index.
Now I want to do a similar thing in the QMenu. I will know the list when the QMenu and its actions get built, but I won't be able to hard code this.
How can I create actions, catch their signals and connect them to the same slot which does different things depending on the action's position (index) in the menu list? There must be some way to solve this since other applications use this. I tried to look at mapping but I couldn't get my head around how to use it for this.
I tried to grab the sender in the slot but was not able to get any useful information from it.
You can associate an index (or any other data) to each action when they are created with QAction::setData and connect the signal QMenu::triggered(QAction*) to your slot.
You'll then be able to retrieve the data through the QAction::data() function of your slot parameter.
MyClass::MyClass() {
// menu creation
for(...) {
QAction *action = ...;
action->setData(10);
...
menu->addAction(action);
}
// only one single signal connection
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(mySlot(QAction*)));
}
void MyClass::mySlot(QAction *action) {
int value = action->data().toInt();
}
Other methods: signal mapping or the use of sender(), are explained in that article of Qt Quaterly.
A more generic (not specific to QMenu) way to approach this is the QActionGroup class. This allows you to isolate specific menu items as a related group, or group different widgets together.
void MyClass::InitMenu(QMenu* menu)
{
QActionGroup* actions1 = new QActionGroup(menu);
actions1->setExclusive(false);
actions1->addAction(menu->addAction(tr("Action1")))->setData(1);
actions1->addAction(menu->addAction(tr("Action2")))->setData(2);
actions1->addAction(menu->addAction(tr("Action3")))->setData(3);
actions1->addAction(menu->addAction(tr("Action4")))->setData(4);
actions1->addAction(menu->addAction(tr("Action5")))->setData(5);
connect(actions1, SIGNAL(triggered(QAction*)), SLOT(MySlot(QAction*)));
QActionGroup* actions2 = new QActionGroup(menu);
actions2->addAction(menu->addAction(tr("Undo Action1")))->setData(1);
actions2->addAction(menu->addAction(tr("Undo Action2")))->setData(2);
//...
connect(actions2, SIGNAL(triggered(QAction*)), SLOT(MyUndoSlot(QAction*)));
}
and in the slot:
void MyClass::MySlot(QAction* triggeredAction)
{
// use either the action itself... or an offset
int value = triggeredAction->data().toInt()
}
You can also have a QMap of QActions and ints and as soon as you add your action to the menu you can also add it to your map with a value that is +1 different from the previous one. You can then wire QAction::triggered to a generic slot, from where you can get the sender of the signal by calling sender(), dynamic cast it to a QAction and then look up with value in your map:
class MyClass {
public:
void Init();
private slots:
void onTriggered();
private:
QMap<QAction*, int> _actionToInt;
}
MyClass::Init() {
QMenu* menu = new QMenu();
// Loop for illustration purposes
// For general purpose keep an index and increment it every time you add
for(int i=0; i<10; ++i) {
QAction* action = menu->addAction("Item1");
_actionToInt.insert(action, i);
connect(action, &QAction::triggered, this, &MyClass::onTriggered);
}
}
void MyClass::onTriggered() {
QAction* action = qobject_cast<QAction*>(sender());
//For safety purposes
if (action && _actionToInt.contains(action) {
//And here you have your index!
int index = _actionToInt.value(action);
}
}

Qt Drag&Drop with own widgets?

I created a little widget on my own, including a QProgressBar and a QLabel in a QVBoxLayout. It has also a function which returns the text of the label (self-created).
Now in my MainWindow I have two other QHBoxLayouts and I want to drag and drop my widget from one to another. It also works when I click on the little free space between the QLabel and the QProgressBar. But when I click on one of them directly, the application crashed and burned painfully.
I also know where it fails. My mousePressEvent looks like this:
void DragDrop::mousePressEvent(QMouseEvent *event) {
// !!!!---- make sure ONLY MyWidgets are here, else: CRASH ----!!!!
MyWidget *child = static_cast<MyWidget*>(childAt(event->pos()));
if (!child)
return;
qDebug() << child->returnLabelText();
...
}
So when I click on the ProgressBar, it will cast the ProgressBar, not my own widget. And because the QProgressBar doesn't have a function like returnLabelText() (but my widget does) it fails.
What is a better method to get my widget?
QWidget::childAt(int,int) returns the child widget, not the parent widget. In your case, it returns the QProgressBar. You then try to cast into a MyWidget, which it is not. What you are looking for is for the parent of the QProgressBar (or QLabel).
static_cast does not verify the type of the object you are trying to cast, and will always yield a non-null pointer even if the cast is invalid. What you are looking for here is dynamic_cast, which will return NULL if the object is not of the type you are looking for. Since you are looking for the parent (or an ancestor) of the widget being clicked, you could use a loop to iterate through the clicked widget's ancestry to find the instance of MyWidget you are looking for.
void DragDrop::mousePressEvent(QMouseEvent *event) {
QWidget *widget = childAt(event->pos());
do {
MyWidget *myWidget = dynamic_cast<MyWidget*>(widget);
widget = widget->parentWidget();
} while (myWidget == NULL && widget != NULL)
if (myWidget == NULL)
return;
qDebug() << myWidget->returnLabelText();
// ...
}

Prevent a QMenu from closing when one of its QAction is triggered

I'm using a QMenu as context menu. This menu is filled with QActions. One of these QActions is checkable, and I'd like to be able to check/uncheck it without closing the context menu (and having to re-open it again to choose the option that I want).
I've tried disconnecting the signals emitted by the checkable QAction with no luck.
Any ideas? Thanks.
Use a QWidgetAction and QCheckBox for a "checkable action" which doesn't cause the menu to close.
QCheckBox *checkBox = new QCheckBox(menu);
QWidgetAction *checkableAction = new QWidgetAction(menu);
checkableAction->setDefaultWidget(checkBox);
menu->addAction(checkableAction);
In some styles, this won't appear exactly the same as a checkable action. For example, for the Plastique style, the check box needs to be indented a bit.
There doesn't seem to be any elegant way to prevent the menu from closing. However, the menu will only close if the action can actually trigger, i.e. it is enabled. So, the most elegant solution I found is to trick the menu by shortly disabling the action at the moment when it would be triggered.
Subclass QMenu
Reimplement relevant event handlers (like mouseReleaseEvent())
In the event handler, disable the action, then call base class' implementation, then enable the action again, and trigger it manually
This is an example of reimplemented mouseReleaseEvent():
void mouseReleaseEvent(QMouseEvent *e)
{
QAction *action = activeAction();
if (action && action->isEnabled()) {
action->setEnabled(false);
QMenu::mouseReleaseEvent(e);
action->setEnabled(true);
action->trigger();
}
else
QMenu::mouseReleaseEvent(e);
}
To make the solution perfect, similar should be done in all event handlers that may trigger the action, like keyPressEvent(), etc...
The trouble is that it is not always easy to know whether your reimplementation should actually trigger the action, or even which action should be triggered. The most difficult is probably action triggering by mnemonics: you would need to reimplement the complex algorithm in QMenu::keyPressEvent() yourself.
This is my solution:
// this menu don't hide, if action in actions_with_showed_menu is chosen.
class showed_menu : public QMenu
{
Q_OBJECT
public:
showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; }
showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; }
void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); }
virtual void setVisible (bool visible)
{
if (is_ignore_hide)
{
is_ignore_hide = false;
return;
}
QMenu::setVisible (visible);
}
virtual void mouseReleaseEvent (QMouseEvent *e)
{
const QAction *action = actionAt (e->pos ());
if (action)
if (actions_with_showed_menu.contains (action))
is_ignore_hide = true;
QMenu::mouseReleaseEvent (e);
}
private:
// clicking on this actions don't close menu
QSet <const QAction *> actions_with_showed_menu;
bool is_ignore_hide;
};
showed_menu *menu = new showed_menu ();
QAction *action = menu->addAction (new QAction (menu));
menu->add_action_with_showed_menu (action);
Here are couple ideas I've had... Not sure at all they will work tho ;)
1) Try to catch the Event by using the QMenu's method aboutToHide(); Maybe you can "Cancel" the hide process ?
2) Maybe you could consider using an EventFilter ?
Try to have a look at : http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter
3) Otherwise you could reimplement QMenu to add your own behavior, but it seems a lot of work to me...
Hope this helps a bit !
(I started with Andy's answer, so thank you Andy!)
1) aboutToHide() works, by re-popping the menu at a cached position, BUT it can also enter an infinite loop. Testing if the mouse is clicked outside the menu to ignore re-opening should do the trick.
2) I tried an event filter but it blocks the actual click to the menu item.
3) Use both.
Here is a dirty pattern to prove that it works. This keeps the menu open when the user holds down CTRL when clicking:
# in __init__ ...
self.options_button.installEventFilter(self)
self.options_menu.installEventFilter(self)
self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu)
self.__options_menu_pos_cache = None
self.__options_menu_open = False
def onAboutToHideOptionsMenu(self):
if self.__options_menu_open: # Option + avoid an infinite loop
self.__options_menu_open = False # Turn it off to "reset"
self.options_menu.popup(self.__options_menu_pos_cache)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonRelease:
if obj is self.options_menu:
if event.modifiers() == QtCore.Qt.ControlModifier:
self.__options_menu_open = True
return False
self.__options_menu_pos_cache = event.globalPos()
self.options_menu.popup(event.globalPos())
return True
return False
I say it is dirty because the widget here is acting as an event filter for both the button that opens the menu as well as the menu itself. Using explicit event filter classes would be easy enough to add and it would make things a little easier to follow.
The bools could probably be replaced with a check to see if the mouse is over the menu, and if not, don't pop it open. However, the CTRL key still has to be factored in for my use case, so it probably isn't far off a nice solution as it is.
When the user holds down CTRL and clicks on the menu, it flips a switch so the menu opens itself back up when it tried to close. The position is cached so it opens at the same position. There is a quick flicker, but it feels OK since the user knows they are holding a key down to make this work.
At the end of the day (literally) I already had the whole menu doing the right thing. I just wanted to add this functionality and I definitely didn't want to change to using a widget just for this. For this reason, I am keeping even this dirty patch for now.
Subclass QMenu and override setVisible. You can utilize activeAction() to know if an action was selected and the visible arg to see if the QMenu is trying to close, then you can override and call QMenu::setVisible(...) with the value you want.
class ComponentMenu : public QMenu
{
public:
using QMenu::QMenu;
void setVisible(bool visible) override
{
// Don't hide the menu when holding Shift down
if (!visible && activeAction())
if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
return;
QMenu::setVisible(visible);
}
};
Connect the QMenu.show to the action trigger. I know this is code for Qt5 (and in Python), but the principle should be back compatible.
from PyQt5 import QtWidgets
class CheckableMenu(QtWidgets.QMenuBar):
def __init__(self,parent=None):
super().__init__(parent)
self.menuObj=QtWidgets.QMenu("View")
self.addMenu(self.menuObj)
for i in ['Both','Even','Odd']: #my checkable items
t=QtWidgets.QAction(i,self.menuObj,checkable=True)
t.triggered.connect(self.menuObj.show)
self.menuObj.addAction(t)
Starting from baysmith solution, the checkbox didn´t work as I was expecting, because I was connecting to the action triggered(), rather than connecting to the checkbox toggled(bool). I´m using the code to open a menu with several checkboxes when I press a button :
QMenu menu;
QCheckBox *checkBox = new QCheckBox("Show Grass", &menu);
checkBox->setChecked(m_showGrass);
QWidgetAction *action = new QWidgetAction(&menu);
action->setDefaultWidget(checkBox);
menu.addAction(action);
//connect(action, SIGNAL(triggered()), this, SLOT(ToggleShowHardscape_Grass()));
connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(ToggleShowHardscape_Grass()));
menu.exec(QCursor::pos() + QPoint(-300, 20));
For my use case, this works like a charm
I have been struggling with this for half a day.
There was many accepted answers on the net suggesting overriding setVisible function of the QMenu which did not work for me at all.
I found a solution based on this post (The last reply by the OP)
my C++ implementation for this matter is as follows:
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease)
{
auto action = static_cast<QMenu*>(watched)->activeAction();
if (action && action->isCheckable())
{
action->trigger();
return true;
}
}
return QObject::eventFilter(watched, event);
}