diff options
author | Jan Arve Saether <[email protected]> | 2016-02-18 13:28:59 +0100 |
---|---|---|
committer | J-P Nurmi <[email protected]> | 2016-02-26 13:18:27 +0000 |
commit | 974643829f677247a8b06aba563a28714b48f1a9 (patch) | |
tree | 2e040aacedff10bb0f579081bbae21693fea01be | |
parent | fb2710a7f182ffb910f6b121e8bc125a4f61dcdf (diff) |
Move QtQuick.Layouts to qtdeclarative from qtquickcontrols
This is in order for it to be available without having to install
Qt Quick Controls
Change-Id: I3f0d0dc108829947cd189b7861944e556e00cef3
Reviewed-by: J-P Nurmi <[email protected]>
39 files changed, 6232 insertions, 8 deletions
diff --git a/examples/quick/layouts/doc/src/qtquicklayouts-examples.qdoc b/examples/quick/layouts/doc/src/qtquicklayouts-examples.qdoc new file mode 100644 index 0000000000..23e36c0469 --- /dev/null +++ b/examples/quick/layouts/doc/src/qtquicklayouts-examples.qdoc @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ +/*! + \title Qt Quick Layouts - Basic Example + \example layouts + \brief Demonstrates how to use layout types to arrange a UI + \image qtquicklayouts-example-layouts.png + \ingroup qtquickexamples + + This example shows how to easily arrange UI components into + \l{Qt Quick Layouts}{layouts} with \l{GridLayout}, \l{RowLayout}, and + \l{ColumnLayout}. + + \include examples-run.qdocinc +*/ + + + diff --git a/examples/quick/layouts/layouts.pro b/examples/quick/layouts/layouts.pro new file mode 100644 index 0000000000..4d676ef49b --- /dev/null +++ b/examples/quick/layouts/layouts.pro @@ -0,0 +1,14 @@ +TEMPLATE = app + +QT += qml quick + +SOURCES += main.cpp + +RESOURCES += \ + layouts.qrc +EXAMPLE_FILES = \ + layouts.qml + +target.path = $$[QT_INSTALL_EXAMPLES]/quick/layouts +INSTALLS += target + diff --git a/examples/quick/layouts/layouts.qml b/examples/quick/layouts/layouts.qml new file mode 100644 index 0000000000..48299f36aa --- /dev/null +++ b/examples/quick/layouts/layouts.qml @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.3 + +ApplicationWindow { + visible: true + title: "Basic layouts" + property int margin: 11 + width: mainLayout.implicitWidth + 2 * margin + height: mainLayout.implicitHeight + 2 * margin + minimumWidth: mainLayout.Layout.minimumWidth + 2 * margin + minimumHeight: mainLayout.Layout.minimumHeight + 2 * margin + + ColumnLayout { + id: mainLayout + anchors.fill: parent + anchors.margins: margin + GroupBox { + id: rowBox + title: "Row layout" + Layout.fillWidth: true + + RowLayout { + id: rowLayout + anchors.fill: parent + TextField { + placeholderText: "This wants to grow horizontally" + Layout.fillWidth: true + } + Button { + text: "Button" + } + } + } + + GroupBox { + id: gridBox + title: "Grid layout" + Layout.fillWidth: true + + GridLayout { + id: gridLayout + rows: 3 + flow: GridLayout.TopToBottom + anchors.fill: parent + + Label { text: "Line 1" } + Label { text: "Line 2" } + Label { text: "Line 3" } + + TextField { } + TextField { } + TextField { } + + TextArea { + text: "This widget spans over three rows in the GridLayout.\n" + + "All items in the GridLayout are implicitly positioned from top to bottom." + Layout.rowSpan: 3 + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } + TextArea { + id: t3 + text: "This fills the whole cell" + Layout.minimumHeight: 30 + Layout.fillHeight: true + Layout.fillWidth: true + } + GroupBox { + id: stackBox + title: "Stack layout" + implicitWidth: 200 + implicitHeight: 60 + Layout.fillWidth: true + Layout.fillHeight: true + StackLayout { + id: stackLayout + anchors.fill: parent + + function advance() { currentIndex = (currentIndex + 1) % count } + + Repeater { + id: stackRepeater + model: 5 + Rectangle { + color: Qt.hsla((0.5 + index)/stackRepeater.count, 0.3, 0.7, 1) + Button { anchors.centerIn: parent; text: "Page " + (index + 1); onClicked: { stackLayout.advance() } } + } + } + } + } + } +} diff --git a/examples/quick/layouts/layouts.qmlproject b/examples/quick/layouts/layouts.qmlproject new file mode 100644 index 0000000000..e5a8bf02ca --- /dev/null +++ b/examples/quick/layouts/layouts.qmlproject @@ -0,0 +1,16 @@ +import QmlProject 1.1 + +Project { + mainFile: "main.qml" + + /* Include .qml, .js, and image files from current directory and subdirectories */ + QmlFiles { + directory: "." + } + JavaScriptFiles { + directory: "." + } + ImageFiles { + directory: "." + } +} diff --git a/examples/quick/layouts/layouts.qrc b/examples/quick/layouts/layouts.qrc new file mode 100644 index 0000000000..1e01c43fe1 --- /dev/null +++ b/examples/quick/layouts/layouts.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/layouts"> + <file>layouts.qml</file> +</qresource> +</RCC> diff --git a/examples/quick/layouts/main.cpp b/examples/quick/layouts/main.cpp new file mode 100644 index 0000000000..ef3e27a799 --- /dev/null +++ b/examples/quick/layouts/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char* argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:///layouts/layouts.qml"))); + + return app.exec(); +} diff --git a/examples/quick/quick.pro b/examples/quick/quick.pro index c5ef46173c..e66e24882e 100644 --- a/examples/quick/quick.pro +++ b/examples/quick/quick.pro @@ -6,6 +6,7 @@ SUBDIRS = quick-accessibility \ canvas \ imageelements \ keyinteraction \ + layouts \ localstorage \ models \ views \ diff --git a/src/imports/imports.pro b/src/imports/imports.pro index f7002f9ed5..ff7b6e75af 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -10,6 +10,7 @@ SUBDIRS += \ qtHaveModule(quick) { SUBDIRS += \ + layouts \ qtquick2 \ particles \ window \ diff --git a/src/imports/layouts/layouts.pro b/src/imports/layouts/layouts.pro new file mode 100644 index 0000000000..26574150de --- /dev/null +++ b/src/imports/layouts/layouts.pro @@ -0,0 +1,22 @@ +CXX_MODULE = qml +TARGET = qquicklayoutsplugin +TARGETPATH = QtQuick/Layouts +IMPORT_VERSION = 1.2 + +QT *= qml-private quick-private gui-private core-private + +SOURCES += plugin.cpp \ + qquicklayout.cpp \ + qquicklinearlayout.cpp \ + qquickstacklayout.cpp \ + qquickgridlayoutengine.cpp \ + qquicklayoutstyleinfo.cpp + +HEADERS += \ + qquicklayout_p.h \ + qquicklinearlayout_p.h \ + qquickstacklayout_p.h \ + qquickgridlayoutengine_p.h \ + qquicklayoutstyleinfo_p.h + +load(qml_plugin) diff --git a/src/imports/layouts/plugin.cpp b/src/imports/layouts/plugin.cpp new file mode 100644 index 0000000000..4552b7219b --- /dev/null +++ b/src/imports/layouts/plugin.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQml/qqmlextensionplugin.h> + +#include "qquicklinearlayout_p.h" +#include "qquickstacklayout_p.h" + +QT_BEGIN_NAMESPACE + +//![class decl] +class QtQuickLayoutsPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface/1.0") +public: + virtual void registerTypes(const char *uri) + { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtQuick.Layouts")); + Q_UNUSED(uri); + + qmlRegisterType<QQuickRowLayout>(uri, 1, 0, "RowLayout"); + qmlRegisterType<QQuickColumnLayout>(uri, 1, 0, "ColumnLayout"); + qmlRegisterType<QQuickGridLayout>(uri, 1, 0, "GridLayout"); + qmlRegisterType<QQuickStackLayout>(uri, 1, 3, "StackLayout"); + qmlRegisterUncreatableType<QQuickLayout>(uri, 1, 0, "Layout", + QStringLiteral("Do not create objects of type Layout")); + qmlRegisterUncreatableType<QQuickLayout>(uri, 1, 2, "Layout", + QStringLiteral("Do not create objects of type Layout")); + qmlRegisterRevision<QQuickGridLayoutBase, 1>(uri, 1, 1); + } +}; +//![class decl] + +QT_END_NAMESPACE + +#include "plugin.moc" diff --git a/src/imports/layouts/plugins.qmltypes b/src/imports/layouts/plugins.qmltypes new file mode 100644 index 0000000000..b130215b62 --- /dev/null +++ b/src/imports/layouts/plugins.qmltypes @@ -0,0 +1,102 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable QtQuick.Layouts 1.2' + +Module { + dependencies: [] + Component { + name: "QQuickColumnLayout" + defaultProperty: "data" + prototype: "QQuickLinearLayout" + exports: ["QtQuick.Layouts/ColumnLayout 1.0"] + exportMetaObjectRevisions: [0] + } + Component { + name: "QQuickGridLayout" + defaultProperty: "data" + prototype: "QQuickGridLayoutBase" + exports: ["QtQuick.Layouts/GridLayout 1.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Flow" + values: { + "LeftToRight": 0, + "TopToBottom": 1 + } + } + Property { name: "columnSpacing"; type: "double" } + Property { name: "rowSpacing"; type: "double" } + Property { name: "columns"; type: "int" } + Property { name: "rows"; type: "int" } + Property { name: "flow"; type: "Flow" } + } + Component { + name: "QQuickGridLayoutBase" + defaultProperty: "data" + prototype: "QQuickLayout" + Property { name: "layoutDirection"; revision: 1; type: "Qt::LayoutDirection" } + Signal { name: "layoutDirectionChanged"; revision: 1 } + } + Component { + name: "QQuickLayout" + defaultProperty: "data" + prototype: "QQuickItem" + exports: ["QtQuick.Layouts/Layout 1.0", "QtQuick.Layouts/Layout 1.2"] + isCreatable: false + exportMetaObjectRevisions: [0, 0] + attachedType: "QQuickLayoutAttached" + } + Component { + name: "QQuickLayoutAttached" + prototype: "QObject" + Property { name: "minimumWidth"; type: "double" } + Property { name: "minimumHeight"; type: "double" } + Property { name: "preferredWidth"; type: "double" } + Property { name: "preferredHeight"; type: "double" } + Property { name: "maximumWidth"; type: "double" } + Property { name: "maximumHeight"; type: "double" } + Property { name: "fillHeight"; type: "bool" } + Property { name: "fillWidth"; type: "bool" } + Property { name: "row"; type: "int" } + Property { name: "column"; type: "int" } + Property { name: "rowSpan"; type: "int" } + Property { name: "columnSpan"; type: "int" } + Property { name: "alignment"; type: "Qt::Alignment" } + Property { name: "margins"; type: "double" } + Property { name: "leftMargin"; type: "double" } + Property { name: "topMargin"; type: "double" } + Property { name: "rightMargin"; type: "double" } + Property { name: "bottomMargin"; type: "double" } + } + Component { + name: "QQuickLinearLayout" + defaultProperty: "data" + prototype: "QQuickGridLayoutBase" + Property { name: "spacing"; type: "double" } + } + Component { + name: "QQuickRowLayout" + defaultProperty: "data" + prototype: "QQuickLinearLayout" + exports: ["QtQuick.Layouts/RowLayout 1.0"] + exportMetaObjectRevisions: [0] + } + Component { + name: "QQuickStackLayout" + defaultProperty: "data" + prototype: "QQuickLayout" + exports: ["QtQuick.Layouts/StackLayout 1.3"] + exportMetaObjectRevisions: [0] + Property { name: "count"; type: "int"; isReadonly: true } + Property { name: "currentIndex"; type: "int" } + Method { + name: "itemAt" + type: "QQuickItem*" + Parameter { name: "index"; type: "int" } + } + } +} diff --git a/src/imports/layouts/qmldir b/src/imports/layouts/qmldir new file mode 100644 index 0000000000..00f85f7d64 --- /dev/null +++ b/src/imports/layouts/qmldir @@ -0,0 +1,5 @@ +module QtQuick.Layouts +plugin qquicklayoutsplugin +classname QtQuickLayoutsPlugin +typeinfo plugins.qmltypes +designersupported diff --git a/src/imports/layouts/qquickgridlayoutengine.cpp b/src/imports/layouts/qquickgridlayoutengine.cpp new file mode 100644 index 0000000000..fe716f0694 --- /dev/null +++ b/src/imports/layouts/qquickgridlayoutengine.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickitem.h" +#include "qquickgridlayoutengine_p.h" +#include "qquicklayout_p.h" + +QT_BEGIN_NAMESPACE + +void QQuickGridLayoutEngine::setAlignment(QQuickItem *quickItem, Qt::Alignment alignment) +{ + if (QQuickGridLayoutItem *item = findLayoutItem(quickItem)) { + item->setAlignment(alignment); + invalidate(); + } +} + +Qt::Alignment QQuickGridLayoutEngine::alignment(QQuickItem *quickItem) const +{ + if (QGridLayoutItem *item = findLayoutItem(quickItem)) + return item->alignment(); + return 0; +} + +QT_END_NAMESPACE diff --git a/src/imports/layouts/qquickgridlayoutengine_p.h b/src/imports/layouts/qquickgridlayoutengine_p.h new file mode 100644 index 0000000000..86f56a5af4 --- /dev/null +++ b/src/imports/layouts/qquickgridlayoutengine_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKGRIDLAYOUTENGINE_P_H +#define QQUICKGRIDLAYOUTENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the graphics view layout classes. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/private/qgridlayoutengine_p.h> +#include <QtGui/private/qlayoutpolicy_p.h> +#include <QtCore/qmath.h> + +#include "qquickitem.h" +#include "qquicklayout_p.h" +#include "qdebug.h" +QT_BEGIN_NAMESPACE + +class QQuickGridLayoutItem : public QGridLayoutItem { +public: + QQuickGridLayoutItem(QQuickItem *item, int row, int column, + int rowSpan = 1, int columnSpan = 1, Qt::Alignment alignment = 0) + : QGridLayoutItem(row, column, rowSpan, columnSpan, alignment), m_item(item), sizeHintCacheDirty(true), useFallbackToWidthOrHeight(true) {} + + + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint) const + { + Q_UNUSED(constraint); // Quick Layouts does not support constraint atm + return effectiveSizeHints()[which]; + } + + QSizeF *effectiveSizeHints() const + { + if (!sizeHintCacheDirty) + return cachedSizeHints; + + QQuickLayout::effectiveSizeHints_helper(m_item, cachedSizeHints, 0, useFallbackToWidthOrHeight); + useFallbackToWidthOrHeight = false; + + sizeHintCacheDirty = false; + return cachedSizeHints; + } + + void setCachedSizeHints(QSizeF *sizeHints) + { + for (int i = 0; i < Qt::NSizeHints; ++i) { + cachedSizeHints[i] = sizeHints[i]; + } + sizeHintCacheDirty = false; + } + + void invalidate() + { + quickLayoutDebug() << "engine::invalidate()"; + sizeHintCacheDirty = true; + } + + QLayoutPolicy::Policy sizePolicy(Qt::Orientation orientation) const + { + return QQuickLayout::effectiveSizePolicy_helper(m_item, orientation, attachedLayoutObject(m_item, false)); + } + + void setGeometry(const QRectF &rect) + { + QQuickLayoutAttached *info = attachedLayoutObject(m_item, false); + const QRectF r = info ? rect.marginsRemoved(info->qMargins()) : rect; + const QSizeF oldSize(m_item->width(), m_item->height()); + const QSizeF newSize = r.size(); + m_item->setPosition(r.topLeft()); + if (newSize == oldSize) { + if (QQuickLayout *lay = qobject_cast<QQuickLayout *>(m_item)) { + if (lay->arrangementIsDirty()) + lay->rearrange(newSize); + } + } else { + m_item->setSize(newSize); + } + } + + QQuickItem *layoutItem() const { return m_item; } + + QQuickItem *m_item; +private: + mutable QSizeF cachedSizeHints[Qt::NSizeHints]; + mutable unsigned sizeHintCacheDirty : 1; + mutable unsigned useFallbackToWidthOrHeight : 1; +}; + +class QQuickGridLayoutEngine : public QGridLayoutEngine { +public: + QQuickGridLayoutEngine() : QGridLayoutEngine(Qt::AlignVCenter, true /*snapToPixelGrid*/) { } + + int indexOf(QQuickItem *item) const { + for (int i = 0; i < q_items.size(); ++i) { + if (item == static_cast<QQuickGridLayoutItem*>(q_items.at(i))->layoutItem()) + return i; + } + return -1; + } + + QQuickGridLayoutItem *findLayoutItem(QQuickItem *layoutItem) const + { + for (int i = q_items.count() - 1; i >= 0; --i) { + QQuickGridLayoutItem *item = static_cast<QQuickGridLayoutItem*>(q_items.at(i)); + if (item->layoutItem() == layoutItem) + return item; + } + return 0; + } + + void setAlignment(QQuickItem *quickItem, Qt::Alignment alignment); + Qt::Alignment alignment(QQuickItem *quickItem) const; + +}; + + + +QT_END_NAMESPACE + +#endif // QQUICKGRIDLAYOUTENGINE_P_H diff --git a/src/imports/layouts/qquicklayout.cpp b/src/imports/layouts/qquicklayout.cpp new file mode 100644 index 0000000000..d4d4e1703d --- /dev/null +++ b/src/imports/layouts/qquicklayout.cpp @@ -0,0 +1,1081 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicklayout_p.h" +#include <QEvent> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qnumeric.h> +#include <QtCore/qmath.h> +#include <limits> + +/*! + \qmltype Layout + \instantiates QQuickLayoutAttached + \inqmlmodule QtQuick.Layouts + \ingroup layouts + \brief Provides attached properties for items pushed onto a \l GridLayout, + \l RowLayout or \l ColumnLayout. + + An object of type Layout is attached to children of the layout to provide layout specific + information about the item. + The properties of the attached object influence how the layout will arrange the items. + + For instance, you can specify \l minimumWidth, \l preferredWidth, and + \l maximumWidth if the default values are not satisfactory. + + When a layout is resized, items may grow or shrink. Due to this, items have a + \l{Layout::minimumWidth}{minimum size}, \l{Layout::preferredWidth}{preferred size} and a + \l{Layout::maximumWidth}{maximum size}. + + If minimum size has not been explicitly specified on an item, the size is set to \c 0. + If maximum size has not been explicitly specified on an item, the size is set to + \c Number.POSITIVE_INFINITY. + + For layouts, the implicit minimum and maximum sizes depend on the content of the layouts. + + The \l fillWidth and \l fillHeight properties can either be \c true or \c false. If they are \c + false, the item's size will be fixed to its preferred size. Otherwise, it will grow or shrink + between its minimum and maximum size as the layout is resized. + + \note It is not recommended to have bindings to the x, y, width, or height properties of items + in a layout, since this would conflict with the goals of Layout, and can also cause binding + loops. + + + \sa GridLayout + \sa RowLayout + \sa ColumnLayout +*/ + +QT_BEGIN_NAMESPACE + +QQuickLayoutAttached::QQuickLayoutAttached(QObject *parent) + : QObject(parent), + m_minimumWidth(0), + m_minimumHeight(0), + m_preferredWidth(-1), + m_preferredHeight(-1), + m_maximumWidth(std::numeric_limits<qreal>::infinity()), + m_maximumHeight(std::numeric_limits<qreal>::infinity()), + m_defaultMargins(0), + m_row(-1), + m_column(-1), + m_rowSpan(1), + m_columnSpan(1), + m_fillWidth(false), + m_fillHeight(false), + m_isFillWidthSet(false), + m_isFillHeightSet(false), + m_isMinimumWidthSet(false), + m_isMinimumHeightSet(false), + m_isMaximumWidthSet(false), + m_isMaximumHeightSet(false), + m_changesNotificationEnabled(true), + m_isLeftMarginSet(false), + m_isTopMarginSet(false), + m_isRightMarginSet(false), + m_isBottomMarginSet(false), + m_alignment(0) +{ + +} + +/*! + \qmlattachedproperty real Layout::minimumWidth + + This property holds the minimum width of an item in a layout. + The default value is the item's implicit minimum width. + + If the item is a layout, the implicit minimum width will be the minimum width the layout can + have without any of its items shrinking below their minimum width. + The implicit minimum width for any other item is \c 0. + + Setting this value to -1 will reset the width back to its implicit minimum width. + + + \sa preferredWidth + \sa maximumWidth +*/ +void QQuickLayoutAttached::setMinimumWidth(qreal width) +{ + if (qIsNaN(width)) + return; + m_isMinimumWidthSet = width >= 0; + if (m_minimumWidth == width) + return; + + m_minimumWidth = width; + invalidateItem(); + emit minimumWidthChanged(); +} + +/*! + \qmlattachedproperty real Layout::minimumHeight + + This property holds the minimum height of an item in a layout. + The default value is the item's implicit minimum height. + + If the item is a layout, the implicit minimum height will be the minimum height the layout can + have without any of its items shrinking below their minimum height. + The implicit minimum height for any other item is \c 0. + + Setting this value to -1 will reset the height back to its implicit minimum height. + + \sa preferredHeight + \sa maximumHeight +*/ +void QQuickLayoutAttached::setMinimumHeight(qreal height) +{ + if (qIsNaN(height)) + return; + m_isMinimumHeightSet = height >= 0; + if (m_minimumHeight == height) + return; + + m_minimumHeight = height; + invalidateItem(); + emit minimumHeightChanged(); +} + +/*! + \qmlattachedproperty real Layout::preferredWidth + + This property holds the preferred width of an item in a layout. + If the preferred width is \c -1 it will be ignored, and the layout + will use \l{Item::implicitWidth}{implicitWidth} instead. + The default is \c -1. + + \sa minimumWidth + \sa maximumWidth +*/ +void QQuickLayoutAttached::setPreferredWidth(qreal width) +{ + if (qIsNaN(width) || m_preferredWidth == width) + return; + + m_preferredWidth = width; + invalidateItem(); + emit preferredWidthChanged(); +} + +/*! + \qmlattachedproperty real Layout::preferredHeight + + This property holds the preferred height of an item in a layout. + If the preferred height is \c -1 it will be ignored, and the layout + will use \l{Item::implicitHeight}{implicitHeight} instead. + The default is \c -1. + + \sa minimumHeight + \sa maximumHeight +*/ +void QQuickLayoutAttached::setPreferredHeight(qreal height) +{ + if (qIsNaN(height) || m_preferredHeight == height) + return; + + m_preferredHeight = height; + invalidateItem(); + emit preferredHeightChanged(); +} + +/*! + \qmlattachedproperty real Layout::maximumWidth + + This property holds the maximum width of an item in a layout. + The default value is the item's implicit maximum width. + + If the item is a layout, the implicit maximum width will be the maximum width the layout can + have without any of its items growing beyond their maximum width. + The implicit maximum width for any other item is \c Number.POSITIVE_INFINITY. + + Setting this value to \c -1 will reset the width back to its implicit maximum width. + + \sa minimumWidth + \sa preferredWidth +*/ +void QQuickLayoutAttached::setMaximumWidth(qreal width) +{ + if (qIsNaN(width)) + return; + m_isMaximumWidthSet = width >= 0; + if (m_maximumWidth == width) + return; + + m_maximumWidth = width; + invalidateItem(); + emit maximumWidthChanged(); +} + +/*! + \qmlattachedproperty real Layout::maximumHeight + + The default value is the item's implicit maximum height. + + If the item is a layout, the implicit maximum height will be the maximum height the layout can + have without any of its items growing beyond their maximum height. + The implicit maximum height for any other item is \c Number.POSITIVE_INFINITY. + + Setting this value to \c -1 will reset the height back to its implicit maximum height. + + \sa minimumHeight + \sa preferredHeight +*/ +void QQuickLayoutAttached::setMaximumHeight(qreal height) +{ + if (qIsNaN(height)) + return; + m_isMaximumHeightSet = height >= 0; + if (m_maximumHeight == height) + return; + + m_maximumHeight = height; + invalidateItem(); + emit maximumHeightChanged(); +} + +void QQuickLayoutAttached::setMinimumImplicitSize(const QSizeF &sz) +{ + bool emitWidthChanged = false; + bool emitHeightChanged = false; + if (!m_isMinimumWidthSet && m_minimumWidth != sz.width()) { + m_minimumWidth = sz.width(); + emitWidthChanged = true; + } + if (!m_isMinimumHeightSet && m_minimumHeight != sz.height()) { + m_minimumHeight = sz.height(); + emitHeightChanged = true; + } + // Only invalidate the item once, and make sure we emit signal changed after the call to + // invalidateItem() + if (emitWidthChanged || emitHeightChanged) { + invalidateItem(); + if (emitWidthChanged) + emit minimumWidthChanged(); + if (emitHeightChanged) + emit minimumHeightChanged(); + } +} + +void QQuickLayoutAttached::setMaximumImplicitSize(const QSizeF &sz) +{ + bool emitWidthChanged = false; + bool emitHeightChanged = false; + if (!m_isMaximumWidthSet && m_maximumWidth != sz.width()) { + m_maximumWidth = sz.width(); + emitWidthChanged = true; + } + if (!m_isMaximumHeightSet && m_maximumHeight != sz.height()) { + m_maximumHeight = sz.height(); + emitHeightChanged = true; + } + // Only invalidate the item once, and make sure we emit changed signal after the call to + // invalidateItem() + if (emitWidthChanged || emitHeightChanged) { + invalidateItem(); + if (emitWidthChanged) + emit maximumWidthChanged(); + if (emitHeightChanged) + emit maximumHeightChanged(); + } +} + +/*! + \qmlattachedproperty bool Layout::fillWidth + + If this property is \c true, the item will be as wide as possible while respecting + the given constraints. If the property is \c false, the item will have a fixed width + set to the preferred width. + The default is \c false, except for layouts themselves, which default to \c true. + + \sa fillHeight +*/ +void QQuickLayoutAttached::setFillWidth(bool fill) +{ + m_isFillWidthSet = true; + if (m_fillWidth != fill) { + m_fillWidth = fill; + invalidateItem(); + emit fillWidthChanged(); + } +} + +/*! + \qmlattachedproperty bool Layout::fillHeight + + If this property is \c true, the item will be as tall as possible while respecting + the given constraints. If the property is \c false, the item will have a fixed height + set to the preferred height. + The default is \c false, except for layouts themselves, which default to \c true. + + \sa fillWidth +*/ +void QQuickLayoutAttached::setFillHeight(bool fill) +{ + m_isFillHeightSet = true; + if (m_fillHeight != fill) { + m_fillHeight = fill; + invalidateItem(); + emit fillHeightChanged(); + } +} + +/*! + \qmlattachedproperty int Layout::row + + This property allows you to specify the row position of an item in a \l GridLayout. + + If both \l column and this property are not set, it is up to the layout to assign a cell to the item. + + The default value is \c 0. + + \sa column + \sa rowSpan +*/ +void QQuickLayoutAttached::setRow(int row) +{ + if (row >= 0 && row != m_row) { + m_row = row; + repopulateLayout(); + emit rowChanged(); + } +} + +/*! + \qmlattachedproperty int Layout::column + + This property allows you to specify the column position of an item in a \l GridLayout. + + If both \l row and this property are not set, it is up to the layout to assign a cell to the item. + + The default value is \c 0. + + \sa row + \sa columnSpan +*/ +void QQuickLayoutAttached::setColumn(int column) +{ + if (column >= 0 && column != m_column) { + m_column = column; + repopulateLayout(); + emit columnChanged(); + } +} + + +/*! + \qmlattachedproperty Qt.Alignment Layout::alignment + + This property allows you to specify the alignment of an item within the cell(s) it occupies. + + The default value is \c 0, which means it will be \c{Qt.AlignVCenter | Qt.AlignLeft} + + A valid alignment is a combination of the following flags: + \list + \li Qt::AlignLeft + \li Qt::AlignHCenter + \li Qt::AlignRight + \li Qt::AlignTop + \li Qt::AlignVCenter + \li Qt::AlignBottom + \li Qt::AlignBaseline + \endlist + +*/ +void QQuickLayoutAttached::setAlignment(Qt::Alignment align) +{ + if (align != m_alignment) { + m_alignment = align; + if (QQuickLayout *layout = parentLayout()) { + layout->setAlignment(item(), align); + invalidateItem(); + } + emit alignmentChanged(); + } +} + +/*! + \qmlattachedproperty real Layout::margins + + Sets the margins outside of an item to all have the same value. The item + itself does not evaluate its own margins. It is the parent's responsibility + to decide if it wants to evaluate the margins. + + Specifically, margins are only evaluated by ColumnLayout, RowLayout, + GridLayout, and other layout-like containers, such as SplitView, where the + effective cell size of an item will be increased as the margins are + increased. + + Therefore, if an item with margins is a child of another \c Item, its + position, size and implicit size will remain unchanged. + + Combining margins with alignment will align the item \e including its + margins. For instance, a vertically-centered Item with a top margin of \c 1 + and a bottom margin of \c 9 will cause the Items effective alignment within + the cell to be 4 pixels above the center. + + The default value is \c 0. + + \sa leftMargin + \sa topMargin + \sa rightMargin + \sa bottomMargin + + \since QtQuick.Layouts 1.2 +*/ +void QQuickLayoutAttached::setMargins(qreal m) +{ + if (m == m_defaultMargins) + return; + + m_defaultMargins = m; + invalidateItem(); + if (!m_isLeftMarginSet && m_margins.left() != m) + emit leftMarginChanged(); + if (!m_isTopMarginSet && m_margins.top() != m) + emit topMarginChanged(); + if (!m_isRightMarginSet && m_margins.right() != m) + emit rightMarginChanged(); + if (!m_isBottomMarginSet && m_margins.bottom() != m) + emit bottomMarginChanged(); + emit marginsChanged(); +} + +/*! + \qmlattachedproperty real Layout::leftMargin + + Specifies the left margin outside of an item. + If the value is not set, it will use the value from \l margins. + + \sa margins + + \since QtQuick.Layouts 1.2 +*/ +void QQuickLayoutAttached::setLeftMargin(qreal m) +{ + const bool changed = leftMargin() != m; + m_margins.setLeft(m); + m_isLeftMarginSet = true; + if (changed) { + invalidateItem(); + emit leftMarginChanged(); + } +} + +void QQuickLayoutAttached::resetLeftMargin() +{ + const bool changed = m_isLeftMarginSet && (m_defaultMargins != m_margins.left()); + m_isLeftMarginSet = false; + if (changed) { + invalidateItem(); + emit leftMarginChanged(); + } +} + +/*! + \qmlattachedproperty real Layout::topMargin + + Specifies the top margin outside of an item. + If the value is not set, it will use the value from \l margins. + + \sa margins + + \since QtQuick.Layouts 1.2 +*/ +void QQuickLayoutAttached::setTopMargin(qreal m) +{ + const bool changed = topMargin() != m; + m_margins.setTop(m); + m_isTopMarginSet = true; + if (changed) { + invalidateItem(); + emit topMarginChanged(); + } +} + +void QQuickLayoutAttached::resetTopMargin() +{ + const bool changed = m_isTopMarginSet && (m_defaultMargins != m_margins.top()); + m_isTopMarginSet = false; + if (changed) { + invalidateItem(); + emit topMarginChanged(); + } +} + +/*! + \qmlattachedproperty real Layout::rightMargin + + Specifies the right margin outside of an item. + If the value is not set, it will use the value from \l margins. + + \sa margins + + \since QtQuick.Layouts 1.2 +*/ +void QQuickLayoutAttached::setRightMargin(qreal m) +{ + const bool changed = rightMargin() != m; + m_margins.setRight(m); + m_isRightMarginSet = true; + if (changed) { + invalidateItem(); + emit rightMarginChanged(); + } +} + +void QQuickLayoutAttached::resetRightMargin() +{ + const bool changed = m_isRightMarginSet && (m_defaultMargins != m_margins.right()); + m_isRightMarginSet = false; + if (changed) { + invalidateItem(); + emit rightMarginChanged(); + } +} + +/*! + \qmlattachedproperty real Layout::bottomMargin + + Specifies the bottom margin outside of an item. + If the value is not set, it will use the value from \l margins. + + \sa margins + + \since QtQuick.Layouts 1.2 +*/ +void QQuickLayoutAttached::setBottomMargin(qreal m) +{ + const bool changed = bottomMargin() != m; + m_margins.setBottom(m); + m_isBottomMarginSet = true; + if (changed) { + invalidateItem(); + emit bottomMarginChanged(); + } +} + +void QQuickLayoutAttached::resetBottomMargin() +{ + const bool changed = m_isBottomMarginSet && (m_defaultMargins != m_margins.bottom()); + m_isBottomMarginSet = false; + if (changed) { + invalidateItem(); + emit bottomMarginChanged(); + } +} + + +/*! + \qmlattachedproperty int Layout::rowSpan + + This property allows you to specify the row span of an item in a \l GridLayout. + + The default value is \c 1. + + \sa columnSpan + \sa row +*/ +void QQuickLayoutAttached::setRowSpan(int span) +{ + if (span != m_rowSpan) { + m_rowSpan = span; + repopulateLayout(); + emit rowSpanChanged(); + } +} + + +/*! + \qmlattachedproperty int Layout::columnSpan + + This property allows you to specify the column span of an item in a \l GridLayout. + + The default value is \c 1. + + \sa rowSpan + \sa column +*/ +void QQuickLayoutAttached::setColumnSpan(int span) +{ + if (span != m_columnSpan) { + m_columnSpan = span; + repopulateLayout(); + emit columnSpanChanged(); + } +} + + +qreal QQuickLayoutAttached::sizeHint(Qt::SizeHint which, Qt::Orientation orientation) const +{ + qreal result = 0; + if (QQuickLayout *layout = qobject_cast<QQuickLayout *>(item())) { + const QSizeF sz = layout->sizeHint(which); + result = (orientation == Qt::Horizontal ? sz.width() : sz.height()); + } else { + if (which == Qt::MaximumSize) + result = std::numeric_limits<qreal>::infinity(); + } + return result; +} + +void QQuickLayoutAttached::invalidateItem() +{ + if (!m_changesNotificationEnabled) + return; + quickLayoutDebug() << "QQuickLayoutAttached::invalidateItem"; + if (QQuickLayout *layout = parentLayout()) { + layout->invalidate(item()); + } +} + +void QQuickLayoutAttached::repopulateLayout() +{ + if (QQuickLayout *layout = parentLayout()) + layout->updateLayoutItems(); +} + +QQuickLayout *QQuickLayoutAttached::parentLayout() const +{ + QQuickItem *parentItem = item(); + if (parentItem) { + parentItem = parentItem->parentItem(); + return qobject_cast<QQuickLayout *>(parentItem); + } else { + qWarning("Layout must be attached to Item elements"); + } + return 0; +} + +QQuickItem *QQuickLayoutAttached::item() const +{ + return qobject_cast<QQuickItem *>(parent()); +} + + +QQuickLayout::QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent) + : QQuickItem(dd, parent), + m_dirty(false) +{ +} + +QQuickLayout::~QQuickLayout() +{ + d_func()->m_isReady = false; +} + +QQuickLayoutAttached *QQuickLayout::qmlAttachedProperties(QObject *object) +{ + return new QQuickLayoutAttached(object); +} + +void QQuickLayout::updatePolish() +{ + rearrange(QSizeF(width(), height())); +} + +void QQuickLayout::componentComplete() +{ + Q_D(QQuickLayout); + d->m_disableRearrange = true; + QQuickItem::componentComplete(); // will call our geometryChanged(), (where isComponentComplete() == true) + d->m_disableRearrange = false; + d->m_isReady = true; +} + +void QQuickLayout::invalidate(QQuickItem * /*childItem*/) +{ + if (m_dirty) + return; + + m_dirty = true; + + if (!qobject_cast<QQuickLayout *>(parentItem())) { + quickLayoutDebug() << "QQuickLayout::invalidate(), polish()"; + polish(); + } +} + +bool QQuickLayout::shouldIgnoreItem(QQuickItem *child, QQuickLayoutAttached *&info, QSizeF *sizeHints) const +{ + Q_D(const QQuickLayout); + bool ignoreItem = true; + QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(child); + if (childPrivate->explicitVisible) { + effectiveSizeHints_helper(child, sizeHints, &info, true); + QSizeF effectiveMaxSize = sizeHints[Qt::MaximumSize]; + if (!effectiveMaxSize.isNull()) { + QSizeF &prefS = sizeHints[Qt::PreferredSize]; + if (effectiveSizePolicy_helper(child, Qt::Horizontal, info) == QLayoutPolicy::Fixed) + effectiveMaxSize.setWidth(prefS.width()); + if (effectiveSizePolicy_helper(child, Qt::Vertical, info) == QLayoutPolicy::Fixed) + effectiveMaxSize.setHeight(prefS.height()); + } + ignoreItem = effectiveMaxSize.isNull(); + } + + if (ignoreItem) + d->m_ignoredItems << child; + return ignoreItem; +} +struct QQuickItemPublic : public QQuickItem { + static bool isCompleted(QQuickItem *item) { + return static_cast<QQuickItemPublic*>(item)->isComponentComplete(); + } +}; + +void QQuickLayout::itemChange(ItemChange change, const ItemChangeData &value) +{ + if (change == ItemChildAddedChange) { + QQuickItem *item = value.item; + QObject::connect(item, SIGNAL(implicitWidthChanged()), this, SLOT(invalidateSenderItem())); + QObject::connect(item, SIGNAL(implicitHeightChanged()), this, SLOT(invalidateSenderItem())); + QObject::connect(item, SIGNAL(baselineOffsetChanged(qreal)), this, SLOT(invalidateSenderItem())); + QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::SiblingOrder); + if (isReady()) + updateLayoutItems(); + } else if (change == ItemChildRemovedChange) { + QQuickItem *item = value.item; + QObject::disconnect(item, SIGNAL(implicitWidthChanged()), this, SLOT(invalidateSenderItem())); + QObject::disconnect(item, SIGNAL(implicitHeightChanged()), this, SLOT(invalidateSenderItem())); + QObject::disconnect(item, SIGNAL(baselineOffsetChanged(qreal)), this, SLOT(invalidateSenderItem())); + QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::SiblingOrder); + if (isReady()) + updateLayoutItems(); + } + QQuickItem::itemChange(change, value); +} + +void QQuickLayout::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickLayout); + QQuickItem::geometryChanged(newGeometry, oldGeometry); + if (d->m_disableRearrange || !isReady() || !newGeometry.isValid()) + return; + + quickLayoutDebug() << "QQuickStackLayout::geometryChanged" << newGeometry << oldGeometry; + rearrange(newGeometry.size()); +} + +void QQuickLayout::invalidateSenderItem() +{ + if (!isReady()) + return; + QQuickItem *item = static_cast<QQuickItem *>(sender()); + Q_ASSERT(item); + invalidate(item); +} + +bool QQuickLayout::isReady() const +{ + return d_func()->m_isReady; +} + +void QQuickLayout::itemSiblingOrderChanged(QQuickItem *item) +{ + Q_UNUSED(item); + updateLayoutItems(); +} + +void QQuickLayout::rearrange(const QSizeF &/*size*/) +{ + m_dirty = false; +} + + +/* + The layout engine assumes: + 1. minimum <= preferred <= maximum + 2. descent is within minimum and maximum bounds (### verify) + + This function helps to ensure that by the following rules (in the following order): + 1. If minimum > maximum, set minimum = maximum + 2. Clamp preferred to be between the [minimum,maximum] range. + 3. If descent > minimum, set descent = minimum (### verify if this is correct, it might + need some refinements to multiline texts) + + If any values are "not set" (i.e. negative), they will be left untouched, so that we + know which values needs to be fetched from the implicit hints (not user hints). + */ +static void normalizeHints(qreal &minimum, qreal &preferred, qreal &maximum, qreal &descent) +{ + if (minimum >= 0 && maximum >= 0 && minimum > maximum) + minimum = maximum; + + if (preferred >= 0) { + if (minimum >= 0 && preferred < minimum) { + preferred = minimum; + } else if (maximum >= 0 && preferred > maximum) { + preferred = maximum; + } + } + + if (minimum >= 0 && descent > minimum) + descent = minimum; +} + +static void boundSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() < result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() < result.height()) + result.setHeight(size.height()); +} + +static void expandSize(QSizeF &result, const QSizeF &size) +{ + if (size.width() >= 0 && size.width() > result.width()) + result.setWidth(size.width()); + if (size.height() >= 0 && size.height() > result.height()) + result.setHeight(size.height()); +} + +static inline void combineHints(qreal ¤t, qreal fallbackHint) +{ + if (current < 0) + current = fallbackHint; +} + +static inline void combineSize(QSizeF &result, const QSizeF &fallbackSize) +{ + combineHints(result.rwidth(), fallbackSize.width()); + combineHints(result.rheight(), fallbackSize.height()); +} + +static inline void combineImplicitHints(QQuickLayoutAttached *info, Qt::SizeHint which, QSizeF *size) +{ + if (!info) return; + + Q_ASSERT(which == Qt::MinimumSize || which == Qt::MaximumSize); + + const QSizeF constraint(which == Qt::MinimumSize + ? QSizeF(info->minimumWidth(), info->minimumHeight()) + : QSizeF(info->maximumWidth(), info->maximumHeight())); + + if (!info->isExtentExplicitlySet(Qt::Horizontal, which)) + combineHints(size->rwidth(), constraint.width()); + if (!info->isExtentExplicitlySet(Qt::Vertical, which)) + combineHints(size->rheight(), constraint.height()); +} + +typedef qreal (QQuickLayoutAttached::*SizeGetter)() const; + +/*! + \internal + Note: Can potentially return the attached QQuickLayoutAttached object through \a attachedInfo. + + It is like this is because it enables it to be reused. + + The goal of this function is to return the effective minimum, preferred and maximum size hints + that the layout will use for this item. + This function takes care of gathering all explicitly set size hints, normalizes them so + that min < pref < max. + Further, the hints _not_explicitly_ set will then be initialized with the implicit size hints, + which is usually derived from the content of the layouts (or items). + + The following table illustrates the preference of the properties used for measuring layout + items. If present, the USER properties will be preferred. If USER properties are not present, + the HINT properties will be preferred. Finally, the FALLBACK properties will be used as an + ultimate fallback. + + Note that one can query if the value of Layout.minimumWidth or Layout.maximumWidth has been + explicitly or implicitly set with QQuickLayoutAttached::isExtentExplicitlySet(). This + determines if it should be used as a USER or as a HINT value. + + Fractional size hints will be ceiled to the closest integer. This is in order to give some + slack when the items are snapped to the pixel grid. + + | *Minimum* | *Preferred* | *Maximum* | ++----------------+----------------------+-----------------------+--------------------------+ +|USER (explicit) | Layout.minimumWidth | Layout.preferredWidth | Layout.maximumWidth | +|HINT (implicit) | Layout.minimumWidth | implicitWidth | Layout.maximumWidth | +|FALLBACK | 0 | width | Number.POSITIVE_INFINITY | ++----------------+----------------------+-----------------------+--------------------------+ + */ +void QQuickLayout::effectiveSizeHints_helper(QQuickItem *item, QSizeF *cachedSizeHints, QQuickLayoutAttached **attachedInfo, bool useFallbackToWidthOrHeight) +{ + for (int i = 0; i < Qt::NSizeHints; ++i) + cachedSizeHints[i] = QSizeF(); + QQuickLayoutAttached *info = attachedLayoutObject(item, false); + // First, retrieve the user-specified hints from the attached "Layout." properties + if (info) { + struct Getters { + SizeGetter call[NSizes]; + }; + + static Getters horGetters = { + {&QQuickLayoutAttached::minimumWidth, &QQuickLayoutAttached::preferredWidth, &QQuickLayoutAttached::maximumWidth}, + }; + + static Getters verGetters = { + {&QQuickLayoutAttached::minimumHeight, &QQuickLayoutAttached::preferredHeight, &QQuickLayoutAttached::maximumHeight} + }; + for (int i = 0; i < NSizes; ++i) { + SizeGetter getter = horGetters.call[i]; + Q_ASSERT(getter); + + if (info->isExtentExplicitlySet(Qt::Horizontal, (Qt::SizeHint)i)) + cachedSizeHints[i].setWidth((info->*getter)()); + + getter = verGetters.call[i]; + Q_ASSERT(getter); + if (info->isExtentExplicitlySet(Qt::Vertical, (Qt::SizeHint)i)) + cachedSizeHints[i].setHeight((info->*getter)()); + } + } + + QSizeF &minS = cachedSizeHints[Qt::MinimumSize]; + QSizeF &prefS = cachedSizeHints[Qt::PreferredSize]; + QSizeF &maxS = cachedSizeHints[Qt::MaximumSize]; + QSizeF &descentS = cachedSizeHints[Qt::MinimumDescent]; + + // For instance, will normalize the following user-set hints + // from: [10, 5, 60] + // to: [10, 10, 60] + normalizeHints(minS.rwidth(), prefS.rwidth(), maxS.rwidth(), descentS.rwidth()); + normalizeHints(minS.rheight(), prefS.rheight(), maxS.rheight(), descentS.rheight()); + + // All explicit values gathered, now continue to gather the implicit sizes + + //--- GATHER MAXIMUM SIZE HINTS --- + combineImplicitHints(info, Qt::MaximumSize, &maxS); + combineSize(maxS, QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity())); + // implicit max or min sizes should not limit an explicitly set preferred size + expandSize(maxS, prefS); + expandSize(maxS, minS); + + //--- GATHER MINIMUM SIZE HINTS --- + combineImplicitHints(info, Qt::MinimumSize, &minS); + expandSize(minS, QSizeF(0,0)); + boundSize(minS, prefS); + boundSize(minS, maxS); + + //--- GATHER PREFERRED SIZE HINTS --- + // First, from implicitWidth/Height + qreal &prefWidth = prefS.rwidth(); + qreal &prefHeight = prefS.rheight(); + if (prefWidth < 0 && item->implicitWidth() > 0) + prefWidth = qCeil(item->implicitWidth()); + if (prefHeight < 0 && item->implicitHeight() > 0) + prefHeight = qCeil(item->implicitHeight()); + + // If that fails, make an ultimate fallback to width/height + + if (!info && (prefWidth < 0 || prefHeight < 0)) + info = attachedLayoutObject(item); + + if (useFallbackToWidthOrHeight && info) { + /* This block is a bit hacky, but if we want to support using width/height + as preferred size hints in layouts, (which we think most people expect), + we only want to use the initial width. + This is because the width will change due to layout rearrangement, and the preferred + width should return the same value, regardless of the current width. + We therefore store the width in the implicitWidth attached property. + Since the layout listens to changes of implicitWidth, (it will + basically cause an invalidation of the layout), we have to disable that + notification while we set the implicit width (and height). + + Only use this fallback the first time the size hint is queried. Otherwise, we might + end up picking a width that is different than what was specified in the QML. + */ + if (prefWidth < 0 || prefHeight < 0) { + item->blockSignals(true); + if (prefWidth < 0) { + prefWidth = item->width(); + item->setImplicitWidth(prefWidth); + } + if (prefHeight < 0) { + prefHeight = item->height(); + item->setImplicitHeight(prefHeight); + } + item->blockSignals(false); + } + } + + + + // Normalize again after the implicit hints have been gathered + expandSize(prefS, minS); + boundSize(prefS, maxS); + + //--- GATHER DESCENT + // Minimum descent is only applicable for the effective minimum height, + // so we gather the descent last. + const qreal minimumDescent = minS.height() - item->baselineOffset(); + descentS.setHeight(minimumDescent); + + if (info) { + QMarginsF margins = info->qMargins(); + QSizeF extraMargins(margins.left() + margins.right(), margins.top() + margins.bottom()); + minS += extraMargins; + prefS += extraMargins; + maxS += extraMargins; + descentS += extraMargins; + } + if (attachedInfo) + *attachedInfo = info; +} + +/*! + \internal + + Assumes \a info is set (if the object has an attached property) + */ +QLayoutPolicy::Policy QQuickLayout::effectiveSizePolicy_helper(QQuickItem *item, Qt::Orientation orientation, QQuickLayoutAttached *info) +{ + bool fillExtent = false; + bool isSet = false; + if (info) { + if (orientation == Qt::Horizontal) { + isSet = info->isFillWidthSet(); + if (isSet) fillExtent = info->fillWidth(); + } else { + isSet = info->isFillHeightSet(); + if (isSet) fillExtent = info->fillHeight(); + } + } + if (!isSet && qobject_cast<QQuickLayout*>(item)) + fillExtent = true; + return fillExtent ? QLayoutPolicy::Preferred : QLayoutPolicy::Fixed; + +} + + + +QT_END_NAMESPACE diff --git a/src/imports/layouts/qquicklayout_p.h b/src/imports/layouts/qquicklayout_p.h new file mode 100644 index 0000000000..c7f04c1fed --- /dev/null +++ b/src/imports/layouts/qquicklayout_p.h @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLAYOUT_P_H +#define QQUICKLAYOUT_P_H + +#include <QPointer> +#include <QQuickItem> +#include <private/qquickitem_p.h> +#include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtGui/private/qlayoutpolicy_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickLayoutAttached; + +#if 0 && !defined(QT_NO_DEBUG) && !defined(QT_NO_DEBUG_OUTPUT) +# define quickLayoutDebug QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).debug +#else +# define quickLayoutDebug QT_NO_QDEBUG_MACRO +#endif + +class QQuickLayoutPrivate; +class QQuickLayout : public QQuickItem, public QQuickItemChangeListener + +{ + Q_OBJECT +public: + enum SizeHint { + MinimumSize = 0, + PreferredSize, + MaximumSize, + NSizes + }; + + explicit QQuickLayout(QQuickLayoutPrivate &dd, QQuickItem *parent = 0); + ~QQuickLayout(); + + static QQuickLayoutAttached *qmlAttachedProperties(QObject *object); + + + void componentComplete() Q_DECL_OVERRIDE; + virtual QSizeF sizeHint(Qt::SizeHint whichSizeHint) const = 0; + virtual void setAlignment(QQuickItem *item, Qt::Alignment align) = 0; + virtual void invalidate(QQuickItem * childItem = 0); + virtual void updateLayoutItems() = 0; + + // iterator + virtual QQuickItem *itemAt(int index) const = 0; + virtual int itemCount() const = 0; + + virtual void rearrange(const QSizeF &); + bool arrangementIsDirty() const { return m_dirty; } + + static void effectiveSizeHints_helper(QQuickItem *item, QSizeF *cachedSizeHints, QQuickLayoutAttached **info, bool useFallbackToWidthOrHeight); + static QLayoutPolicy::Policy effectiveSizePolicy_helper(QQuickItem *item, Qt::Orientation orientation, QQuickLayoutAttached *info); + bool shouldIgnoreItem(QQuickItem *child, QQuickLayoutAttached *&info, QSizeF *sizeHints) const; + + void itemChange(ItemChange change, const ItemChangeData &value) Q_DECL_OVERRIDE; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) Q_DECL_OVERRIDE; + bool isReady() const; + + + /* QQuickItemChangeListener */ + void itemSiblingOrderChanged(QQuickItem *item) Q_DECL_OVERRIDE; + +protected: + void updatePolish() Q_DECL_OVERRIDE; + + enum Orientation { + Vertical = 0, + Horizontal, + NOrientations + }; + +protected slots: + void invalidateSenderItem(); + +private: + bool m_dirty; + + Q_DECLARE_PRIVATE(QQuickLayout) + + friend class QQuickLayoutAttached; +}; + + +class QQuickLayoutPrivate : public QQuickItemPrivate +{ + Q_DECLARE_PUBLIC(QQuickLayout) +public: + QQuickLayoutPrivate() : m_isReady(false), m_disableRearrange(true) {} + +protected: + unsigned m_isReady : 1; + unsigned m_disableRearrange : 1; + mutable QSet<QQuickItem *> m_ignoredItems; +}; + + +class QQuickLayoutAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth NOTIFY minimumWidthChanged) + Q_PROPERTY(qreal minimumHeight READ minimumHeight WRITE setMinimumHeight NOTIFY minimumHeightChanged) + Q_PROPERTY(qreal preferredWidth READ preferredWidth WRITE setPreferredWidth NOTIFY preferredWidthChanged) + Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged) + Q_PROPERTY(qreal maximumWidth READ maximumWidth WRITE setMaximumWidth NOTIFY maximumWidthChanged) + Q_PROPERTY(qreal maximumHeight READ maximumHeight WRITE setMaximumHeight NOTIFY maximumHeightChanged) + Q_PROPERTY(bool fillHeight READ fillHeight WRITE setFillHeight NOTIFY fillHeightChanged) + Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged) + Q_PROPERTY(int row READ row WRITE setRow NOTIFY rowChanged) + Q_PROPERTY(int column READ column WRITE setColumn NOTIFY columnChanged) + Q_PROPERTY(int rowSpan READ rowSpan WRITE setRowSpan NOTIFY rowSpanChanged) + Q_PROPERTY(int columnSpan READ columnSpan WRITE setColumnSpan NOTIFY columnSpanChanged) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) + + Q_PROPERTY(qreal margins READ margins WRITE setMargins NOTIFY marginsChanged) + Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin RESET resetLeftMargin NOTIFY leftMarginChanged) + Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin RESET resetTopMargin NOTIFY topMarginChanged) + Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin RESET resetRightMargin NOTIFY rightMarginChanged) + Q_PROPERTY(qreal bottomMargin READ bottomMargin WRITE setBottomMargin RESET resetBottomMargin NOTIFY bottomMarginChanged) + +public: + QQuickLayoutAttached(QObject *object); + + qreal minimumWidth() const { return !m_isMinimumWidthSet ? sizeHint(Qt::MinimumSize, Qt::Horizontal) : m_minimumWidth; } + void setMinimumWidth(qreal width); + + qreal minimumHeight() const { return !m_isMinimumHeightSet ? sizeHint(Qt::MinimumSize, Qt::Vertical) : m_minimumHeight; } + void setMinimumHeight(qreal height); + + qreal preferredWidth() const { return m_preferredWidth; } + void setPreferredWidth(qreal width); + + qreal preferredHeight() const { return m_preferredHeight; } + void setPreferredHeight(qreal width); + + qreal maximumWidth() const { return !m_isMaximumWidthSet ? sizeHint(Qt::MaximumSize, Qt::Horizontal) : m_maximumWidth; } + void setMaximumWidth(qreal width); + + qreal maximumHeight() const { return !m_isMaximumHeightSet ? sizeHint(Qt::MaximumSize, Qt::Vertical) : m_maximumHeight; } + void setMaximumHeight(qreal height); + + void setMinimumImplicitSize(const QSizeF &sz); + void setMaximumImplicitSize(const QSizeF &sz); + + bool fillWidth() const { return m_fillWidth; } + void setFillWidth(bool fill); + bool isFillWidthSet() const { return m_isFillWidthSet; } + + bool fillHeight() const { return m_fillHeight; } + void setFillHeight(bool fill); + bool isFillHeightSet() const { return m_isFillHeightSet; } + + int row() const { return qMax(m_row, 0); } + void setRow(int row); + bool isRowSet() const { return m_row >= 0; } + int column() const { return qMax(m_column, 0); } + void setColumn(int column); + bool isColumnSet() const { return m_column >= 0; } + + int rowSpan() const { return m_rowSpan; } + void setRowSpan(int span); + int columnSpan() const { return m_columnSpan; } + void setColumnSpan(int span); + + Qt::Alignment alignment() const { return m_alignment; } + void setAlignment(Qt::Alignment align); + + qreal margins() const { return m_defaultMargins; } + void setMargins(qreal m); + + qreal leftMargin() const { return m_isLeftMarginSet ? m_margins.left() : m_defaultMargins; } + void setLeftMargin(qreal m); + void resetLeftMargin(); + + qreal topMargin() const { return m_isTopMarginSet ? m_margins.top() : m_defaultMargins; } + void setTopMargin(qreal m); + void resetTopMargin(); + + qreal rightMargin() const { return m_isRightMarginSet ? m_margins.right() : m_defaultMargins; } + void setRightMargin(qreal m); + void resetRightMargin(); + + qreal bottomMargin() const { return m_isBottomMarginSet ? m_margins.bottom() : m_defaultMargins; } + void setBottomMargin(qreal m); + void resetBottomMargin(); + + QMarginsF qMargins() const { + return QMarginsF(leftMargin(), topMargin(), rightMargin(), bottomMargin()); + } + + bool setChangesNotificationEnabled(bool enabled) + { + const bool old = m_changesNotificationEnabled; + m_changesNotificationEnabled = enabled; + return old; + } + + qreal sizeHint(Qt::SizeHint which, Qt::Orientation orientation) const; + + bool isExtentExplicitlySet(Qt::Orientation o, Qt::SizeHint whichSize) const + { + switch (whichSize) { + case Qt::MinimumSize: + return o == Qt::Horizontal ? m_isMinimumWidthSet : m_isMinimumHeightSet; + case Qt::MaximumSize: + return o == Qt::Horizontal ? m_isMaximumWidthSet : m_isMaximumHeightSet; + case Qt::PreferredSize: + return true; // Layout.preferredWidth is always explicitly set + case Qt::MinimumDescent: // Not supported + case Qt::NSizeHints: + return false; + } + return false; + } + +signals: + void minimumWidthChanged(); + void minimumHeightChanged(); + void preferredWidthChanged(); + void preferredHeightChanged(); + void maximumWidthChanged(); + void maximumHeightChanged(); + void fillWidthChanged(); + void fillHeightChanged(); + void leftMarginChanged(); + void topMarginChanged(); + void rightMarginChanged(); + void bottomMarginChanged(); + void marginsChanged(); + void rowChanged(); + void columnChanged(); + void rowSpanChanged(); + void columnSpanChanged(); + void alignmentChanged(); + +private: + void invalidateItem(); + void repopulateLayout(); + QQuickLayout *parentLayout() const; + QQuickItem *item() const; +private: + qreal m_minimumWidth; + qreal m_minimumHeight; + qreal m_preferredWidth; + qreal m_preferredHeight; + qreal m_maximumWidth; + qreal m_maximumHeight; + + qreal m_defaultMargins; + QMarginsF m_margins; + + // GridLayout specific properties + int m_row; + int m_column; + int m_rowSpan; + int m_columnSpan; + + unsigned m_fillWidth : 1; + unsigned m_fillHeight : 1; + unsigned m_isFillWidthSet : 1; + unsigned m_isFillHeightSet : 1; + unsigned m_isMinimumWidthSet : 1; + unsigned m_isMinimumHeightSet : 1; + // preferredWidth and preferredHeight are always explicit, since + // their implicit equivalent is implicitWidth and implicitHeight + unsigned m_isMaximumWidthSet : 1; + unsigned m_isMaximumHeightSet : 1; + unsigned m_changesNotificationEnabled : 1; + unsigned m_isLeftMarginSet : 1; + unsigned m_isTopMarginSet : 1; + unsigned m_isRightMarginSet : 1; + unsigned m_isBottomMarginSet : 1; + Qt::Alignment m_alignment; + friend class QQuickLayout; +}; + +inline QQuickLayoutAttached *attachedLayoutObject(QQuickItem *item, bool create = true) +{ + return static_cast<QQuickLayoutAttached *>(qmlAttachedPropertiesObject<QQuickLayout>(item, create)); +} + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickLayout) +QML_DECLARE_TYPEINFO(QQuickLayout, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKLAYOUT_P_H diff --git a/src/imports/layouts/qquicklayoutstyleinfo.cpp b/src/imports/layouts/qquicklayoutstyleinfo.cpp new file mode 100644 index 0000000000..c33ceffb2d --- /dev/null +++ b/src/imports/layouts/qquicklayoutstyleinfo.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/private/qfont_p.h> + +#include "qquicklayoutstyleinfo_p.h" + + +QT_BEGIN_NAMESPACE + +QQuickLayoutStyleInfo::QQuickLayoutStyleInfo() +{ +} + +qreal QQuickLayoutStyleInfo::spacing(Qt::Orientation /*orientation*/) const +{ +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_QNX) || defined(Q_OS_WINRT) + // On Android and iOS the default spacing between each UI element is 8dp + qreal spacing = 8.0; +#else + qreal spacing = 5.0; +#endif + +#ifndef Q_OS_OSX + // On OS X the DPI is always 72 so we should not scale it + spacing = qRound(spacing * (qreal(qt_defaultDpiX()) / 96.0)); +#endif + + return spacing; +} + +qreal QQuickLayoutStyleInfo::windowMargin(Qt::Orientation /*orientation*/) const +{ + return 0; +} + +bool QQuickLayoutStyleInfo::hasChangedCore() const +{ + // never changes + return false; +} + +QT_END_NAMESPACE + diff --git a/src/imports/layouts/qquicklayoutstyleinfo_p.h b/src/imports/layouts/qquicklayoutstyleinfo_p.h new file mode 100644 index 0000000000..ce86c2a37d --- /dev/null +++ b/src/imports/layouts/qquicklayoutstyleinfo_p.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLAYOUTSTYLEINFO_P_H +#define QQUICKLAYOUTSTYLEINFO_P_H + +#include <QtGui/private/qabstractlayoutstyleinfo_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickLayoutStyleInfo : public QAbstractLayoutStyleInfo +{ +public: + QQuickLayoutStyleInfo(); + + qreal spacing(Qt::Orientation orientation) const Q_DECL_OVERRIDE; + qreal windowMargin(Qt::Orientation orientation) const Q_DECL_OVERRIDE; + bool hasChangedCore() const Q_DECL_OVERRIDE; + +}; + +QT_END_NAMESPACE + +#endif // QQUICKLAYOUTSTYLEINFO_P_H diff --git a/src/imports/layouts/qquicklinearlayout.cpp b/src/imports/layouts/qquicklinearlayout.cpp new file mode 100644 index 0000000000..2f8af4c58b --- /dev/null +++ b/src/imports/layouts/qquicklinearlayout.cpp @@ -0,0 +1,908 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicklinearlayout_p.h" +#include "qquickgridlayoutengine_p.h" +#include "qquicklayoutstyleinfo_p.h" +#include <QtCore/qnumeric.h> +#include "qdebug.h" +#include <limits> + +/*! + \qmltype RowLayout + \instantiates QQuickRowLayout + \inherits Item + \inqmlmodule QtQuick.Layouts + \ingroup layouts + \brief Identical to \l GridLayout, but having only one row. + + It is available as a convenience for developers, as it offers a cleaner API. + + Items in a RowLayout support these attached properties: + \list + \li \l{Layout::minimumWidth}{Layout.minimumWidth} + \li \l{Layout::minimumHeight}{Layout.minimumHeight} + \li \l{Layout::preferredWidth}{Layout.preferredWidth} + \li \l{Layout::preferredHeight}{Layout.preferredHeight} + \li \l{Layout::maximumWidth}{Layout.maximumWidth} + \li \l{Layout::maximumHeight}{Layout.maximumHeight} + \li \l{Layout::fillWidth}{Layout.fillWidth} + \li \l{Layout::fillHeight}{Layout.fillHeight} + \li \l{Layout::alignment}{Layout.alignment} + \endlist + + \image rowlayout.png + + \code + RowLayout { + id: layout + anchors.fill: parent + spacing: 6 + Rectangle { + color: 'teal' + Layout.fillWidth: true + Layout.minimumWidth: 50 + Layout.preferredWidth: 100 + Layout.maximumWidth: 300 + Layout.minimumHeight: 150 + Text { + anchors.centerIn: parent + text: parent.width + 'x' + parent.height + } + } + Rectangle { + color: 'plum' + Layout.fillWidth: true + Layout.minimumWidth: 100 + Layout.preferredWidth: 200 + Layout.preferredHeight: 100 + Text { + anchors.centerIn: parent + text: parent.width + 'x' + parent.height + } + } + } + \endcode + + Read more about attached properties \l{QML Object Attributes}{here}. + \sa ColumnLayout + \sa GridLayout + \sa Row +*/ + +/*! + \qmltype ColumnLayout + \instantiates QQuickColumnLayout + \inherits Item + \inqmlmodule QtQuick.Layouts + \ingroup layouts + \brief Identical to \l GridLayout, but having only one column. + + It is available as a convenience for developers, as it offers a cleaner API. + + Items in a ColumnLayout support these attached properties: + \list + \li \l{Layout::minimumWidth}{Layout.minimumWidth} + \li \l{Layout::minimumHeight}{Layout.minimumHeight} + \li \l{Layout::preferredWidth}{Layout.preferredWidth} + \li \l{Layout::preferredHeight}{Layout.preferredHeight} + \li \l{Layout::maximumWidth}{Layout.maximumWidth} + \li \l{Layout::maximumHeight}{Layout.maximumHeight} + \li \l{Layout::fillWidth}{Layout.fillWidth} + \li \l{Layout::fillHeight}{Layout.fillHeight} + \li \l{Layout::alignment}{Layout.alignment} + \endlist + + \image columnlayout.png + + \code + ColumnLayout{ + spacing: 2 + + Rectangle { + Layout.alignment: Qt.AlignCenter + color: "red" + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + } + + Rectangle { + Layout.alignment: Qt.AlignRight + color: "green" + Layout.preferredWidth: 40 + Layout.preferredHeight: 70 + } + + Rectangle { + Layout.alignment: Qt.AlignBottom + Layout.fillHeight: true + color: "blue" + Layout.preferredWidth: 70 + Layout.preferredHeight: 40 + } + } + \endcode + + Read more about attached properties \l{QML Object Attributes}{here}. + + \sa RowLayout + \sa GridLayout + \sa Column +*/ + + +/*! + \qmltype GridLayout + \instantiates QQuickGridLayout + \inherits Item + \inqmlmodule QtQuick.Layouts + \ingroup layouts + \brief Provides a way of dynamically arranging items in a grid. + + + + If the GridLayout is resized, all items in the layout will be rearranged. It is similar + to the widget-based QGridLayout. All visible children of the GridLayout element will belong to + the layout. If you want a layout with just one row or one column, you can use the + \l RowLayout or \l ColumnLayout. These offer a bit more convenient API, and improve + readability. + + By default items will be arranged according to the \l flow property. The default value of + the \l flow property is \c GridLayout.LeftToRight. + + If the \l columns property is specified, it will be treated as a maximum limit of how many + columns the layout can have, before the auto-positioning wraps back to the beginning of the + next row. The \l columns property is only used when \l flow is \c GridLayout.LeftToRight. + + \image gridlayout.png + + \code + GridLayout { + id: grid + columns: 3 + + Text { text: "Three"; font.bold: true; } + Text { text: "words"; color: "red" } + Text { text: "in"; font.underline: true } + Text { text: "a"; font.pixelSize: 20 } + Text { text: "row"; font.strikeout: true } + } + \endcode + + The \l rows property works in a similar way, but items are auto-positioned vertically. The \l + rows property is only used when \l flow is \c GridLayout.TopToBottom. + + You can specify which cell you want an item to occupy by setting the + \l{Layout::row}{Layout.row} and \l{Layout::column}{Layout.column} properties. You can also + specify the row span or column span by setting the \l{Layout::rowSpan}{Layout.rowSpan} or + \l{Layout::columnSpan}{Layout.columnSpan} properties. + + + Items in a GridLayout support these attached properties: + \list + \li \l{Layout::row}{Layout.row} + \li \l{Layout::column}{Layout.column} + \li \l{Layout::rowSpan}{Layout.rowSpan} + \li \l{Layout::columnSpan}{Layout.columnSpan} + \li \l{Layout::minimumWidth}{Layout.minimumWidth} + \li \l{Layout::minimumHeight}{Layout.minimumHeight} + \li \l{Layout::preferredWidth}{Layout.preferredWidth} + \li \l{Layout::preferredHeight}{Layout.preferredHeight} + \li \l{Layout::maximumWidth}{Layout.maximumWidth} + \li \l{Layout::maximumHeight}{Layout.maximumHeight} + \li \l{Layout::fillWidth}{Layout.fillWidth} + \li \l{Layout::fillHeight}{Layout.fillHeight} + \li \l{Layout::alignment}{Layout.alignment} + \endlist + + Read more about attached properties \l{QML Object Attributes}{here}. + + \sa RowLayout + \sa ColumnLayout + \sa Grid +*/ + + + +QT_BEGIN_NAMESPACE + +QQuickGridLayoutBase::QQuickGridLayoutBase() + : QQuickLayout(*new QQuickGridLayoutBasePrivate) +{ + +} + +QQuickGridLayoutBase::QQuickGridLayoutBase(QQuickGridLayoutBasePrivate &dd, + Qt::Orientation orientation, + QQuickItem *parent /*= 0*/) + : QQuickLayout(dd, parent) +{ + Q_D(QQuickGridLayoutBase); + d->orientation = orientation; + d->styleInfo = new QQuickLayoutStyleInfo; +} + +Qt::Orientation QQuickGridLayoutBase::orientation() const +{ + Q_D(const QQuickGridLayoutBase); + return d->orientation; +} + +void QQuickGridLayoutBase::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickGridLayoutBase); + if (d->orientation == orientation) + return; + + d->orientation = orientation; + invalidate(); +} + +QSizeF QQuickGridLayoutBase::sizeHint(Qt::SizeHint whichSizeHint) const +{ + Q_D(const QQuickGridLayoutBase); + return d->engine.sizeHint(whichSizeHint, QSizeF(), d->styleInfo); +} + +/*! + \qmlproperty enumeration GridLayout::layoutDirection + \since QtQuick.Layouts 1.1 + + This property holds the layout direction of the grid layout - it controls whether items are + laid out from left to right or right to left. If \c Qt.RightToLeft is specified, + left-aligned items will be right-aligned and right-aligned items will be left-aligned. + + Possible values: + + \list + \li Qt.LeftToRight (default) - Items are laid out from left to right. + \li Qt.RightToLeft - Items are laid out from right to left. + \endlist + + \sa RowLayout::layoutDirection, ColumnLayout::layoutDirection +*/ +Qt::LayoutDirection QQuickGridLayoutBase::layoutDirection() const +{ + Q_D(const QQuickGridLayoutBase); + return d->m_layoutDirection; +} + +void QQuickGridLayoutBase::setLayoutDirection(Qt::LayoutDirection dir) +{ + Q_D(QQuickGridLayoutBase); + d->m_layoutDirection = dir; + invalidate(); +} + +Qt::LayoutDirection QQuickGridLayoutBase::effectiveLayoutDirection() const +{ + Q_D(const QQuickGridLayoutBase); + return !d->effectiveLayoutMirror == (layoutDirection() == Qt::LeftToRight) + ? Qt::LeftToRight : Qt::RightToLeft; +} + +void QQuickGridLayoutBase::setAlignment(QQuickItem *item, Qt::Alignment alignment) +{ + Q_D(QQuickGridLayoutBase); + d->engine.setAlignment(item, alignment); +} + +QQuickGridLayoutBase::~QQuickGridLayoutBase() +{ + Q_D(QQuickGridLayoutBase); + + /* Avoid messy deconstruction, should give: + * Faster deconstruction + * Less risk of signals reaching already deleted objects + */ + for (int i = 0; i < itemCount(); ++i) { + QQuickItem *item = itemAt(i); + QObject::disconnect(item, SIGNAL(destroyed()), this, SLOT(onItemDestroyed())); + QObject::disconnect(item, SIGNAL(visibleChanged()), this, SLOT(onItemVisibleChanged())); + QObject::disconnect(item, SIGNAL(implicitWidthChanged()), this, SLOT(invalidateSenderItem())); + QObject::disconnect(item, SIGNAL(implicitHeightChanged()), this, SLOT(invalidateSenderItem())); + } + delete d->styleInfo; +} + +void QQuickGridLayoutBase::componentComplete() +{ + quickLayoutDebug() << objectName() << "QQuickGridLayoutBase::componentComplete()" << parent(); + QQuickLayout::componentComplete(); + updateLayoutItems(); + + QQuickItem *par = parentItem(); + if (qobject_cast<QQuickLayout*>(par)) + return; + rearrange(QSizeF(width(), height())); +} + +/* + Invalidation happens like this as a reaction to that a size hint changes on an item "a": + + Suppose we have the following Qml document: + RowLayout { + id: l1 + RowLayout { + id: l2 + Item { + id: a + } + Item { + id: b + } + } + } + + 1. l2->invalidateChildItem(a) is called on l2, where item refers to "a". + (this will dirty the cached size hints of item "a") + 2. l2->invalidate() is called + this will : + i) invalidate the layout engine + ii) dirty the cached size hints of item "l2" (by calling parentLayout()->invalidateChildItem + + */ +/*! + \internal + + Invalidates \a childItem and this layout. + After a call to invalidate, the next call to retrieve e.g. sizeHint will be up-to date. + This function will also call QQuickLayout::invalidate(0), to ensure that the parent layout + is invalidated. + */ +void QQuickGridLayoutBase::invalidate(QQuickItem *childItem) +{ + Q_D(QQuickGridLayoutBase); + if (!isReady()) + return; + if (d->m_rearranging) { + d->m_invalidateAfterRearrange << childItem; + return; + } + + quickLayoutDebug() << "QQuickGridLayoutBase::invalidate()"; + + if (childItem) { + if (QQuickGridLayoutItem *layoutItem = d->engine.findLayoutItem(childItem)) + layoutItem->invalidate(); + if (d->m_ignoredItems.contains(childItem)) { + updateLayoutItems(); + return; + } + } + // invalidate engine + d->engine.invalidate(); + + QQuickLayout::invalidate(this); + + QQuickLayoutAttached *info = attachedLayoutObject(this); + + const QSizeF min = sizeHint(Qt::MinimumSize); + const QSizeF pref = sizeHint(Qt::PreferredSize); + const QSizeF max = sizeHint(Qt::MaximumSize); + + const bool old = info->setChangesNotificationEnabled(false); + info->setMinimumImplicitSize(min); + info->setMaximumImplicitSize(max); + info->setChangesNotificationEnabled(old); + if (pref.width() == implicitWidth() && pref.height() == implicitHeight()) { + // In case setImplicitSize does not emit implicit{Width|Height}Changed + if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem())) + parentLayout->invalidate(this); + } else { + setImplicitSize(pref.width(), pref.height()); + } +} + +void QQuickGridLayoutBase::updateLayoutItems() +{ + Q_D(QQuickGridLayoutBase); + if (!isReady()) + return; + if (d->m_rearranging) { + d->m_updateAfterRearrange = true; + return; + } + + quickLayoutDebug() << "QQuickGridLayoutBase::updateLayoutItems"; + d->engine.deleteItems(); + insertLayoutItems(); + + invalidate(); + quickLayoutDebug() << "QQuickGridLayoutBase::updateLayoutItems LEAVING"; +} + +QQuickItem *QQuickGridLayoutBase::itemAt(int index) const +{ + Q_D(const QQuickGridLayoutBase); + return static_cast<QQuickGridLayoutItem*>(d->engine.itemAt(index))->layoutItem(); +} + +int QQuickGridLayoutBase::itemCount() const +{ + Q_D(const QQuickGridLayoutBase); + return d->engine.itemCount(); +} + +void QQuickGridLayoutBase::itemChange(ItemChange change, const ItemChangeData &value) +{ + if (change == ItemChildAddedChange) { + quickLayoutDebug() << "ItemChildAddedChange"; + QQuickItem *item = value.item; + QObject::connect(item, SIGNAL(destroyed()), this, SLOT(onItemDestroyed())); + QObject::connect(item, SIGNAL(visibleChanged()), this, SLOT(onItemVisibleChanged())); + } else if (change == ItemChildRemovedChange) { + quickLayoutDebug() << "ItemChildRemovedChange"; + QQuickItem *item = value.item; + QObject::disconnect(item, SIGNAL(destroyed()), this, SLOT(onItemDestroyed())); + QObject::disconnect(item, SIGNAL(visibleChanged()), this, SLOT(onItemVisibleChanged())); + } + + QQuickLayout::itemChange(change, value); +} + +void QQuickGridLayoutBase::removeGridItem(QGridLayoutItem *gridItem) +{ + Q_D(QQuickGridLayoutBase); + const int index = gridItem->firstRow(d->orientation); + d->engine.removeItem(gridItem); + d->engine.removeRows(index, 1, d->orientation); +} + +void QQuickGridLayoutBase::onItemVisibleChanged() +{ + if (!isReady()) + return; + quickLayoutDebug() << "QQuickGridLayoutBase::onItemVisibleChanged"; + updateLayoutItems(); +} + +void QQuickGridLayoutBase::onItemDestroyed() +{ + if (!isReady()) + return; + Q_D(QQuickGridLayoutBase); + quickLayoutDebug() << "QQuickGridLayoutBase::onItemDestroyed"; + QQuickItem *inDestruction = static_cast<QQuickItem *>(sender()); + if (QQuickGridLayoutItem *gridItem = d->engine.findLayoutItem(inDestruction)) { + removeGridItem(gridItem); + delete gridItem; + invalidate(); + } +} + +void QQuickGridLayoutBase::rearrange(const QSizeF &size) +{ + Q_D(QQuickGridLayoutBase); + if (!isReady()) + return; + + d->m_rearranging = true; + quickLayoutDebug() << objectName() << "QQuickGridLayoutBase::rearrange()" << size; + Qt::LayoutDirection visualDir = effectiveLayoutDirection(); + d->engine.setVisualDirection(visualDir); + + /* + qreal left, top, right, bottom; + left = top = right = bottom = 0; // ### support for margins? + if (visualDir == Qt::RightToLeft) + qSwap(left, right); + */ + + // Set m_dirty to false in case size hint changes during arrangement. + // This could happen if there is a binding like implicitWidth: height + QQuickLayout::rearrange(size); + d->engine.setGeometries(QRectF(QPointF(0,0), size), d->styleInfo); + d->m_rearranging = false; + + for (QQuickItem *invalid : qAsConst(d->m_invalidateAfterRearrange)) + invalidate(invalid); + d->m_invalidateAfterRearrange.clear(); + + if (d->m_updateAfterRearrange) { + updateLayoutItems(); + d->m_updateAfterRearrange = false; + } +} + +/********************************** + ** + ** QQuickGridLayout + ** + **/ +QQuickGridLayout::QQuickGridLayout(QQuickItem *parent /* = 0*/) + : QQuickGridLayoutBase(*new QQuickGridLayoutPrivate, Qt::Horizontal, parent) +{ +} + +/*! + \qmlproperty real GridLayout::columnSpacing + + This property holds the spacing between each column. + The default value is \c 5. +*/ +qreal QQuickGridLayout::columnSpacing() const +{ + Q_D(const QQuickGridLayout); + return d->engine.spacing(Qt::Horizontal, d->styleInfo); +} + +void QQuickGridLayout::setColumnSpacing(qreal spacing) +{ + Q_D(QQuickGridLayout); + if (qIsNaN(spacing) || columnSpacing() == spacing) + return; + + d->engine.setSpacing(spacing, Qt::Horizontal); + invalidate(); +} + +/*! + \qmlproperty real GridLayout::rowSpacing + + This property holds the spacing between each row. + The default value is \c 5. +*/ +qreal QQuickGridLayout::rowSpacing() const +{ + Q_D(const QQuickGridLayout); + return d->engine.spacing(Qt::Vertical, d->styleInfo); +} + +void QQuickGridLayout::setRowSpacing(qreal spacing) +{ + Q_D(QQuickGridLayout); + if (qIsNaN(spacing) || rowSpacing() == spacing) + return; + + d->engine.setSpacing(spacing, Qt::Vertical); + invalidate(); +} + +/*! + \qmlproperty int GridLayout::columns + + This property holds the column limit for items positioned if \l flow is + \c GridLayout.LeftToRight. + The default value is that there is no limit. +*/ +int QQuickGridLayout::columns() const +{ + Q_D(const QQuickGridLayout); + return d->columns; +} + +void QQuickGridLayout::setColumns(int columns) +{ + Q_D(QQuickGridLayout); + if (d->columns == columns) + return; + d->columns = columns; + updateLayoutItems(); + emit columnsChanged(); +} + + +/*! + \qmlproperty int GridLayout::rows + + This property holds the row limit for items positioned if \l flow is \c GridLayout.TopToBottom. + The default value is that there is no limit. +*/ +int QQuickGridLayout::rows() const +{ + Q_D(const QQuickGridLayout); + return d->rows; +} + +void QQuickGridLayout::setRows(int rows) +{ + Q_D(QQuickGridLayout); + if (d->rows == rows) + return; + d->rows = rows; + updateLayoutItems(); + emit rowsChanged(); +} + + +/*! + \qmlproperty enumeration GridLayout::flow + + This property holds the flow direction of items that does not have an explicit cell + position set. + It is used together with the \l columns or \l rows property, where + they specify when flow is reset to the next row or column respectively. + + Possible values are: + + \list + \li GridLayout.LeftToRight (default) - Items are positioned next to + each other, then wrapped to the next line. + \li GridLayout.TopToBottom - Items are positioned next to each + other from top to bottom, then wrapped to the next column. + \endlist + + \sa rows + \sa columns +*/ +QQuickGridLayout::Flow QQuickGridLayout::flow() const +{ + Q_D(const QQuickGridLayout); + return d->flow; +} + +void QQuickGridLayout::setFlow(QQuickGridLayout::Flow flow) +{ + Q_D(QQuickGridLayout); + if (d->flow == flow) + return; + d->flow = flow; + // If flow is changed, the layout needs to be repopulated + updateLayoutItems(); + emit flowChanged(); +} + +void QQuickGridLayout::insertLayoutItems() +{ + Q_D(QQuickGridLayout); + + int nextCellPos[2] = {0,0}; + int &nextColumn = nextCellPos[0]; + int &nextRow = nextCellPos[1]; + + const int flowOrientation = flow(); + int &flowColumn = nextCellPos[flowOrientation]; + int &flowRow = nextCellPos[1 - flowOrientation]; + int flowBound = (flowOrientation == QQuickGridLayout::LeftToRight) ? columns() : rows(); + + if (flowBound < 0) + flowBound = std::numeric_limits<int>::max(); + + d->m_ignoredItems.clear(); + QSizeF sizeHints[Qt::NSizeHints]; + const auto items = childItems(); + for (QQuickItem *child : items) { + QQuickLayoutAttached *info = 0; + + // Will skip all items with effective maximum width/height == 0 + if (shouldIgnoreItem(child, info, sizeHints)) + continue; + + Qt::Alignment alignment = 0; + int row = -1; + int column = -1; + int span[2] = {1,1}; + int &columnSpan = span[0]; + int &rowSpan = span[1]; + + bool invalidRowColumn = false; + if (info) { + if (info->isRowSet() || info->isColumnSet()) { + // If row is specified and column is not specified (or vice versa), + // the unspecified component of the cell position should default to 0 + row = column = 0; + if (info->isRowSet()) { + row = info->row(); + invalidRowColumn = row < 0; + } + if (info->isColumnSet()) { + column = info->column(); + invalidRowColumn = column < 0; + } + } + if (invalidRowColumn) { + qWarning("QQuickGridLayoutBase::insertLayoutItems: invalid row/column: %d", + row < 0 ? row : column); + return; + } + rowSpan = info->rowSpan(); + columnSpan = info->columnSpan(); + if (columnSpan < 1 || rowSpan < 1) { + qWarning("QQuickGridLayoutBase::addItem: invalid row span/column span: %d", + rowSpan < 1 ? rowSpan : columnSpan); + return; + } + + alignment = info->alignment(); + } + + Q_ASSERT(columnSpan >= 1); + Q_ASSERT(rowSpan >= 1); + const int sp = span[flowOrientation]; + if (sp > flowBound) + return; + + if (row >= 0) + nextRow = row; + if (column >= 0) + nextColumn = column; + + if (row < 0 || column < 0) { + /* if row or column is not specified, find next position by + advancing in the flow direction until there is a cell that + can accept the item. + + The acceptance rules are pretty simple, but complexity arises + when an item requires several cells (due to spans): + 1. Check if the cells that the item will require + does not extend beyond columns (for LeftToRight) or + rows (for TopToBottom). + 2. Check if the cells that the item will require is not already + taken by another item. + */ + bool cellAcceptsItem; + while (true) { + // Check if the item does not span beyond the layout bound + cellAcceptsItem = (flowColumn + sp) <= flowBound; + + // Check if all the required cells are not taken + for (int rs = 0; cellAcceptsItem && rs < rowSpan; ++rs) { + for (int cs = 0; cellAcceptsItem && cs < columnSpan; ++cs) { + if (d->engine.itemAt(nextRow + rs, nextColumn + cs)) { + cellAcceptsItem = false; + } + } + } + if (cellAcceptsItem) + break; + ++flowColumn; + if (flowColumn == flowBound) { + flowColumn = 0; + ++flowRow; + } + } + } + column = nextColumn; + row = nextRow; + QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(child, row, column, rowSpan, columnSpan, alignment); + layoutItem->setCachedSizeHints(sizeHints); + + d->engine.insertItem(layoutItem, -1); + } +} + +/********************************** + ** + ** QQuickLinearLayout + ** + **/ +QQuickLinearLayout::QQuickLinearLayout(Qt::Orientation orientation, + QQuickItem *parent /*= 0*/) + : QQuickGridLayoutBase(*new QQuickLinearLayoutPrivate, orientation, parent) +{ +} + +/*! + \qmlproperty enumeration RowLayout::layoutDirection + \since QtQuick.Layouts 1.1 + + This property holds the layout direction of the row layout - it controls whether items are laid + out from left ro right or right to left. If \c Qt.RightToLeft is specified, + left-aligned items will be right-aligned and right-aligned items will be left-aligned. + + Possible values: + + \list + \li Qt.LeftToRight (default) - Items are laid out from left to right. + \li Qt.RightToLeft - Items are laid out from right to left + \endlist + + \sa GridLayout::layoutDirection, ColumnLayout::layoutDirection +*/ +/*! + \qmlproperty enumeration ColumnLayout::layoutDirection + \since QtQuick.Layouts 1.1 + + This property holds the layout direction of the column layout - it controls whether items are laid + out from left ro right or right to left. If \c Qt.RightToLeft is specified, + left-aligned items will be right-aligned and right-aligned items will be left-aligned. + + Possible values: + + \list + \li Qt.LeftToRight (default) - Items are laid out from left to right. + \li Qt.RightToLeft - Items are laid out from right to left + \endlist + + \sa GridLayout::layoutDirection, RowLayout::layoutDirection +*/ + + +/*! + \qmlproperty real RowLayout::spacing + + This property holds the spacing between each cell. + The default value is \c 5. +*/ +/*! + \qmlproperty real ColumnLayout::spacing + + This property holds the spacing between each cell. + The default value is \c 5. +*/ + +qreal QQuickLinearLayout::spacing() const +{ + Q_D(const QQuickLinearLayout); + return d->engine.spacing(d->orientation, d->styleInfo); +} + +void QQuickLinearLayout::setSpacing(qreal space) +{ + Q_D(QQuickLinearLayout); + if (qIsNaN(space) || spacing() == space) + return; + + d->engine.setSpacing(space, Qt::Horizontal | Qt::Vertical); + invalidate(); +} + +void QQuickLinearLayout::insertLayoutItems() +{ + Q_D(QQuickLinearLayout); + d->m_ignoredItems.clear(); + QSizeF sizeHints[Qt::NSizeHints]; + const auto items = childItems(); + for (QQuickItem *child : items) { + Q_ASSERT(child); + QQuickLayoutAttached *info = 0; + + // Will skip all items with effective maximum width/height == 0 + if (shouldIgnoreItem(child, info, sizeHints)) + continue; + + Qt::Alignment alignment = 0; + if (info) + alignment = info->alignment(); + + const int index = d->engine.rowCount(d->orientation); + d->engine.insertRow(index, d->orientation); + + int gridRow = 0; + int gridColumn = index; + if (d->orientation == Qt::Vertical) + qSwap(gridRow, gridColumn); + QQuickGridLayoutItem *layoutItem = new QQuickGridLayoutItem(child, gridRow, gridColumn, 1, 1, alignment); + layoutItem->setCachedSizeHints(sizeHints); + d->engine.insertItem(layoutItem, index); + } +} + +QT_END_NAMESPACE diff --git a/src/imports/layouts/qquicklinearlayout_p.h b/src/imports/layouts/qquicklinearlayout_p.h new file mode 100644 index 0000000000..86404f8d79 --- /dev/null +++ b/src/imports/layouts/qquicklinearlayout_p.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKLINEARLAYOUT_P_H +#define QQUICKLINEARLAYOUT_P_H + +#include "qquicklayout_p.h" +#include "qquickgridlayoutengine_p.h" + +QT_BEGIN_NAMESPACE + +/********************************** + ** + ** QQuickGridLayoutBase + ** + **/ +class QQuickGridLayoutBasePrivate; + +class QQuickGridLayoutBase : public QQuickLayout +{ + Q_OBJECT + + Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged REVISION 1) + +public: + + QQuickGridLayoutBase(); + + explicit QQuickGridLayoutBase(QQuickGridLayoutBasePrivate &dd, + Qt::Orientation orientation, + QQuickItem *parent = 0); + + ~QQuickGridLayoutBase(); + void componentComplete() Q_DECL_OVERRIDE; + void invalidate(QQuickItem *childItem = 0) Q_DECL_OVERRIDE; + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + QSizeF sizeHint(Qt::SizeHint whichSizeHint) const Q_DECL_OVERRIDE; + Qt::LayoutDirection layoutDirection() const; + void setLayoutDirection(Qt::LayoutDirection dir); + Qt::LayoutDirection effectiveLayoutDirection() const; + void setAlignment(QQuickItem *item, Qt::Alignment align) Q_DECL_OVERRIDE; + +protected: + void updateLayoutItems() Q_DECL_OVERRIDE; + QQuickItem *itemAt(int index) const Q_DECL_OVERRIDE; + int itemCount() const Q_DECL_OVERRIDE; + + void rearrange(const QSizeF &size) Q_DECL_OVERRIDE; + virtual void insertLayoutItems() {} + void itemChange(ItemChange change, const ItemChangeData &data) Q_DECL_OVERRIDE; + +signals: + Q_REVISION(1) void layoutDirectionChanged(); + +protected slots: + void onItemVisibleChanged(); + void onItemDestroyed(); + +private: + void removeGridItem(QGridLayoutItem *gridItem); + Q_DECLARE_PRIVATE(QQuickGridLayoutBase) +}; + +class QQuickLayoutStyleInfo; + +class QQuickGridLayoutBasePrivate : public QQuickLayoutPrivate +{ + Q_DECLARE_PUBLIC(QQuickGridLayoutBase) + +public: + QQuickGridLayoutBasePrivate() : m_rearranging(false) + , m_updateAfterRearrange(false) + , m_layoutDirection(Qt::LeftToRight) + {} + + void mirrorChange() Q_DECL_OVERRIDE + { + Q_Q(QQuickGridLayoutBase); + q->invalidate(); + } + + QQuickGridLayoutEngine engine; + Qt::Orientation orientation; + unsigned m_rearranging : 1; + unsigned m_updateAfterRearrange : 1; + QVector<QQuickItem *> m_invalidateAfterRearrange; + Qt::LayoutDirection m_layoutDirection : 2; + + QQuickLayoutStyleInfo *styleInfo; +}; + +/********************************** + ** + ** QQuickGridLayout + ** + **/ +class QQuickGridLayoutPrivate; +class QQuickGridLayout : public QQuickGridLayoutBase +{ + Q_OBJECT + + Q_PROPERTY(qreal columnSpacing READ columnSpacing WRITE setColumnSpacing NOTIFY columnSpacingChanged) + Q_PROPERTY(qreal rowSpacing READ rowSpacing WRITE setRowSpacing NOTIFY rowSpacingChanged) + Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged) + Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) + Q_PROPERTY(Flow flow READ flow WRITE setFlow NOTIFY flowChanged) +public: + explicit QQuickGridLayout(QQuickItem *parent = 0); + qreal columnSpacing() const; + void setColumnSpacing(qreal spacing); + qreal rowSpacing() const; + void setRowSpacing(qreal spacing); + + int columns() const; + void setColumns(int columns); + int rows() const; + void setRows(int rows); + + Q_ENUMS(Flow) + enum Flow { LeftToRight, TopToBottom }; + Flow flow() const; + void setFlow(Flow flow); + + void insertLayoutItems(); + +signals: + void columnSpacingChanged(); + void rowSpacingChanged(); + + void columnsChanged(); + void rowsChanged(); + + void flowChanged(); +private: + Q_DECLARE_PRIVATE(QQuickGridLayout) +}; + +class QQuickGridLayoutPrivate : public QQuickGridLayoutBasePrivate +{ + Q_DECLARE_PUBLIC(QQuickGridLayout) +public: + QQuickGridLayoutPrivate(): columns(-1), rows(-1), flow(QQuickGridLayout::LeftToRight) {} + int columns; + int rows; + QQuickGridLayout::Flow flow; +}; + + +/********************************** + ** + ** QQuickLinearLayout + ** + **/ +class QQuickLinearLayoutPrivate; +class QQuickLinearLayout : public QQuickGridLayoutBase +{ + Q_OBJECT + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) +public: + explicit QQuickLinearLayout(Qt::Orientation orientation, + QQuickItem *parent = 0); + void insertLayoutItem(QQuickItem *item); + qreal spacing() const; + void setSpacing(qreal spacing); + + void insertLayoutItems(); + +signals: + void spacingChanged(); +private: + Q_DECLARE_PRIVATE(QQuickLinearLayout) +}; + +class QQuickLinearLayoutPrivate : public QQuickGridLayoutBasePrivate +{ + Q_DECLARE_PUBLIC(QQuickLinearLayout) +public: + QQuickLinearLayoutPrivate() {} +}; + + +/********************************** + ** + ** QQuickRowLayout + ** + **/ +class QQuickRowLayout : public QQuickLinearLayout +{ + Q_OBJECT + +public: + explicit QQuickRowLayout(QQuickItem *parent = 0) + : QQuickLinearLayout(Qt::Horizontal, parent) {} +}; + + +/********************************** + ** + ** QQuickColumnLayout + ** + **/ +class QQuickColumnLayout : public QQuickLinearLayout +{ + Q_OBJECT + +public: + explicit QQuickColumnLayout(QQuickItem *parent = 0) + : QQuickLinearLayout(Qt::Vertical, parent) {} +}; + +QT_END_NAMESPACE + +#endif // QQUICKLINEARLAYOUT_P_H diff --git a/src/imports/layouts/qquickstacklayout.cpp b/src/imports/layouts/qquickstacklayout.cpp new file mode 100644 index 0000000000..a223dd0374 --- /dev/null +++ b/src/imports/layouts/qquickstacklayout.cpp @@ -0,0 +1,339 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickstacklayout_p.h" +#include <limits> + +/*! + \qmltype StackLayout + \instantiates QQuickStackLayout + \inherits Item + \inqmlmodule QtQuick.Layouts + \ingroup layouts + \brief The StackLayout class provides a stack of items where + only one item is visible at a time. + + The current visible item can be modified by setting the \l currentIndex property. + The index corresponds to the order of the StackLayout's children. + + In contrast to most other layouts, child Items' \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} properties + default to \c true. As a consequence, child items are by default filled to match the size of the StackLayout as long as their + \l{Layout::maximumWidth}{Layout.maximumWidth} or \l{Layout::maximumHeight}{Layout.maximumHeight} does not prevent it. + + Items are added to the layout by reparenting the item to the layout. Similarly, removal is done by reparenting the item from the layout. + Both of these operations will affect the layout's \l count property. + + The following code will create a StackLayout where only the 'plum' rectangle is visible. + \code + StackLayout { + id: layout + anchors.fill: parent + currentIndex: 1 + Rectangle { + color: 'teal' + implicitWidth: 200 + implicitHeight: 200 + } + Rectangle { + color: 'plum' + implicitWidth: 300 + implicitHeight: 200 + } + } + \endcode + + Items in a StackLayout support these attached properties: + \list + \li \l{Layout::minimumWidth}{Layout.minimumWidth} + \li \l{Layout::minimumHeight}{Layout.minimumHeight} + \li \l{Layout::preferredWidth}{Layout.preferredWidth} + \li \l{Layout::preferredHeight}{Layout.preferredHeight} + \li \l{Layout::maximumWidth}{Layout.maximumWidth} + \li \l{Layout::maximumHeight}{Layout.maximumHeight} + \li \l{Layout::fillWidth}{Layout.fillWidth} + \li \l{Layout::fillHeight}{Layout.fillHeight} + \endlist + + Read more about attached properties \l{QML Object Attributes}{here}. + \sa ColumnLayout + \sa GridLayout + \sa RowLayout + \sa StackView +*/ + +QQuickStackLayout::QQuickStackLayout(QQuickItem *parent) : + QQuickLayout(*new QQuickStackLayoutPrivate, parent) +{ +} + +/*! + \qmlproperty int StackLayout::count + + This property holds the number of items that belong to the layout. + + Only items that are children of the StackLayout will be candidates for layouting. +*/ +int QQuickStackLayout::count() const +{ + Q_D(const QQuickStackLayout); + return d->count; +} + +/*! + \qmlproperty int StackLayout::currentIndex + + This property holds the index of the child item that is currently visible in the StackLayout. + By default it will be \c -1 for an empty layout, otherwise the default is \c 0 (referring to the first item). +*/ +int QQuickStackLayout::currentIndex() const +{ + Q_D(const QQuickStackLayout); + return d->currentIndex; +} + +void QQuickStackLayout::setCurrentIndex(int index) +{ + Q_D(QQuickStackLayout); + if (index != d->currentIndex) { + QQuickItem *prev = itemAt(d->currentIndex); + QQuickItem *next = itemAt(index); + d->currentIndex = index; + d->explicitCurrentIndex = true; + if (prev) + prev->setVisible(false); + if (next) + next->setVisible(true); + + if (isComponentComplete()) { + rearrange(QSizeF(width(), height())); + emit currentIndexChanged(); + } + } +} + +void QQuickStackLayout::componentComplete() +{ + QQuickLayout::componentComplete(); // will call our geometryChange(), (where isComponentComplete() == true) + + updateLayoutItems(); + + QQuickItem *par = parentItem(); + if (qobject_cast<QQuickLayout*>(par)) + return; + + rearrange(QSizeF(width(), height())); +} + +QSizeF QQuickStackLayout::sizeHint(Qt::SizeHint whichSizeHint) const +{ + QSizeF &askingFor = m_cachedSizeHints[whichSizeHint]; + if (!askingFor.isValid()) { + QSizeF &minS = m_cachedSizeHints[Qt::MinimumSize]; + QSizeF &prefS = m_cachedSizeHints[Qt::PreferredSize]; + QSizeF &maxS = m_cachedSizeHints[Qt::MaximumSize]; + + minS = QSizeF(0,0); + prefS = QSizeF(0,0); + maxS = QSizeF(std::numeric_limits<qreal>::infinity(), std::numeric_limits<qreal>::infinity()); + + const int count = itemCount(); + m_cachedItemSizeHints.resize(count); + for (int i = 0; i < count; ++i) { + SizeHints &hints = m_cachedItemSizeHints[i]; + QQuickStackLayout::collectItemSizeHints(itemAt(i), hints.array); + minS = minS.expandedTo(hints.min()); + prefS = prefS.expandedTo(hints.pref()); + //maxS = maxS.boundedTo(hints.max()); // Can be resized to be larger than any of its items. + // This is the same as QStackLayout does it. + // Not sure how descent makes sense here... + } + } + return askingFor; +} + +int QQuickStackLayout::indexOf(QQuickItem *childItem) const +{ + if (childItem) { + int indexOfItem = 0; + const auto items = childItems(); + for (QQuickItem *item : items) { + if (shouldIgnoreItem(item)) + continue; + if (childItem == item) + return indexOfItem; + ++indexOfItem; + } + } + return -1; +} + +QQuickItem *QQuickStackLayout::itemAt(int index) const +{ + const auto items = childItems(); + for (QQuickItem *item : items) { + if (shouldIgnoreItem(item)) + continue; + if (index == 0) + return item; + --index; + } + return 0; +} + +int QQuickStackLayout::itemCount() const +{ + int count = 0; + const auto items = childItems(); + for (QQuickItem *item : items) { + if (shouldIgnoreItem(item)) + continue; + ++count; + } + return count; +} + +void QQuickStackLayout::setAlignment(QQuickItem * /*item*/, Qt::Alignment /*align*/) +{ + // ### Do we have to respect alignment? +} + +void QQuickStackLayout::invalidate(QQuickItem *childItem) +{ + Q_D(QQuickStackLayout); + if (d->m_ignoredItems.contains(childItem)) { + // If an invalid item gets a valid size, it should be included, as it was added to the layout + updateLayoutItems(); + return; + } + + const int indexOfChild = indexOf(childItem); + if (indexOfChild >= 0 && indexOfChild < m_cachedItemSizeHints.count()) { + m_cachedItemSizeHints[indexOfChild].min() = QSizeF(); + m_cachedItemSizeHints[indexOfChild].pref() = QSizeF(); + m_cachedItemSizeHints[indexOfChild].max() = QSizeF(); + } + + for (int i = 0; i < Qt::NSizeHints; ++i) + m_cachedSizeHints[i] = QSizeF(); + QQuickLayout::invalidate(this); + + QQuickLayoutAttached *info = attachedLayoutObject(this); + + const QSizeF min = sizeHint(Qt::MinimumSize); + const QSizeF pref = sizeHint(Qt::PreferredSize); + const QSizeF max = sizeHint(Qt::MaximumSize); + + const bool old = info->setChangesNotificationEnabled(false); + info->setMinimumImplicitSize(min); + info->setMaximumImplicitSize(max); + info->setChangesNotificationEnabled(old); + if (pref.width() == implicitWidth() && pref.height() == implicitHeight()) { + // In case setImplicitSize does not emit implicit{Width|Height}Changed + if (QQuickLayout *parentLayout = qobject_cast<QQuickLayout *>(parentItem())) + parentLayout->invalidate(this); + } else { + setImplicitSize(pref.width(), pref.height()); + } +} + +void QQuickStackLayout::updateLayoutItems() +{ + Q_D(QQuickStackLayout); + d->m_ignoredItems.clear(); + const int count = itemCount(); + int oldIndex = d->currentIndex; + if (!d->explicitCurrentIndex) + d->currentIndex = (count > 0 ? 0 : -1); + + if (d->currentIndex != oldIndex) + emit currentIndexChanged(); + + if (count != d->count) { + d->count = count; + emit countChanged(); + } + for (int i = 0; i < count; ++i) + itemAt(i)->setVisible(d->currentIndex == i); + + invalidate(); +} + +void QQuickStackLayout::rearrange(const QSizeF &newSize) +{ + Q_D(QQuickStackLayout); + if (newSize.isNull() || !newSize.isValid()) + return; + (void)sizeHint(Qt::PreferredSize); // Make sure m_cachedItemSizeHints are valid + + if (d->currentIndex == -1 || d->currentIndex >= m_cachedItemSizeHints.count()) + return; + QQuickStackLayout::SizeHints &hints = m_cachedItemSizeHints[d->currentIndex]; + QQuickItem *item = itemAt(d->currentIndex); + Q_ASSERT(item); + item->setPosition(QPointF(0,0)); // ### respect alignment? + item->setSize(newSize.expandedTo(hints.min()).boundedTo(hints.max())); + QQuickLayout::rearrange(newSize); +} + +void QQuickStackLayout::collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints) +{ + QQuickLayoutAttached *info = 0; + QQuickLayout::effectiveSizeHints_helper(item, sizeHints, &info, true); + if (!info) + return; + if (info->isFillWidthSet() && !info->fillWidth()) { + const qreal pref = sizeHints[Qt::PreferredSize].width(); + sizeHints[Qt::MinimumSize].setWidth(pref); + sizeHints[Qt::MaximumSize].setWidth(pref); + } + + if (info->isFillHeightSet() && !info->fillHeight()) { + const qreal pref = sizeHints[Qt::PreferredSize].height(); + sizeHints[Qt::MinimumSize].setHeight(pref); + sizeHints[Qt::MaximumSize].setHeight(pref); + } +} + +bool QQuickStackLayout::shouldIgnoreItem(QQuickItem *item) const +{ + const bool ignored = QQuickItemPrivate::get(item)->isTransparentForPositioner(); + if (ignored) + d_func()->m_ignoredItems << item; + return ignored; +} diff --git a/src/imports/layouts/qquickstacklayout_p.h b/src/imports/layouts/qquickstacklayout_p.h new file mode 100644 index 0000000000..7b6400c3a3 --- /dev/null +++ b/src/imports/layouts/qquickstacklayout_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Layouts module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSTACKLAYOUT_H +#define QQUICKSTACKLAYOUT_H + +#include <qquicklayout_p.h> + +class QQuickStackLayoutPrivate; + +class QQuickStackLayout : public QQuickLayout +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + +public: + explicit QQuickStackLayout(QQuickItem *parent = 0); + int count() const; + int currentIndex() const; + void setCurrentIndex(int index); + + void componentComplete() Q_DECL_OVERRIDE; + QSizeF sizeHint(Qt::SizeHint whichSizeHint) const Q_DECL_OVERRIDE; + void setAlignment(QQuickItem *item, Qt::Alignment align) Q_DECL_OVERRIDE; + void invalidate(QQuickItem *childItem = 0) Q_DECL_OVERRIDE; + void updateLayoutItems() Q_DECL_OVERRIDE; + void rearrange(const QSizeF &) Q_DECL_OVERRIDE; + + // iterator + Q_INVOKABLE QQuickItem *itemAt(int index) const Q_DECL_OVERRIDE; + int itemCount() const Q_DECL_OVERRIDE; + int indexOf(QQuickItem *item) const; + + + +signals: + void currentIndexChanged(); + void countChanged(); + +public slots: + +private: + static void collectItemSizeHints(QQuickItem *item, QSizeF *sizeHints); + bool shouldIgnoreItem(QQuickItem *item) const; + Q_DECLARE_PRIVATE(QQuickStackLayout) + + QList<QQuickItem*> m_items; + + typedef struct { + inline QSizeF &min() { return array[Qt::MinimumSize]; } + inline QSizeF &pref() { return array[Qt::PreferredSize]; } + inline QSizeF &max() { return array[Qt::MaximumSize]; } + QSizeF array[Qt::NSizeHints]; + } SizeHints; + + mutable QVector<SizeHints> m_cachedItemSizeHints; + mutable QSizeF m_cachedSizeHints[Qt::NSizeHints]; +}; + +class QQuickStackLayoutPrivate : public QQuickLayoutPrivate +{ + Q_DECLARE_PUBLIC(QQuickStackLayout) +public: + QQuickStackLayoutPrivate() : count(0), currentIndex(-1), explicitCurrentIndex(false) {} +private: + int count; + int currentIndex; + bool explicitCurrentIndex; +}; + +#endif // QQUICKSTACKLAYOUT_H diff --git a/src/quick/doc/images/columnlayout.png b/src/quick/doc/images/columnlayout.png Binary files differnew file mode 100644 index 0000000000..f03eb7b996 --- /dev/null +++ b/src/quick/doc/images/columnlayout.png diff --git a/src/quick/doc/images/gridlayout.png b/src/quick/doc/images/gridlayout.png Binary files differnew file mode 100644 index 0000000000..493813c481 --- /dev/null +++ b/src/quick/doc/images/gridlayout.png diff --git a/src/quick/doc/images/qtquicklayouts-example-layouts.png b/src/quick/doc/images/qtquicklayouts-example-layouts.png Binary files differnew file mode 100644 index 0000000000..94619bae3f --- /dev/null +++ b/src/quick/doc/images/qtquicklayouts-example-layouts.png diff --git a/src/quick/doc/images/rowlayout-minimum.png b/src/quick/doc/images/rowlayout-minimum.png Binary files differnew file mode 100644 index 0000000000..5875325c54 --- /dev/null +++ b/src/quick/doc/images/rowlayout-minimum.png diff --git a/src/quick/doc/images/rowlayout.png b/src/quick/doc/images/rowlayout.png Binary files differnew file mode 100644 index 0000000000..519a62fddd --- /dev/null +++ b/src/quick/doc/images/rowlayout.png diff --git a/src/quick/doc/qtquick.qdocconf b/src/quick/doc/qtquick.qdocconf index 044c1696ff..4f141a733a 100644 --- a/src/quick/doc/qtquick.qdocconf +++ b/src/quick/doc/qtquick.qdocconf @@ -33,7 +33,7 @@ qhp.QtQuick.subprojects.examples.selectors = fake:example tagfile = ../../../doc/qtquick/qtquick.tags -depends += qtcore qtxmlpatterns qtqml qtgui qtlinguist qtquickcontrols qtquicklayouts qtdoc qtquickdialogs qtsensors qtwidgets qmake qtmultimedia qtgraphicaleffects +depends += qtcore qtxmlpatterns qtqml qtgui qtlinguist qtquickcontrols qtdoc qtquickdialogs qtsensors qtwidgets qmake qtmultimedia qtgraphicaleffects headerdirs += ..\ ../../quickwidgets diff --git a/src/quick/doc/snippets/qml/windowconstraints.qml b/src/quick/doc/snippets/qml/windowconstraints.qml new file mode 100644 index 0000000000..3f3534f494 --- /dev/null +++ b/src/quick/doc/snippets/qml/windowconstraints.qml @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 + +Window { + //! [binddefaultsize] + width: layout.implicitWidth + height: layout.implicitHeight + //! [binddefaultsize] + //! [bindconstraints] + minimumWidth: layout.Layout.minimumWidth + minimumHeight: layout.Layout.minimumHeight + maximumWidth: 1000 + maximumHeight: layout.Layout.maximumHeight + //! [bindconstraints] + + //! [rowlayout] + //! [anchoring] + RowLayout { + id: layout + anchors.fill: parent + //! [anchoring] + spacing: 6 + Rectangle { + color: 'azure' + Layout.fillWidth: true + Layout.minimumWidth: 50 + Layout.preferredWidth: 100 + Layout.maximumWidth: 300 + Layout.minimumHeight: 150 + Text { + anchors.centerIn: parent + text: parent.width + 'x' + parent.height + } + } + Rectangle { + color: 'plum' + Layout.fillWidth: true + Layout.minimumWidth: 100 + Layout.preferredWidth: 200 + Layout.preferredHeight: 100 + Text { + anchors.centerIn: parent + text: parent.width + 'x' + parent.height + } + } + } + //! [rowlayout] +} diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc new file mode 100644 index 0000000000..0be66fad2c --- /dev/null +++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-index.qdoc @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtquicklayouts-index.html + \title Qt Quick Layouts + \brief A module with a set of QML elements that arrange QML items in a user interface. + + Qt Quick Layouts are a set of QML types used to arrange items in a user interface. In contrast + to \l{Item Positioners}{positioners}, Qt Quick Layouts can also resize their items. This makes + them well suited for resizable user interfaces. Since layouts are items they can consequently + be nested. + + The module is new in Qt 5.1 and requires \l{Qt Quick} 2.1. + + Visit the \l{Qt Quick Layouts Overview} page to get started. + + \section1 Layouts + + \annotatedlist layouts + + \section1 Related information + + \list + \li \l{Qt Quick} + \li \l{Qt Quick Layouts Overview} + \li \l{Qt Quick Layouts - Basic Example} + \li \l{Qt Quick Layouts QML Types}{Qt Quick Layouts QML Types} + \endlist +*/ diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc new file mode 100644 index 0000000000..1b6e7dc539 --- /dev/null +++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts-overview.qdoc @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page qtquicklayouts-overview.html + \title Qt Quick Layouts Overview + \brief A set of APIs for arranging QML items in a user interface. + + Qt Quick Layouts are items that are used to arrange items in a user interface. Since Qt Quick + Layouts also resize their items, they are well suited for resizable user interfaces. + + \section1 Getting started + + The QML types can be imported into your application using the following import statement in your \c {.qml} file. + + \code + import QtQuick.Layouts 1.2 + \endcode + + \section1 Key Features + + + Some of the key features are: + + \list + \li \l{Layout::alignment}{Alignment} of items can be specified with the + \l{Layout::alignment}{Layout.alignment} property + \li \l{Layout::fillWidth}{Resizable items} can be specified with the + \l{Layout::fillWidth}{Layout.fillWidth} and \l{Layout::fillHeight}{Layout.fillHeight} + properties. + \li \l{Size constraints} can be specified with + \l{Layout::minimumWidth}{Layout.minimumWidth}, + \l{Layout::preferredWidth}{Layout.preferredWidth}, and + \l{Layout::maximumWidth}{Layout.maximumWidth} properties ("Width" can be replaced + with "Height" for specifying similar constraints to the height). + \li \l{RowLayout::spacing}{Spacings} can be specified with \l{RowLayout::spacing}{spacing}, + \l{GridLayout::rowSpacing}{rowSpacing} or \l{GridLayout::columnSpacing}{columnSpacing} + \endlist + + In addition to the above features, GridLayout adds these features: + \list + \li \l{Layout::row}{Grid coordinates} can be specified with the \l{Layout::row}{Layout.row} and + \l{Layout::column}{Layout.column}. + \li \l{GridLayout::flow}{Automatic grid coordinates} used together with the + \l{GridLayout::flow}{flow}, \l{GridLayout::rows}{rows}, and + \l{GridLayout::columns}{columns} properties. + \li \l{Layout::columnSpan}{Spans} across rows or columns can be specified with the + \l{Layout::rowSpan}{Layout.rowSpan} and \l{Layout::columnSpan}{Layout.columnSpan} + properties. + \endlist + + + + \section1 Size Constraints + Since an item can be resized by its layout, the layout needs to know the + \l{Layout::minimumWidth}{minimum}, \l{Layout::preferredWidth}{preferred}, + and \l{Layout::maximumWidth}{maximum} sizes of all items where \l{Layout::fillWidth}{Layout.fillWidth} or + \l{Layout::fillHeight}{Layout.fillHeight} is set to \c true. + For instance, the following will produce a layout with two rectangles lying side-by-side that + stretches horizontally. The azure rectangle can be resized from 50x150 to 300x150, and the plum + rectangle can be resized from 100x100 to ∞x100. + + \snippet windowconstraints.qml rowlayout + + \image rowlayout-minimum.png "RowLayout at its minimum" + + Combining each item's constraints will give these implicit constraints to the layout element: + + \table + \header + \li + \li minimum + \li preferred + \li maximum + \row + \li implicit constraints (width) + \li 156 + \li 306 + \li ∞ (\c Number.POSITIVE_INFINITY) + \row + \li implicit constraints (heights) + \li 150 + \li 150 + \li 150 + \endtable + + Thus, the layout cannot be narrower than 156 or be taller or shorter than 150 without breaking + any of the constraints of its child items. + + \section2 Specifying Preferred Size + For each item, the effective preferred size may come from one of several candidate properties. + For determining the effective preferred size, it will query these candidate properties in the + following order, and use the first candidate with a valid width or height. + + \table + \header + \li Candidate properties + \li Description + \row + \li \l{Layout::preferredWidth}{Layout.preferredWidth} or + \l{Layout::preferredHeight}{Layout.preferredHeight} + \li These properties are supposed to be modified by the application if the default implicit + size does not give the optimal arrangement. + \row + \li \l{Item::implicitWidth}{implicitWidth} or \l{Item::implicitHeight}{implicitHeight} + \li These properties are supposed to be supplied by each item to give a meaningful ideal size, + for example the size needed to display all the contents of a \l Text type. + An implicit width or height of \c 0 is interpreted as invalid. + \row + \li \l{Item::width}{width} and \l{Item::height}{height} + \li If none of the above properties are valid, the layout will resort to the + \l{Item::width}{width} and \l{Item::height}{height} properties. + \endtable + + An item can specify \l{Layout::preferredWidth}{Layout.preferredWidth} without having to specify + \l{Layout::preferredHeight}{Layout.preferredHeight}. In this case, the effective preferred + height will be determined from the \l{Item::implicitHeight}{implicitHeight} (or ultimately + \l{Item::height}{height}). + + \note Resorting to \l{Item::width}{width} or \l{Item::height}{height} properties is only + provided as a final fallback. If you want to override the preferred size, it is recommended to + use \l{Layout::preferredWidth}{Layout.preferredWidth} or + \l{Layout::preferredHeight}{Layout.preferredHeight}. Relying on the \l{Item::width}{width} or + \l{Item::height}{height} properties for specifying the preferred size might give some + unexpected behavior. For instance, changing the \l{Item::width}{width} or + \l{Item::height}{height} properties won't trigger a layout rearrangement. Also, when the layout + is forced to do a full rebuild it might use the actual width and height, and not the width and + height specified in the QML file. + + + \section1 Connecting windows and layouts + You can just use normal anchoring concepts to ensure that the layout will follow the window + resizing. + + \snippet qml/windowconstraints.qml anchoring + + The size constraints of layouts can be used to ensure that the window cannot be resized beyond + the layout constraints. You can take the size constraints from the layout and set these + constraints on the minimumWidth, minimumHeight, maximumWidth, and maximumHeight of the Window + element. The following code ensures that the window cannot be resized beyond the constraints of + the layout: + + \snippet qml/windowconstraints.qml bindconstraints + + \note Since layout.Layout.maximumWidth is infinite in this case, we cannot bind that to the + maximumWidth property of Window, since that is an integer number. We therefore set a fixed + maximum width to 1000. + + Finally, you usually want the initial size of the window to be the layout's implicit size: + + \snippet qml/windowconstraints.qml binddefaultsize +*/ diff --git a/src/quick/doc/src/concepts/layouts/qtquicklayouts.qdoc b/src/quick/doc/src/concepts/layouts/qtquicklayouts.qdoc new file mode 100644 index 0000000000..8f390c83db --- /dev/null +++ b/src/quick/doc/src/concepts/layouts/qtquicklayouts.qdoc @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \qmlmodule QtQuick.Layouts 1.3 + \title Qt Quick Layouts QML Types + \ingroup qmlmodules + \brief Provides QML types for arranging QML items in a user interface. + + The \l{Qt Quick Layouts} module provides QML types for arranging + QML items in a user interface. + These QML types work in conjunction with \l{Qt Quick} and + \l{Qt Quick Controls}. + + The QML types can be imported into your application using the + following import statement in your .qml file. + + \code + import QtQuick.Layouts 1.3 + \endcode + +*/ diff --git a/src/quick/doc/src/qtquick.qdoc b/src/quick/doc/src/qtquick.qdoc index 98a77a48d8..4bdd02241d 100644 --- a/src/quick/doc/src/qtquick.qdoc +++ b/src/quick/doc/src/qtquick.qdoc @@ -101,6 +101,7 @@ Additional Qt Quick information: containing a JavaScript interface for an SQLite database \li \l{Qt Quick Particles QML Types}{Particles} - provides a particle system for Qt Quick + \li \l{Qt Quick Layouts}{Layouts} - provides layouts for arranging Qt Quick items \li \l{Qt Quick Window QML Types}{Window} - contains types for creating top-level windows and accessing screen information \li \l{Qt Quick Dialogs}{Dialogs} - contains types for creating and diff --git a/tests/auto/quick/examples/tst_examples.cpp b/tests/auto/quick/examples/tst_examples.cpp index 240e8a791e..548b20a80a 100644 --- a/tests/auto/quick/examples/tst_examples.cpp +++ b/tests/auto/quick/examples/tst_examples.cpp @@ -291,7 +291,6 @@ void tst_examples::sgsnippets_data() void tst_examples::sgsnippets() { - QQuickWindow window; QFETCH(QString, file); @@ -301,19 +300,26 @@ void tst_examples::sgsnippets() QCOMPARE(component.status(), QQmlComponent::Ready); QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); + QQuickWindow *window = qobject_cast<QQuickWindow*>(object.data()); QQuickItem *root = qobject_cast<QQuickItem *>(object.data()); - if (!root) + if (!root && !window) { component.completeCreate(); - QVERIFY(root); + QVERIFY(false); + } + if (!window) + window = new QQuickWindow; - window.resize(240, 320); - window.show(); - QVERIFY(QTest::qWaitForWindowExposed(&window)); + window->resize(240, 320); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); - root->setParentItem(window.contentItem()); + if (root) + root->setParentItem(window->contentItem()); component.completeCreate(); qApp->processEvents(); + if (root) + delete window; } QTEST_MAIN(tst_examples) diff --git a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml new file mode 100644 index 0000000000..6a1c0632ad --- /dev/null +++ b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml @@ -0,0 +1,1036 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Layouts 1.1 + +Item { + id: container + width: 200 + height: 200 + TestCase { + id: testCase + name: "Tests_GridLayout" + when: windowShown + width: 200 + height: 200 + + Component { + id: layout_flow_Component + GridLayout { + columns: 4 + columnSpacing: 0 + rowSpacing: 0 + Repeater { + model: 6 + Rectangle { + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Text { text: index } + } + } + } + } + + function test_flow() + { + var layout = layout_flow_Component.createObject(container); + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[3], "itemRect", [30, 0, 10, 10]) + + tryCompare(layout.children[4], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[5], "itemRect", [10, 10, 10, 10]) + + layout.rows = 4 + layout.flow = GridLayout.TopToBottom + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 20, 10, 10]) + tryCompare(layout.children[3], "itemRect", [ 0, 30, 10, 10]) + + tryCompare(layout.children[4], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[5], "itemRect", [10, 10, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_flowLeftToRight_Component + GridLayout { + columns: 4 + columnSpacing: 0 + rowSpacing: 0 + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,1) + id: r2 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + Layout.rowSpan: 2 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // (0,1) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 0 + Layout.column: 1 + } + Rectangle { + // This one won't fit on the left and right sides of the big black box + // inserted at (3,0) + id: r4 + color: "red" + width: 20 + height: 20 + Layout.columnSpan: 2 + Layout.rowSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // continue flow from (0,2) + id: r5 + color: "black" + width: 20 + height: 20 + Layout.row: 0 + Layout.column: 2 + } + Repeater { + // ...and let the rest of the items automatically fill in the empty cells + model: 8 + Rectangle { + color: "red" + width: 20 + height: 20 + Text { text: index } + } + } + } + } + + function test_flowLeftToRight() { + var layout = layout_flowLeftToRight_Component.createObject(container); + compare(layout.implicitWidth, 80); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 20); + compare(layout.children[2].y, 0); + compare(layout.children[3].x, 0); + compare(layout.children[3].y, 60); + compare(layout.children[4].x, 40); + compare(layout.children[4].y, 0); + + // assumes that the repeater is the last item among the items it creates + compare(layout.children[5].x, 60); + compare(layout.children[5].y, 00); + compare(layout.children[6].x, 00); + compare(layout.children[6].y, 20); + compare(layout.children[7].x, 60); + compare(layout.children[7].y, 20); + compare(layout.children[8].x, 00); + compare(layout.children[8].y, 40); + compare(layout.children[9].x, 60); + compare(layout.children[9].y, 40); + compare(layout.children[10].x, 40); + compare(layout.children[10].y, 60); + compare(layout.children[11].x, 60); + compare(layout.children[11].y, 60); + compare(layout.children[12].x, 40); + compare(layout.children[12].y, 80); + + layout.destroy(); + } + + + Component { + id: layout_flowLeftToRightDefaultPositions_Component + GridLayout { + columns: 2 + columnSpacing: 0 + rowSpacing: 0 + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + // gray rectangles are items with just one row or just one column specified + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,0) + id: r2 + color: "gray" + width: 20 + height: 20 + Layout.row: 1 + } + Rectangle { + // (1,1) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + } + Rectangle { + // (1,0), warning emitted + id: r4 + color: "gray" + width: 20 + height: 20 + Layout.row: 1 + } + } + } + + function test_flowLeftToRightDefaultPositions() { + ignoreWarning("QGridLayoutEngine::addItem: Cell (1, 0) already taken"); + var layout = layout_flowLeftToRightDefaultPositions_Component.createObject(container); + compare(layout.implicitWidth, 40); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 0); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 20); + compare(layout.children[2].y, 20); + layout.destroy(); + } + + + Component { + id: layout_flowTopToBottom_Component + GridLayout { + rows: 4 + columnSpacing: 0 + rowSpacing: 0 + flow: GridLayout.TopToBottom + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,1) + id: r2 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + Layout.rowSpan: 2 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // (2,0) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 2 + Layout.column: 0 + } + Rectangle { + // This one won't fit on the left and right sides of the big black box + // inserted at (0,3) + id: r4 + color: "red" + width: 20 + height: 20 + Layout.rowSpan: 2 + Layout.fillHeight: true + } + Rectangle { + // continue flow from (1,0) + id: r5 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 0 + } + Repeater { + // ...and let the rest of the items automatically fill in the empty cells + model: 8 + Rectangle { + color: "red" + width: 20 + height: 20 + Text { text: index } + } + } + } + } + + function test_flowTopToBottom() { + var layout = layout_flowTopToBottom_Component.createObject(container); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 0); + compare(layout.children[2].y, 40); + compare(layout.children[3].x, 60); + compare(layout.children[3].y, 0); + compare(layout.children[4].x, 0); + compare(layout.children[4].y, 20); + + // The repeated items + compare(layout.children[5].x, 0); + compare(layout.children[5].y, 60); + compare(layout.children[6].x, 20); + compare(layout.children[6].y, 0); + compare(layout.children[7].x, 20); + compare(layout.children[7].y, 60); + compare(layout.children[8].x, 40); + compare(layout.children[8].y, 0); + compare(layout.children[9].x, 40); + compare(layout.children[9].y, 60); + compare(layout.children[10].x, 60); + compare(layout.children[10].y, 40); + compare(layout.children[11].x, 60); + compare(layout.children[11].y, 60); + compare(layout.children[12].x, 80); + compare(layout.children[12].y, 0); + + layout.destroy(); + } + + Component { + id: layout_spanAcrossEmptyRows_Component + /* This test has a large number of empty rows and columns, but there is one item + that spans across some of these empty rows/columns. + Do not modify (especially do not add items unless you understand what this is + testing) + */ + + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + // black rectangles are explicitly positioned with row,column + Rectangle { + // (0,0) + id: r0 + color: "black" + Layout.row: 0 + Layout.column: 0 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,1) + id: r1 + color: "black" + Layout.row: 0 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.rowSpan: 2 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,99) + id: r2 + color: "black" + Layout.row: 0 + Layout.column: 99 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + function test_spanAcrossEmptyRows() { + var layout = layout_spanAcrossEmptyRows_Component.createObject(container); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 0); + compare(layout.children[2].x, 40); + compare(layout.children[2].y, 0); + + compare(layout.implicitWidth, 60); + compare(layout.Layout.maximumWidth, 120); + + layout.destroy(); + } + + Component { + id: layout_spanIsMoreThanColumns_Component + + GridLayout { + columnSpacing: 1 + rowSpacing: 1 + columns: 2 + + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.columnSpan: 3 + } + } + } + + function test_spanIsMoreThanColumns() { + var layout = layout_spanIsMoreThanColumns_Component.createObject(container); + // item was not added, therefore implicit width is 0 + compare(layout.implicitWidth, 0); + layout.destroy(); + } + + function test_sizeHints() { + var layout = layout_spanAcrossEmptyRows_Component.createObject(container); + compare(layout.visible, true) + + var minWidth = layout.Layout.minimumWidth + var minHeight = layout.Layout.minimumHeight + + var prefWidth = layout.implicitWidth + var prefHeight = layout.implicitHeight + + var maxWidth = layout.Layout.maximumWidth + var maxHeight = layout.Layout.maximumHeight + + layout.visible = false + compare(minWidth, layout.Layout.minimumWidth) + compare(minHeight, layout.Layout.minimumHeight) + compare(prefWidth, layout.implicitWidth) + compare(prefHeight, layout.implicitHeight) + compare(maxWidth, layout.Layout.maximumWidth) + compare(maxHeight, layout.Layout.maximumHeight) + + layout.destroy(); + } + + Component { + id: layout_alignment_Component + GridLayout { + columns: 2 + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + // First one should auto position itself at (0,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,1) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignBottom + } + Rectangle { + // (1,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignRight + } + Rectangle { + // (1,1) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + Rectangle { + // (2,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + Layout.alignment: Qt.AlignRight + Layout.columnSpan: 2 + } + Rectangle { + // (3,0) + property var itemRect: [x, y, width, height] + baselineOffset: 7 + color: "red" + Layout.row: 3 + Layout.column: 0 + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + Rectangle { + // (3,1) + property var itemRect: [x, y, width, height] + baselineOffset: 7 + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignRight | Qt.AlignBaseline + } + + } + } + + function test_alignment() + { + var layout = layout_alignment_Component.createObject(container); + layout.width = 60; + layout.height = 100; + + + tryCompare(layout.children[0], "itemRect", [ 0, 0, 40, 40]); + tryCompare(layout.children[1], "itemRect", [40, 20, 20, 20]); + tryCompare(layout.children[2], "itemRect", [20, 40, 20, 20]); + tryCompare(layout.children[3], "itemRect", [45, 40, 10, 10]); + tryCompare(layout.children[4], "itemRect", [30, 60, 30, 30]); + tryCompare(layout.children[5], "itemRect", [ 0, 90, 10, 10]); + tryCompare(layout.children[6], "itemRect", [50, 90, 10, 10]); + + + layout.children[1].Layout.alignment = Qt.AlignTop + tryCompare(layout.children[1], "x", 40); + tryCompare(layout.children[1], "y", 0); + + layout.children[2].Layout.alignment = Qt.AlignLeft + tryCompare(layout.children[2], "x", 0); + tryCompare(layout.children[2], "y", 40); + + layout.children[3].Layout.alignment = Qt.AlignLeft|Qt.AlignVCenter + tryCompare(layout.children[3], "x", 40); + tryCompare(layout.children[3], "y", 45); + + layout.children[4].Layout.alignment = Qt.AlignLeft + tryCompare(layout.children[4], "x", 0); + tryCompare(layout.children[4], "y", 60); + + layout.destroy(); + } + + + Component { + id: layout_rightToLeft_Component + GridLayout { + layoutDirection: Qt.RightToLeft + columnSpacing: 0 + rowSpacing: 0 + columns: 3 + Rectangle { + property var itemRect: [x, y, width, height] + color: "#cbffc4" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignCenter + } + Rectangle { + property var itemRect: [x, y, width, height] + color: "#c4d1ff" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignRight + } + Rectangle { + property var itemRect: [x, y, width, height] + color: "#ffd5c4" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignLeft + } + } + } + + function verifyIsRightToLeft(layout) + { + tryCompare(layout.children[0], "itemRect", [125, 0, 50, 50]); + tryCompare(layout.children[1], "itemRect", [60, 0, 50, 50]); + tryCompare(layout.children[2], "itemRect", [10, 0, 50, 50]); + } + + function verifyIsLeftToRight(layout) + { + tryCompare(layout.children[0], "itemRect", [5, 0, 50, 50]); + tryCompare(layout.children[1], "itemRect", [70, 0, 50, 50]); + tryCompare(layout.children[2], "itemRect", [120, 0, 50, 50]); + } + + function test_rightToLeft() + { + var layout = layout_rightToLeft_Component.createObject(container); + layout.width = 180; + layout.height = 50; + + // Right To Left + verifyIsRightToLeft(layout) + layout.LayoutMirroring.enabled = true + layout.layoutDirection = Qt.LeftToRight + verifyIsRightToLeft(layout) + + // Left To Right + layout.LayoutMirroring.enabled = false + layout.layoutDirection = Qt.LeftToRight + verifyIsLeftToRight(layout); + layout.LayoutMirroring.enabled = true + layout.layoutDirection = Qt.RightToLeft + verifyIsLeftToRight(layout); + + layout.LayoutMirroring.enabled = false + verifyIsRightToLeft(layout) + + layout.layoutDirection = Qt.LeftToRight + verifyIsLeftToRight(layout); + + layout.LayoutMirroring.enabled = true + verifyIsRightToLeft(layout) + + layout.destroy(); + } + + Component { + id: layout_columnsOrRowsChanged_Component + GridLayout { + id: layout + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: 4 + Rectangle { + property var itemRect: [x, y, width, height] + width: 10 + height: 10 + color: "#ff0000" + } + } + } + } + + function test_columnsChanged() + { + var layout = layout_columnsOrRowsChanged_Component.createObject(container); + layout.width = 40; + layout.height = 20; + tryCompare(layout.children[0], "itemRect", [ 0, 5, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 5, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 5, 10, 10]) + tryCompare(layout.children[3], "itemRect", [30, 5, 10, 10]) + + layout.columns = 2 + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[3], "itemRect", [20, 10, 10, 10]) + + layout.destroy() + } + + function test_rowsChanged() + { + var layout = layout_columnsOrRowsChanged_Component.createObject(container); + layout.flow = GridLayout.TopToBottom + layout.width = 20; + layout.height = 40; + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 20, 10, 10]) + tryCompare(layout.children[3], "itemRect", [ 0, 30, 10, 10]) + + layout.rows = 2 + tryCompare(layout.children[0], "itemRect", [ 0, 5, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 25, 10, 10]) + tryCompare(layout.children[2], "itemRect", [10, 5, 10, 10]) + tryCompare(layout.children[3], "itemRect", [10, 25, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_columnOrRowChanged_Component + GridLayout { + id: layout + rowSpacing: 0 + columnSpacing: 0 + Rectangle { + property var itemRect: [x, y, width, height] + width: 10 + height: 10 + Layout.column: 0 + color: "#ff0000" + } + Rectangle { + property var itemRect: [x, y, width, height] + Layout.column: 1 + width: 10 + height: 10 + color: "#ff0000" + } + Rectangle { + property var itemRect: [x, y, width, height] + //Layout.column: 2 + width: 10 + height: 10 + color: "#ff0000" + } + } + } + + function test_columnOrRowChanged() + { + var layout = layout_columnOrRowChanged_Component.createObject(container); + layout.width = layout.implicitWidth + layout.height = layout.implicitHeight + // c0-c1-c2 + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.children[0].Layout.column = 3 + //c1-c2-c0 + tryCompare(layout.children[0], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [10, 0, 10, 10]) + + layout.children[2].Layout.column = 4 + //c1-c0-c2 + tryCompare(layout.children[0], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.children[0].Layout.row = 1 + // two rows, so we adjust it to its new implicitHeight + layout.height = layout.implicitHeight + //c1 c2 + // c0 + tryCompare(layout.children[0], "itemRect", [10, 10, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_baselines_Component + GridLayout { + id: layout + columnSpacing: 0 + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + baselineOffset: 10 + } + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + } + } + } + function test_baselines() + { + var layout = layout_baselines_Component.createObject(container); + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 10) + + layout.children[0].Layout.alignment = Qt.AlignBaseline + layout.children[1].Layout.alignment = Qt.AlignBaseline + + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 10, 10, 10]) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 20) + + layout.destroy(); + } + + Component { + id: layout_spacings_Component + GridLayout { + id: layout + Repeater { + model: 2 + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + } + } + } + } + + function test_spacings() + { + var layout = layout_spacings_Component.createObject(container); + + // breaks down below -19. This is acceptable, since it means that the implicit size of the layout is negative + var testSpacings = [Number.NaN, 0, 10, -5, -19] + layout.rowSpacing = 0 + for (var i = 0; i < testSpacings.length; ++i) { + var sp = testSpacings[i] + if (isNaN(sp)) { + sp = 5 // Test defaults + } else { + layout.columnSpacing = sp + } + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10 + sp, 0, 10, 10]) + compare(layout.implicitWidth, 20 + sp) + } + + // do not crash + layout.columnSpacing = -100 + waitForRendering(layout) + verify(isFinite(layout.implicitWidth)) + layout.destroy(); + } + + Component { + id: layout_alignToPixelGrid_Component + GridLayout { + columns: 3 + rowSpacing: 0 + columnSpacing: 2 + Repeater { + model: 3*3 + Rectangle { + color: "red" + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + } + + function test_alignToPixelGrid() + { + var layout = layout_alignToPixelGrid_Component.createObject(container) + layout.width = 30 + layout.height = 28 + + var rectWidth = (layout.width - 2 * layout.columnSpacing)/3 + var rectHeight = layout.height/3 + + waitForRendering(layout); + + var sp = layout.columnSpacing + var idealGeom = [0,0,rectWidth,rectHeight] + for (var r = 0; r < 3; ++r) { + idealGeom[0] = 0 + idealGeom[2] = rectWidth + for (var c = 0; c < 3; ++c) { + var child = layout.children[3*r + c] + var visualGeom = [child.x, child.y, child.x + child.width, child.y + child.height] + + // verify that visualGeom is an integer number + for (var i = 0; i < 2; ++i) + compare(visualGeom[i] % 1, 0) + + // verify that x,y is no more than one pixel from idealGeom + fuzzyCompare(visualGeom[0], idealGeom[0], 1) + fuzzyCompare(visualGeom[1], idealGeom[1], 1) + + // verify that the visual size is no more than 1 pixel taller/wider than the ideal size. + verify(visualGeom[2] <= idealGeom[2] + 1) + verify(visualGeom[3] <= idealGeom[3] + 1) + idealGeom[0] = idealGeom[2] + sp + idealGeom[2] = idealGeom[0] + rectWidth + } + idealGeom[1] = idealGeom[3] + idealGeom[3] = idealGeom[1] + rectHeight + } + + layout.destroy() + } + + Component { + + id: layout_Margins_Component + GridLayout { + columns: 2 + rowSpacing: 0 + columnSpacing: 0 + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.margins: 10 + Layout.leftMargin: 2 + Layout.topMargin: 3 + Layout.rightMargin: 4 + Layout.bottomMargin: 4 + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.leftMargin: 4 + Layout.topMargin: 5 + Layout.rightMargin: 6 + Layout.bottomMargin: 6 + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.leftMargin: 3 + Layout.topMargin: 4 + Layout.rightMargin: 5 + Layout.bottomMargin: 5 + } + } + } + + function test_Margins() + { + var layout = layout_Margins_Component.createObject(container) + + compare(layout.implicitWidth, 3 + 20 + 5 + 4 + 20 + 6) + compare(layout.implicitHeight, 5 + 20 + 6 + 4 + 20 + 5) + layout.width = layout.implicitWidth + layout.height = layout.implicitHeight + + waitForRendering(layout) + + var c0 = layout.children[0] + var c1 = layout.children[1] + var c2 = layout.children[2] + + compare(c0.x, 2) + compare(c0.y, 5) + compare(c1.x, 3 + 20 + 5 + 4) + compare(c1.y, 5) + compare(c2.x, 3) + compare(c2.y, 5 + 20 + 6 + 4) + + // reset left|rightMargin. It should then use the generic "margins" property + c0.Layout.leftMargin = undefined + compare(layout.implicitWidth, 10 + 20 + 4 + 4 + 20 + 6) + c0.Layout.bottomMargin = undefined + compare(layout.implicitHeight, 3 + 20 + 10 + 4 + 20 + 5) + } + + Component { + id: layout_invalidateWhileRearranging_Component + + GridLayout { + columns: 1 + Rectangle { + height: 50 + Layout.fillWidth: true + color: 'blue' + } + + Rectangle { + height: 50 + Layout.fillWidth: true + color: 'red' + onYChanged: { + visible = false; + } + } + } + } + + function test_invalidateWhileRearranging_QTBUG_44139() + { + var layout = layout_invalidateWhileRearranging_Component.createObject(container) + + waitForRendering(layout); + verify(layout.children[1].visible == false); + layout.destroy() + } + } +} diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml new file mode 100644 index 0000000000..4b47b396a3 --- /dev/null +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -0,0 +1,921 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Layouts 1.0 + +Item { + id: container + width: 200 + height: 200 + TestCase { + id: testCase + name: "Tests_RowLayout" + when: windowShown + width: 200 + height: 200 + + function itemRect(item) + { + return [item.x, item.y, item.width, item.height]; + } + + function test_fixedAndExpanding() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + width: 15; \ + spacing: 0; \ + property alias r1: _r1; \ + Rectangle { \ + id: _r1; \ + width: 5; \ + height: 10; \ + color: "#8080ff"; \ + Layout.fillWidth: false \ + } \ + property alias r2: _r2; \ + Rectangle { \ + id: _r2; \ + width: 10; \ + height: 20; \ + color: "#c0c0ff"; \ + Layout.fillWidth: true \ + } \ + } ' + + var lay = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(lay, 'implicitWidth', 15); + compare(lay.implicitHeight, 20); + compare(lay.height, 20); + lay.width = 30 + compare(lay.r1.x, 0); + compare(lay.r1.width, 5); + compare(lay.r2.x, 5); + compare(lay.r2.width, 25); + lay.destroy() + } + + function test_allExpanding() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + width: 15; \ + spacing: 0; \ + property alias r1: _r1; \ + Rectangle { \ + id: _r1; \ + width: 5; \ + height: 10; \ + color: "#8080ff"; \ + Layout.fillWidth: true \ + } \ + property alias r2: _r2; \ + Rectangle { \ + id: _r2; \ + width: 10; \ + height: 20; \ + color: "#c0c0ff"; \ + Layout.fillWidth: true \ + } \ + } ' + + var tmp = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(tmp, 'implicitWidth', 15); + compare(tmp.implicitHeight, 20); + compare(tmp.height, 20); + tmp.width = 30 + compare(tmp.r1.width, 10); + compare(tmp.r2.width, 20); + compare(tmp.Layout.minimumWidth, 0) + compare(tmp.Layout.maximumWidth, Number.POSITIVE_INFINITY) + tmp.destroy() + } + + function test_initialNestedLayouts() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + ColumnLayout { \ + id : col; \ + property alias row: _row; \ + objectName: "col"; \ + anchors.fill: parent; \ + RowLayout { \ + id : _row; \ + property alias r1: _r1; \ + property alias r2: _r2; \ + objectName: "row"; \ + spacing: 0; \ + Rectangle { \ + id: _r1; \ + color: "red"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + } \ + Rectangle { \ + id: _r2; \ + color: "green"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + Layout.fillWidth: true; \ + } \ + } \ + } ' + var col = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(col, 'width', 200); + tryCompare(col.row, 'width', 200); + tryCompare(col.row.r1, 'width', 50); + tryCompare(col.row.r2, 'width', 150); + col.destroy() + } + + function test_implicitSize() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + objectName: "row"; \ + spacing: 0; \ + height: 30; \ + anchors.left: parent.left; \ + anchors.right: parent.right; \ + Rectangle { \ + color: "red"; \ + height: 2; \ + Layout.minimumWidth: 50; \ + } \ + Rectangle { \ + color: "green"; \ + width: 10; \ + Layout.minimumHeight: 4; \ + } \ + Rectangle { \ + implicitWidth: 1000; \ + Layout.maximumWidth: 40; \ + implicitHeight: 6 \ + } \ + } ' + var row = Qt.createQmlObject(test_layoutStr, container, ''); + compare(row.implicitWidth, 50 + 10 + 40); + compare(row.implicitHeight, 6); + row.destroy() + } + + function test_countGeometryChanges() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + ColumnLayout { \ + id : col; \ + property alias row: _row; \ + objectName: "col"; \ + anchors.fill: parent; \ + RowLayout { \ + id : _row; \ + property alias r1: _r1; \ + property alias r2: _r2; \ + objectName: "row"; \ + spacing: 0; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Rectangle { \ + id: _r1; \ + color: "red"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Layout.fillWidth: true; \ + } \ + Rectangle { \ + id: _r2; \ + color: "green"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Layout.fillWidth: true; \ + } \ + } \ + } ' + var col = Qt.createQmlObject(test_layoutStr, container, ''); + compare(col.width, 200); + compare(col.row.width, 200); + compare(col.row.r1.width, 100); + compare(col.row.r2.width, 100); + compare(col.row.r1.counter, 1); + compare(col.row.r2.counter, 1); + verify(col.row.counter <= 2); + col.destroy() + } + + Component { + id: layoutItem_Component + Rectangle { + implicitWidth: 20 + implicitHeight: 20 + } + } + + Component { + id: columnLayoutItem_Component + ColumnLayout { + spacing: 0 + } + } + + Component { + id: layout_addAndRemoveItems_Component + RowLayout { + spacing: 0 + } + } + + function test_addAndRemoveItems() + { + var layout = layout_addAndRemoveItems_Component.createObject(container) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + + var rect0 = layoutItem_Component.createObject(layout) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 20) + + var rect1 = layoutItem_Component.createObject(layout) + rect1.Layout.preferredWidth = 30; + rect1.Layout.preferredHeight = 30; + compare(layout.implicitWidth, 50) + compare(layout.implicitHeight, 30) + + var col = columnLayoutItem_Component.createObject(layout) + var rect2 = layoutItem_Component.createObject(col) + rect2.Layout.fillHeight = true + var rect3 = layoutItem_Component.createObject(col) + rect3.Layout.fillHeight = true + + compare(layout.implicitWidth, 70) + compare(col.implicitHeight, 40) + compare(layout.implicitHeight, 40) + + rect3.destroy() + wait(0) // this will hopefully effectuate the destruction of the object + + col.destroy() + wait(0) + compare(layout.implicitWidth, 50) + compare(layout.implicitHeight, 30) + + rect0.destroy() + wait(0) + compare(layout.implicitWidth, 30) + compare(layout.implicitHeight, 30) + + rect1.destroy() + wait(0) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + + layout.destroy() + } + + Component { + id: layout_alignment_Component + RowLayout { + spacing: 0 + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.fillHeight: true + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + // use default alignment + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignTop + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignBottom + } + } + } + + function test_alignment() + { + var layout = layout_alignment_Component.createObject(container); + layout.width = 100; + layout.height = 40; + + compare(itemRect(layout.children[0]), [ 0, 0, 20, 40]); + compare(itemRect(layout.children[1]), [20, 10, 20, 20]); + compare(itemRect(layout.children[2]), [40, 0, 20, 20]); + compare(itemRect(layout.children[3]), [60, 10, 20, 20]); + compare(itemRect(layout.children[4]), [80, 20, 20, 20]); + layout.destroy(); + } + + Component { + id: layout_sizeHintNormalization_Component + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + id: r1 + color: "red" + Layout.minimumWidth: 1 + Layout.preferredWidth: 2 + Layout.maximumWidth: 3 + + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 + Layout.maximumHeight: 20 + Layout.fillWidth: true + } + } + } + + function test_sizeHintNormalization_data() { + return [ + { tag: "fallbackValues", widthHints: [-1, -1, -1], implicitWidth: 42, expected:[0,42,Number.POSITIVE_INFINITY]}, + { tag: "acceptZeroWidths", widthHints: [0, 0, 0], implicitWidth: 42, expected:[0,0,0]}, + { tag: "123", widthHints: [1,2,3], expected:[1,2,3]}, + { tag: "132", widthHints: [1,3,2], expected:[1,2,2]}, + { tag: "213", widthHints: [2,1,3], expected:[2,2,3]}, + { tag: "231", widthHints: [2,3,1], expected:[1,1,1]}, + { tag: "321", widthHints: [3,2,1], expected:[1,1,1]}, + { tag: "312", widthHints: [3,1,2], expected:[2,2,2]}, + + { tag: "1i3", widthHints: [1,-1,3], implicitWidth: 2, expected:[1,2,3]}, + { tag: "1i2", widthHints: [1,-1,2], implicitWidth: 3, expected:[1,2,2]}, + { tag: "2i3", widthHints: [2,-1,3], implicitWidth: 1, expected:[2,2,3]}, + { tag: "2i1", widthHints: [2,-1,1], implicitWidth: 3, expected:[1,1,1]}, + { tag: "3i1", widthHints: [3,-1,1], implicitWidth: 2, expected:[1,1,1]}, + { tag: "3i2", widthHints: [3,-1,2], implicitWidth: 1, expected:[2,2,2]}, + ]; + } + + function test_sizeHintNormalization(data) { + var layout = layout_sizeHintNormalization_Component.createObject(container); + if (data.implicitWidth !== undefined) { + layout.children[0].implicitWidth = data.implicitWidth + } + layout.children[0].Layout.minimumWidth = data.widthHints[0]; + layout.children[0].Layout.preferredWidth = data.widthHints[1]; + layout.children[0].Layout.maximumWidth = data.widthHints[2]; + wait(0); // Trigger processEvents() (allow LayoutRequest to be processed) + var normalizedResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth] + compare(normalizedResult, data.expected); + layout.destroy(); + } + + Component { + id: layout_sizeHint_Component + RowLayout { + property int implicitWidthChangedCount : 0 + onImplicitWidthChanged: { ++implicitWidthChangedCount } + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + id: r1 + color: "red" + Layout.minimumWidth: 1 + Layout.preferredWidth: 2 + Layout.maximumWidth: 3 + + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 + Layout.maximumHeight: 20 + Layout.fillWidth: true + } + } + } + } + + function test_sizeHint_data() { + return [ + { tag: "propagateNone", layoutHints: [10, 20, 30], childHints: [11, 21, 31], expected:[10, 20, 30]}, + { tag: "propagateMinimumWidth", layoutHints: [-1, 20, 30], childHints: [10, 21, 31], expected:[10, 20, 30]}, + { tag: "propagatePreferredWidth", layoutHints: [10, -1, 30], childHints: [11, 20, 31], expected:[10, 20, 30]}, + { tag: "propagateMaximumWidth", layoutHints: [10, 20, -1], childHints: [11, 21, 30], expected:[10, 20, 30]}, + { tag: "propagateAll", layoutHints: [-1, -1, -1], childHints: [10, 20, 30], expected:[10, 20, 30]}, + { tag: "propagateCrazy", layoutHints: [-1, -1, -1], childHints: [40, 21, 30], expected:[30, 30, 30]}, + { tag: "expandMinToExplicitPref", layoutHints: [-1, 1, -1], childHints: [11, 21, 31], expected:[ 1, 1, 31]}, + { tag: "expandMaxToExplicitPref", layoutHints: [-1, 99, -1], childHints: [11, 21, 31], expected:[11, 99, 99]}, + { tag: "expandAllToExplicitMin", layoutHints: [99, -1, -1], childHints: [11, 21, 31], expected:[99, 99, 99]}, + { tag: "expandPrefToExplicitMin", layoutHints: [24, -1, -1], childHints: [11, 21, 31], expected:[24, 24, 31]}, + { tag: "boundPrefToExplicitMax", layoutHints: [-1, -1, 19], childHints: [11, 21, 31], expected:[11, 19, 19]}, + { tag: "boundAllToExplicitMax", layoutHints: [-1, -1, 9], childHints: [11, 21, 31], expected:[ 9, 9, 9]}, + ]; + } + + function itemSizeHints(item) { + return [item.Layout.minimumWidth, item.implicitWidth, item.Layout.maximumWidth] + } + + function test_sizeHint(data) { + var layout = layout_sizeHint_Component.createObject(container) + + var grid = layout.children[0] + grid.Layout.minimumWidth = data.layoutHints[0] + grid.Layout.preferredWidth = data.layoutHints[1] + grid.Layout.maximumWidth = data.layoutHints[2] + + var child = grid.children[0] + if (data.implicitWidth !== undefined) { + child.implicitWidth = data.implicitWidth + } + child.Layout.minimumWidth = data.childHints[0] + child.Layout.preferredWidth = data.childHints[1] + child.Layout.maximumWidth = data.childHints[2] + + var effectiveSizeHintResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth] + compare(effectiveSizeHintResult, data.expected) + layout.destroy() + } + + function test_sizeHintPropagationCount() { + var layout = layout_sizeHint_Component.createObject(container) + var child = layout.children[0].children[0] + + child.Layout.minimumWidth = -1 + compare(itemSizeHints(layout), [0, 2, 3]) + child.Layout.preferredWidth = -1 + compare(itemSizeHints(layout), [0, 0, 3]) + child.Layout.maximumWidth = -1 + compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY]) + layout.Layout.maximumWidth = 1000 + compare(itemSizeHints(layout), [0, 0, 1000]) + layout.Layout.maximumWidth = -1 + compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY]) + + layout.implicitWidthChangedCount = 0 + child.Layout.minimumWidth = 10 + compare(itemSizeHints(layout), [10, 10, Number.POSITIVE_INFINITY]) + compare(layout.implicitWidthChangedCount, 1) + + child.Layout.preferredWidth = 20 + compare(itemSizeHints(layout), [10, 20, Number.POSITIVE_INFINITY]) + compare(layout.implicitWidthChangedCount, 2) + + child.Layout.maximumWidth = 30 + compare(itemSizeHints(layout), [10, 20, 30]) + compare(layout.implicitWidthChangedCount, 2) + + child.Layout.maximumWidth = 15 + compare(itemSizeHints(layout), [10, 15, 15]) + compare(layout.implicitWidthChangedCount, 3) + + child.Layout.maximumWidth = 30 + compare(itemSizeHints(layout), [10, 20, 30]) + compare(layout.implicitWidthChangedCount, 4) + + layout.Layout.maximumWidth = 29 + compare(layout.Layout.maximumWidth, 29) + layout.Layout.maximumWidth = -1 + compare(layout.Layout.maximumWidth, 30) + + layout.destroy() + } + + Component { + id: layout_change_implicitWidth_during_rearrange + ColumnLayout { + width: 100 + height: 20 + RowLayout { + spacing: 0 + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: false + implicitWidth: height + color: "red" + } + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "blue" + } + } + } + } + + function test_change_implicitWidth_during_rearrange() { + var layout = layout_change_implicitWidth_during_rearrange.createObject(container) + var red = layout.children[0].children[0] + var blue = layout.children[0].children[1] + waitForRendering(layout); + tryCompare(red, 'width', 20) + tryCompare(blue, 'width', 80) + layout.height = 40 + tryCompare(red, 'width', 40) + tryCompare(blue, 'width', 60) + layout.destroy() + } + + Component { + id: layout_addIgnoredItem_Component + RowLayout { + spacing: 0 + Rectangle { + id: r + } + } + } + + function test_addIgnoredItem() + { + var layout = layout_addIgnoredItem_Component.createObject(container) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + var r = layout.children[0] + r.Layout.preferredWidth = 20 + r.Layout.preferredHeight = 30 + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 30) + + layout.destroy(); + } + + + Component { + id: layout_rowLayout_Component + RowLayout { + } + } + + function test_stretchItem_data() + { + return [ + { expectedWidth: 0}, + { preferredWidth: 20, expectedWidth: 20}, + { preferredWidth: 0, expectedWidth: 0}, + { preferredWidth: 20, fillWidth: true, expectedWidth: 100}, + { width: 20, fillWidth: true, expectedWidth: 100}, + { width: 0, fillWidth: true, expectedWidth: 100}, + { preferredWidth: 0, fillWidth: true, expectedWidth: 100}, + { preferredWidth: 1, maximumWidth: 0, fillWidth: true, expectedWidth: 0}, + { preferredWidth: 0, minimumWidth: 1, expectedWidth: 1}, + ]; + } + + function test_stretchItem(data) + { + var layout = layout_rowLayout_Component.createObject(container) + var r = layoutItem_Component.createObject(layout) + // Reset previously relevant properties + r.width = 0 + r.implicitWidth = 0 + compare(layout.implicitWidth, 0) + + if (data.preferredWidth !== undefined) + r.Layout.preferredWidth = data.preferredWidth + if (data.fillWidth !== undefined) + r.Layout.fillWidth = data.fillWidth + if (data.width !== undefined) + r.width = data.width + if (data.minimumWidth !== undefined) + r.Layout.minimumWidth = data.minimumWidth + if (data.maximumWidth !== undefined) + r.Layout.maximumWidth = data.maximumWidth + + layout.width = 100 + + compare(r.width, data.expectedWidth) + + layout.destroy(); + } + + Component { + id: layout_alignToPixelGrid_Component + RowLayout { + spacing: 2 + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.alignment: Qt.AlignVCenter + } + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.alignment: Qt.AlignVCenter + } + } + } + function test_alignToPixelGrid() + { + var layout = layout_alignToPixelGrid_Component.createObject(container) + layout.width = 21 + layout.height = 21 + var r0 = layout.children[0] + compare(r0.x, 0) // 0.0 + compare(r0.y, 6) // 5.5 + var r1 = layout.children[1] + compare(r1.x, 12) // 11.5 + compare(r1.y, 6) // 5.5 + layout.destroy(); + } + + Component { + id: test_distributeToPixelGrid_Component + RowLayout { + spacing: 0 + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 50 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 20 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 70 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + } + } + + function test_distributeToPixelGrid_data() { + return [ + { tag: "narrow", spacing: 0, width: 60 }, + { tag: "belowPreferred", spacing: 0, width: 130 }, + { tag: "belowPreferredWithSpacing", spacing: 10, width: 130 }, + { tag: "abovePreferred", spacing: 0, width: 150 }, + { tag: "stretchSomethingToMaximum", spacing: 0, width: 240, + expected: [90, 60, 90] }, + { tag: "minSizeHasFractions", spacing: 2, width: 31 + 4, hints: [{min: 10+1/3}, {min: 10+1/3}, {min: 10+1/3}], + /*expected: [11, 11, 11]*/ }, /* verify that nothing gets allocated a size smaller than its minimum */ + { tag: "maxSizeHasFractions", spacing: 2, width: 271 + 4, hints: [{max: 90+1/3}, {max: 90+1/3}, {max: 90+1/3}], + /*expected: [90, 90, 90]*/ }, /* verify that nothing gets allocated a size larger than its maximum */ + { tag: "fixedSizeHasFractions", spacing: 2, width: 31 + 4, hints: [{min: 10+1/3, max: 10+1/3}, {min: 10+1/3, max: 10+1/3}, {min: 10+1/3, max: 10+1/3}], + /*expected: [11, 11, 11]*/ }, /* verify that nothing gets allocated a size smaller than its minimum */ + ]; + } + + function test_distributeToPixelGrid(data) + { + // CONFIGURATION + var layout = test_distributeToPixelGrid_Component.createObject(container) + layout.spacing = data.spacing + layout.width = data.width + layout.height = 10 + var kids = layout.children + + if (data.hasOwnProperty('hints')) { + var hints = data.hints + for (var i = 0; i < hints.length; ++i) { + var h = hints[i] + if (h.hasOwnProperty('min')) + kids[i].Layout.minimumWidth = h.min + if (h.hasOwnProperty('pref')) + kids[i].Layout.preferredWidth = h.pref + if (h.hasOwnProperty('max')) + kids[i].Layout.maximumWidth = h.max + } + } + waitForRendering(layout) + + var sum = 2 * layout.spacing + // TEST + for (var i = 0; i < kids.length; ++i) { + compare(kids[i].x % 1, 0) // checks if position is a whole integer + // verify if the items are within the size constraints as specified + verify(kids[i].width >= kids[i].Layout.minimumWidth) + verify(kids[i].width <= kids[i].Layout.maximumWidth) + if (data.hasOwnProperty('expected')) + compare(kids[i].width, data.expected[i]) + sum += kids[i].width + } + fuzzyCompare(sum, layout.width, 1) + + layout.destroy(); + } + + + + Component { + id: layout_deleteLayout + ColumnLayout { + property int dummyproperty: 0 // yes really - its needed + RowLayout { + Text { text: "label1" } // yes, both are needed + Text { text: "label2" } + } + } + } + + function test_destroyLayout() + { + var layout = layout_deleteLayout.createObject(container) + layout.children[0].children[0].visible = true + layout.visible = false + layout.destroy() // Do not crash + } + + function test_sizeHintWithHiddenChildren(data) { + var layout = layout_sizeHint_Component.createObject(container) + var grid = layout.children[0] + var child = grid.children[0] + + // Implicit sizes are not affected by the visibility of the parent layout. + // This is in order for the layout to know the preferred size it should show itself at. + compare(grid.visible, true) // LAYOUT SHOWN + compare(grid.implicitWidth, 2); + child.visible = false + compare(grid.implicitWidth, 0); + child.visible = true + compare(grid.implicitWidth, 2); + + grid.visible = false // LAYOUT HIDDEN + compare(grid.implicitWidth, 2); + child.visible = false + expectFail('', 'If GridLayout is hidden, GridLayout is not notified when child is explicitly hidden') + compare(grid.implicitWidth, 0); + child.visible = true + compare(grid.implicitWidth, 2); + + layout.destroy(); + } + + Component { + id: row_sizeHint_Component + Row { + Rectangle { + id: r1 + color: "red" + width: 2 + height: 20 + } + } + } + + function test_sizeHintWithHiddenChildrenForRow(data) { + var row = row_sizeHint_Component.createObject(container) + var child = row.children[0] + compare(row.visible, true) // POSITIONER SHOWN + compare(row.implicitWidth, 2); + child.visible = false + tryCompare(row, 'implicitWidth', 0); + child.visible = true + tryCompare(row, 'implicitWidth', 2); + + row.visible = false // POSITIONER HIDDEN + compare(row.implicitWidth, 2); + child.visible = false + expectFail('', 'If Row is hidden, Row is not notified when child is explicitly hidden') + compare(row.implicitWidth, 0); + child.visible = true + compare(row.implicitWidth, 2); + } + + Component { + id: rearrangeNestedLayouts_Component + RowLayout { + id: layout + anchors.fill: parent + width: 200 + height: 20 + RowLayout { + id: row + spacing: 0 + + Rectangle { + id: fixed + color: 'red' + implicitWidth: 20 + implicitHeight: 20 + } + Rectangle { + id: filler + color: 'grey' + Layout.fillWidth: true + implicitHeight: 20 + } + } + } + } + + function test_rearrangeNestedLayouts() + { + var layout = rearrangeNestedLayouts_Component.createObject(container) + var fixed = layout.children[0].children[0] + var filler = layout.children[0].children[1] + + compare(itemRect(fixed), [0,0,20,20]) + compare(itemRect(filler), [20,0,180,20]) + + fixed.implicitWidth = 100 + waitForRendering(layout) + compare(itemRect(fixed), [0,0,100,20]) + compare(itemRect(filler), [100,0,100,20]) + } + + Component { + id: changeChildrenOfHiddenLayout_Component + RowLayout { + property int childCount: 1 + Repeater { + model: parent.childCount + Text { + text: 'Just foo it' + } + } + } + } + function test_changeChildrenOfHiddenLayout() + { + var layout = changeChildrenOfHiddenLayout_Component.createObject(container) + var child = layout.children[0] + waitForRendering(layout) + layout.visible = false + waitForRendering(layout) + // Remove and add children to the hidden layout.. + layout.childCount = 0 + waitForRendering(layout) + layout.childCount = 1 + waitForRendering(layout) + layout.destroy() + } + } +} diff --git a/tests/auto/quick/qquicklayouts/qquicklayouts.pro b/tests/auto/quick/qquicklayouts/qquicklayouts.pro new file mode 100644 index 0000000000..9ed3e076be --- /dev/null +++ b/tests/auto/quick/qquicklayouts/qquicklayouts.pro @@ -0,0 +1,13 @@ +QT += core-private gui-private qml-private +TEMPLATE=app +TARGET=tst_qquicklayouts + +CONFIG += qmltestcase +SOURCES += tst_qquicklayouts.cpp + +TESTDATA = data/* + +OTHER_FILES += \ + data/tst_rowlayout.qml \ + data/tst_gridlayout.qml + diff --git a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp new file mode 100644 index 0000000000..373019091f --- /dev/null +++ b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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 https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtQuickTest/quicktest.h> +QUICK_TEST_MAIN(qquicklayouts) diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro index f25a28d45b..13bd6d78e2 100644 --- a/tests/auto/quick/quick.pro +++ b/tests/auto/quick/quick.pro @@ -21,6 +21,7 @@ PRIVATETESTS += \ qquickfontloader_static \ qquickfontmetrics \ qquickimageprovider \ + qquicklayouts \ qquickpath \ qquicksmoothedanimation \ qquickspringanimation \ |