File indexing completed on 2024-05-19 04:29:53

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