Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qquickpopuppositioner.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickoverlay_p.h"
8#include "qquickpopup_p_p.h"
9
10#include <QtCore/qloggingcategory.h>
11#include <QtQml/qqmlinfo.h>
12#include <QtQuick/private/qquickitem_p.h>
13
15
16Q_LOGGING_CATEGORY(lcPopupPositioner, "qt.quick.controls.popuppositioner")
17
18static const QQuickItemPrivate::ChangeTypes AncestorChangeTypes = QQuickItemPrivate::Geometry
19 | QQuickItemPrivate::Parent
20 | QQuickItemPrivate::Children;
21
22static const QQuickItemPrivate::ChangeTypes ItemChangeTypes = QQuickItemPrivate::Geometry
23 | QQuickItemPrivate::Parent;
24
26 : m_popup(popup)
27{
28}
29
37
42
47
49{
50 if (m_parentItem == parent)
51 return;
52
53 if (m_parentItem) {
54 QQuickItemPrivate::get(m_parentItem)->removeItemChangeListener(this, ItemChangeTypes);
56 }
57
58 m_parentItem = parent;
59
60 if (!parent)
61 return;
62
63 QQuickItemPrivate::get(parent)->addItemChangeListener(this, ItemChangeTypes);
65 // Store the scale property so the end result of any transition that could effect the scale
66 // does not influence the top left of the final popup, so it doesn't appear to flip from one
67 // position to another as a result
68 m_popupScale = m_popup->popupItem()->scale();
69 if (m_popup->popupItem()->isVisible())
70 QQuickPopupPrivate::get(m_popup)->reposition();
71}
72
74{
75 QQuickItem *popupItem = m_popup->popupItem();
76 if (!popupItem->isVisible())
77 return;
78
79 if (m_positioning) {
80 popupItem->polish();
81 return;
82 }
83
84 qCDebug(lcPopupPositioner) << "reposition called for" << m_popup;
85
86 const qreal w = popupItem->width() * m_popupScale;
87 const qreal h = popupItem->height() * m_popupScale;
88 const qreal iw = popupItem->implicitWidth() * m_popupScale;
89 const qreal ih = popupItem->implicitHeight() * m_popupScale;
90
91 bool widthAdjusted = false;
92 bool heightAdjusted = false;
94
95 const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr;
96 const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(centerInParent);
97 QRectF rect(!centerInParent ? p->allowHorizontalMove ? p->x : popupItem->x() : 0,
98 !centerInParent ? p->allowVerticalMove ? p->y : popupItem->y() : 0,
99 !p->hasWidth && iw > 0 ? iw : w,
100 !p->hasHeight && ih > 0 ? ih : h);
101 bool relaxEdgeConstraint = p->relaxEdgeConstraint;
102 if (m_parentItem) {
103 // m_parentItem is the parent that the popup should open in,
104 // and popupItem()->parentItem() is the overlay, so the mapToItem() calls below
105 // effectively map the rect to scene coordinates.
106 if (centerInParent) {
107 if (centerInParent != parentItem() && !centerInOverlay) {
108 qmlWarning(m_popup) << "Popup can only be centered within its immediate parent or Overlay.overlay";
109 return;
110 }
111
112 if (centerInOverlay) {
113 rect.moveCenter(QPointF(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0)));
114 // Popup cannot be moved outside window bounds when its centered with overlay
115 relaxEdgeConstraint = false;
116 } else {
117 const QPointF parentItemCenter = QPointF(qRound(m_parentItem->width() / 2), qRound(m_parentItem->height() / 2));
118 rect.moveCenter(m_parentItem->mapToItem(popupItem->parentItem(), parentItemCenter));
119 }
120 } else {
121 rect.moveTopLeft(m_parentItem->mapToItem(popupItem->parentItem(), rect.topLeft()));
122 }
123
124 if (p->window) {
125 const QMarginsF margins = p->getMargins();
126 QRectF bounds(qMax<qreal>(0.0, margins.left()),
127 qMax<qreal>(0.0, margins.top()),
128 p->window->width() - qMax<qreal>(0.0, margins.left()) - qMax<qreal>(0.0, margins.right()),
129 p->window->height() - qMax<qreal>(0.0, margins.top()) - qMax<qreal>(0.0, margins.bottom()));
130
131 // if the popup doesn't fit horizontally inside the window, try flipping it around (left <-> right)
132 if (p->allowHorizontalFlip && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
133 const QPointF newTopLeft(m_parentItem->width() - p->x - rect.width(), p->y);
134 const QRectF flipped(m_parentItem->mapToItem(popupItem->parentItem(), newTopLeft),
135 rect.size());
136 if (flipped.intersected(bounds).width() > rect.intersected(bounds).width())
137 rect.moveLeft(flipped.left());
138 }
139
140 // if the popup doesn't fit vertically inside the window, try flipping it around (above <-> below)
141 if (p->allowVerticalFlip && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
142 const QPointF newTopLeft(p->x, m_parentItem->height() - p->y - rect.height());
143 const QRectF flipped(m_parentItem->mapToItem(popupItem->parentItem(), newTopLeft),
144 rect.size());
145 if (flipped.intersected(bounds).height() > rect.intersected(bounds).height())
146 rect.moveTop(flipped.top());
147 }
148
149 // push inside the margins if specified
150 if (p->allowVerticalMove) {
151 if (margins.top() >= 0 && rect.top() < bounds.top())
152 rect.moveTop(margins.top());
153 if (margins.bottom() >= 0 && rect.bottom() > bounds.bottom())
154 rect.moveBottom(bounds.bottom());
155 }
156 if (p->allowHorizontalMove) {
157 if (margins.left() >= 0 && rect.left() < bounds.left())
158 rect.moveLeft(margins.left());
159 if (margins.right() >= 0 && rect.right() > bounds.right())
160 rect.moveRight(bounds.right());
161 }
162
163 if (iw > 0 && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
164 // neither the flipped or pushed geometry fits inside the window, choose
165 // whichever side (left vs. right) fits larger part of the popup
166 if (p->allowHorizontalMove && p->allowHorizontalFlip) {
167 if (rect.left() < bounds.left() && bounds.left() + rect.width() <= bounds.right())
168 rect.moveLeft(bounds.left());
169 else if (rect.right() > bounds.right() && bounds.right() - rect.width() >= bounds.left())
170 rect.moveRight(bounds.right());
171 }
172
173 // as a last resort, adjust the width to fit the window
174 // Negative margins don't require resize as popup not pushed within
175 // the boundary. But otherwise, retain existing behavior of resizing
176 // for items, such as menus, which enables flip.
177 if (p->allowHorizontalResize) {
178 if ((margins.left() >= 0 || !relaxEdgeConstraint)
179 && (rect.left() < bounds.left())) {
180 rect.setLeft(bounds.left());
181 widthAdjusted = true;
182 }
183 if ((margins.right() >= 0 || !relaxEdgeConstraint)
184 && (rect.right() > bounds.right())) {
185 rect.setRight(bounds.right());
186 widthAdjusted = true;
187 }
188 }
189 } else if (iw > 0 && rect.left() >= bounds.left() && rect.right() <= bounds.right()
190 && iw != w) {
191 // restore original width
192 rect.setWidth(iw);
193 widthAdjusted = true;
194 }
195
196 if (ih > 0 && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
197 // neither the flipped or pushed geometry fits inside the window, choose
198 // whichever side (above vs. below) fits larger part of the popup
199 if (p->allowVerticalMove && p->allowVerticalFlip) {
200 if (rect.top() < bounds.top() && bounds.top() + rect.height() <= bounds.bottom())
201 rect.moveTop(bounds.top());
202 else if (rect.bottom() > bounds.bottom() && bounds.bottom() - rect.height() >= bounds.top())
203 rect.moveBottom(bounds.bottom());
204 }
205
206 // as a last resort, adjust the height to fit the window
207 // Negative margins don't require resize as popup not pushed within
208 // the boundary. But otherwise, retain existing behavior of resizing
209 // for items, such as menus, which enables flip.
210 if (p->allowVerticalResize) {
211 if ((margins.top() >= 0 || !relaxEdgeConstraint)
212 && (rect.top() < bounds.top())) {
213 rect.setTop(bounds.top());
214 heightAdjusted = true;
215 }
216 if ((margins.bottom() >= 0 || !relaxEdgeConstraint)
217 && (rect.bottom() > bounds.bottom())) {
218 rect.setBottom(bounds.bottom());
219 heightAdjusted = true;
220 }
221 }
222 } else if (ih > 0 && rect.top() >= bounds.top() && rect.bottom() <= bounds.bottom()
223 && ih != h) {
224 // restore original height
225 rect.setHeight(ih);
226 heightAdjusted = true;
227 }
228 }
229 }
230
231 m_positioning = true;
232
233 popupItem->setPosition(rect.topLeft());
234
235 // If the popup was assigned a parent, rect will be in scene coordinates,
236 // so we need to map its top left back to item coordinates.
237 // However, if centering within the overlay, the coordinates will be relative
238 // to the window, so we don't need to do anything.
239 const QPointF effectivePos = m_parentItem && !centerInOverlay ? m_parentItem->mapFromScene(rect.topLeft()) : rect.topLeft();
240 if (!qFuzzyCompare(p->effectiveX, effectivePos.x())) {
241 p->effectiveX = effectivePos.x();
243 }
244 if (!qFuzzyCompare(p->effectiveY, effectivePos.y())) {
245 p->effectiveY = effectivePos.y();
247 }
248
249 if (!p->hasWidth && widthAdjusted && rect.width() > 0) {
250 popupItem->setWidth(rect.width() / m_popupScale);
251 // The popup doesn't have an explicit width, so we should respect that by not
252 // making our call above an explicit assignment. If we don't, the popup won't
253 // resize after being repositioned in some cases.
254 QQuickItemPrivate::get(popupItem)->widthValidFlag = false;
255 }
256 if (!p->hasHeight && heightAdjusted && rect.height() > 0) {
257 popupItem->setHeight(rect.height() / m_popupScale);
258 QQuickItemPrivate::get(popupItem)->heightValidFlag = false;
259 }
260 m_positioning = false;
261
262 qCDebug(lcPopupPositioner) << "- new popupItem geometry:"
263 << popupItem->x() << popupItem->y() << popupItem->width() << popupItem->height();
264}
265
271
276
282
284{
285 if (item == m_parentItem)
286 return;
287
288 QQuickItem *p = item;
289 while (p) {
290 QQuickItemPrivate::get(p)->removeItemChangeListener(this, AncestorChangeTypes);
291 p = p->parentItem();
292 }
293}
294
296{
297 if (item == m_parentItem)
298 return;
299
300 QQuickItem *p = item;
301 while (p) {
302 QQuickItemPrivate::get(p)->updateOrAddItemChangeListener(this, AncestorChangeTypes);
303 p = p->parentItem();
304 }
305}
306
\inmodule QtCore
Definition qmargins.h:270
constexpr qreal right() const noexcept
Returns the right margin.
Definition qmargins.h:383
constexpr qreal left() const noexcept
Returns the left margin.
Definition qmargins.h:377
constexpr qreal top() const noexcept
Returns the top margin.
Definition qmargins.h:380
constexpr qreal bottom() const noexcept
Returns the bottom margin.
Definition qmargins.h:386
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
static QQuickItemPrivate * get(QQuickItem *item)
The QQuickItem class provides the most basic of all visual items in \l {Qt Quick}.
Definition qquickitem.h:63
qreal implicitWidth
Definition qquickitem.h:114
Q_INVOKABLE QPointF mapToItem(const QQuickItem *item, const QPointF &point) const
Maps the given point in this item's coordinate system to the equivalent point within item's coordinat...
qreal x
\qmlproperty real QtQuick::Item::x \qmlproperty real QtQuick::Item::y \qmlproperty real QtQuick::Item...
Definition qquickitem.h:72
QPointF mapFromScene(const QPointF &point) const
Maps the given point in the scene's coordinate system to the equivalent point within this item's coor...
qreal y
Defines the item's y position relative to its parent.
Definition qquickitem.h:73
bool isVisible() const
void setHeight(qreal)
qreal width
This property holds the width of this item.
Definition qquickitem.h:75
QQuickItem * parentItem() const
qreal implicitHeight
Definition qquickitem.h:115
qreal height
This property holds the height of this item.
Definition qquickitem.h:76
void setPosition(const QPointF &)
void setWidth(qreal)
qreal scale
\qmlproperty real QtQuick::Item::scale This property holds the scale factor for this item.
Definition qquickitem.h:107
void polish()
Schedules a polish event for this item.
void itemChildRemoved(QQuickItem *, QQuickItem *child) override
void setParentItem(QQuickItem *parent)
QQuickItem * parentItem() const
void itemParentChanged(QQuickItem *, QQuickItem *parent) override
void itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &) override
void addAncestorListeners(QQuickItem *item)
QQuickPopup * popup() const
void removeAncestorListeners(QQuickItem *item)
static QQuickPopupPrivate * get(QQuickPopup *popup)
void xChanged()
void yChanged()
\inmodule QtCore\reentrant
Definition qrect.h:484
constexpr qreal bottom() const noexcept
Returns the y-coordinate of the rectangle's bottom edge.
Definition qrect.h:500
constexpr qreal height() const noexcept
Returns the height of the rectangle.
Definition qrect.h:732
constexpr qreal width() const noexcept
Returns the width of the rectangle.
Definition qrect.h:729
constexpr qreal left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
Definition qrect.h:497
constexpr qreal top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
Definition qrect.h:498
QRectF intersected(const QRectF &other) const noexcept
Definition qrect.h:847
constexpr qreal right() const noexcept
Returns the x-coordinate of the rectangle's right edge.
Definition qrect.h:499
rect
[4]
Combined button and popup list for selecting options.
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:327
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLfloat GLfloat GLfloat w
[0]
GLfloat GLfloat GLfloat GLfloat h
GLfloat GLfloat p
[1]
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
static const QQuickItemPrivate::ChangeTypes ItemChangeTypes
static QT_BEGIN_NAMESPACE const QQuickItemPrivate::ChangeTypes AncestorChangeTypes
#define emit
double qreal
Definition qtypes.h:187
QGraphicsItem * item
QLayoutItem * child
[0]