PDA

View Full Version : QToolButton: Menu in setMenu() overriding actions set in addAction!



iw2nhl
17th August 2007, 17:38
Hi,
I see this message every time I show a menu inside a QToolButton in a QToolBar:
QToolButton: Menu in setMenu() overriding actions set in addAction!

What I want to do is a button which can change function (to switch the selection mode of a QGraphicsView).
In the code there is tool button for "scroll" mode and a tool button for "select" mode wich contains a menu with "select intersected" and "select contained".
All the 3 actions are mutually exclusive.
The code works very well and does exactly what I needed, but I don't like that message which is shown each time I click to show the menu (not when I click an action).

Here is the code used to create them:


// Create the toolbar
m_selectionToolbar = new QToolBar;
m_selectionToolbar->setOrientation(Qt::Horizontal);
m_selectionToolbar->setWindowTitle(tr("Selection toolbar", "Toolbar"));

// Group mutually exclusive actions
QActionGroup *actionGroup = new QActionGroup(this);

// Use mouse to scroll the view
m_actionScrollHand = new QAction(actionGroup);
m_actionScrollHand->setCheckable(true);
m_actionScrollHand->setText(tr("&Scroll", "Action"));
m_actionScrollHand->setStatusTip(tr("Drag to scroll the draw area", "Action"));
m_actionScrollHand->setIcon(QIcon(":/images/move.png"));
connect(m_actionScrollHand, SIGNAL(triggered()), this, SLOT(actionScrollHand_triggered()));
m_selectionToolbar->addAction(m_actionScrollHand);

// Use mouse to select items
m_selectionModeMenu = new QMenu;

m_actionIntersectsItem = new QAction(actionGroup);
m_actionIntersectsItem->setCheckable(true);
m_actionIntersectsItem->setText(tr("Select &intersected", "Action"));
m_actionIntersectsItem->setStatusTip(tr("Drag to select items intersected by the rubber band", "Action"));
m_actionIntersectsItem->setIcon(QIcon(":/images/selectintersected.png"));
connect(m_actionIntersectsItem, SIGNAL(triggered()), this, SLOT(actionIntersectsItem_triggered()));
m_selectionModeMenu->addAction(m_actionIntersectsItem);

m_actionContainsItem = new QAction(actionGroup);
m_actionContainsItem->setCheckable(true);
m_actionContainsItem->setText(tr("Select &contained", "Action"));
m_actionContainsItem->setStatusTip(tr("Drag to select items contained by the rubber band", "Action"));
m_actionContainsItem->setIcon(QIcon(":/images/selectcontained.png"));
connect(m_actionContainsItem, SIGNAL(triggered()), this, SLOT(actionContainsItem_triggered()));
m_selectionModeMenu->addAction(m_actionContainsItem);

m_selectionModeButton = new QToolButton;
m_selectionModeButton->setPopupMode(QToolButton::MenuButtonPopup);
m_selectionModeButton->setMenu(m_selectionModeMenu);

// Set default action
m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);
m_actionIntersectsItem->setChecked(true);

m_selectionToolbar->addWidget(m_selectionModeButton);


And here are the slots:


void GraphicsDrawView::actionScrollHand_triggered()
{
m_drawView->setDragMode(QGraphicsView::ScrollHandDrag);
}

void GraphicsDrawView::actionIntersectsItem_triggered()
{
m_drawView->setDragMode(QGraphicsView::RubberBandDrag);
m_drawView->setRubberBandSelectionMode(Qt::IntersectsItemShape );
m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);
}

void GraphicsDrawView::actionContainsItem_triggered()
{
m_drawView->setDragMode(QGraphicsView::RubberBandDrag);
m_drawView->setRubberBandSelectionMode(Qt::ContainsItemShape);
m_selectionModeButton->setDefaultAction(m_actionContainsItem);
}


Here instead are the variables (which are in the header):


QPointer< DrawView > m_drawView; // Inherits QGraphicsView
QPointer< QToolBar > m_selectionToolbar;
QAction *m_actionScrollHand;
QAction *m_actionIntersectsItem;
QAction *m_actionContainsItem;
QToolButton *m_selectionModeButton;
QMenu *m_selectionModeMenu;

wysota
17th August 2007, 18:31
How about not using QToolButton but a QAction and its setMenu() method instead and then adding the action to the toolbar like any other action?

iw2nhl
17th August 2007, 23:52
How about not using QToolButton but a QAction and its setMenu() method instead and then adding the action to the toolbar like any other action?
I tried adding this code:


QAction *m_menuAction;

m_menuAction = new QAction(this);
m_menuAction->setMenu(m_selectionModeMenu);
m_drawToolbar->addAction(m_menuAction);


It works the same as what I was doing before, but I cannot set the visible action, I get an empty QToolButton with a menu. Is there something wrong with my code?
I think the QToolButton is empty because it is showing m_menuAction which does not have any text or icon.

I tried also to set the default action as I did before with this code:

((QToolButton*)(m_drawToolbar->widgetForAction(m_menuAction)))->setDefaultAction(m_actionIntersectsItem);
It works (with no strange messages), except for the problem that I get my 2 actions in a sub-menu + the default action in the main menu (what is happening???:confused:).

Moreover I discovered that in my original code the message is shown only after I set the default action with:

m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);
If no default action is set, no message is shown, but in this case I have again an empty QToolButton!

wysota
18th August 2007, 09:26
The "default action" is the action you just created. Simply assign it an icon, text and whatever else you want. That's probably why you received the message in the first place - you tried to override the default action.

iw2nhl
19th August 2007, 14:44
The "default action" is the action you just created. Simply assign it an icon, text and whatever else you want. That's probably why you received the message in the first place - you tried to override the default action.
I don't create an action, but a toolbutton!
The second test I did creating an action instead of the toolbutton did not show any more the message, but created an unwanted sub-menu.

The default action must change depending on the user clicks in the menu.
When a user clicks a menu item, the menu item's action become the default action, so that subsequent clicks on the toolbutton activate that action without using the menu.
The menu is used to change the action performed by the toolbutton click.
For this I don't want the toolbutton to have another action for himself.

Here is what the documentation of QToolButton::setDefaultAction() says:

void QToolButton::setDefaultAction ( QAction * action ) [slot]
Sets the default action to action.
If a tool button has a default action, the action defines the button's properties like text, icon, tool tip, etc.
So it seems exactly what I'm doing: I set the button's properties using the default action.

wysota
19th August 2007, 15:39
I don't create an action, but a toolbutton!
I meant the one in the second test. That's the "default action" for a tool button that will be created automatically by Qt when you add the action to the toolbar.


The default action must change depending on the user clicks in the menu.
I'm not sure I understand what you want then...

Isn't this what you want?


#include <QApplication>
#include <QMainWindow>
#include <QToolBar>
#include <QToolButton>
#include <QAction>
#include <QMenu>

/*
Connecting POS1 and POS2 actions triggered signals to
a custom slot that will hide act and show POS1 or POS2
respectively and assign it the menu will switch the visible
action and retain the menu that allows to choose one of
the remaining actions.
*/

int main(int argc, char **argv){
QApplication app(argc, argv);
QMainWindow mw;
QToolBar *tb = mw.addToolBar("test");
QAction *act = new QAction(&mw);
act->setIcon(QPixmap("/usr/share/icons/crystalsvg/32x32/actions/exit.png"));
act->setToolTip("Test menu");
QMenu *menu = new QMenu(&mw);
menu->addAction("POS1"); // real actions here
menu->addAction("POS2"); // real actions here
act->setMenu(menu);
tb->addAction(act);
QObject::connect(act, SIGNAL(triggered()), &mw, SLOT(close()));
mw.show();
return app.exec();
}

iw2nhl
19th August 2007, 17:49
Yes, it is almost what I'm doing, but the idea is a little different:
in the toolbar I want to have 2 buttons, say A (simple QToolButton) and B (QToolButton with arrow for menu) and the user 90% of the times clicks on A or B, but sometimes it wants to change what B does, so he clicks the B menu, change the action and B from now on performs the new action.
The menu of B shows all the possible assignable actions to button B, while the button himself, when clicked, performs the last selected action. Button B also changes icon/text/tooltip depending on the selected action so that the user knows how it is working.

Note that setDefaultAction() is doing exactly what I described above, just it shows the message in console every time the user clicks the arrow to show the menu of B.

In your code, in the comment you say:
"a custom slot that will hide act and show POS1 or POS2"
What do you mean with "hide act", here act is the QToolButton, how can you hide the action and show POS1 or POS2? Sorry if it's a stupid question, may be I'm not understanding something...

wysota
19th August 2007, 20:45
act is an action, not a toolbutton. It can be a menu entry as well. If you hide an action (set its visible property to false) all toolbuttons and menu entries associated with it will be hidden. The most trivial implementation of what you want is to have n actions in a toolbar (and each has the menu associated with it), but only show one of them at a time.

I'm currently looking into the sources to find out exactly why you get that message, but it'll take some time - I have to download the latest Qt source archive.

marcel
19th August 2007, 21:49
OK. Here's what happens in 4.3.1( Sorry Wysota, I promise not to answer any posts until next Saturday :)):

A tool button has a list of assigned actions( inherited from QWidget).
When you set the menu via setMenu:


m_selectionModeButton->setMenu(m_selectionModeMenu);
the default(??) action of m_SelectionModeMenu will be added to m_SelectionModeButton's action list.

Next, you added:


m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);If m_actionIntersectsItem was not already the default action for the menu, it causes the tool button to add it to its action list.

The message you were complaining about is outputted in QToolButton: popupTimerDone, called, for example when the menu is shown.


if(menuAction) {
actualMenu = menuAction->menu();
if (q->actions().size() > 1)
qWarning("QToolButton: Menu in setMenu() overriding actions set in addAction!");
}
As you can see later in the function, if you set any menu with setMenu, then this menu will get displayed, rendering any actions that you add via addAction useless( they will be overwritten by the action you will select in the menu ).
Basically the message is just a warning, so you should ignore it if you don't want to fix it.

To fix it, use:


m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);before setting the menu to the toolbutton with setMenu. This way you add only one action to the button( the current one).

Regards

wysota
19th August 2007, 23:09
If you mean this, then it also causes the warning to appear.

#include <QtGui>

int main(int argc, char **argv){
QApplication app(argc, argv);
QMainWindow mw;
QToolBar *tb = mw.addToolBar("Test");
QMenu *menu = new QMenu(&mw);
QAction *act1 = new QAction(QPixmap("/usr/share/icons/crystalsvg/22x22/actions/filenew.png"), "ACT1", &mw);
QAction *act2 = new QAction(QPixmap(), "ACT2", &mw);
QAction *act3 = new QAction(QPixmap(), "ACT3", &mw);
menu->addAction(act1);
menu->addAction(act2);
menu->addAction(act3);
QToolButton *b = new QToolButton(&mw);
b->setDefaultAction(act1);
b->setMenu(menu);
b->setPopupMode(QToolButton::MenuButtonPopup);
tb->addWidget(b);
mw.show();
return app.exec();
}

I'd ignore the "default action" and do the thing manually or just turn off debugging messages ;)

iw2nhl
19th August 2007, 23:17
Thank you marcel for your detailed explanation, it was very helpful!
I could solve the problem!

Although your solution did not work (changing the position of setDefaultAction() did not change anything, moreover I still needed to call it in the slots at user request), this sentence made me understand the problem:

As you can see later in the function, if you set any menu with setMenu, then this menu will get displayed, rendering any actions that you add via addAction useless( they will be overwritten by the action you will select in the menu ).
As you told, every QWidget has a list of actions, so adding a QMenu to it was not necessary and went to override the internal list of actions. The list was not empty because of setDefaultAction() which added the action to the list of widget actions in some way.
The solutions now is very simple: instead of adding the actions to a QMenu and setting the menu to the QToolButton, you need only to add the actions directly to the QToolButton!
In this way the menu is auto-generated with the list of the QWidget actions.
Here is the code:


m_selectionModeButton = new QToolButton;
m_selectionModeButton->setPopupMode(QToolButton::MenuButtonPopup);
m_selectionModeButton->addAction(m_actionIntersectsItem); // New line!
m_selectionModeButton->addAction(m_actionContainsItem); // New line!
m_selectionModeButton->setDefaultAction(m_actionIntersectsItem);

// Removed lines
//m_selectionModeMenu = new QMenu;
//m_selectionModeMenu->addAction(m_actionIntersectsItem);
//m_selectionModeMenu->addAction(m_actionContainsItem);
//m_selectionModeButton->setMenu(m_selectionModeMenu);

Now the code is even simpler!

I hope this solution can help someone else too in the future!

Thank you very much to every one for the help and thank you again marcel for the problem description which made me find the solution!

marcel
20th August 2007, 04:57
Although your solution did not work (changing the position of setDefaultAction() did not change anything,
Well, I didn't test it... Seemed like it would work at the moment.

Regards

ggupta81
21st November 2008, 04:42
The solutions now is very simple: instead of adding the actions to a QMenu and setting the menu to the QToolButton, you need only to add the actions directly to the QToolButton!

This is not always when one has to create a separate menu not only the action. One example of such case is when someone has to enable the tearing off on menu using "setTearOffEnabled(true)".

So adding the actions to QMenu and setting the menu to QToolButton is needed int his case. How to overcome the problem here?