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();
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};
48
49PopupPlasmaWindowPrivate::PopupPlasmaWindowPrivate(PopupPlasmaWindow *_q)
50 : q(_q)
51{
52}
53
54/**
55 * PopupPlasmaWindowPrivate::updateSlideEffect
56 * @param anchorRect - the rect around where the popup should be placed relative to the parent window
57 * @param relativePopupPosition - the final rect of the popup relative to the parent window
58 *
59 * This is based purely on position in prepartion for being called in a wayland configure event
60 */
61void PopupPlasmaWindowPrivate::updateEffectivePopupDirection(const QRect &anchorRect, const QRect &relativePopupPosition)
62{
63 Qt::Edge effectivePopupDirection = Qt::TopEdge;
64 if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
65 if (relativePopupPosition.center().y() >= anchorRect.center().y()) {
66 effectivePopupDirection = Qt::BottomEdge;
67 } else {
68 effectivePopupDirection = Qt::TopEdge;
69 }
70 }
71 if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
72 if (relativePopupPosition.center().x() >= anchorRect.center().x()) {
73 effectivePopupDirection = Qt::RightEdge;
74 } else {
75 effectivePopupDirection = Qt::LeftEdge;
76 }
77 }
78
79 if (effectivePopupDirection != m_effectivePopupDirection) {
80 Q_EMIT q->effectivePopupDirectionChanged();
81 m_effectivePopupDirection = effectivePopupDirection;
82 }
83}
84
85void PopupPlasmaWindowPrivate::updateSlideEffect()
86{
87 KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge;
88 if (!m_animated) {
89 KWindowEffects::slideWindow(q, slideLocation);
90 return;
91 }
92 switch (m_effectivePopupDirection) {
93 case Qt::TopEdge:
94 slideLocation = KWindowEffects::BottomEdge;
95 break;
96 case Qt::BottomEdge:
97 slideLocation = KWindowEffects::TopEdge;
98 break;
99 case Qt::LeftEdge:
100 slideLocation = KWindowEffects::RightEdge;
101 break;
102 case Qt::RightEdge:
103 slideLocation = KWindowEffects::LeftEdge;
104 break;
105 }
106 KWindowEffects::slideWindow(q, slideLocation);
107}
108
109void PopupPlasmaWindowPrivate::updatePosition()
110{
111 m_needsReposition = false;
112
113 if (!m_visualParent || !m_visualParent->window()) {
114 qCWarning(LOG_PLASMAQUICK) << "Exposed with no visual parent. Window positioning broken.";
115 return;
116 }
117 q->setTransientParent(m_visualParent->window());
118 TransientPlacementHint placementHint;
119 QRectF parentAnchorRect = QRectF(m_visualParent->mapToScene(QPointF(0, 0)), m_visualParent->size());
120
121 if (!m_floating) {
122 QRect windowVisibleRect = m_visualParent->window()->mask().boundingRect();
123 // pad parentAnchorRect to the window it's in, so that the popup appears outside the panel
124 // even if the tooltip area does not fill it
125 if (m_popupDirection == Qt::TopEdge || m_popupDirection == Qt::BottomEdge) {
126 parentAnchorRect.setTop(windowVisibleRect.top());
127 // QRect::bottom() is off by one
128 parentAnchorRect.setBottom(windowVisibleRect.bottom() + 1);
129 }
130 if (m_popupDirection == Qt::LeftEdge || m_popupDirection == Qt::RightEdge) {
131 parentAnchorRect.setLeft(windowVisibleRect.left());
132 // QRect::right() is off by one
133 parentAnchorRect.setRight(windowVisibleRect.right() + 1);
134 }
135 }
136
137 placementHint.setParentAnchorArea(parentAnchorRect.toRect());
138 placementHint.setParentAnchor(m_popupDirection);
139 placementHint.setPopupAnchor(PlasmaQuickPrivate::oppositeEdge(m_popupDirection));
140 placementHint.setConstrainByAnchorWindow(true);
141 placementHint.setFlipConstraintAdjustments(m_floating ? Qt::Vertical : Qt::Orientations());
142 placementHint.setMargin(m_margin);
143
144 const QRect popupPosition = TransientPlacementHelper::popupRect(q, placementHint);
145
146 QRect relativePopupPosition = popupPosition;
147 if (m_visualParent->window()) {
148 relativePopupPosition = relativePopupPosition.translated(-m_visualParent->window()->position());
149 }
150 updateEffectivePopupDirection(parentAnchorRect.toRect(), relativePopupPosition);
151 updateSlideEffect();
152
154 updatePositionX11(popupPosition.topLeft());
156 updatePositionWayland(popupPosition.topLeft());
157 }
158
159 updateBorders(popupPosition);
160}
161
162void PopupPlasmaWindowPrivate::updatePositionX11(const QPoint &position)
163{
164 q->setPosition(position);
165}
166
167void PopupPlasmaWindowPrivate::updatePositionWayland(const QPoint &position)
168{
169 // still update's Qt internal reference as it's used by the next dialog
170 // this can be dropped when we're using true semantic positioning in the backend
171 q->setPosition(position);
172
173 PlasmaShellWaylandIntegration::get(q)->setPosition(position);
174}
175
176void PopupPlasmaWindowPrivate::updateBorders(const QRect &globalPosition)
177{
178 // disables borders for the edges that are touching the screen edge
179
180 QScreen *screen = QGuiApplication::screenAt(globalPosition.center());
181 if (!screen) {
182 return;
183 }
184 const QRect screenGeometry = screen->geometry();
185
187
188 if (m_margin) {
189 q->setBorders(enabledBorders);
190 return;
191 }
192
193 if (m_removeBorderStrategy & PopupPlasmaWindow::AtScreenEdges) {
194 if (globalPosition.top() <= screenGeometry.top()) {
195 enabledBorders.setFlag(Qt::TopEdge, false);
196 }
197 if (globalPosition.bottom() >= screenGeometry.bottom()) {
198 enabledBorders.setFlag(Qt::BottomEdge, false);
199 }
200 if (globalPosition.left() <= screenGeometry.left()) {
201 enabledBorders.setFlag(Qt::LeftEdge, false);
202 }
203 if (globalPosition.right() >= screenGeometry.right()) {
204 enabledBorders.setFlag(Qt::RightEdge, false);
205 }
206 }
207 if (m_removeBorderStrategy & PopupPlasmaWindow::AtPanelEdges) {
208 switch (m_popupDirection) {
209 case Qt::LeftEdge:
210 enabledBorders.setFlag(Qt::RightEdge, false);
211 break;
212 case Qt::RightEdge:
213 enabledBorders.setFlag(Qt::LeftEdge, false);
214 break;
215 case Qt::BottomEdge:
216 enabledBorders.setFlag(Qt::TopEdge, false);
217 break;
218 case Qt::TopEdge:
219 default:
220 enabledBorders.setFlag(Qt::BottomEdge, false);
221 break;
222 }
223 }
224 q->setBorders(enabledBorders);
225}
226
227void PopupPlasmaWindowPrivate::updateVisualParentWindow()
228{
229 if (m_visualParentWindow) {
230 QObject::disconnect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
231 QObject::disconnect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
232 }
233
234 m_visualParentWindow = m_visualParent ? m_visualParent->window() : nullptr;
235
236 if (m_visualParentWindow) {
237 QObject::connect(m_visualParentWindow, &QQuickWindow::yChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
238 QObject::connect(m_visualParentWindow, &QQuickWindow::xChanged, q, &PopupPlasmaWindow::queuePositionUpdate);
239 }
240}
241
242PopupPlasmaWindow::PopupPlasmaWindow(const QString &svgPrefix)
243 : PlasmaWindow(svgPrefix)
244 , d(new PopupPlasmaWindowPrivate(this))
245{
246}
247
248PopupPlasmaWindow::~PopupPlasmaWindow()
249{
250}
251
252void PopupPlasmaWindow::setVisualParent(QQuickItem *item)
253{
254 if (item == d->m_visualParent) {
255 return;
256 }
257
258 if (d->m_visualParent) {
259 disconnect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
260 }
261
262 d->m_visualParent = item;
263 d->updateVisualParentWindow();
264
265 if (d->m_visualParent) {
266 connect(d->m_visualParent, SIGNAL(windowChanged(QQuickWindow *)), this, SLOT(updateVisualParentWindow()));
267 }
268
269 Q_EMIT visualParentChanged();
270 queuePositionUpdate();
271}
272
273QQuickItem *PopupPlasmaWindow::visualParent() const
274{
275 return d->m_visualParent;
276}
277
278Qt::Edge PopupPlasmaWindow::popupDirection() const
279{
280 return d->m_popupDirection;
281}
282
283void PopupPlasmaWindow::setPopupDirection(Qt::Edge popupDirection)
284{
285 if (popupDirection == d->m_popupDirection) {
286 return;
287 }
288 d->m_popupDirection = popupDirection;
289
290 if (isExposed()) {
291 qCWarning(LOG_PLASMAQUICK) << "location should be set before showing popup window";
292 }
293 queuePositionUpdate();
294
295 Q_EMIT popupDirectionChanged();
296}
297
298Qt::Edge PopupPlasmaWindow::effectivePopupDirection() const
299{
300 return d->m_effectivePopupDirection;
301}
302
303bool PopupPlasmaWindow::floating() const
304{
305 return d->m_floating;
306}
307
308void PopupPlasmaWindow::setFloating(bool floating)
309{
310 if (floating == d->m_floating) {
311 return;
312 }
313 d->m_floating = floating;
314 queuePositionUpdate();
315 Q_EMIT floatingChanged();
316}
317
318bool PopupPlasmaWindow::animated() const
319{
320 return d->m_animated;
321}
322
323void PopupPlasmaWindow::setAnimated(bool animated)
324{
325 d->m_animated = animated;
326 queuePositionUpdate();
327 Q_EMIT animatedChanged();
328}
329
330PopupPlasmaWindow::RemoveBorders PopupPlasmaWindow::removeBorderStrategy() const
331{
332 return d->m_removeBorderStrategy;
333}
334
335void PopupPlasmaWindow::setRemoveBorderStrategy(PopupPlasmaWindow::RemoveBorders strategy)
336{
337 if (d->m_removeBorderStrategy == strategy) {
338 return;
339 }
340
341 d->m_removeBorderStrategy = strategy;
342 queuePositionUpdate(); // This will update borders as well
343 Q_EMIT removeBorderStrategyChanged();
344}
345
346int PopupPlasmaWindow::margin() const
347{
348 return d->m_margin;
349}
350
351void PopupPlasmaWindow::setMargin(int margin)
352{
353 if (d->m_margin == margin) {
354 return;
355 }
356
357 d->m_margin = margin;
358 queuePositionUpdate();
359 Q_EMIT marginChanged();
360}
361
362bool PopupPlasmaWindow::event(QEvent *event)
363{
364 switch (event->type()) {
366 if (d->m_needsReposition) {
367 d->updatePosition();
368 }
369 break;
370 case QEvent::Show:
371 d->updatePosition();
372 break;
373 case QEvent::Resize:
374 d->updatePosition();
375 break;
376 default:
377 break;
378 }
380}
381
382void PopupPlasmaWindow::queuePositionUpdate()
383{
384 d->m_needsReposition = true;
385}
386}
387
388#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-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:09:37 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.