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"