File indexing completed on 2024-09-29 03:35:29

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #ifndef KHamburgerMenu_H
0009 #define KHamburgerMenu_H
0010 
0011 #include <kconfigwidgets_export.h>
0012 
0013 #include <QWidgetAction>
0014 
0015 #include <memory>
0016 
0017 class KHamburgerMenuPrivate;
0018 
0019 class QMenuBar;
0020 
0021 /**
0022  * @class KHamburgerMenu khamburgermenu.h KHamburgerMenu
0023  *
0024  * @short A menu that substitutes a menu bar when necessary
0025  *
0026  * Allowing users to toggle the visibility of the menu bar and/or toolbars,
0027  * while pretty/"simple by default", can lead to various grave usability issues.
0028  * This class makes it easy to prevent all of them.
0029  *
0030  * Simply add a KHamburgerMenu to your UI (typically to a QToolBar) and make
0031  * it aware of a QMenuBar like this:
0032  *
0033  * \code
0034  * auto hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
0035  * toolBar()->addAction(hamburgerMenu);
0036  * hamburgerMenu->hideActionsOf(toolBar());
0037  * // after the QMenuBar has been initialised
0038  * hamburgerMenu->setMenuBar(menuBar()); // line not needed if there is no QMenuBar
0039  * \endcode
0040  *
0041  * The added menu button will only be visible when the QMenuBar is hidden.
0042  * With this minimal initialisation it will contain the contents of the menu bar.
0043  * If a user (also) hides the container the KHamburgerMenu was added to they
0044  * might find themselves without a way to get a menu back. To prevent this, it is
0045  * recommended to add the hamburgerMenu to prominent context menus like the one
0046  * of your central widget preferably at the first position. Simply write:
0047  *
0048  * \code
0049  * hamburgerMenu->addActionToMenu(contextMenu);
0050  * \endcode
0051  *
0052  * The added menu will only be visible if the QMenuBar is hidden and the
0053  * hamburgerMenu->createdWidgets() are all invisible to the user.
0054  *
0055  * **Populating the KHamburgerMenu**
0056  *
0057  * This is easy:
0058  *
0059  * \code
0060  * auto menu = new QMenu(this);
0061  * menu->addAction(action);
0062  * // Add actions, separators, etc. like usual.
0063  * hamburgerMenu->setMenu(menu);
0064  * \endcode
0065  *
0066  * You probably do not want this to happen on startup. Therefore KHamburgerMenu
0067  * provides the signal aboutToShowMenu that you can connect to a function containing
0068  * the previous statements.
0069  *
0070  * \code
0071  * connect(hamburgerMenu, &KHamburgerMenu::aboutToShowMenu,
0072  *         this, &MainWindow::updateHamburgerMenu);
0073  * // You might want to disconnect the signal after initial creation if the contents never change.
0074  * \endcode
0075  *
0076  * **Deciding what to put on the hamburger menu**
0077  *
0078  * 1. Be sure to add all of the most important actions. Actions which are already
0079  *    visible on QToolBars, etc. will not show up in the hamburgerMenu. To manage
0080  *    which containers KHamburgerMenu should watch for redundancy use
0081  *    hideActionsOf(QWidget *) and showActionsOf(QWidget *).
0082  *    When a KHamburgerMenu is added to a widget, hideActionsOf(that widget)
0083  *    will automatically be called.
0084  * 2. Do not worry about adding all actions the application has to offer.
0085  *    The KHamburgerMenu will automatically have a section advertising excluded
0086  *    actions which can be found in the QMenuBar. There will also be the
0087  *    showMenuBarAction if you set it with setShowMenuBarAction().
0088  * 3. Do not worry about the help menu. KHamburgerMenu will automatically contain
0089  *    a help menu as the second to last item (if you set a QMenuBar which is
0090  *    expected to have the help menu as the last action).
0091  *
0092  * **Open menu by shortcut**
0093  *
0094  * For visually impaired users it is important to have a consistent way to open a general-purpose
0095  * menu. Triggering the keyboard shortcut bound to KHamburgerMenu will always open a menu.
0096  * - If setMenuBar() was called and that menu bar is visible, the shortcut will open the first menu
0097  *   of that menu bar.
0098  * - Otherwise, if there is a visible KHamburgerMenu button in the user interface, that menu will
0099  *   open.
0100  * - Otherwise, KHamburgerMenu's menu will open at the mouse cursor position.
0101  *
0102  * @since 5.81
0103  */
0104 class KCONFIGWIDGETS_EXPORT KHamburgerMenu : public QWidgetAction
0105 {
0106     Q_OBJECT
0107     Q_DECLARE_PRIVATE(KHamburgerMenu)
0108 
0109 public:
0110     explicit KHamburgerMenu(QObject *parent);
0111 
0112     ~KHamburgerMenu() override;
0113 
0114     /**
0115      * Associates this KHamburgerMenu with @p menuBar. The KHamburgerMenu will from now
0116      * on only be visible when @p menuBar is hidden.
0117      * (Menu bars with QMenuBar::isNativeMenuBar() == true are considered hidden.)
0118      *
0119      * Furthermore the KHamburgerMenu will have the help menu from the @p menuBar added
0120      * at the end. There will also be a special sub-menu advertising actions which are
0121      * only available in the menu bar unless advertiseMenuBar(false) was called.
0122      *
0123      * @param menuBar   The QMenuBar the KHamburgerMenu should be associated with.
0124      *                  This can be set to nullptr.
0125      */
0126     void setMenuBar(QMenuBar *menuBar);
0127 
0128     /** @see setMenuBar() */
0129     QMenuBar *menuBar() const;
0130 
0131     /**
0132      * By default the KHamburgerMenu contains a special sub-menu that advertises actions
0133      * of the menu bar which would otherwise not be visible or discoverable for the user.
0134      * This method removes or re-adds that sub-menu.
0135      *
0136      * @param advertise sets whether the special sub-menu that advertises menu bar only
0137      *                  actions should exist.
0138      */
0139     void setMenuBarAdvertised(bool advertise);
0140 
0141     /** @see setMenuBarAdvertised() */
0142     bool menuBarAdvertised() const;
0143 
0144     /**
0145      * Adds the @p showMenuBarAction as the first item of the sub-menu which advertises actions
0146      * from the menu bar.
0147      * @see setMenuBarAdvertised()
0148      */
0149     void setShowMenuBarAction(QAction *showMenuBarAction);
0150 
0151     /**
0152      * Adds this KHamburgerMenu to @p menu.
0153      * It will only be visible in the menu if both the menu bar and all of this
0154      * QWidgetAction's createdWidgets() are invisible.
0155      * If it is visible in the menu, then opening the menu emits the aboutToShowMenu
0156      * signal.
0157      *
0158      * @param menu The menu this KHamburgerMenu is supposed to appear in.
0159      */
0160     void addToMenu(QMenu *menu);
0161 
0162     /**
0163      * Inserts this KHamburgerMenu to @p menu's list of actions, before the action @p before.
0164      * It will only be visible in the menu if both the menu bar and all of this
0165      * QWidgetAction's createdWidgets() are invisible.
0166      * If it is visible in the menu, then opening the menu emits the aboutToShowMenu
0167      * signal.
0168      *
0169      * @param before The action before which KHamburgerMenu should be inserted.
0170      * @param menu The menu this KHamburgerMenu is supposed to appear in.
0171      *
0172      * @see QWidget::insertAction(), QMenu::insertMenu()
0173      *
0174      * @since 5.99
0175      */
0176     void insertIntoMenuBefore(QMenu *menu, QAction *before);
0177 
0178     /**
0179      * Adds @p widget to a list of widgets that should be monitored for their actions().
0180      * If the widget is a QMenu, its actions will be treated as known to the user.
0181      * If the widget isn't a QMenu, its actions will only be treated as known to the user
0182      * when the widget is actually visible.
0183      * @param widget A widget that contains actions which should not show up in the
0184      *               KHamburgerMenu redundantly.
0185      */
0186     void hideActionsOf(QWidget *widget);
0187 
0188     /**
0189      * Reverses a hideActionsOf(widget) method call.
0190      * @see hideActionsOf()
0191      */
0192     void showActionsOf(QWidget *widget);
0193 
0194 Q_SIGNALS:
0195     /**
0196      * This signal is emitted when a hamburger menu button is about to be pressed down.
0197      * It is also emitted when a QMenu that contains a visible KHamburgerMenu emits
0198      * QMenu::aboutToShow.
0199      */
0200     void aboutToShowMenu();
0201 
0202 protected:
0203     /**
0204      * @see QWidgetAction::createWidget
0205      */
0206     virtual QWidget *createWidget(QWidget *parent) override;
0207 
0208 private:
0209     std::unique_ptr<KHamburgerMenuPrivate> const d_ptr;
0210 };
0211 
0212 #endif // KHamburgerMenu_H