1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qquickoverlay_p.h"
38#include "qquickpopuppositioner_p_p.h"
39#include "qquickpopupanchors_p.h"
40#include "qquickpopupitem_p_p.h"
41#include "qquickpopup_p_p.h"
42
43#include <QtQml/qqmlinfo.h>
44#include <QtQuick/private/qquickitem_p.h>
45
46QT_BEGIN_NAMESPACE
47
48static const QQuickItemPrivate::ChangeTypes AncestorChangeTypes = QQuickItemPrivate::Geometry
49 | QQuickItemPrivate::Parent
50 | QQuickItemPrivate::Children;
51
52static const QQuickItemPrivate::ChangeTypes ItemChangeTypes = QQuickItemPrivate::Geometry
53 | QQuickItemPrivate::Parent;
54
55QQuickPopupPositioner::QQuickPopupPositioner(QQuickPopup *popup)
56 : m_popup(popup)
57{
58}
59
60QQuickPopupPositioner::~QQuickPopupPositioner()
61{
62 if (m_parentItem) {
63 QQuickItemPrivate::get(item: m_parentItem)->removeItemChangeListener(this, types: ItemChangeTypes);
64 removeAncestorListeners(item: m_parentItem->parentItem());
65 }
66}
67
68QQuickPopup *QQuickPopupPositioner::popup() const
69{
70 return m_popup;
71}
72
73QQuickItem *QQuickPopupPositioner::parentItem() const
74{
75 return m_parentItem;
76}
77
78void QQuickPopupPositioner::setParentItem(QQuickItem *parent)
79{
80 if (m_parentItem == parent)
81 return;
82
83 if (m_parentItem) {
84 QQuickItemPrivate::get(item: m_parentItem)->removeItemChangeListener(this, types: ItemChangeTypes);
85 removeAncestorListeners(item: m_parentItem->parentItem());
86 }
87
88 m_parentItem = parent;
89
90 if (!parent)
91 return;
92
93 QQuickItemPrivate::get(item: parent)->addItemChangeListener(listener: this, types: ItemChangeTypes);
94 addAncestorListeners(item: parent->parentItem());
95 // Store the scale property so the end result of any transition that could effect the scale
96 // does not influence the top left of the final popup, so it doesn't appear to flip from one
97 // position to another as a result
98 m_popupScale = m_popup->popupItem()->scale();
99 if (m_popup->popupItem()->isVisible())
100 QQuickPopupPrivate::get(popup: m_popup)->reposition();
101}
102
103void QQuickPopupPositioner::reposition()
104{
105 QQuickItem *popupItem = m_popup->popupItem();
106 if (!popupItem->isVisible())
107 return;
108
109 if (m_positioning) {
110 popupItem->polish();
111 return;
112 }
113
114 const qreal w = popupItem->width() * m_popupScale;
115 const qreal h = popupItem->height() * m_popupScale;
116 const qreal iw = popupItem->implicitWidth() * m_popupScale;
117 const qreal ih = popupItem->implicitHeight() * m_popupScale;
118
119 bool widthAdjusted = false;
120 bool heightAdjusted = false;
121 QQuickPopupPrivate *p = QQuickPopupPrivate::get(popup: m_popup);
122
123 const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr;
124 const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(object: centerInParent);
125 QRectF rect(!centerInParent ? p->allowHorizontalMove ? p->x : popupItem->x() : 0,
126 !centerInParent ? p->allowVerticalMove ? p->y : popupItem->y() : 0,
127 !p->hasWidth && iw > 0 ? iw : w,
128 !p->hasHeight && ih > 0 ? ih : h);
129 if (m_parentItem) {
130 // m_parentItem is the parent that the popup should open in,
131 // and popupItem()->parentItem() is the overlay, so the mapToItem() calls below
132 // effectively map the rect to scene coordinates.
133 if (centerInParent) {
134 if (centerInParent != parentItem() && !centerInOverlay) {
135 qmlWarning(me: m_popup) << "Popup can only be centered within its immediate parent or Overlay.overlay";
136 return;
137 }
138
139 if (centerInOverlay) {
140 rect.moveCenter(p: QPointF(qRound(d: centerInOverlay->width() / 2.0), qRound(d: centerInOverlay->height() / 2.0)));
141 } else {
142 const QPointF parentItemCenter = QPointF(qRound(d: m_parentItem->width() / 2), qRound(d: m_parentItem->height() / 2));
143 rect.moveCenter(p: m_parentItem->mapToItem(item: popupItem->parentItem(), point: parentItemCenter));
144 }
145 } else {
146 rect.moveTopLeft(p: m_parentItem->mapToItem(item: popupItem->parentItem(), point: rect.topLeft()));
147 }
148
149 if (p->window) {
150 const QMarginsF margins = p->getMargins();
151 QRectF bounds(qMax<qreal>(a: 0.0, b: margins.left()),
152 qMax<qreal>(a: 0.0, b: margins.top()),
153 p->window->width() - qMax<qreal>(a: 0.0, b: margins.left()) - qMax<qreal>(a: 0.0, b: margins.right()),
154 p->window->height() - qMax<qreal>(a: 0.0, b: margins.top()) - qMax<qreal>(a: 0.0, b: margins.bottom()));
155 if (p->window->contentOrientation() == Qt::LandscapeOrientation || p->window->contentOrientation() == Qt::InvertedLandscapeOrientation)
156 bounds = bounds.transposed();
157
158 // if the popup doesn't fit horizontally inside the window, try flipping it around (left <-> right)
159 if (p->allowHorizontalFlip && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
160 const QRectF flipped(m_parentItem->mapToScene(point: QPointF(m_parentItem->width() - p->x - rect.width(), p->y)), rect.size());
161 if (flipped.intersected(r: bounds).width() > rect.intersected(r: bounds).width())
162 rect.moveLeft(pos: flipped.left());
163 }
164
165 // if the popup doesn't fit vertically inside the window, try flipping it around (above <-> below)
166 if (p->allowVerticalFlip && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
167 const QRectF flipped(m_parentItem->mapToScene(point: QPointF(p->x, m_parentItem->height() - p->y - rect.height())), rect.size());
168 if (flipped.intersected(r: bounds).height() > rect.intersected(r: bounds).height())
169 rect.moveTop(pos: flipped.top());
170 }
171
172 // push inside the margins if specified
173 if (p->allowVerticalMove) {
174 if (margins.top() >= 0 && rect.top() < bounds.top())
175 rect.moveTop(pos: margins.top());
176 if (margins.bottom() >= 0 && rect.bottom() > bounds.bottom())
177 rect.moveBottom(pos: bounds.bottom());
178 }
179 if (p->allowHorizontalMove) {
180 if (margins.left() >= 0 && rect.left() < bounds.left())
181 rect.moveLeft(pos: margins.left());
182 if (margins.right() >= 0 && rect.right() > bounds.right())
183 rect.moveRight(pos: bounds.right());
184 }
185
186 if (iw > 0 && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
187 // neither the flipped or pushed geometry fits inside the window, choose
188 // whichever side (left vs. right) fits larger part of the popup
189 if (p->allowHorizontalMove && p->allowHorizontalFlip) {
190 if (rect.left() < bounds.left() && bounds.left() + rect.width() <= bounds.right())
191 rect.moveLeft(pos: bounds.left());
192 else if (rect.right() > bounds.right() && bounds.right() - rect.width() >= bounds.left())
193 rect.moveRight(pos: bounds.right());
194 }
195
196 // as a last resort, adjust the width to fit the window
197 if (p->allowHorizontalResize) {
198 if (rect.left() < bounds.left()) {
199 rect.setLeft(bounds.left());
200 widthAdjusted = true;
201 }
202 if (rect.right() > bounds.right()) {
203 rect.setRight(bounds.right());
204 widthAdjusted = true;
205 }
206 }
207 } else if (iw > 0 && rect.left() >= bounds.left() && rect.right() <= bounds.right()
208 && iw != w) {
209 // restore original width
210 rect.setWidth(iw);
211 widthAdjusted = true;
212 }
213
214 if (ih > 0 && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
215 // neither the flipped or pushed geometry fits inside the window, choose
216 // whichever side (above vs. below) fits larger part of the popup
217 if (p->allowVerticalMove && p->allowVerticalFlip) {
218 if (rect.top() < bounds.top() && bounds.top() + rect.height() <= bounds.bottom())
219 rect.moveTop(pos: bounds.top());
220 else if (rect.bottom() > bounds.bottom() && bounds.bottom() - rect.height() >= bounds.top())
221 rect.moveBottom(pos: bounds.bottom());
222 }
223
224 // as a last resort, adjust the height to fit the window
225 if (p->allowVerticalResize) {
226 if (rect.top() < bounds.top()) {
227 rect.setTop(bounds.top());
228 heightAdjusted = true;
229 }
230 if (rect.bottom() > bounds.bottom()) {
231 rect.setBottom(bounds.bottom());
232 heightAdjusted = true;
233 }
234 }
235 } else if (ih > 0 && rect.top() >= bounds.top() && rect.bottom() <= bounds.bottom()
236 && ih != h) {
237 // restore original height
238 rect.setHeight(ih);
239 heightAdjusted = true;
240 }
241 }
242 }
243
244 m_positioning = true;
245
246 popupItem->setPosition(rect.topLeft());
247
248 // If the popup was assigned a parent, rect will be in scene coordinates,
249 // so we need to map its top left back to item coordinates.
250 // However, if centering within the overlay, the coordinates will be relative
251 // to the window, so we don't need to do anything.
252 const QPointF effectivePos = m_parentItem && !centerInOverlay ? m_parentItem->mapFromScene(point: rect.topLeft()) : rect.topLeft();
253 if (!qFuzzyCompare(p1: p->effectiveX, p2: effectivePos.x())) {
254 p->effectiveX = effectivePos.x();
255 emit m_popup->xChanged();
256 }
257 if (!qFuzzyCompare(p1: p->effectiveY, p2: effectivePos.y())) {
258 p->effectiveY = effectivePos.y();
259 emit m_popup->yChanged();
260 }
261
262 if (!p->hasWidth && widthAdjusted && rect.width() > 0)
263 popupItem->setWidth(rect.width() / m_popupScale);
264 if (!p->hasHeight && heightAdjusted && rect.height() > 0)
265 popupItem->setHeight(rect.height() / m_popupScale);
266 m_positioning = false;
267}
268
269void QQuickPopupPositioner::itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &)
270{
271 if (m_parentItem && m_popup->popupItem()->isVisible())
272 QQuickPopupPrivate::get(popup: m_popup)->reposition();
273}
274
275void QQuickPopupPositioner::itemParentChanged(QQuickItem *, QQuickItem *parent)
276{
277 addAncestorListeners(item: parent);
278}
279
280void QQuickPopupPositioner::itemChildRemoved(QQuickItem *item, QQuickItem *child)
281{
282 if (child == m_parentItem || child->isAncestorOf(child: m_parentItem))
283 removeAncestorListeners(item);
284}
285
286void QQuickPopupPositioner::removeAncestorListeners(QQuickItem *item)
287{
288 if (item == m_parentItem)
289 return;
290
291 QQuickItem *p = item;
292 while (p) {
293 QQuickItemPrivate::get(item: p)->removeItemChangeListener(this, types: AncestorChangeTypes);
294 p = p->parentItem();
295 }
296}
297
298void QQuickPopupPositioner::addAncestorListeners(QQuickItem *item)
299{
300 if (item == m_parentItem)
301 return;
302
303 QQuickItem *p = item;
304 while (p) {
305 QQuickItemPrivate::get(item: p)->updateOrAddItemChangeListener(listener: this, types: AncestorChangeTypes);
306 p = p->parentItem();
307 }
308}
309
310QT_END_NAMESPACE
311

source code of qtquickcontrols2/src/quicktemplates2/qquickpopuppositioner.cpp