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) {
198 if (popupRect.width() > parentRect.width()) {
199 parentRect.moveLeft(parentRect.center().x() - popupRect.width() / 2);
200 parentRect.setWidth(popupRect.width());
201 }
202 if (parentRect.left() < screenArea.left()) {
203 parentRect.moveLeft(screenArea.left());
204 }
205 if (parentRect.right() > screenArea.right()) {
206 parentRect.moveRight(screenArea.right());
207 }
208 parentRect.setHeight(screenArea.height());
209 parentRect.moveTop(screenArea.top());
210 } else {
211 if (popupRect.height() > parentRect.height()) {
212 parentRect.moveTop(parentRect.center().y() - popupRect.height() / 2);
213 parentRect.setHeight(popupRect.height());
214 }
215 if (parentRect.top() < screenArea.top()) {
216 parentRect.moveTop(screenArea.top());
217 }
218 if (parentRect.bottom() > screenArea.bottom()) {
219 parentRect.moveBottom(screenArea.bottom());
220 }
221 parentRect.setWidth(screenArea.width());
222 parentRect.moveLeft(screenArea.left());
223 }
224 screenArea = parentRect;
225 }
226
227 QVariant restrictedPopupGeometry = w->property("restrictedPopupGeometry");
228 if (restrictedPopupGeometry.canConvert<QRect>()) {
229 screenArea = restrictedPopupGeometry.toRect();
230 }
231
232 auto inScreenArea = [screenArea](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
233 if (edges & Qt::LeftEdge && target.left() < screenArea.left()) {
234 return false;
235 }
236 if (edges & Qt::TopEdge && target.top() < screenArea.top()) {
237 return false;
238 }
239 if (edges & Qt::RightEdge && target.right() > screenArea.right()) {
240 return false;
241 }
242 if (edges & Qt::BottomEdge && target.bottom() > screenArea.bottom()) {
243 return false;
244 }
245 return true;
246 };
247
248 // if that fits, we don't need to do anything
249 if (inScreenArea(popupRect)) {
250 return popupRect.marginsRemoved(margin);
251 }
252 // Otherwise,
253 if (placement.flipConstraintAdjustments() & Qt::Horizontal) {
254 if (!inScreenArea(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
255 // flip both edges (if either bit is set, XOR both)
256 auto flippedParentAnchor = placement.parentAnchor();
257 if (flippedParentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
258 flippedParentAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
259 }
260 auto flippedPopupAnchor = placement.popupAnchor();
261 if (flippedPopupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
262 flippedPopupAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
263 }
264 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
265 // if it still doesn't fit we should continue with the unflipped version
266 if (inScreenArea(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
267 popupRect.moveLeft(flippedPopupRect.left());
268 }
269 }
270 }
271 if (placement.slideConstraintAdjustments() & Qt::Horizontal) {
272 if (!inScreenArea(popupRect, Qt::LeftEdge)) {
273 popupRect.moveLeft(screenArea.left());
274 }
275 if (!inScreenArea(popupRect, Qt::RightEdge)) {
276 popupRect.moveRight(screenArea.right());
277 }
278 }
279 if (placement.flipConstraintAdjustments() & Qt::Vertical) {
280 if (!inScreenArea(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
281 // flip both edges (if either bit is set, XOR both)
282 auto flippedParentAnchor = placement.parentAnchor();
283 if (flippedParentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
284 flippedParentAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
285 }
286 auto flippedPopupAnchor = placement.popupAnchor();
287 if (flippedPopupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
288 flippedPopupAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
289 }
290 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
291 // if it still doesn't fit we should continue with the unflipped version
292 if (inScreenArea(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
293 popupRect.moveTop(flippedPopupRect.top());
294 }
295 }
296 }
297 if (placement.slideConstraintAdjustments() & Qt::Vertical) {
298 if (!inScreenArea(popupRect, Qt::TopEdge)) {
299 popupRect.moveTop(screenArea.top());
300 }
301 if (!inScreenArea(popupRect, Qt::BottomEdge)) {
302 popupRect.moveBottom(screenArea.bottom());
303 }
304 }
305 return popupRect.marginsRemoved(margin);
306}
QVariant property(const char *name) const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
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 setHeight(int height)
void setWidth(int width)
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-2025 The KDE developers.
Generated on Fri Apr 11 2025 11:56:56 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.