Libplasma

transientplacementhint.cpp
1/*
2 SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "transientplacementhint_p.h"
7#include <QSharedData>
8
9#include <QDebug>
10#include <QGuiApplication>
11#include <QScreen>
12#include <QWindow>
13
14// This class is proposed for Qt6.something, but it's not there yet.
15// keep as an implementation detail, and then drop eventually (famous last words)
16
17class TransientPlacementHintPrivate : public QSharedData
18{
19public:
20 QRect parentAnchorRect;
21 bool constrainByAnchorWindow;
22 Qt::Edges parentAnchor = Qt::BottomEdge | Qt::RightEdge;
23 Qt::Edges popupAnchor = Qt::TopEdge | Qt::LeftEdge;
24 Qt::Orientations slideConstraintAdjustments = Qt::Horizontal | Qt::Vertical;
25 Qt::Orientations flipConstraintAdjustments;
26 int margin = 0;
27};
28/*!
29 * Constructs a new QTransientPlacementHint
30 */
31TransientPlacementHint::TransientPlacementHint()
32 : d(new TransientPlacementHintPrivate)
33{
34}
35
36TransientPlacementHint::~TransientPlacementHint()
37{
38}
39
40TransientPlacementHint::TransientPlacementHint(const TransientPlacementHint &other)
41{
42 d = other.d;
43}
44TransientPlacementHint &TransientPlacementHint::operator=(const TransientPlacementHint &other)
45{
46 d = other.d;
47 return *this;
48}
49
50bool TransientPlacementHint::isValid() const
51{
52 return d->parentAnchorRect.isValid();
53}
54
55void TransientPlacementHint::setParentAnchorArea(const QRect &parentAnchorRect)
56{
57 d->parentAnchorRect = parentAnchorRect;
58}
59
60QRect TransientPlacementHint::parentAnchorArea() const
61{
62 return d->parentAnchorRect;
63}
64
65void TransientPlacementHint::setParentAnchor(Qt::Edges parentAnchor)
66{
67 d->parentAnchor = parentAnchor;
68}
69
70Qt::Edges TransientPlacementHint::parentAnchor() const
71{
72 return d->parentAnchor;
73}
74
75void TransientPlacementHint::setPopupAnchor(Qt::Edges popupAnchor)
76{
77 d->popupAnchor = popupAnchor;
78}
79
80Qt::Edges TransientPlacementHint::popupAnchor() const
81{
82 return d->popupAnchor;
83}
84
85void TransientPlacementHint::setConstrainByAnchorWindow(bool constrainByAnchorWindow)
86{
87 d->constrainByAnchorWindow = constrainByAnchorWindow;
88}
89
90bool TransientPlacementHint::constrainByAnchorWindow() const
91{
92 return d->constrainByAnchorWindow;
93}
94
95void TransientPlacementHint::setSlideConstraintAdjustments(Qt::Orientations slideConstraintAdjustments)
96{
97 d->slideConstraintAdjustments = slideConstraintAdjustments;
98}
99
100Qt::Orientations TransientPlacementHint::slideConstraintAdjustments() const
101{
102 return d->slideConstraintAdjustments;
103}
104
105void TransientPlacementHint::setFlipConstraintAdjustments(Qt::Orientations flipConstraintAdjustments)
106{
107 d->flipConstraintAdjustments = flipConstraintAdjustments;
108}
109
110Qt::Orientations TransientPlacementHint::flipConstraintAdjustments() const
111{
112 return d->flipConstraintAdjustments;
113}
114
115int TransientPlacementHint::margin() const
116{
117 return d->margin;
118}
119
120void TransientPlacementHint::setMargin(int margin)
121{
122 d->margin = margin;
123}
124
125static QPoint popupPosition(const QRect &anchorRect, const Qt::Edges parentAnchor, const Qt::Edges popupAnchor, const QSize &popupSize)
126{
127 QPoint anchorPoint;
128 switch (parentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
129 case Qt::LeftEdge:
130 anchorPoint.setX(anchorRect.x());
131 break;
132 case Qt::RightEdge:
133 anchorPoint.setX(anchorRect.x() + anchorRect.width());
134 break;
135 default:
136 anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0));
137 }
138 switch (parentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
139 case Qt::TopEdge:
140 anchorPoint.setY(anchorRect.y());
141 break;
142 case Qt::BottomEdge:
143 anchorPoint.setY(anchorRect.y() + anchorRect.height());
144 break;
145 default:
146 anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0));
147 }
148 // calculate where the top left point of the popup will end up with the applied popup anchor
149 QPoint popupPosAdjust;
150 switch (popupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
151 case Qt::LeftEdge:
152 popupPosAdjust.setX(0);
153 break;
154 case Qt::RightEdge:
155 popupPosAdjust.setX(-popupSize.width());
156 break;
157 default:
158 popupPosAdjust.setX(qRound(-popupSize.width() / 2.0));
159 }
160 switch (popupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
161 case Qt::TopEdge:
162 popupPosAdjust.setY(0);
163 break;
164 case Qt::BottomEdge:
165 popupPosAdjust.setY(-popupSize.height());
166 break;
167 default:
168 popupPosAdjust.setY(qRound(-popupSize.height() / 2.0));
169 }
170 return anchorPoint + popupPosAdjust;
171}
172
173QRect TransientPlacementHelper::popupRect(QWindow *w, const TransientPlacementHint &placement)
174{
175 // We are not checking the placement being valid, as visual parents with size 0 is an
176 // allowed thing, also, every PlasmoidItem will initially be 0x0 when created
177 QScreen *screen = nullptr;
178 QRect globalParentAnchorRect = placement.parentAnchorArea();
179 if (w->transientParent()) {
180 globalParentAnchorRect = globalParentAnchorRect.translated(w->transientParent()->position());
181 screen = w->transientParent()->screen();
182 }
183
184 const QMargins margin(placement.margin(), placement.margin(), placement.margin(), placement.margin());
185 QSize paddedWindowSize = w->size().grownBy(margin);
186 QRect popupRect = QRect(popupPosition(globalParentAnchorRect, placement.parentAnchor(), placement.popupAnchor(), paddedWindowSize), paddedWindowSize);
187
188 if (!screen)
189 screen = qApp->screenAt(globalParentAnchorRect.center());
190 if (!screen)
191 screen = qApp->primaryScreen();
192
193 QRect screenArea = screen->geometry();
194
195 if (placement.constrainByAnchorWindow()) {
196 QRect parentRect = w->transientParent()->geometry();
197 if ((placement.parentAnchor() == Qt::TopEdge || placement.parentAnchor() == Qt::BottomEdge) && popupRect.width() <= parentRect.width()) {
198 screenArea.setRight(parentRect.right());
199 screenArea.setLeft(parentRect.left());
200 } else if (popupRect.height() <= parentRect.height()) {
201 screenArea.setTop(parentRect.top());
202 screenArea.setBottom(parentRect.bottom());
203 }
204 }
205
206 QVariant restrictedPopupGeometry = w->property("restrictedPopupGeometry");
207 if (restrictedPopupGeometry.canConvert<QRect>()) {
208 screenArea = restrictedPopupGeometry.toRect();
209 }
210
211 auto inScreenArea = [screenArea](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
212 if (edges & Qt::LeftEdge && target.left() < screenArea.left()) {
213 return false;
214 }
215 if (edges & Qt::TopEdge && target.top() < screenArea.top()) {
216 return false;
217 }
218 if (edges & Qt::RightEdge && target.right() > screenArea.right()) {
219 return false;
220 }
221 if (edges & Qt::BottomEdge && target.bottom() > screenArea.bottom()) {
222 return false;
223 }
224 return true;
225 };
226
227 // if that fits, we don't need to do anything
228 if (inScreenArea(popupRect)) {
229 return popupRect.marginsRemoved(margin);
230 }
231 // Otherwise,
232 if (placement.flipConstraintAdjustments() & Qt::Horizontal) {
233 if (!inScreenArea(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
234 // flip both edges (if either bit is set, XOR both)
235 auto flippedParentAnchor = placement.parentAnchor();
236 if (flippedParentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
237 flippedParentAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
238 }
239 auto flippedPopupAnchor = placement.popupAnchor();
240 if (flippedPopupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
241 flippedPopupAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
242 }
243 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
244 // if it still doesn't fit we should continue with the unflipped version
245 if (inScreenArea(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
246 popupRect.moveLeft(flippedPopupRect.left());
247 }
248 }
249 }
250 if (placement.slideConstraintAdjustments() & Qt::Horizontal) {
251 if (!inScreenArea(popupRect, Qt::LeftEdge)) {
252 popupRect.moveLeft(screenArea.left());
253 }
254 if (!inScreenArea(popupRect, Qt::RightEdge)) {
255 popupRect.moveRight(screenArea.right());
256 }
257 }
258 if (placement.flipConstraintAdjustments() & Qt::Vertical) {
259 if (!inScreenArea(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
260 // flip both edges (if either bit is set, XOR both)
261 auto flippedParentAnchor = placement.parentAnchor();
262 if (flippedParentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
263 flippedParentAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
264 }
265 auto flippedPopupAnchor = placement.popupAnchor();
266 if (flippedPopupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
267 flippedPopupAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
268 }
269 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
270 // if it still doesn't fit we should continue with the unflipped version
271 if (inScreenArea(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
272 popupRect.moveTop(flippedPopupRect.top());
273 }
274 }
275 }
276 if (placement.slideConstraintAdjustments() & Qt::Vertical) {
277 if (!inScreenArea(popupRect, Qt::TopEdge)) {
278 popupRect.moveTop(screenArea.top());
279 }
280 if (!inScreenArea(popupRect, Qt::BottomEdge)) {
281 popupRect.moveBottom(screenArea.bottom());
282 }
283 }
284 return popupRect.marginsRemoved(margin);
285}
QVariant property(const char *name) const const
void setX(int x)
void setY(int y)
int bottom() const const
QPoint center() const const
int height() const const
int left() const const
QRect marginsRemoved(const QMargins &margins) const const
void moveBottom(int y)
void moveLeft(int x)
void moveRight(int x)
void moveTop(int y)
int right() const const
void setBottom(int y)
void setLeft(int x)
void setRight(int x)
void setTop(int y)
int top() const const
QRect translated(const QPoint &offset) const const
int width() const const
int x() const const
int y() const const
QSize grownBy(QMargins margins) const const
int height() const const
int width() const const
typedef Edges
typedef Orientations
bool canConvert() const const
QRect toRect() const const
virtual QSize size() const const override
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.