File indexing completed on 2024-05-12 16:02:29
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "KisPopupButton.h" 0009 0010 #include <QPointer> 0011 #include <QApplication> 0012 #include <QFrame> 0013 #include <QHBoxLayout> 0014 #include <QKeyEvent> 0015 #include <QScreen> 0016 #include <QStyleOption> 0017 #include <QStylePainter> 0018 #include <QWindow> 0019 0020 #include "kis_global.h" 0021 #include <kis_debug.h> 0022 0023 0024 class KisPopupButtonFrame : public QFrame 0025 { 0026 public: 0027 KisPopupButtonFrame(QWidget *parent, bool detach) 0028 : QFrame(parent) 0029 { 0030 setDetached(detach); 0031 } 0032 0033 void setDetached(bool detach) 0034 { 0035 #if defined Q_OS_ANDROID || defined Q_OS_MACOS 0036 // for some reason when calling destroy() the platform window isn't 0037 // hidden first, this corrupts state of the window stack 0038 hide(); 0039 #endif 0040 0041 // Need to destroy the platform window before changing window flags 0042 // so that Qt knows to actually apply the new flags... 0043 // At least on Windows, not doing this may result in weird window drop 0044 // shadows. 0045 destroy(); 0046 if (detach) { 0047 setWindowFlags(Qt::Dialog); 0048 setFrameStyle(QFrame::NoFrame); 0049 } else { 0050 setWindowFlags(Qt::Popup); 0051 setFrameStyle(QFrame::Box | QFrame::Plain); 0052 } 0053 } 0054 0055 protected: 0056 void keyPressEvent(QKeyEvent *event) override 0057 { 0058 if (event->matches(QKeySequence::Cancel)) { 0059 event->accept(); 0060 hide(); 0061 } else { 0062 QFrame::keyPressEvent(event); 0063 } 0064 } 0065 0066 bool event(QEvent *e) override 0067 { 0068 if (e->type() == QEvent::Close) { 0069 e->ignore(); 0070 hide(); 0071 return true; 0072 } 0073 return QFrame::event(e); 0074 } 0075 }; 0076 0077 0078 struct KisPopupButton::Private { 0079 Private() 0080 : frameLayout(nullptr) 0081 {} 0082 QPointer<KisPopupButtonFrame> frame; 0083 QPointer<QWidget> popupWidget; 0084 QPointer<QHBoxLayout> frameLayout; 0085 bool arrowVisible { true }; 0086 bool isPopupDetached { false }; 0087 bool isDetachedGeometrySet { false }; 0088 }; 0089 0090 KisPopupButton::KisPopupButton(QWidget* parent) 0091 : QToolButton(parent) 0092 , m_d(new Private) 0093 { 0094 setObjectName("KisPopupButton"); 0095 connect(this, SIGNAL(released()), SLOT(showPopupWidget())); 0096 } 0097 0098 KisPopupButton::~KisPopupButton() 0099 { 0100 delete m_d->frame; 0101 delete m_d; 0102 } 0103 0104 void KisPopupButton::setPopupWidgetDetached(bool detach) 0105 { 0106 m_d->isPopupDetached = detach; 0107 if (m_d->frame) { 0108 bool wasVisible = isPopupWidgetVisible(); 0109 m_d->frame->setDetached(detach); 0110 if (wasVisible) { 0111 // Setting the window flags closes the widget, so make it visible again. 0112 setPopupWidgetVisible(true); 0113 if (detach) { 0114 m_d->isDetachedGeometrySet = true; 0115 } 0116 adjustPosition(); 0117 } 0118 } 0119 } 0120 0121 void KisPopupButton::setPopupWidget(QWidget* widget) 0122 { 0123 if (widget) { 0124 delete m_d->frame; 0125 m_d->frame = new KisPopupButtonFrame(this->window(), m_d->isPopupDetached); 0126 m_d->frame->setProperty("_kis_excludeFromLayoutThumbnail", true); 0127 m_d->frame->setObjectName("popup frame"); 0128 m_d->frame->setWindowTitle(widget->windowTitle()); 0129 m_d->frameLayout = new QHBoxLayout(m_d->frame.data()); 0130 m_d->frameLayout->setMargin(0); 0131 m_d->frameLayout->setSizeConstraint(QLayout::SetFixedSize); 0132 m_d->frame->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); 0133 m_d->popupWidget = widget; 0134 m_d->popupWidget->setParent(m_d->frame.data()); 0135 m_d->frameLayout->addWidget(m_d->popupWidget); 0136 } 0137 } 0138 0139 void KisPopupButton::setPopupWidgetWidth(int w) 0140 { 0141 m_d->frame->resize(w, m_d->frame->height()); 0142 } 0143 0144 void KisPopupButton::showPopupWidget() 0145 { 0146 if (m_d->popupWidget && !m_d->frame->isVisible()) { 0147 setPopupWidgetVisible(true); 0148 adjustPosition(); 0149 } else { 0150 hidePopupWidget(); 0151 } 0152 } 0153 0154 void KisPopupButton::hidePopupWidget() 0155 { 0156 setPopupWidgetVisible(false); 0157 } 0158 0159 void KisPopupButton::setPopupWidgetVisible(bool visible) 0160 { 0161 if (m_d->popupWidget) { 0162 if (visible) { 0163 m_d->frame->raise(); 0164 m_d->frame->show(); 0165 m_d->frame->activateWindow(); 0166 } else { 0167 m_d->frame->setVisible(false); 0168 } 0169 } 0170 } 0171 0172 bool KisPopupButton::isPopupWidgetVisible() 0173 { 0174 return m_d->popupWidget && m_d->frame->isVisible(); 0175 } 0176 0177 void KisPopupButton::paintEvent ( QPaintEvent * event ) 0178 { 0179 QToolButton::paintEvent(event); 0180 if (m_d->arrowVisible) { 0181 paintPopupArrow(); 0182 } 0183 } 0184 0185 void KisPopupButton::paintPopupArrow() 0186 { 0187 QStylePainter p(this); 0188 QStyleOption option; 0189 option.rect = QRect(rect().right() - 15, rect().bottom() - 15, 14, 14); 0190 option.palette = palette(); 0191 option.palette.setBrush(QPalette::ButtonText, Qt::black); // Force color to black 0192 option.state = QStyle::State_Enabled; 0193 p.setBrush(Qt::black); // work around some theme that don't use QPalette::ButtonText like they should, but instead the QPainter brushes and pen 0194 p.setPen(Qt::black); 0195 p.drawPrimitive(QStyle::PE_IndicatorArrowDown, option); 0196 } 0197 0198 void KisPopupButton::adjustPosition() 0199 { 0200 // If popup is not detached, or if its detached geometry hasn't been set, 0201 // we first move the popup to the "current" screen. 0202 if (!m_d->isPopupDetached || !m_d->isDetachedGeometrySet) { 0203 QScreen *currentScreen = [this]() { 0204 QWindow *mainWinHandle = this->window()->windowHandle(); 0205 if (mainWinHandle) { 0206 return mainWinHandle->screen(); 0207 } 0208 return QApplication::primaryScreen(); 0209 }(); 0210 QWindow *winHandle = m_d->frame->windowHandle(); 0211 if (winHandle) { 0212 winHandle->setScreen(currentScreen); 0213 } 0214 } 0215 0216 QSize popSize = m_d->popupWidget->size(); 0217 QRect popupRect(this->mapToGlobal(QPoint(0, this->size().height())), popSize); 0218 0219 // Get the available geometry of the screen which contains the popup. 0220 QScreen *screen = [this]() { 0221 QWindow *winHandle = m_d->frame->windowHandle(); 0222 if (winHandle && winHandle->screen()) { 0223 return winHandle->screen(); 0224 } 0225 return QApplication::primaryScreen(); 0226 }(); 0227 QRect screenRect = screen->availableGeometry(); 0228 if (m_d->isPopupDetached) { 0229 if (m_d->isDetachedGeometrySet) { 0230 popupRect.moveTo(m_d->frame->geometry().topLeft()); 0231 } else { 0232 popupRect.moveTo(this->window()->geometry().center() - QRect(QPoint(0, 0), popSize).center()); 0233 m_d->isDetachedGeometrySet = true; 0234 } 0235 } 0236 popupRect = kisEnsureInRect(popupRect, screenRect); 0237 0238 m_d->frame->setGeometry(popupRect); 0239 } 0240 0241 void KisPopupButton::setArrowVisible (bool v) 0242 { 0243 if (v) { 0244 m_d->arrowVisible = true; 0245 } else { 0246 m_d->arrowVisible = false; 0247 } 0248 }