Libplasma

popupplasmawindow.cpp
1/*
2 SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "popupplasmawindow.h"
7
8#include <kwindoweffects.h>
9#include <kwindowsystem.h>
10
11#include "debug_p.h"
12#include <QGuiApplication>
13#include <QScreen>
14#include <qnamespace.h>
15#include <qtmetamacros.h>
16
17#include "plasmashellwaylandintegration.h"
18#include "transientplacementhint_p.h"
19#include "utils.h"
20
21namespace PlasmaQuick
22{
23
24class PopupPlasmaWindowPrivate
25{
26public:
27 PopupPlasmaWindowPrivate(PopupPlasmaWindow *_q);
28
29 void updateEffectivePopupDirection(const QRect &anchorRect, const QRect &relativePopupPosition);
30 void updateSlideEffect(const QRect &globalPosition);
31 void updatePosition();
32 void updatePositionX11(const QPoint &position);
33 void updatePositionWayland(const QPoint &position);
34 void updateBorders(const QRect &globalPosition);
35 void updateVisualParentWindow();
36
37 PopupPlasmaWindow *q;
38 QPointer<QQuickItem> m_visualParent;
39 QPointer<QQuickWindow> m_visualParentWindow;
40 PopupPlasmaWindow::RemoveBorders m_removeBorderStrategy = PopupPlasmaWindow::Never;
41 bool m_needsReposition = false;
42 bool m_floating = false;
43 bool m_animated = false;
44 int m_margin = 0;
45 Qt::Edge m_popupDirection = Qt::TopEdge;
46 Qt::Edge m_effectivePopupDirection = Qt::TopEdge;
47 Qt::Edges m_nearbyBorders;
48};
49
50PopupPlasmaWindowPrivate::PopupPlasmaWindowPrivate(PopupPlasmaWindow *_q)
51 : q(_q)
52{
53}
54
55/**
56 * PopupPlasmaWindowPrivate::updateSlideEffect
57 * @param anchorRect - the rect around where the popup should be placed relative to the parent window
58 * @param relativePopupPosition - the final rect of the popup relative to the parent window
59 *
60 * This is based purely on position in prepartion for being called in a wayland configure event
61 */
62void PopupPlasmaWindowPrivate::updateEffectivePopupDirection(const QRect &anchorRect, const QRect &relativePopupPosition)
63{
64 Qt::Edge effectivePopupDirection = Qt::TopEdge;
65 if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
66 if (relativePopupPosition.center().y() >= anchorRect.center().y()) {
67 effectivePopupDirection = Qt::BottomEdge;
68 } else {
69 effectivePopupDirection = Qt::TopEdge;
70 }
71 }
72 if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
73 if (relativePopupPosition.center().x() >= anchorRect.center().x()) {
74 effectivePopupDirection = Qt::RightEdge;
75 } else {
76 effectivePopupDirection = Qt::LeftEdge;
77 }
78 }
79
80 if (effectivePopupDirection != m_effectivePopupDirection) {
81 Q_EMIT q->effectivePopupDirectionChanged();
82 m_effectivePopupDirection = effectivePopupDirection;
83 }
84}
85
86void PopupPlasmaWindowPrivate::updateSlideEffect(const QRect &globalPosition)
87{
88 KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
89
90 int slideOffset = -1;
91 QScreen *screen = QGuiApplication::screenAt(globalPosition.center());
92 if (screen && m_margin > 0) {
93 const QRect screenGeometry = screen->geometry();
94 switch (m_effectivePopupDirection) {
95 case Qt::TopEdge:
96 slideOffset = screenGeometry.bottom() - globalPosition.bottom() - m_margin;
97 break;
98 case Qt::BottomEdge:
99 slideOffset = globalPosition.top() - screenGeometry.top() - m_margin;
100 break;
101 case Qt::LeftEdge:
102 slideOffset = screenGeometry.right() - globalPosition.right() - m_margin;
103 break;
104 case Qt::RightEdge:
105 slideOffset = globalPosition.left() - screenGeometry.left() - m_margin;
106 break;
107 }
108 }
109
110 if (!m_animated) {
111 KWindowEffects::slideWindow(q, slideLocation);
112 return;
113 }
114 switch (m_effectivePopupDirection) {
115 case Qt::TopEdge:
116 slideLocation = KWindowEffects::BottomEdge;
117 break;
118 case Qt::BottomEdge:
119 slideLocation = KWindowEffects::TopEdge;
120 break;
121 case Qt::LeftEdge:
122 slideLocation = KWindowEffects::RightEdge;
123 break;
124 case Qt::RightEdge:
125 slideLocation = KWindowEffects::LeftEdge;
126 break;
127 }
128 KWindowEffects::slideWindow(q, slideLocation, slideOffset);
129}
130
131void PopupPlasmaWindowPrivate::updatePosition()
132{
133 m_needsReposition = false;
134
135 if (!m_visualParent || !m_visualParent->window()) {
136 qCWarning(LOG_PLASMAQUICK) << "Exposed with no visual parent. Window positioning broken.";
137 return;
138 }
139 q->setTransientParent(m_visualParent->window());
140 TransientPlacementHint placementHint;
141 QRectF parentAnchorRect = QRectF(m_visualParent->mapToScene(QPointF(0, 0)), m_visualParent->size());
142
143 if (!m_floating) {
144 QRect windowVisibleRect = m_visualParent->window()->mask().boundingRect();
145 // pad parentAnchorRect to the window it's in, so that the popup appears outside the panel
146 // even if the tooltip area does not fill it
147 if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
148 parentAnchorRect.setTop(windowVisibleRect.top());
149 // QRect::bottom() is off by one
150 parentAnchorRect.setBottom(windowVisibleRect.bottom() + 1);
151 }
152 if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
153 parentAnchorRect.setLeft(windowVisibleRect.left());
154 // QRect::right() is off by one
155 parentAnchorRect.setRight(windowVisibleRect.right() + 1);
156 }
157 }
158
159 placementHint.setParentAnchorArea(parentAnchorRect.toRect());
160 placementHint.setParentAnchor(m_popupDirection);
161 placementHint.setPopupAnchor(PlasmaQuickPrivate::oppositeEdge(m_popupDirection));
162 placementHint.setConstrainByAnchorWindow(true);
163 placementHint.setFlipConstraintAdjustments(m_floating ? Qt::Vertical : Qt::Orientations());
164 placementHint.setMargin(m_margin);
165
166 const QRect popupPosition = TransientPlacementHelper::popupRect(q, placementHint);
167
168 QRect relativePopupPosition = popupPosition;
169 if (m_visualParent->window()) {
170 relativePopupPosition = relativePopupPosition.translated(-m_visualParent->window()->position());
171 }
172 updateEffectivePopupDirection(parentAnchorRect.toRect(), relativePopupPosition);
173 updateSlideEffect(popupPosition);
174
176 updatePositionX11(popupPosition.topLeft());
178 updatePositionWayland(popupPosition.topLeft());
179 }
180
181 updateBorders(popupPosition);
182}
183
184void PopupPlasmaWindowPrivate::updatePositionX11(const QPoint &position)
185{
186 q->setPosition(position);
187}
188
189void PopupPlasmaWindowPrivate::updatePositionWayland(const QPoint &position)
190{
191 // still update's Qt internal reference as it's used by the next dialog
192 // this can be dropped when we're using true semantic positioning in the backend
193 q->setPosition(position);
194
195 PlasmaShellWaylandIntegration::get(q)->setPosition(position);
196}
197
198void PopupPlasmaWindowPrivate::updateBorders(const QRect &globalPosition)
199{
200 // disables borders for the edges that are touching the screen edge
201
202 QScreen *screen = QGuiApplication::screenAt(globalPosition.center());
203 if (!screen) {
204 return;
205 }
206 const QRect screenGeometry = screen->geometry();
207
209
210 if (m_removeBorderStrategy & PopupPlasmaWindow::AtScreenEdges) {
211 if (globalPosition.top() - m_margin <= screenGeometry.top()) {
212 enabledBorders.setFlag(Qt::TopEdge, false);
213 }
214 if (globalPosition.bottom() + m_margin >= screenGeometry.bottom()) {
215 enabledBorders.setFlag(Qt::BottomEdge, false);
216 }
217 if (globalPosition.left() - m_margin <= screenGeometry.left()) {
218 enabledBorders.setFlag(Qt::LeftEdge, false);
219 }
220 if (globalPosition.right() + m_margin >= screenGeometry.right()) {
221 enabledBorders.setFlag(Qt::RightEdge, false);
222 }
223 }
224 if (m_removeBorderStrategy & PopupPlasmaWindow::AtPanelEdges) {
225 switch (m_popupDirection) {
226 case Qt::LeftEdge:
227 enabledBorders.setFlag(Qt::RightEdge, false);
228 break;
229 case Qt::RightEdge:
230 enabledBorders.setFlag(Qt::LeftEdge, false);
231 break;
232 case Qt::BottomEdge:
233 enabledBorders.setFlag(Qt::TopEdge, false);
234 break;
235 case Qt::TopEdge:
236 default:
237 enabledBorders.setFlag(Qt::BottomEdge, false);
238 break;
239 }
240 }
241
242 Qt::Edges newNearbyBorders = ~enabledBorders;
243 if (newNearbyBorders.testFlag(Qt::LeftEdge) && newNearbyBorders.testFlag(Qt::RightEdge)) {
244 newNearbyBorders.setFlag(Qt::LeftEdge, false);
245 newNearbyBorders.setFlag(Qt::RightEdge, false);
246 }
247 if (newNearbyBorders.testFlag(Qt::TopEdge) && newNearbyBorders.testFlag(Qt::BottomEdge)) {
248 newNearbyBorders.setFlag(Qt::TopEdge, false);
249 newNearbyBorders.setFlag(Qt::BottomEdge, false);
250 }
251 newNearbyBorders.setFlag(PlasmaQuickPrivate::oppositeEdge(m_effectivePopupDirection), true);
252
253 if (newNearbyBorders != m_nearbyBorders) {
254 m_nearbyBorders = newNearbyBorders;
255 Q_EMIT q->nearbyBordersChanged();
256 }
257
258 if (m_margin) {
260 return;
261 }
262
263 q->setBorders(enabledBorders);
264}
265
266void PopupPlasmaWindowPrivate::updateVisualParentWindow()
267{
268 if (m_visualParentWindow) {
269 QObject::disconnect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
270 QObject::disconnect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
271 }
272
273 m_visualParentWindow = m_visualParent ? m_visualParent->window() : nullptr;
274
275 if (m_visualParentWindow) {
276 QObject::connect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
277 QObject::connect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
278 }
279}
280
281PopupPlasmaWindow::PopupPlasmaWindow(const QString &svgPrefix)
282 : PlasmaWindow(svgPrefix)
283 , d(new PopupPlasmaWindowPrivate(this))
284{
285}
286
287PopupPlasmaWindow::~PopupPlasmaWindow()
288{
289}
290
291void PopupPlasmaWindow::setVisualParent(QQuickItem *item)
292{
293 if (item == d->m_visualParent) {
294 return;
295 }
296
297 if (d->m_visualParent) {
298 disconnect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
299 }
300
301 d->m_visualParent = item;
302 d->updateVisualParentWindow();
303
304 if (d->m_visualParent) {
305 connect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
306 }
307
308 Q_EMIT visualParentChanged();
309 queuePositionUpdate();
310}
311
312QQuickItem *PopupPlasmaWindow::visualParent() const
313{
314 return d->m_visualParent;
315}
316
317Qt::Edge PopupPlasmaWindow::popupDirection() const
318{
319 return d->m_popupDirection;
320}
321
322void PopupPlasmaWindow::setPopupDirection(Qt::Edge popupDirection)
323{
324 if (popupDirection == d->m_popupDirection) {
325 return;
326 }
327 d->m_popupDirection = popupDirection;
328
329 if (isExposed()) {
330 qCWarning(LOG_PLASMAQUICK) << "location should be set before showing popup window";
331 }
332 queuePositionUpdate();
333
334 Q_EMIT popupDirectionChanged();
335}
336
337Qt::Edge PopupPlasmaWindow::effectivePopupDirection() const
338{
339 return d->m_effectivePopupDirection;
340}
341
342bool PopupPlasmaWindow::floating() const
343{
344 return d->m_floating;
345}
346
347void PopupPlasmaWindow::setFloating(bool floating)
348{
349 if (floating == d->m_floating) {
350 return;
351 }
352 d->m_floating = floating;
353 queuePositionUpdate();
354 Q_EMIT floatingChanged();
355}
356
357bool PopupPlasmaWindow::animated() const
358{
359 return d->m_animated;
360}
361
362void PopupPlasmaWindow::setAnimated(bool animated)
363{
364 d->m_animated = animated;
365 queuePositionUpdate();
366 Q_EMIT animatedChanged();
367}
368
369PopupPlasmaWindow::RemoveBorders PopupPlasmaWindow::removeBorderStrategy() const
370{
371 return d->m_removeBorderStrategy;
372}
373
374void PopupPlasmaWindow::setRemoveBorderStrategy(PopupPlasmaWindow::RemoveBorders strategy)
375{
376 if (d->m_removeBorderStrategy == strategy) {
377 return;
378 }
379
380 d->m_removeBorderStrategy = strategy;
381 queuePositionUpdate(); // This will update borders as well
382 Q_EMIT removeBorderStrategyChanged();
383}
384
385int PopupPlasmaWindow::margin() const
386{
387 return d->m_margin;
388}
389
390Qt::Edges PopupPlasmaWindow::nearbyBorders() const
391{
392 return d->m_nearbyBorders;
393}
394
395void PopupPlasmaWindow::setMargin(int margin)
396{
397 if (d->m_margin == margin) {
398 return;
399 }
400
401 d->m_margin = margin;
402 queuePositionUpdate();
403 Q_EMIT marginChanged();
404}
405
406bool PopupPlasmaWindow::event(QEvent *event)
407{
408 switch (event->type()) {
410 if (d->m_needsReposition) {
411 d->updatePosition();
412 }
413 break;
414 case QEvent::Show:
415 d->updatePosition();
416 break;
417 case QEvent::Resize:
418 d->updatePosition();
419 break;
420 default:
421 break;
422 }
424}
425
426void PopupPlasmaWindow::queuePositionUpdate()
427{
428 d->m_needsReposition = true;
429}
430}
431
432#include "moc_popupplasmawindow.cpp"
static bool isPlatformX11()
static bool isPlatformWayland()
static PlasmaShellWaylandIntegration * get(QWindow *window)
Returns the relevant PlasmaWaylandShellIntegration instance for this window creating one if needed.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void slideWindow(QWindow *window, SlideFromLocation location, int offset=-1)
The EdgeEventForwarder class This class forwards edge events to be replayed within the given margin T...
Definition action.h:20
QScreen * screenAt(const QPoint &point)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
int x() const const
int y() const const
virtual bool event(QEvent *event) override
int bottom() const const
QPoint center() const const
int left() const const
int right() const const
int top() const const
QPoint topLeft() const const
QRect translated(const QPoint &offset) const const
void setBottom(qreal y)
void setLeft(qreal x)
void setRight(qreal x)
void setTop(qreal y)
QRect toRect() const const
Vertical
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void xChanged(int arg)
void yChanged(int arg)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:34:35 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.