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 if (placement.parentAnchor() == Qt::TopEdge || placement.parentAnchor() == Qt::BottomEdge) {
197 screenArea.setRight(w->transientParent()->geometry().right());
198 screenArea.setLeft(w->transientParent()->geometry().left());
199 } else {
200 screenArea.setTop(w->transientParent()->geometry().top());
201 screenArea.setBottom(w->transientParent()->geometry().bottom());
202 }
203 }
204
205 QVariant restrictedPopupGeometry = w->property("restrictedPopupGeometry");
206 if (restrictedPopupGeometry.canConvert<QRect>()) {
207 screenArea = restrictedPopupGeometry.toRect();
208 }
209
210 auto inScreenArea = [screenArea](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
211 if (edges & Qt::LeftEdge && target.left() < screenArea.left()) {
212 return false;
213 }
214 if (edges & Qt::TopEdge && target.top() < screenArea.top()) {
215 return false;
216 }
217 if (edges & Qt::RightEdge && target.right() > screenArea.right()) {
218 return false;
219 }
220 if (edges & Qt::BottomEdge && target.bottom() > screenArea.bottom()) {
221 return false;
222 }
223 return true;
224 };
225
226 // if that fits, we don't need to do anything
227 if (inScreenArea(popupRect)) {
228 return popupRect.marginsRemoved(margin);
229 }
230 // Otherwise,
231 if (placement.flipConstraintAdjustments() & Qt::Horizontal) {
232 if (!inScreenArea(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
233 // flip both edges (if either bit is set, XOR both)
234 auto flippedParentAnchor = placement.parentAnchor();
235 if (flippedParentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
236 flippedParentAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
237 }
238 auto flippedPopupAnchor = placement.popupAnchor();
239 if (flippedPopupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
240 flippedPopupAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
241 }
242 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
243 // if it still doesn't fit we should continue with the unflipped version
244 if (inScreenArea(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
245 popupRect.moveLeft(flippedPopupRect.left());
246 }
247 }
248 }
249 if (placement.slideConstraintAdjustments() & Qt::Horizontal) {
250 if (!inScreenArea(popupRect, Qt::LeftEdge)) {
251 popupRect.moveLeft(screenArea.left());
252 }
253 if (!inScreenArea(popupRect, Qt::RightEdge)) {
254 popupRect.moveRight(screenArea.right());
255 }
256 }
257 if (placement.flipConstraintAdjustments() & Qt::Vertical) {
258 if (!inScreenArea(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
259 // flip both edges (if either bit is set, XOR both)
260 auto flippedParentAnchor = placement.parentAnchor();
261 if (flippedParentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
262 flippedParentAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
263 }
264 auto flippedPopupAnchor = placement.popupAnchor();
265 if (flippedPopupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
266 flippedPopupAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
267 }
268 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
269 // if it still doesn't fit we should continue with the unflipped version
270 if (inScreenArea(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
271 popupRect.moveTop(flippedPopupRect.top());
272 }
273 }
274 }
275 if (placement.slideConstraintAdjustments() & Qt::Vertical) {
276 if (!inScreenArea(popupRect, Qt::TopEdge)) {
277 popupRect.moveTop(screenArea.top());
278 }
279 if (!inScreenArea(popupRect, Qt::BottomEdge)) {
280 popupRect.moveBottom(screenArea.bottom());
281 }
282 }
283 return popupRect.marginsRemoved(margin);
284}
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 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.