10.1.1.  QActions, QToolbars, and QActionGroups

[ fromfile: menus.xml id: actions ]

Because an application might provide a variety of different ways for the user to issue the same command (e.g., menus, toolbar buttons, keyboard shortcuts), encapsulating each command as an action helps to ensure consistent, synchronized behavior across the application. QAction emits signals and connects to slots as needed.

In Qt GUI applications, actions are typically "triggered" in one of the following ways:

There are several overloaded forms of QMenu::addAction(). We use the version inherited from QWidget, addAction(QAction*), in Example 10.3.

Here you can see how to add actions to menus, action groups, and toolbars. We started by deriving a class from QMainWindow and equipping it with several QAction members plus a QActionGroup and a QToolBar.

Example 10.3. src/widgets/menus/study.h

[ . . . . ]
class Study : public QMainWindow {
    Q_OBJECT
 public:
    explicit Study(QWidget* parent=0);
 public slots:
    void actionEvent(QAction* act);
 private:
    QActionGroup* actionGroup;  1
    QToolBar* toolbar;          2

    QAction* useTheForce;
    QAction* useTheDarkSide;
    QAction* studyWithObiWan;
    QAction* studyWithYoda;
    QAction* studyWithEmperor;
    QAction* fightYoda;
    QAction* fightDarthVader;
    QAction* fightObiWan;
    QAction* fightEmperor;
protected:
    QAction* addChoice(QString name, QString text);

};
[ . . . . ]

1

For catching the signals

2

For displaying the actions as buttons

<include src="src/widgets/menus/study.h" href="src/widgets/menus/study.h" id="studyh" mode="cpp"/>


The constructor for this class sets up the menus and installs them in the QMenuBar that is already part of the base class, as you can see in Example 10.4.

Example 10.4. src/widgets/menus/study.cpp

[ . . . . ]

Study::Study(QWidget* parent) : QMainWindow(parent) {
    actionGroup = new QActionGroup(this);
    actionGroup->setExclusive(false);
    statusBar();

    QWidget::setWindowTitle( "to become a jedi, you wish?"); 1

    QMenu* useMenu = new QMenu("&Use", this);
    QMenu* studyMenu = new QMenu("&Study", this);
    QMenu* fightMenu = new QMenu("&Fight", this);

    useTheForce = addChoice("useTheForce", "Use The &Force");
    useTheForce->setStatusTip("This is the start of a journey...");
    useTheForce->setEnabled(true);
    useMenu->addAction(useTheForce);                         2
[ . . . . ]

    studyWithObiWan = addChoice("studyWithObiWan", "&Study With Obi Wan");
    studyMenu->addAction(studyWithObiWan);
    studyWithObiWan->setStatusTip("He will certainly open doors "
                                   "for you...");

    fightObiWan = addChoice("fightObiWan", "Fight &Obi Wan");
    fightMenu->addAction(fightObiWan);
    fightObiWan->setStatusTip("You'll learn some tricks from him "
                               "that way, for sure!");
[ . . . . ]

    QMainWindow::menuBar()->addMenu(useMenu);
    QMainWindow::menuBar()->addMenu(studyMenu);
    QMainWindow::menuBar()->addMenu(fightMenu);

    toolbar = new QToolBar("Choice ToolBar", this);          3
    toolbar->addActions(actionGroup->actions());

    QMainWindow::addToolBar(Qt::LeftToolBarArea, toolbar);


    QObject::connect(actionGroup, SIGNAL(triggered(QAction*)),
            this, SLOT(actionEvent(QAction*)));              4

    QWidget::move(300, 300);
    QWidget::resize(300, 300);
}

1

Some of the ClassName:: prefixes used here are not necessary, because the functions can be called on "this". The class names can be used to explicitly call a baser- version, or show the reader which version is called.

2

It's already in a QActionGroup, but we also add it to a QMenu.

3

This gives us visible buttons in a dockable widget for each of the QActions.

4

Instead of connecting each individual action's signal, perform one connect to an actionGroup that contains them all.

<include src="src/widgets/menus/study.cpp" allfiles="1" segid="study" href="src/widgets/menus/study.cpp" mode="cpp" id="studycpp"/>


It is possible to connect individual QAction triggered() signals to individual slots. In Example 10.5 we group related QActions together in a QActionGroup. If any member of this group is triggered, QActionGroup emits a single signal, triggered(QAction*), which makes it possible to handle all of those actions in a uniform way. The signal carries a pointer to the particular action that was triggered so that the appropriate response can be chosen.

Example 10.5. src/widgets/menus/study.cpp

[ . . . . ]


// Factory function for creating QActions initialized in a uniform way
QAction* Study::addChoice(QString name, QString text) {
    QAction* retval = new QAction(text, this);
    retval->setObjectName(name);
    retval->setEnabled(false);
    retval->setCheckable(true);
    actionGroup->addAction(retval); 1
    return retval;
}

1

Add every action to a QActionGroup so we need only one signal connected to one slot.

<include src="src/widgets/menus/study.cpp" mode="cpp" href="src/widgets/menus/study.cpp" id="addchoice-cpp" segid="addchoice"/>


After being created, each QAction is added to three other objects, (via addAction()):

  1. A QActionGroup, for signal handling

  2. A QMenu, one of three possible pulldown menus in a QMenuBar

  3. A QToolBar, where it is rendered as a button

To make this example a bit more interesting, we established some logical dependencies between the menu choices to make them consistent with the plots of the various movies. This logic is expressed in the actionEvent() function shown in Example 10.6.

Example 10.6. src/widgets/menus/study.cpp

[ . . . . ]

void Study::actionEvent(QAction* act) {
   QString name = act->objectName();
   QString msg = QString();

   if (act == useTheForce ) {
       studyWithObiWan->setEnabled(true);
       fightObiWan->setEnabled(true);
       useTheDarkSide->setEnabled(true);
   }
   if (act == useTheDarkSide) {
       studyWithYoda->setEnabled(false);
       fightYoda->setEnabled(true);
       studyWithEmperor->setEnabled(true);
       fightEmperor->setEnabled(true);
       fightDarthVader->setEnabled(true);
   }
   if (act == studyWithObiWan) {
       fightObiWan->setEnabled(true);
       fightDarthVader->setEnabled(true);
       studyWithYoda->setEnabled(true);
   }
[ . . . . ]

   if (act == fightObiWan ) {
       if (studyWithEmperor->isChecked()) {
            msg = "You are victorious!";
       }
       else {
           msg = "You lose.";
           act->setChecked(false);
           studyWithYoda->setEnabled(false);
       }
   }
[ . . . . ]

   if (msg != QString()) {
      QMessageBox::information(this, "Result", msg, "ok");
   }
}

<include src="src/widgets/menus/study.cpp" mode="cpp" href="src/widgets/menus/study.cpp" id="study-actionevent" segid="actionevent"/>


Because all actions are in a QActionGroup, a single triggered(QAction*) results in a call to actionEvent().

Figure 10.3.  Checkable Actions in Menus and Toolbars

Checkable Actions in Menus and Toolbars

All menu choices except one are initially disabled. As the user selects from the available choices, other options become enabled or disabled, as shown in Figure 10.3. Notice that the there is consistency between the buttons and the choices in the menus. Clicking an enabled button causes the corresponding menu item to be checked. QAction stores the state (enabled/checked), and the QMenu and QToolBar provide views of the QAction.