Krita Source Code Documentation
Loading...
Searching...
No Matches
KisPopupButton.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "KisPopupButton.h"
9
10#include <QPointer>
11#include <QApplication>
12#include <QFrame>
13#include <QHBoxLayout>
14#include <QKeyEvent>
15#include <QScreen>
16#include <QStyleOption>
17#include <QStylePainter>
18#include <QWindow>
19#include <QMenu>
20
21#include "kis_global.h"
22#include <kis_debug.h>
23
24
25class KisPopupButtonFrame : public QFrame
26{
27 QHBoxLayout* frameLayout {0};
28
29public:
30 KisPopupButtonFrame(QWidget *parent, bool detach, bool popupIsMenu)
31 : QFrame(parent)
32 {
33 setObjectName("KisPopupButtonFrame");
34 setProperty("_kis_excludeFromLayoutThumbnail", true);
35 frameLayout = new QHBoxLayout(this);
36 frameLayout->setContentsMargins(0, 0, 0, 0);
37
38 setDetached(detach, popupIsMenu);
39 }
40
41 void setDetached(bool detach, bool popupIsMenu)
42 {
43#if defined Q_OS_ANDROID || defined Q_OS_MACOS
44 // for some reason when calling destroy() the platform window isn't
45 // hidden first, this corrupts state of the window stack
46 hide();
47#endif
48
49 // Need to destroy the platform window before changing window flags
50 // so that Qt knows to actually apply the new flags...
51 // At least on Windows, not doing this may result in weird window drop
52 // shadows.
53 destroy();
54
55 if (detach) {
56 setWindowFlags(Qt::Dialog);
57 setFrameStyle(QFrame::NoFrame);
58 }
59 else {
60 setWindowFlags(Qt::Popup);
61 // Menus already have a frame, don't need another.
62 if (popupIsMenu) {
63 setFrameStyle(QFrame::NoFrame);
64 } else {
65 setFrameStyle(QFrame::Box | QFrame::Plain);
66 }
67 }
68
69 updateGeometry();
70 }
71
72protected:
73 void keyPressEvent(QKeyEvent *event) override
74 {
75 if (event->matches(QKeySequence::Cancel)) {
76 event->accept();
77 hide();
78 } else {
79 QFrame::keyPressEvent(event);
80 }
81 }
82
83 bool event(QEvent *e) override
84 {
85 if (e->type() == QEvent::Close) {
86 e->ignore();
87 hide();
88 return true;
89 }
90 return QFrame::event(e);
91 }
92};
93
94
97 // : frameLayout(nullptr)
98 {}
101 bool arrowVisible { true };
102 bool isPopupDetached { false };
103 bool isDetachedGeometrySet { false };
104};
105
107 : QToolButton(parent)
108 , m_d(new Private)
109{
110 setObjectName("KisPopupButton");
111 connect(this, SIGNAL(released()), SLOT(showPopupWidget()));
112}
113
115{
116 delete m_d->frame;
117 delete m_d;
118}
119
121{
122 m_d->isPopupDetached = detach;
123 if (m_d->frame) {
124 bool wasVisible = isPopupWidgetVisible();
125 m_d->frame->setDetached(detach, qobject_cast<QMenu *>(m_d->popupWidget));
126 if (wasVisible) {
127 // Setting the window flags closes the widget, so make it visible again.
129 if (detach) {
131 }
133 }
134 }
135}
136
138{
139 if (widget) {
140 delete m_d->frame;
141
142 // Bit wonky to assign a popup menu to a popup widget, so they need
143 // extra coddling to make them work properly.
144 QMenu *menu = qobject_cast<QMenu *>(widget);
145 m_d->frame = new KisPopupButtonFrame(this->window(), m_d->isPopupDetached, menu);
146 m_d->frame->setWindowTitle(widget->windowTitle());
147
148 m_d->popupWidget = widget;
149
150 m_d->frame->layout()->addWidget(m_d->popupWidget);
151
152 if (menu) {
153 // The menu may decide to hide itself in response to user input.
154 // Compensate for that by catching the situation where we'd hide
155 // the menu while the frame is still up and show it again.
156 connect(menu, &QMenu::aboutToHide, this, &KisPopupButton::slotMenuAboutToHide);
157 // If the menu still hides itself somehow, make it show up again.
158 connect(this, &KisPopupButton::sigPopupWidgetShown, menu, &QMenu::show);
159 }
160 }
161}
162
164{
165 m_d->frame->resize(w, m_d->frame->height());
166}
167
169{
170 if (m_d->popupWidget && !m_d->frame->isVisible()) {
172 if (!m_d->isPopupDetached) {
174 }
175 } else {
177 }
178}
179
184
186{
187 if (m_d->popupWidget) {
188 if (visible) {
189 if (m_d->isPopupDetached) {
190 // Force layout sizing before positioning
191 m_d->popupWidget->adjustSize();
192 m_d->frame->adjustSize();
194 }
195 m_d->frame->raise();
196 m_d->frame->show();
197 m_d->frame->activateWindow();
198 Q_EMIT sigPopupWidgetShown();
199 } else {
200 m_d->frame->setVisible(false);
201 }
202 }
203}
204
206{
207 return m_d->popupWidget && m_d->frame->isVisible();
208}
209
210void KisPopupButton::paintEvent ( QPaintEvent * event )
211{
212 QToolButton::paintEvent(event);
213 if (m_d->arrowVisible) {
215 }
216}
217
219{
220 QStylePainter p(this);
221 QStyleOption option;
222 option.rect = QRect(rect().right() - 15, rect().bottom() - 15, 14, 14);
223 option.palette = palette();
224 option.palette.setBrush(QPalette::ButtonText, Qt::black); // Force color to black
225 option.state = QStyle::State_Enabled;
226 p.setBrush(Qt::black); // work around some theme that don't use QPalette::ButtonText like they should, but instead the QPainter brushes and pen
227 p.setPen(Qt::black);
228 p.drawPrimitive(QStyle::PE_IndicatorArrowDown, option);
229}
230
232{
233 QScreen *currentScreen = nullptr;
234 auto getCurrentScreen = [this, &currentScreen] {
235 if (!currentScreen) {
236 QWindow *mainWinHandle = window()->windowHandle();
237 if (mainWinHandle) {
238 currentScreen = mainWinHandle->screen();
239 } else {
240 currentScreen = QApplication::primaryScreen();
241 }
242 }
243 return currentScreen;
244 };
245
246 // If popup is not detached, or if its detached geometry hasn't been set,
247 // we first move the popup to the "current" screen.
249 QWindow *winHandle = m_d->frame->windowHandle();
250 if (winHandle) {
251 winHandle->setScreen(getCurrentScreen());
252 }
253 }
254
255 // Attach to the button if it's visible, else attach to the cursor.
256 QPoint pos = this->isVisible() ? this->mapToGlobal(QPoint(0, this->size().height())) : QCursor().pos();
257 QSize popSize = m_d->popupWidget->size();
258 QRect popupRect(pos, popSize);
259
260 // Get the available geometry of the screen which contains the popup.
261 QScreen *screen = [this, &getCurrentScreen]() {
262 QWindow *winHandle = m_d->frame->windowHandle();
263 if (winHandle && winHandle->screen()) {
264 return winHandle->screen();
265 }
266 return getCurrentScreen();
267 }();
268 QRect screenRect = screen->availableGeometry();
269 if (m_d->isPopupDetached) {
271 popupRect.moveTo(m_d->frame->geometry().topLeft());
272 } else {
273 popupRect.moveTo(this->window()->geometry().center() - QRect(QPoint(0, 0), popSize).center());
275 }
276 }
277 popupRect = kisEnsureInRect(popupRect, screenRect);
278
279 m_d->frame->setGeometry(popupRect);
280}
281
283{
284 if (v) {
285 m_d->arrowVisible = true;
286 } else {
287 m_d->arrowVisible = false;
288 }
289}
290
292{
293 // Menus will hide themselves when clicking on one of their separators,
294 // which just ends up with a blank popup frame. Fight back against this by
295 // showing the menu again if the frame is still up when the menu disappears.
296 if (m_d->popupWidget && m_d->frame->isVisible()) {
297 m_d->popupWidget->show();
298 }
299}
const Params2D p
qreal v
bool event(QEvent *e) override
void setDetached(bool detach, bool popupIsMenu)
QHBoxLayout * frameLayout
void keyPressEvent(QKeyEvent *event) override
KisPopupButtonFrame(QWidget *parent, bool detach, bool popupIsMenu)
void setPopupWidgetVisible(bool visible)
void setPopupWidgetWidth(int w)
void setPopupWidgetDetached(bool detach)
void setPopupWidget(QWidget *widget)
~KisPopupButton() override
Private *const m_d
void sigPopupWidgetShown()
void setArrowVisible(bool v)
void paintEvent(QPaintEvent *event) override
KisPopupButton(QWidget *parent)
void adjustPosition()
adjustPosition adjusts the position of the popup widget based on the position of this button and the ...
QRect kisEnsureInRect(QRect rc, const QRect &bounds)
Definition kis_global.h:291
rgba palette[MAX_PALETTE]
Definition palette.c:35
QPointer< QWidget > popupWidget
QPointer< KisPopupButtonFrame > frame