blob: ebd8ff29452c46ad4e139a333df3db9d9ba6f122 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qquickoverlay_p.h"
#include "qquickpopuppositioner_p_p.h"
#include "qquickpopupanchors_p.h"
#include "qquickpopupitem_p_p.h"
#include "qquickpopup_p_p.h"
#include <QtQml/qqmlinfo.h>
#include <QtQuick/private/qquickitem_p.h>
QT_BEGIN_NAMESPACE
static const QQuickItemPrivate::ChangeTypes AncestorChangeTypes = QQuickItemPrivate::Geometry
| QQuickItemPrivate::Parent
| QQuickItemPrivate::Children;
static const QQuickItemPrivate::ChangeTypes ItemChangeTypes = QQuickItemPrivate::Geometry
| QQuickItemPrivate::Parent;
QQuickPopupPositioner::QQuickPopupPositioner(QQuickPopup *popup)
: m_popup(popup)
{
}
QQuickPopupPositioner::~QQuickPopupPositioner()
{
if (m_parentItem) {
QQuickItemPrivate::get(m_parentItem)->removeItemChangeListener(this, ItemChangeTypes);
removeAncestorListeners(m_parentItem->parentItem());
}
}
QQuickPopup *QQuickPopupPositioner::popup() const
{
return m_popup;
}
QQuickItem *QQuickPopupPositioner::parentItem() const
{
return m_parentItem;
}
void QQuickPopupPositioner::setParentItem(QQuickItem *parent)
{
if (m_parentItem == parent)
return;
if (m_parentItem) {
QQuickItemPrivate::get(m_parentItem)->removeItemChangeListener(this, ItemChangeTypes);
removeAncestorListeners(m_parentItem->parentItem());
}
m_parentItem = parent;
if (!parent)
return;
QQuickItemPrivate::get(parent)->addItemChangeListener(this, ItemChangeTypes);
addAncestorListeners(parent->parentItem());
if (m_popup->popupItem()->isVisible())
QQuickPopupPrivate::get(m_popup)->reposition();
}
void QQuickPopupPositioner::reposition()
{
QQuickItem *popupItem = m_popup->popupItem();
if (!popupItem->isVisible())
return;
if (m_positioning) {
popupItem->polish();
return;
}
const qreal w = popupItem->width();
const qreal h = popupItem->height();
const qreal iw = popupItem->implicitWidth();
const qreal ih = popupItem->implicitHeight();
bool widthAdjusted = false;
bool heightAdjusted = false;
QQuickPopupPrivate *p = QQuickPopupPrivate::get(m_popup);
const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr;
const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(centerInParent);
QRectF rect(!centerInParent ? p->allowHorizontalMove ? p->x : popupItem->x() : 0,
!centerInParent ? p->allowVerticalMove ? p->y : popupItem->y() : 0,
!p->hasWidth && iw > 0 ? iw : w,
!p->hasHeight && ih > 0 ? ih : h);
if (m_parentItem) {
// m_parentItem is the parent that the popup should open in,
// and popupItem()->parentItem() is the overlay, so the mapToItem() calls below
// effectively map the rect to scene coordinates.
if (centerInParent) {
if (centerInParent != parentItem() && !centerInOverlay) {
qmlWarning(m_popup) << "Popup can only be centered within its immediate parent or Overlay.overlay";
return;
}
if (centerInOverlay) {
rect.moveCenter(QPointF(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0)));
} else {
const QPointF parentItemCenter = QPointF(qRound(m_parentItem->width() / 2), qRound(m_parentItem->height() / 2));
rect.moveCenter(m_parentItem->mapToItem(popupItem->parentItem(), parentItemCenter));
}
} else {
rect.moveTopLeft(m_parentItem->mapToItem(popupItem->parentItem(), rect.topLeft()));
}
if (p->window) {
const QMarginsF margins = p->getMargins();
QRectF bounds(qMax<qreal>(0.0, margins.left()),
qMax<qreal>(0.0, margins.top()),
p->window->width() - qMax<qreal>(0.0, margins.left()) - qMax<qreal>(0.0, margins.right()),
p->window->height() - qMax<qreal>(0.0, margins.top()) - qMax<qreal>(0.0, margins.bottom()));
if (p->window->contentOrientation() == Qt::LandscapeOrientation || p->window->contentOrientation() == Qt::InvertedLandscapeOrientation)
bounds = bounds.transposed();
// if the popup doesn't fit horizontally inside the window, try flipping it around (left <-> right)
if (p->allowHorizontalFlip && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
const QRectF flipped(m_parentItem->mapToScene(QPointF(m_parentItem->width() - p->x - rect.width(), p->y)), rect.size());
if (flipped.intersected(bounds).width() > rect.intersected(bounds).width())
rect.moveLeft(flipped.left());
}
// if the popup doesn't fit vertically inside the window, try flipping it around (above <-> below)
if (p->allowVerticalFlip && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
const QRectF flipped(m_parentItem->mapToScene(QPointF(p->x, m_parentItem->height() - p->y - rect.height())), rect.size());
if (flipped.intersected(bounds).height() > rect.intersected(bounds).height())
rect.moveTop(flipped.top());
}
// push inside the margins if specified
if (p->allowVerticalMove) {
if (margins.top() >= 0 && rect.top() < bounds.top())
rect.moveTop(margins.top());
if (margins.bottom() >= 0 && rect.bottom() > bounds.bottom())
rect.moveBottom(bounds.bottom());
}
if (p->allowHorizontalMove) {
if (margins.left() >= 0 && rect.left() < bounds.left())
rect.moveLeft(margins.left());
if (margins.right() >= 0 && rect.right() > bounds.right())
rect.moveRight(bounds.right());
}
if (iw > 0 && (rect.left() < bounds.left() || rect.right() > bounds.right())) {
// neither the flipped or pushed geometry fits inside the window, choose
// whichever side (left vs. right) fits larger part of the popup
if (p->allowHorizontalMove && p->allowHorizontalFlip) {
if (rect.left() < bounds.left() && bounds.left() + rect.width() <= bounds.right())
rect.moveLeft(bounds.left());
else if (rect.right() > bounds.right() && bounds.right() - rect.width() >= bounds.left())
rect.moveRight(bounds.right());
}
// as a last resort, adjust the width to fit the window
if (p->allowHorizontalResize) {
if (rect.left() < bounds.left()) {
rect.setLeft(bounds.left());
widthAdjusted = true;
}
if (rect.right() > bounds.right()) {
rect.setRight(bounds.right());
widthAdjusted = true;
}
}
} else if (iw > 0 && rect.left() >= bounds.left() && rect.right() <= bounds.right()
&& iw != w) {
// restore original width
rect.setWidth(iw);
widthAdjusted = true;
}
if (ih > 0 && (rect.top() < bounds.top() || rect.bottom() > bounds.bottom())) {
// neither the flipped or pushed geometry fits inside the window, choose
// whichever side (above vs. below) fits larger part of the popup
if (p->allowVerticalMove && p->allowVerticalFlip) {
if (rect.top() < bounds.top() && bounds.top() + rect.height() <= bounds.bottom())
rect.moveTop(bounds.top());
else if (rect.bottom() > bounds.bottom() && bounds.bottom() - rect.height() >= bounds.top())
rect.moveBottom(bounds.bottom());
}
// as a last resort, adjust the height to fit the window
if (p->allowVerticalResize) {
if (rect.top() < bounds.top()) {
rect.setTop(bounds.top());
heightAdjusted = true;
}
if (rect.bottom() > bounds.bottom()) {
rect.setBottom(bounds.bottom());
heightAdjusted = true;
}
}
} else if (ih > 0 && rect.top() >= bounds.top() && rect.bottom() <= bounds.bottom()
&& ih != h) {
// restore original height
rect.setHeight(ih);
heightAdjusted = true;
}
}
}
m_positioning = true;
popupItem->setPosition(rect.topLeft());
// If the popup was assigned a parent, rect will be in scene coordinates,
// so we need to map its top left back to item coordinates.
// However, if centering within the overlay, the coordinates will be relative
// to the window, so we don't need to do anything.
const QPointF effectivePos = m_parentItem && !centerInOverlay ? m_parentItem->mapFromScene(rect.topLeft()) : rect.topLeft();
if (!qFuzzyCompare(p->effectiveX, effectivePos.x())) {
p->effectiveX = effectivePos.x();
emit m_popup->xChanged();
}
if (!qFuzzyCompare(p->effectiveY, effectivePos.y())) {
p->effectiveY = effectivePos.y();
emit m_popup->yChanged();
}
if (!p->hasWidth && widthAdjusted && rect.width() > 0)
popupItem->setWidth(rect.width());
if (!p->hasHeight && heightAdjusted && rect.height() > 0)
popupItem->setHeight(rect.height());
m_positioning = false;
}
void QQuickPopupPositioner::itemGeometryChanged(QQuickItem *, QQuickGeometryChange, const QRectF &)
{
if (m_parentItem && m_popup->popupItem()->isVisible())
QQuickPopupPrivate::get(m_popup)->reposition();
}
void QQuickPopupPositioner::itemParentChanged(QQuickItem *, QQuickItem *parent)
{
addAncestorListeners(parent);
}
void QQuickPopupPositioner::itemChildRemoved(QQuickItem *item, QQuickItem *child)
{
if (child == m_parentItem || child->isAncestorOf(m_parentItem))
removeAncestorListeners(item);
}
void QQuickPopupPositioner::removeAncestorListeners(QQuickItem *item)
{
if (item == m_parentItem)
return;
QQuickItem *p = item;
while (p) {
QQuickItemPrivate::get(p)->removeItemChangeListener(this, AncestorChangeTypes);
p = p->parentItem();
}
}
void QQuickPopupPositioner::addAncestorListeners(QQuickItem *item)
{
if (item == m_parentItem)
return;
QQuickItem *p = item;
while (p) {
QQuickItemPrivate::get(p)->updateOrAddItemChangeListener(this, AncestorChangeTypes);
p = p->parentItem();
}
}
QT_END_NAMESPACE