How to iterate through a menu's actions in Qt? - qt

I'm working in a project where I need to open (show or popup) automatically the items in the QMenuBar.
Let's say I have the next menu bar:
File Edit Help
-op1 -op1 -op1
-op2 -op2 -op2
To set an action (show the menu associated with that action) I use:
menuBar->setActiveAction(mymenuactionpointer);
As I know, I can use one of the following to get a list of pointers to the elements of QMenuBar:
QMenuBar::actions();
or
QList<Object*> lst1 = QMenuBar::findChildren<QObject*>();
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();
When I use QMenuBar::findChildren<QAction*>() or MenuBar::actions() I got a list of the menus in menubar, I mean, I got "File, Edit, Help" from my QMenuBar, the size of the QList in this case is 3.
When I use QMenuBar::findChildren<QObject*>() I got a list of QObject of size 6, which is the correct number of items in the menu bar. However, I have tried cast to QAction*
QAction *a = (QAction *)lst1.at(0);
QAction *a = qobject_cast<QAction*>(lst1.at(0));
QAction *a = dynamic_cast<QAction*>(lst1.at(0));
In all this cases a is not NULL, but when I try to get the action name QAction::title() it always causes me segmentation fault.
I have been searching and I found here that after getting the menubar actions list, one can ask to QAction::menu() (which returns a valid QMenu pointer if the item is a menu) to know if the item is a QMenu, if yes, one can repeat getting the actions list of that menu, and continue iterating. But this does not work for me, I expected that for
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();
each element "File, Edit Help" QAction::menu() returns a valid menu pointer, so I could get the list of the actions of each menu, but this does not work at all for me.

The correct way to enumerate a QMenu is to use the actions() functions, but there is a catch - some of the actions are submenus, and they need to be iterated recursively. In fact, each QMenu is associated with a QAction, and they both hold pointers to each other - see QMenu::menuAction() and QAction::menu().
It is crucial to understand that each QMenu is also associated with a QAction. So knowing that, the proper implementation is the following:
void enumerateMenu(QMenu *menu)
{
foreach (QAction *action, menu->actions()) {
if (action->isSeparator()) {
qDebug("this action is a separator");
} else if (action->menu()) {
qDebug("action: %s", qUtf8Printable(action->text()));
qDebug(">>> this action is associated with a submenu, iterating it recursively...");
enumerateMenu(action->menu());
qDebug("<<< finished iterating the submenu");
} else {
qDebug("action: %s", qUtf8Printable(action->text()));
}
}
}

Below is how to iterate through every menu item in the menu bar, it will also seek out any menus underneath so there is not need for recursive calling here.
// assuming you have a private QActionGroup* actions; defined in the header..
// ...and a slot named 'onAction(QAction*)' as well... this should work:
QList<QMenu*> lst;
lst = ui->menuBar->findChildren<QMenu*>();
actions = new QActionGroup(this);
foreach (QMenu* m, lst)
{
foreach (QAction* a, m->actions())
{
actions->addAction(a);
}
}
connect(actions,SIGNAL(triggered(QAction*)),this,SLOT(onAction(QAction*)));
As you can see, you can then connect a master slot to handle the various events an action might bring up (i just showed triggered here but you get the idea). Hope this helps.. someone..
Notes
I used the QActionGroup for example purposes on using the list you might iterate through, but you really shouldnt use that unless you are dealing with radio groups since thats what its for. Secondly, if you want the actions because you plan to link them into a single method to handle all items, i suggest you use QMenu's triggered/hovering signals or if you need to know when a menu is about to pop up, you'll need QMenuBar's aboutToShow() signal. I cant think of a reason (for me anyway) that you cant do what you need in those signals since you are passed the QAction* in the slot. But if you MUST do it the otherway, you can do it the way I showed above, you just might not want to use the QActionGroup because of radio grouping is what it is designed for. (you can work around that by not adding items that are 'checkable' into the group.

The reason the qobject_cast is failing is that there are only three QActions with the QMenuBar as the parent. The other three are different QObjects (my guess is the three QMenus), so the cast fails. The QActions associated with those menus are then under those, not the root QMenuBar. I fail to see why you can't build a master list of QActions by recursively iterating through the QMenus.
You may be able to just use the QAction pointer from your UI definition if you are after a known menu, that might not trigger the parent menus. If you are trying to automate testing, trigger() on your desired QAction is probably as detailed as you need. If you are trying to do things in response to user actions, modifying toolbars is probably a better means of contextual feedback, as it doesn't break focus. Some more details on what you're actually trying to accomplish would help.

this puts it all together:
template <class Function>
class QMenuBarIterator {
QMenuBar *i_barP;
void iterate_sub(Function f, size_t tabsL, QMenu* m) {
foreach (QAction *action, m->actions()) {
f(tabsL, action);
if (action->menu()) {
iterate_sub(f, tabsL + 1, action->menu());
}
}
}
public:
QMenuBarIterator(QMenuBar *barP) : i_barP(barP) {}
virtual void operator()(size_t levelL, QAction *actionP) {
}
void iterate(Function f) {
QList<QMenu *> menuBar = i_barP->findChildren<QMenu *>();
foreach (QMenu* m, menuBar) {
f(0, m->menuAction());
iterate_sub(f, 1, m);
}
}
};
/***************************************************************************/
class CMenuLogger {
public:
void operator()(size_t tabsL, QAction *action) {
SuperString tabStr(GetIndentString(tabsL)); // returns a string with tabsL tab characters in it
if (action->isSeparator()) {
qDebug("%s-------------------", tabStr.utf8Z());
} else {
qDebug("%s%s (%s)",
tabStr.utf8Z(),
qUtf8Printable(action->text()),
qUtf8Printable(action->objectName()));
}
}
};
then in your main:
{
QMenuBarIterator<CMenuLogger> bar(ui->menuBar);
bar.iterate(CMenuLogger());
}

Related

in Qt setFocus() for QGraphicsItemGroup

How to be given setFocus() properties into QGraphicsItemGroup item?
I have 3 GraphicsRectItem in QGraphicsItemGroup with bondingrect()
and I want to give setfocus() preporty this QGraphicsItemGroup to can be controled with keyPressEvent
Although your question does not provide enough details, but it seems that you have some graphics items inside a group, and you want to control the items through the keyboard, right?
If so, let's assume that you have some QGraphicsItem items in a QGraphicItemGroup and you want to change the active item by using the Tab key and then do some actions on the active item by using other keys (e.g., rotate it or so on).
My solution is as follows:
define a QGraphicsItem *activeItem pointer which will point to the active item in the group.
overriding the group's keyPressedEvent function as follows:
void MainWindow::keyPressEvent(QKeyEvent *event)
{
//Which key is pressed? ---> <QKeyEvent> must be included
if (event->key() == Qt::Key_Tab) {
/* set activeItem as you desire.
* For example, find the index of the
* item that currently activeItem is pointing
* to and then go to the next item.
*/
//then return
}
if (event->key() == Qt::Key::YourDesiredKey) {
//do your actions and then return
}
}

How to set signals for each action in QMenu?

for(auto s :listofPossibleValues){
// item =s;
action = myMenu.addAction("Set Value to "+s);
connect(action,SIGNAL(triggered(bool)),this,SLOT(menuClicked()));
}
void MainWindow::menuClicked(){
value = new QStandardItem(item);
model->setItem(mainindex->row(),mainindex->column(),value);
}
I add actions and connect signals to the slot to my menu using the code above. Previously I was using the item to be the text. But it will only work for last item.
Does anyone at least know how to get the action that I clicked on?
How can I make it work for each individual item rather than just the last one?
Use the triggered signal of QMenu:
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(menuClicked(QAction*)));
Then, in menuClicked():
void MainWindow::menuClicked(QAction *action) {
// do something with action
}

How can add action with QWidget.addAction()?

In Qt, I want to add some actions in a widget using QWidget.addAction().
I can easily do it with QToolBar.addAction(), but when I use QWidget.addAction(), it doesn't work.
How can I use QWidget.addAction()?
Here is my function:
void Reb::addActionToBar(QString *tabName, QAction *action)
{
//if tab exist, just add the action, else:
tab_widget->addTab(new QWidget(), *tabName);
for(int i = 0 ; i <= tab_widget->count() ; i++) {
if(tab_widget->tabText(i) == tabName) {
action.setParent(tab_widget->widget(i));
tab_widget->widget(i)->addAction(action);
}
}
}
And as you know tab_widget is a QTabWidget...
I have no error but i can't see my action in tab.
QWidget::addAction() does not do add the action to the UI - the only place where the widget's actions are shown is in the widget's context menu, given the right context menu policy.
QTabWidget has no means to display actions in its UI. Actions are usually displayed in toolbars or menubars, so you would need to add the action there.
As a side note, there is no need to pass QStrings by pointer, simply pass the QString by const reference:
void Reb::addActionToBar(const QString &tabName, QAction *action)
Also, your code has an off-by-one error, use i < tab_widget->count() instead of i <= tab_widget->count() to fix that.

QTreeWidget right click menu

I looked around and it seems that the problem is present not only for tree widget but also for other widgets. But in my case, I found a solution, although an incomplete one. I am adding actions to my tree widget, so that when you right click on it, a popup with these actions appears. However, when I add items to my tree widget and I right click on them, the same popup appears.
What I would like to do is that when you right click on the tree widget, a tree widget popup menu appears and when you right click on items, another corresponding popup menu appears. Does anybody knows how to do this?
First,config QTreeWidget to response(emit signal) right mouse click:
treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
Second,connect the signal with your slot "MainWindow::prepareMenu":
connect(treeWidget,&QTreeWidget::customContextMenuRequested,this,&MainWindow::prepareMenu);
Third,create context menu in the slot:
void MainWindow::prepareMenu( const QPoint & pos )
{
QTreeWidget *tree = treeWid;
QTreeWidgetItem *nd = tree->itemAt( pos );
qDebug()<<pos<<nd->text(0);
QAction *newAct = new QAction(QIcon(":/Resource/warning32.ico"), tr("&New"), this);
newAct->setStatusTip(tr("new sth"));
connect(newAct, SIGNAL(triggered()), this, SLOT(newDev()));
QMenu menu(this);
menu.addAction(newAct);
QPoint pt(pos);
menu.exec( tree->mapToGlobal(pos) );
}
First you should set the context menu policy to CustomContextMenu:
treeView->setContextMenuPolicy(Qt::CustomContextMenu);
Then you can connect to the QWidget::customContextMenuRequested(const QPoint&) signal and show your context menu.
For those who prefer to use designer more, here is another way to do it:
1) Set context menu policy to custom context menu
Either by code:
ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
or using graphical designer, click on the tree widget and set it using Property Editor:
2) Create Handler function
In designer, right click on the treeWidget and select "Go to Slot..." option. A window similar to this will appear:
Click on the "CustomContextMenuRequested(QPoint)" option. Handler function will be defined, declared, and it will be connected automatically.
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
// this function will be called on right click
}
This step can also be done by defining and connecting the slot function yourself.
3) Create the options on the context menu.
Go to action editor tab (Usually docked at the bottom of designer). Add actions you want to have on context menu by clicking new button on top left. You will encounter such an interface :
You can (optionally) have a tooltip or icon for the action, or make it checkable. You can crate a shortcut like Ctrl+C for a copy action.
4) Create the menu and fire it
void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
QMenu menu(this); // add menu items
menu.addAction(ui->actionDelete);
menu.addEdit(ui->actionDelete);
...
ui->actionDelete->setData(QVariant(pos)); // if you will need the position data save it to the action
menu.exec( ui->treeWidget->mapToGlobal(pos) );
}
5) Create handler functions for each action
Like in step 2, either create slot function and connect it manually, or right-click on on an action, click the "Go to slots..." option and select triggered() slot.
6) Lastly, apply your logic in the slot function
void MainWindow::on_actionEdit_triggered()
{
QTreeWidgetItem *clickedItem = ui->treeWidget->itemAt(ui->actionDelete->data().toPoint());
// your logic
}
Take a look at overloading QAbstractItemModel and providing your own OnContextMenuRequested. Via this function you can have different items create different context menus.
Here's some shortened pseudo-ish code from one of my projects that may be helpful:
void MyModel::OnContextMenuRequested(const QModelIndex& index, const QPoint& globalPos)
{
// find 'node' corresponding to 'index'
vector<pair<string,BaseNode*> > actions = node->GetActions(true);
if(actions.size()==0) return;
// the ptr list helps us delete the actions
boost::ptr_list<QObject> actionPtrList;
QList<QAction*> qtActions;
for(unsigned int i=0;i<actions.size();i++)
{
QAction* act = new QAction(actions[i].first.c_str(),NULL);
act->setData(qVariantFromValue(actions[i].second));
actionPtrList.push_back(act);
qtActions.append(act);
}
// create and show the context menu
QMenu *menu = new QMenu("Item actions",NULL);
actionPtrList.push_back(menu);
QAction* act = menu->exec(qtActions,globalPos);
if(act==NULL) return;
// act on the resulting action 'act'
}

Expandable list of inputs for QT

I am trying to make a GUI so when you increase the "Article" count, then more of the article inputs show up. For example, if I change the Articles count to 2, I would want another group of inputs to show up for Article 2, and if the Articles count changes to three, there would be three groups of inputs, but since that would use up more space than the window has, it would begin to scroll.
I was thinking of using one of the tree, list, or table widgets, but I am not sure if that is even the right direction I am supposed to be going to. Can anyone push me in the right direction?
Here is a picture, since my description is not good.
You should put all the widgets needed for one article into one single custom widget. Whenever the spin box is changed (code in slot) you can add / remove one instance of such a custom widget to a scroll area.
Within the constructor of this custom widget class (let's call it ArticleWidget), you should define signals in your custom widget which notify about changes made in its child widgets. Connect these within your custom widget:
ArticleWidget::ArticleWidget(QWidget *parent) :
QWidget(parent)
{
ui->setupUi(this); // when you use QtDesigner to design the widget
// propagate signals from my inner widgets to myself:
connect(ui->title, SIGNAL(textChanged(QString)),
SIGNAL(titleChanged(QString)));
}
In the outer widget, whenever creating such a custom widget, connect its signals to your processing slots:
void OuterWidget::articleCountChanged(int)
{
...
if(/*increased*/)
{
ArticleWidget *article = new ArticleWidget(this);
connect(article, SIGNAL(titleChanged(QString)),
SLOT(art_titleChanged(QString)));
ui->scrollAreaViewport->layout()->addWidget(article);
}
...
}
You can access the article widget using sender():
void OuterWidget::art_titleChanged(QString)
{
ArticleWidget *articleWidget = qobject_cast<ArticleWidget*>(sender());
Q_ASSERT(articleWidget); // make sure the signal comes from an ArticleWidget
// if you want to store articles in a vector of custom types,
// you could give this type a pointer to the widget, so you can
// find the index if you have the widget pointer:
foreach(Article *article, articles)
if(article->widget == articleWidget)
article->title = title; // make some changes
}
This code assumes that you hold all your articles in a struct similar to this:
struct ArticleData
{
ArticleWidget *widget;
QString title;
...
};
and have a vector of them in your outer widget class:
QVector<ArticleData*> articles;

Resources