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
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)
virtual bool event(QEvent *ev) override
void xChanged(int arg)
void yChanged(int arg)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 11:57:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.