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 }