File indexing completed on 2024-11-10 03:37:27

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 #include "khamburgermenuhelpers_p.h"
0009 
0010 #include "khamburgermenu.h"
0011 
0012 #include <QEvent>
0013 #include <QGuiApplication>
0014 #include <QMenu>
0015 #include <QMenuBar>
0016 #include <QToolBar>
0017 #include <QToolButton>
0018 #include <QWidget>
0019 #include <QWindow>
0020 
0021 ListenerContainer::ListenerContainer(KHamburgerMenuPrivate *hamburgerMenuPrivate)
0022     : QObject{hamburgerMenuPrivate}
0023     , m_listeners{std::vector<std::unique_ptr<QObject>>(4)}
0024 {
0025 }
0026 
0027 ListenerContainer::~ListenerContainer()
0028 {
0029 }
0030 
0031 bool AddOrRemoveActionListener::eventFilter(QObject * /*watched*/, QEvent *event)
0032 {
0033     if (event->type() == QEvent::ActionAdded || event->type() == QEvent::ActionRemoved) {
0034         static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
0035     }
0036     return false;
0037 }
0038 
0039 void ButtonPressListener::prepareHamburgerButtonForPress(QObject *button)
0040 {
0041     Q_ASSERT(qobject_cast<QToolButton *>(button));
0042 
0043     auto hamburgerMenuPrivate = static_cast<KHamburgerMenuPrivate *>(parent());
0044     auto q = static_cast<KHamburgerMenu *>(hamburgerMenuPrivate->q_ptr);
0045     Q_EMIT q->aboutToShowMenu();
0046     hamburgerMenuPrivate->resetMenu(); // This menu never has a parent which can be
0047     // problematic because it can lead to situations in which the QMenu itself is
0048     // treated like its own window.
0049     // To avoid this we set a sane transientParent() now even if it already has one
0050     // because the menu might be opened from another window this time.
0051     const auto watchedButton = static_cast<QToolButton *>(button);
0052     auto menu = watchedButton->menu();
0053     if (!menu) {
0054         return;
0055     }
0056     prepareParentlessMenuForShowing(menu, watchedButton);
0057 }
0058 
0059 bool ButtonPressListener::eventFilter(QObject *watched, QEvent *event)
0060 {
0061     if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseButtonPress) {
0062         prepareHamburgerButtonForPress(watched);
0063     }
0064     return false;
0065 }
0066 
0067 bool VisibleActionsChangeListener::eventFilter(QObject *watched, QEvent *event)
0068 {
0069     if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
0070         if (!event->spontaneous()) {
0071             static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
0072         }
0073     } else if (event->type() == QEvent::ActionAdded || event->type() == QEvent::ActionRemoved) {
0074         Q_ASSERT_X(qobject_cast<QWidget *>(watched), "VisibileActionsChangeListener", "The watched QObject is expected to be a QWidget.");
0075         if (static_cast<QWidget *>(watched)->isVisible()) {
0076             static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
0077         }
0078     }
0079     return false;
0080 }
0081 
0082 bool VisibilityChangesListener::eventFilter(QObject * /*watched*/, QEvent *event)
0083 {
0084     if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
0085         if (!event->spontaneous()) {
0086             static_cast<KHamburgerMenuPrivate *>(parent())->updateVisibility();
0087         }
0088     }
0089     return false;
0090 }
0091 
0092 bool isMenuBarVisible(const QMenuBar *menuBar)
0093 {
0094     return menuBar && (menuBar->isVisible() && !menuBar->isNativeMenuBar());
0095 }
0096 
0097 bool isWidgetActuallyVisible(const QWidget *widget)
0098 {
0099     Q_CHECK_PTR(widget);
0100     if (widget->width() < 1 || widget->height() < 1) {
0101         return false;
0102     }
0103 
0104     bool actuallyVisible = widget->isVisible();
0105     const QWidget *ancestorWidget = widget->parentWidget();
0106     while (actuallyVisible && ancestorWidget) {
0107         actuallyVisible = ancestorWidget->isVisible();
0108         ancestorWidget = ancestorWidget->parentWidget();
0109     }
0110     return actuallyVisible;
0111 }
0112 
0113 void prepareParentlessMenuForShowing(QMenu *menu, const QWidget *surrogateParent)
0114 {
0115     Q_CHECK_PTR(menu);
0116     // ensure polished so the style can change the surfaceformat of the window which is
0117     // not possible once the window has been created
0118     menu->ensurePolished();
0119     menu->winId(); // trigger being a native widget already, to ensure windowHandle created
0120     // generic code if not known if the available parent widget is a native widget or not
0121 
0122     if (surrogateParent) {
0123         auto parentWindowHandle = surrogateParent->windowHandle();
0124         if (!parentWindowHandle) {
0125             parentWindowHandle = surrogateParent->nativeParentWidget()->windowHandle();
0126         }
0127         menu->windowHandle()->setTransientParent(parentWindowHandle);
0128         return;
0129     }
0130 
0131     menu->windowHandle()->setTransientParent(qGuiApp->focusWindow());
0132     // Worst case: The menu's transientParent is now still nullptr in which case it might open as
0133     // its own window.
0134 }
0135 
0136 void setToolButtonVisible(QWidget *toolButton, bool visible)
0137 {
0138     toolButton->setVisible(visible);
0139     // setVisible() unfortunately has no effect for QWidgetActions on toolbars,
0140     // so we work around this by using setMaximumSize().
0141     if (qobject_cast<QToolBar *>(toolButton->parent())) {
0142         if (visible) {
0143             toolButton->setMaximumSize(QSize(9999999, 9999999));
0144             toolButton->setFocusPolicy(Qt::TabFocus);
0145         } else {
0146             toolButton->setMaximumSize(QSize(0, 0));
0147             toolButton->setFocusPolicy(Qt::NoFocus); // We don't want focus on invisible items.
0148         }
0149     }
0150 }
0151 
0152 bool listContainsWidget(const std::forward_list<QPointer<const QWidget>> &list, const QWidget *widget)
0153 {
0154     for (const auto &item : list) {
0155         if (widget == item) {
0156             return true;
0157         }
0158     }
0159     return false;
0160 }
0161 
0162 #include "moc_khamburgermenuhelpers_p.cpp"