diff options
62 files changed, 5216 insertions, 43 deletions
diff --git a/.qmake.conf b/.qmake.conf index dd4088c7..1a8da95c 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -4,4 +4,4 @@ DEFINES += QT_NO_FOREACH QQC2_SOURCE_TREE = $$PWD -MODULE_VERSION = 5.12.2 +MODULE_VERSION = 5.13.0 diff --git a/src/imports/controls/SplitView.qml b/src/imports/controls/SplitView.qml new file mode 100644 index 00000000..1953cf69 --- /dev/null +++ b/src/imports/controls/SplitView.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Templates 2.5 as T +import QtQuick.Controls 2.5 +import QtQuick.Controls.impl 2.5 + +T.SplitView { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + handle: Rectangle { + implicitWidth: control.orientation === Qt.Horizontal ? 6 : control.width + implicitHeight: control.orientation === Qt.Horizontal ? control.height : 6 + color: T.SplitHandle.pressed ? control.palette.mid + : (T.SplitHandle.hovered ? control.palette.midlight : control.palette.button) + } +} diff --git a/src/imports/controls/controls.pri b/src/imports/controls/controls.pri index 675fcf39..fbf5d075 100644 --- a/src/imports/controls/controls.pri +++ b/src/imports/controls/controls.pri @@ -53,6 +53,7 @@ QML_FILES += \ $$PWD/ScrollView.qml \ $$PWD/Slider.qml \ $$PWD/SpinBox.qml \ + $$PWD/SplitView.qml \ $$PWD/StackView.qml \ $$PWD/SwipeDelegate.qml \ $$PWD/Switch.qml \ diff --git a/src/imports/controls/doc/images/qtquickcontrols2-splitview-custom.png b/src/imports/controls/doc/images/qtquickcontrols2-splitview-custom.png Binary files differnew file mode 100644 index 00000000..7afd3ff8 --- /dev/null +++ b/src/imports/controls/doc/images/qtquickcontrols2-splitview-custom.png diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-splitview-custom.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-splitview-custom.qml new file mode 100644 index 00000000..e202e836 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-splitview-custom.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.12 +import QtQuick.Controls 2.5 + +Item { + width: 200 + height: 100 + + //! [1] + SplitView { + id: splitView + anchors.fill: parent + + handle: Rectangle { + implicitWidth: 4 + implicitHeight: 4 + color: SplitHandle.pressed ? "#81e889" + : (SplitHandle.hovered ? Qt.lighter("#c2f4c6", 1.1) : "#c2f4c6") + } + + Rectangle { + implicitWidth: 150 + color: "#444" + } + Rectangle { + implicitWidth: 50 + color: "#666" + } + } + //! [1] +} diff --git a/src/imports/controls/doc/src/includes/qquickicon.qdocinc b/src/imports/controls/doc/src/includes/qquickicon.qdocinc index a6ab90bb..ba7cede9 100644 --- a/src/imports/controls/doc/src/includes/qquickicon.qdocinc +++ b/src/imports/controls/doc/src/includes/qquickicon.qdocinc @@ -38,5 +38,11 @@ The icon is tinted with the specified color, unless the color is set to \c "transparent". + +\row + \li cache + \li This property specifies whether the icon should be cached. + + The default value is true. \endtable //! [grouped-properties] diff --git a/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc b/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc index d50e4c83..958cc358 100644 --- a/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc +++ b/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc @@ -736,6 +736,15 @@ \snippet qtquickcontrols2-spinbox-custom.qml file + \section2 Customizing SplitView + + SplitView consists of a visual \l {SplitView::handle}{handle} delegate. + + \image qtquickcontrols2-splitview-custom.png + + \snippet qtquickcontrols2-splitview-custom.qml 1 + + \section2 Customizing StackView StackView can have a visual \l {Control::background}{background} diff --git a/src/imports/controls/doc/src/qtquickcontrols2-differences.qdoc b/src/imports/controls/doc/src/qtquickcontrols2-differences.qdoc index 0b09edf6..e30285de 100644 --- a/src/imports/controls/doc/src/qtquickcontrols2-differences.qdoc +++ b/src/imports/controls/doc/src/qtquickcontrols2-differences.qdoc @@ -27,7 +27,7 @@ /*! \page qtquickcontrols2-differences.html - \title Differences between Qt Quick Controls 1 + \title Differences with Qt Quick Controls 1 Qt Quick Controls 1 was originally developed to support desktop platforms, with mobile and embedded support coming shortly afterwards. They have a diff --git a/src/imports/controls/doc/src/qtquickcontrols2-index.qdoc b/src/imports/controls/doc/src/qtquickcontrols2-index.qdoc index f95db512..d425f421 100644 --- a/src/imports/controls/doc/src/qtquickcontrols2-index.qdoc +++ b/src/imports/controls/doc/src/qtquickcontrols2-index.qdoc @@ -154,7 +154,7 @@ \li \l{Deploying Qt Quick Controls 2 Applications}{Deployment} \li \l{Qt Quick Controls 2 Configuration File}{Configuration File} \li \l{Supported Environment Variables in Qt Quick Controls 2}{Environment Variables} - \li \l{Differences between Qt Quick Controls 1} + \li \l{Differences with Qt Quick Controls 1} \endlist \section1 Reference diff --git a/src/imports/controls/fusion/SplitView.qml b/src/imports/controls/fusion/SplitView.qml new file mode 100644 index 00000000..4050b28a --- /dev/null +++ b/src/imports/controls/fusion/SplitView.qml @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Templates 2.5 as T +import QtQuick.Controls 2.5 +import QtQuick.Controls.impl 2.5 +import QtQuick.Controls.Fusion 2.5 + +T.SplitView { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + handle: Rectangle { + implicitWidth: control.orientation === Qt.Horizontal ? 2 : control.width + implicitHeight: control.orientation === Qt.Horizontal ? control.height : 2 + color: T.SplitHandle.pressed ? palette.dark + : (T.SplitHandle.hovered ? control.palette.midlight : control.palette.mid) + } +} diff --git a/src/imports/controls/fusion/fusion.pri b/src/imports/controls/fusion/fusion.pri index c4493c09..72978db5 100644 --- a/src/imports/controls/fusion/fusion.pri +++ b/src/imports/controls/fusion/fusion.pri @@ -51,6 +51,7 @@ QML_FILES += \ $$PWD/SliderGroove.qml \ $$PWD/SliderHandle.qml \ $$PWD/SpinBox.qml \ + $$PWD/SplitView.qml \ $$PWD/SwipeDelegate.qml \ $$PWD/SwitchDelegate.qml \ $$PWD/SwitchIndicator.qml \ diff --git a/src/imports/controls/fusion/qquickfusionstyle.cpp b/src/imports/controls/fusion/qquickfusionstyle.cpp index 98c1894f..7797463a 100644 --- a/src/imports/controls/fusion/qquickfusionstyle.cpp +++ b/src/imports/controls/fusion/qquickfusionstyle.cpp @@ -82,7 +82,7 @@ QColor QQuickFusionStyle::outline(const QPalette &palette) { if (palette.window().style() == Qt::TexturePattern) return QColor(0, 0, 0, 160); - return palette.background().color().darker(140); + return palette.window().color().darker(140); } QColor QQuickFusionStyle::highlightedOutline(const QPalette &palette) diff --git a/src/imports/controls/imagine/SplitView.qml b/src/imports/controls/imagine/SplitView.qml new file mode 100644 index 00000000..a90b3a86 --- /dev/null +++ b/src/imports/controls/imagine/SplitView.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Templates 2.5 as T +import QtQuick.Controls.Imagine 2.5 +import QtQuick.Controls.Imagine.impl 2.5 + +T.SplitView { + id: control + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + handle: NinePatchImage { + source: Imagine.url + "splitview-handle" + NinePatchImageSelector on source { + states: [ + {"vertical": control.orientation === Qt.Vertical}, + {"horizontal":control.orientation === Qt.Horizontal}, + {"disabled": !control.enabled}, + {"pressed": T.SplitHandle.pressed}, + {"mirrored": control.mirrored}, + {"hovered": T.SplitHandle.hovered} + ] + } + } +} diff --git a/src/imports/controls/imagine/design/imagine.sketch b/src/imports/controls/imagine/design/imagine.sketch Binary files differindex ecb437f7..f3535b37 100644 --- a/src/imports/controls/imagine/design/imagine.sketch +++ b/src/imports/controls/imagine/design/imagine.sketch diff --git a/src/imports/controls/imagine/images/splitview-handle-disabled.png b/src/imports/controls/imagine/images/splitview-handle-disabled.png Binary files differnew file mode 100644 index 00000000..0071c196 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-disabled.png diff --git a/src/imports/controls/imagine/images/splitview-handle-disabled@2x.png b/src/imports/controls/imagine/images/splitview-handle-disabled@2x.png Binary files differnew file mode 100644 index 00000000..67cee407 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-disabled@2x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-disabled@3x.png b/src/imports/controls/imagine/images/splitview-handle-disabled@3x.png Binary files differnew file mode 100644 index 00000000..84752ba9 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-disabled@3x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-disabled@4x.png b/src/imports/controls/imagine/images/splitview-handle-disabled@4x.png Binary files differnew file mode 100644 index 00000000..e4be8597 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-disabled@4x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-hovered.png b/src/imports/controls/imagine/images/splitview-handle-hovered.png Binary files differnew file mode 100644 index 00000000..1386d213 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-hovered.png diff --git a/src/imports/controls/imagine/images/splitview-handle-hovered@2x.png b/src/imports/controls/imagine/images/splitview-handle-hovered@2x.png Binary files differnew file mode 100644 index 00000000..4708a47b --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-hovered@2x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-hovered@3x.png b/src/imports/controls/imagine/images/splitview-handle-hovered@3x.png Binary files differnew file mode 100644 index 00000000..2ccc1ff5 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-hovered@3x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-hovered@4x.png b/src/imports/controls/imagine/images/splitview-handle-hovered@4x.png Binary files differnew file mode 100644 index 00000000..6929da70 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-hovered@4x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-pressed.png b/src/imports/controls/imagine/images/splitview-handle-pressed.png Binary files differnew file mode 100644 index 00000000..7face6b4 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-pressed.png diff --git a/src/imports/controls/imagine/images/splitview-handle-pressed@2x.png b/src/imports/controls/imagine/images/splitview-handle-pressed@2x.png Binary files differnew file mode 100644 index 00000000..ad940d0f --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-pressed@2x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-pressed@3x.png b/src/imports/controls/imagine/images/splitview-handle-pressed@3x.png Binary files differnew file mode 100644 index 00000000..d4e19dc2 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-pressed@3x.png diff --git a/src/imports/controls/imagine/images/splitview-handle-pressed@4x.png b/src/imports/controls/imagine/images/splitview-handle-pressed@4x.png Binary files differnew file mode 100644 index 00000000..8ccbbebc --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle-pressed@4x.png diff --git a/src/imports/controls/imagine/images/splitview-handle.png b/src/imports/controls/imagine/images/splitview-handle.png Binary files differnew file mode 100644 index 00000000..c1dffa67 --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle.png diff --git a/src/imports/controls/imagine/images/splitview-handle@2x.png b/src/imports/controls/imagine/images/splitview-handle@2x.png Binary files differnew file mode 100644 index 00000000..180e266a --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle@2x.png diff --git a/src/imports/controls/imagine/images/splitview-handle@3x.png b/src/imports/controls/imagine/images/splitview-handle@3x.png Binary files differnew file mode 100644 index 00000000..35ea51db --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle@3x.png diff --git a/src/imports/controls/imagine/images/splitview-handle@4x.png b/src/imports/controls/imagine/images/splitview-handle@4x.png Binary files differnew file mode 100644 index 00000000..3a1e7e9f --- /dev/null +++ b/src/imports/controls/imagine/images/splitview-handle@4x.png diff --git a/src/imports/controls/imagine/imagine.pri b/src/imports/controls/imagine/imagine.pri index cb6857a5..081a509e 100644 --- a/src/imports/controls/imagine/imagine.pri +++ b/src/imports/controls/imagine/imagine.pri @@ -30,6 +30,7 @@ QML_FILES += \ $$PWD/ScrollIndicator.qml \ $$PWD/Slider.qml \ $$PWD/SpinBox.qml \ + $$PWD/SplitView.qml \ $$PWD/StackView.qml \ $$PWD/SwipeDelegate.qml \ $$PWD/SwipeView.qml \ diff --git a/src/imports/controls/material/SplitView.qml b/src/imports/controls/material/SplitView.qml new file mode 100644 index 00000000..6e5eb54d --- /dev/null +++ b/src/imports/controls/material/SplitView.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Templates 2.5 as T +import QtQuick.Controls 2.5 +import QtQuick.Controls.impl 2.5 +import QtQuick.Controls.Material 2.5 + +T.SplitView { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + handle: Rectangle { + implicitWidth: control.orientation === Qt.Horizontal ? 6 : control.width + implicitHeight: control.orientation === Qt.Horizontal ? control.height : 6 + color: T.SplitHandle.pressed ? control.Material.background + : Qt.lighter(control.Material.background, T.SplitHandle.hovered ? 1.2 : 1.1) + + Rectangle { + color: control.Material.secondaryTextColor + width: control.orientation === Qt.Horizontal ? thickness : length + height: control.orientation === Qt.Horizontal ? length : thickness + radius: thickness + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + property int length: parent.T.SplitHandle.pressed ? 3 : 8 + readonly property int thickness: parent.T.SplitHandle.pressed ? 3 : 1 + + Behavior on length { + NumberAnimation { + duration: 100 + } + } + } + } +} diff --git a/src/imports/controls/material/material.pri b/src/imports/controls/material/material.pri index ab925aa2..bda1fb21 100644 --- a/src/imports/controls/material/material.pri +++ b/src/imports/controls/material/material.pri @@ -53,6 +53,7 @@ QML_FILES += \ $$PWD/Slider.qml \ $$PWD/SliderHandle.qml \ $$PWD/SpinBox.qml \ + $$PWD/SplitView.qml \ $$PWD/StackView.qml \ $$PWD/SwipeDelegate.qml \ $$PWD/SwipeView.qml \ diff --git a/src/imports/controls/plugins.qmltypes b/src/imports/controls/plugins.qmltypes index 4585a9a6..6d414405 100644 --- a/src/imports/controls/plugins.qmltypes +++ b/src/imports/controls/plugins.qmltypes @@ -1282,6 +1282,7 @@ Module { Property { name: "width"; type: "int" } Property { name: "height"; type: "int" } Property { name: "color"; type: "QColor" } + Property { name: "cache"; type: "bool" } } Component { name: "QQuickItemDelegate" diff --git a/src/imports/controls/qtquickcontrols2plugin.cpp b/src/imports/controls/qtquickcontrols2plugin.cpp index 5e9f253d..89d956bd 100644 --- a/src/imports/controls/qtquickcontrols2plugin.cpp +++ b/src/imports/controls/qtquickcontrols2plugin.cpp @@ -56,6 +56,7 @@ #include <QtQuickControls2/private/qquicktumblerview_p.h> #endif #include <QtQuickTemplates2/private/qquickoverlay_p.h> +#include <QtQuickTemplates2/private/qquicksplitview_p.h> #include <QtQuickControls2/private/qquickclippedtext_p.h> #include <QtQuickControls2/private/qquickitemgroup_p.h> #include <QtQuickTemplates2/private/qquicktheme_p_p.h> @@ -192,6 +193,11 @@ void QtQuickControls2Plugin::registerTypes(const char *uri) qmlRegisterType(resolvedUrl(QStringLiteral("MenuBarItem.qml")), uri, 2, 3, "MenuBarItem"); qmlRegisterUncreatableType<QQuickOverlay>(uri, 2, 3, "Overlay", QStringLiteral("Overlay is only available as an attached property.")); + // QtQuick.Controls 2.5 (new types in Qt 5.12) + qmlRegisterType(resolvedUrl(QStringLiteral("SplitView.qml")), uri, 2, 5, "SplitView"); + qmlRegisterUncreatableType<QQuickSplitHandleAttached>(uri, 2, 5, "SplitHandle", + QStringLiteral("SplitHandle is only available as an attached property.")); + // Register the latest version, even if there are no new types or new revisions for existing types yet. // Before Qt 5.12, we would do the following: // diff --git a/src/imports/controls/universal/SplitView.qml b/src/imports/controls/universal/SplitView.qml new file mode 100644 index 00000000..bbcdba9d --- /dev/null +++ b/src/imports/controls/universal/SplitView.qml @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Templates 2.5 as T +import QtQuick.Controls 2.5 +import QtQuick.Controls.impl 2.5 +import QtQuick.Controls.Universal 2.5 + +T.SplitView { + id: control + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + implicitContentWidth + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + + handle: Rectangle { + implicitWidth: control.orientation === Qt.Horizontal ? 6 : control.width + implicitHeight: control.orientation === Qt.Horizontal ? control.height : 6 + color: T.SplitHandle.pressed ? control.Universal.baseMediumColor + : (T.SplitHandle.hovered ? control.Universal.baseMediumLowColor : control.Universal.chromeHighColor) + } +} diff --git a/src/imports/controls/universal/universal.pri b/src/imports/controls/universal/universal.pri index 33d0dcb0..4440acbf 100644 --- a/src/imports/controls/universal/universal.pri +++ b/src/imports/controls/universal/universal.pri @@ -34,6 +34,7 @@ QML_FILES += \ $$PWD/ScrollIndicator.qml \ $$PWD/Slider.qml \ $$PWD/SpinBox.qml \ + $$PWD/SplitView.qml \ $$PWD/StackView.qml \ $$PWD/SwipeDelegate.qml \ $$PWD/SwitchDelegate.qml \ diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index c4ff68fc..510980e3 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -82,6 +82,7 @@ #include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h> #include <QtQuickTemplates2/private/qquickslider_p.h> #include <QtQuickTemplates2/private/qquickspinbox_p.h> +#include <QtQuickTemplates2/private/qquicksplitview_p.h> #include <QtQuickTemplates2/private/qquickstackview_p.h> #include <QtQuickTemplates2/private/qquickswipe_p.h> #include <QtQuickTemplates2/private/qquickswipedelegate_p.h> @@ -343,6 +344,11 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) qmlRegisterType<QQuickPopupAnchors>(); qmlRegisterType<QQuickRangeSlider, 5>(uri, 2, 5, "RangeSlider"); qmlRegisterType<QQuickSlider, 5>(uri, 2, 5, "Slider"); + qmlRegisterType<QQuickSplitView, 5>(uri, 2, 5, "SplitView"); + qmlRegisterType<QQuickSplitViewAttached>(); + qmlRegisterUncreatableType<QQuickSplitHandleAttached>(uri, 2, 5, "SplitHandle", + QStringLiteral("SplitHandle is only available as an attached property.")); + qmlRegisterType<QQuickSplitHandleAttached>(); qmlRegisterType<QQuickSpinBox, 5>(uri, 2, 5, "SpinBox"); qmlRegisterType<QQuickTextArea, 5>(uri, 2, 5, "TextArea"); qmlRegisterType<QQuickTextField, 5>(uri, 2, 5, "TextField"); diff --git a/src/quickcontrols2/qquickiconlabel.cpp b/src/quickcontrols2/qquickiconlabel.cpp index 37e6060a..b246621b 100644 --- a/src/quickcontrols2/qquickiconlabel.cpp +++ b/src/quickcontrols2/qquickiconlabel.cpp @@ -81,6 +81,7 @@ bool QQuickIconLabelPrivate::createImage() image->setSource(icon.source()); image->setSourceSize(QSize(icon.width(), icon.height())); image->setColor(icon.color()); + image->setCache(icon.cache()); QQmlEngine::setContextForObject(image, qmlContext(q)); if (componentComplete) completeComponent(image); @@ -114,6 +115,7 @@ void QQuickIconLabelPrivate::syncImage() image->setSource(icon.source()); image->setSourceSize(QSize(icon.width(), icon.height())); image->setColor(icon.color()); + image->setCache(icon.cache()); const int valign = alignment & Qt::AlignVertical_Mask; image->setVerticalAlignment(static_cast<QQuickImage::VAlignment>(valign)); const int halign = alignment & Qt::AlignHorizontal_Mask; diff --git a/src/quickcontrols2/qquicktumblerview.cpp b/src/quickcontrols2/qquicktumblerview.cpp index a510a1fe..5f5c065d 100644 --- a/src/quickcontrols2/qquicktumblerview.cpp +++ b/src/quickcontrols2/qquicktumblerview.cpp @@ -36,6 +36,7 @@ #include "qquicktumblerview_p.h" +#include <QtCore/qloggingcategory.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquicklistview_p.h> #include <QtQuick/private/qquickpathview_p.h> @@ -45,6 +46,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcTumblerView, "qt.quick.controls.tumblerview") + QQuickTumblerView::QQuickTumblerView(QQuickItem *parent) : QQuickItem(parent) { @@ -59,6 +62,8 @@ QVariant QQuickTumblerView::model() const void QQuickTumblerView::setModel(const QVariant &model) { + qCDebug(lcTumblerView) << "setting model to:" << model << "on" + << (m_pathView ? static_cast<QObject*>(m_pathView) : static_cast<QObject*>(m_listView)); if (model == m_model) return; @@ -85,6 +90,8 @@ QQmlComponent *QQuickTumblerView::delegate() const void QQuickTumblerView::setDelegate(QQmlComponent *delegate) { + qCDebug(lcTumblerView) << "setting delegate to:" << delegate << "on" + << (m_pathView ? static_cast<QObject*>(m_pathView) : static_cast<QObject*>(m_listView)); if (delegate == m_delegate) return; @@ -135,6 +142,8 @@ void QQuickTumblerView::createView() } if (!m_pathView) { + qCDebug(lcTumblerView) << "creating PathView"; + m_pathView = new QQuickPathView; QQmlEngine::setContextForObject(m_pathView, qmlContext(this)); QQml_setParent_noEvent(m_pathView, this); @@ -150,6 +159,8 @@ void QQuickTumblerView::createView() updateView(); // Set the model. updateModel(); + + qCDebug(lcTumblerView) << "finished creating PathView"; } } else { if (m_pathView) { @@ -162,6 +173,8 @@ void QQuickTumblerView::createView() } if (!m_listView) { + qCDebug(lcTumblerView) << "creating ListView"; + m_listView = new QQuickListView; QQmlEngine::setContextForObject(m_listView, qmlContext(this)); QQml_setParent_noEvent(m_listView, this); @@ -181,6 +194,8 @@ void QQuickTumblerView::createView() // which we don't want when the contentItem has just been created. m_listView->setDelegate(m_delegate); m_listView->setHighlightMoveDuration(1000); + + qCDebug(lcTumblerView) << "finished creating ListView"; } } } diff --git a/src/quicktemplates2/qquickicon.cpp b/src/quicktemplates2/qquickicon.cpp index 1b8f4797..5a689108 100644 --- a/src/quicktemplates2/qquickicon.cpp +++ b/src/quicktemplates2/qquickicon.cpp @@ -46,6 +46,7 @@ public: int width = 0; int height = 0; QColor color = Qt::transparent; + bool cache = true; enum ResolveProperties { NameResolved = 0x0001, @@ -53,6 +54,7 @@ public: WidthResolved = 0x0004, HeightResolved = 0x0008, ColorResolved = 0x0010, + CacheResolved = 0x0020, AllPropertiesResolved = 0x1ffff }; @@ -86,7 +88,8 @@ bool QQuickIcon::operator==(const QQuickIcon &other) const && d->source == other.d->source && d->width == other.d->width && d->height == other.d->height - && d->color == other.d->color); + && d->color == other.d->color + && d->cache == other.d->cache); } bool QQuickIcon::operator!=(const QQuickIcon &other) const @@ -199,6 +202,26 @@ void QQuickIcon::resetColor() d->resolveMask &= ~QQuickIconPrivate::ColorResolved; } +bool QQuickIcon::cache() const +{ + return d->cache; +} + +void QQuickIcon::setCache(bool cache) +{ + if ((d->resolveMask & QQuickIconPrivate::CacheResolved) && d->cache == cache) + return; + + d->cache = cache; + d->resolveMask |= QQuickIconPrivate::CacheResolved; +} + +void QQuickIcon::resetCache() +{ + d->cache = true; + d->resolveMask &= ~QQuickIconPrivate::CacheResolved; +} + QQuickIcon QQuickIcon::resolve(const QQuickIcon &other) const { QQuickIcon resolved = *this; @@ -218,6 +241,9 @@ QQuickIcon QQuickIcon::resolve(const QQuickIcon &other) const if (!(d->resolveMask & QQuickIconPrivate::ColorResolved)) resolved.setColor(other.color()); + if (!(d->resolveMask & QQuickIconPrivate::CacheResolved)) + resolved.setCache(other.cache()); + return resolved; } diff --git a/src/quicktemplates2/qquickicon_p.h b/src/quicktemplates2/qquickicon_p.h index 2c95bc9d..57cab720 100644 --- a/src/quicktemplates2/qquickicon_p.h +++ b/src/quicktemplates2/qquickicon_p.h @@ -67,6 +67,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIcon Q_PROPERTY(int width READ width WRITE setWidth RESET resetWidth FINAL) Q_PROPERTY(int height READ height WRITE setHeight RESET resetHeight FINAL) Q_PROPERTY(QColor color READ color WRITE setColor RESET resetColor FINAL) + Q_PROPERTY(bool cache READ cache WRITE setCache RESET resetCache FINAL) public: QQuickIcon(); @@ -99,6 +100,10 @@ public: void setColor(const QColor &color); void resetColor(); + bool cache() const; + void setCache(bool cache); + void resetCache(); + QQuickIcon resolve(const QQuickIcon &other) const; private: diff --git a/src/quicktemplates2/qquicksplitview.cpp b/src/quicktemplates2/qquicksplitview.cpp new file mode 100644 index 00000000..40cc21f5 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview.cpp @@ -0,0 +1,2044 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquicksplitview_p.h" +#include "qquicksplitview_p_p.h" +#include "qquickcontentitem_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qcborarray.h> +#include <QtCore/qcbormap.h> +#include <QtCore/qcborvalue.h> +#include <QtQml/QQmlInfo> + +QT_BEGIN_NAMESPACE + +/*! + \qmltype SplitView + \inherits Control + \instantiates QQuickSplitView + \inqmlmodule QtQuick.Controls + \since 5.12 + \ingroup qtquickcontrols2-containers + \ingroup qtquickcontrols2-focusscopes + \brief Lays out items with a draggable splitter between each item + + SplitView is a control that lays out items horizontally or vertically with + a draggable splitter between each item. + + SplitView supports the following attached properties on items it manages: + + \list + \li \l SplitView.minimumWidth + \li \l SplitView.minimumHeight + \li \l SplitView.preferredWidth + \li \l SplitView.preferredHeight + \li \l SplitView.maximumWidth + \li \l SplitView.maximumHeight + \li \l SplitView.fillWidth (true for only one child) + \li \l SplitView.fillHeight (true for only one child) + \endlist + + In addition, each handle has the following read-only attached properties: + + \list + \li \l SplitHandle.hovered + \li \l SplitHandle.pressed + \endlist + + The preferred size of items in a SplitView can be specified via + \l {Item::}{implicitWidth} and \l {Item::}{implicitHeight} or + \c SplitView.preferredWidth and \c SplitView.preferredHeight: + + \code + SplitView { + anchors.fill: parent + + Item { + SplitView.preferredWidth: 50 + } + + // ... + } + \endcode + + For a horizontal SplitView, it's not necessary to specify the preferred + height of each item, as they will be resized to the height of the view. + This applies in reverse for vertical views. + + When a split handle is dragged, the \c SplitView.preferredWidth or + \c SplitView.preferredHeight property is overwritten, depending on the + \l orientation of the view. + + To limit the size of items in a horizontal view, use the following + properties: + + \code + SplitView { + anchors.fill: parent + + Item { + SplitView.minimumWidth: 25 + SplitView.preferredWidth: 50 + SplitView.maximumWidth: 100 + } + + // ... + } + \endcode + + To limit the size of items in a vertical view, use the following + properties: + + \code + SplitView { + anchors.fill: parent + orientation: Qt.Vertical + + Item { + SplitView.minimumHeight: 25 + SplitView.preferredHeight: 50 + SplitView.maximumHeight: 100 + } + + // ... + } + \endcode + + There will always be one item (the fill item) in the SplitView that has + \c SplitView.fillWidth set to \c true (or \c SplitView.fillHeight, if + \l orientation is \c Qt.Vertical). This means that the item will get all + leftover space when other items have been laid out. By default, the last + visible child of the SplitView will have this set, but it can be changed by + explicitly setting \c fillWidth to \c true on another item. + + A handle can belong to the item either on the left or top side, or on the + right or bottom side: + + \list + \li If the fill item is to the right: the handle belongs to the left + item. + \li If the fill item is on the left: the handle belongs to the right + item. + \endlist + + To create a SplitView with three items, and let the center item get + superfluous space, one could do the following: + + \code + SplitView { + anchors.fill: parent + orientation: Qt.Horizontal + + Rectangle { + implicitWidth: 200 + SplitView.maximumWidth: 400 + color: "lightblue" + Label { + text: "View 1" + anchors.centerIn: parent + } + } + Rectangle { + id: centerItem + SplitView.minimumWidth: 50 + SplitView.fillWidth: true + color: "lightgray" + Label { + text: "View 2" + anchors.centerIn: parent + } + } + Rectangle { + implicitWidth: 200 + color: "lightgreen" + Label { + text: "View 3" + anchors.centerIn: parent + } + } + } + \endcode + + \section1 Serializing SplitView's State + + The main purpose of SplitView is to allow users to easily configure the + size of various UI elements. In addition, the user's preferred sizes should + be remembered across sessions. To achieve this, the values of the \c + SplitView.preferredWidth and \c SplitView.preferredHeight properties can be + serialized using the \l saveState() and \l restoreState() functions: + + \code + import QtQuick.Controls 2.5 + import Qt.labs.settings 1.0 + + ApplicationWindow { + // ... + + Component.onCompleted: splitView.restoreState(settings.splitView) + Component.onDestruction: settings.splitView = splitView.saveState() + + Settings { + id: settings + property var splitView + } + + SplitView { + id: splitView + // ... + } + } + \endcode + + Alternatively, the \l {Settings::}{value()} and \l {Settings::}{setValue()} + functions of \l Settings can be used: + + \code + import QtQuick.Controls 2.5 + import Qt.labs.settings 1.0 + + ApplicationWindow { + // ... + + Component.onCompleted: splitView.restoreState(settings.value("ui/splitview")) + Component.onDestruction: settings.setValue("ui/splitview", splitView.saveState()) + + Settings { + id: settings + } + + SplitView { + id: splitView + // ... + } + } + \endcode + + As the state is saved into a string, it can be written to any kind of file. + + \sa SplitHandle, {Customizing SplitView}, {Container Controls} +*/ + +Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") +Q_LOGGING_CATEGORY(qlcQQuickSplitViewMouse, "qt.quick.controls.splitview.mouse") +Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") + +void QQuickSplitViewPrivate::updateFillIndex() +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; + + m_fillIndex = -1; + int i = 0; + int lastVisibleIndex = -1; + for (; i < count; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (!item->isVisible()) + continue; + + lastVisibleIndex = i; + + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (!attached) + continue; + + if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) { + m_fillIndex = i; + qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << m_fillIndex; + break; + } + } + + if (m_fillIndex == -1) { + // If there was no item with fillWidth/fillHeight set, m_fillIndex will be -1, + // and we'll set it to the last visible item. + // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. + m_fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; + qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << m_fillIndex; + } +} + +/* + Resizes split items according to their preferred size and any constraints. + + If a split item is being resized due to a split handle being dragged, + it will be resized accordingly. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag) +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + for (int index = 0; index < count; ++index) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) { + // The item is not visible, so skip it. + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": split item " << item + << " at index " << index << " is not visible; skipping it and its handles (if any)"; + continue; + } + + const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + const auto sizeData = effectiveSizeData(itemPrivate, attached); + + const bool resizeLeftItem = m_fillIndex > m_pressedHandleIndex; + // True if any handle is pressed. + const bool isAHandlePressed = m_pressedHandleIndex != -1; + // True if this particular item is being resized as a result of a handle being dragged. + const bool isBeingResized = isAHandlePressed && ((resizeLeftItem && index == m_pressedHandleIndex) + || (!resizeLeftItem && index == m_pressedHandleIndex + 1)); + if (isBeingResized) { + indexBeingResizedDueToDrag = index; + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": dragging handle for item"; + } + + const qreal size = horizontal ? width : height; + qreal requestedSize = 0; + if (isBeingResized) { + // Don't let the mouse go past either edge of the SplitView. + const qreal clampedMousePos = horizontal + ? qBound(0.0, m_mousePos.x(), width) + : qBound(0.0, m_mousePos.y(), height); + + // We also need to ensure that the item's edge doesn't go too far + // out and hence give the item more space than is available. + const int firstIndex = resizeLeftItem ? m_pressedHandleIndex + 1 : 0; + const int lastIndex = resizeLeftItem ? contentModel->count() - 1 : m_pressedHandleIndex; + const qreal accumulated = accumulatedSize(firstIndex, lastIndex); + + const qreal mousePosRelativeToLeftHandleEdge = horizontal + ? m_pressPos.x() - m_handlePosBeforePress.x() + : m_pressPos.y() - m_handlePosBeforePress.y(); + + const QQuickItem *pressedHandleItem = m_handleItems.at(m_pressedHandleIndex); + const qreal pressedHandleSize = horizontal ? pressedHandleItem->width() : pressedHandleItem->height(); + + if (resizeLeftItem) { + // The handle shouldn't cross other handles, so use the right edge of + // the first handle to the left as the left edge. + qreal leftEdge = 0; + if (m_pressedHandleIndex - 1 >= 0) { + const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); + leftEdge = horizontal + ? leftHandle->x() + leftHandle->width() + : leftHandle->y() + leftHandle->height(); + } + + // The mouse can be clicked anywhere in the handle, and if we don't account for + // its position within the handle, the handle will jump when dragged. + const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; + + const qreal rightStop = size - accumulated - pressedHandleSize; + qreal leftStop = qMax(leftEdge, pressedHandlePos); + // qBound() doesn't care if min is greater than max, but we do. + if (leftStop > rightStop) + leftStop = rightStop; + const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); + const qreal newItemSize = newHandlePos - leftEdge; + + // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + + /* + Users could conceivably respond to size changes in items by setting attached + SplitView properties: + + onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 + + We handle this by doing another layout after the current layout if the + attached/implicit size properties are set during this layout. However, we also + need to set preferredWidth/Height here (for reasons mentioned in the comment above), + but we don't want this to count as a request for a delayed layout, so we guard against it. + */ + m_ignoreNextLayoutRequest = true; + + if (horizontal) + attached->setPreferredWidth(newItemSize); + else + attached->setPreferredHeight(newItemSize); + + // We still need to use requestedWidth in the setWidth() call below, + // because sizeData has already been calculated and now contains an old + // effectivePreferredWidth value. + requestedSize = newItemSize; + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item + << " (clampedMousePos=" << clampedMousePos + << " pressedHandlePos=" << pressedHandlePos + << " accumulated=" << accumulated + << " leftEdge=" << leftEdge + << " leftStop=" << leftStop + << " rightStop=" << rightStop + << " newHandlePos=" << newHandlePos + << " newItemSize=" << newItemSize << ")"; + } else { // Resizing the item on the right. + // The handle shouldn't cross other handles, so use the left edge of + // the first handle to the right as the right edge. + qreal rightEdge = size; + if (m_pressedHandleIndex + 1 < m_handleItems.size()) { + const QQuickItem *rightHandle = m_handleItems.at(m_pressedHandleIndex + 1); + rightEdge = horizontal ? rightHandle->x() : rightHandle->y(); + } + + // The mouse can be clicked anywhere in the handle, and if we don't account for + // its position within the handle, the handle will jump when dragged. + const qreal pressedHandlePos = clampedMousePos - mousePosRelativeToLeftHandleEdge; + + const qreal leftStop = accumulated - pressedHandleSize; + qreal rightStop = qMin(rightEdge - pressedHandleSize, pressedHandlePos); + // qBound() doesn't care if min is greater than max, but we do. + if (rightStop < leftStop) + rightStop = leftStop; + const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); + const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize); + + // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + + m_ignoreNextLayoutRequest = true; + + if (horizontal) + attached->setPreferredWidth(newItemSize); + else + attached->setPreferredHeight(newItemSize); + + // We still need to use requestedSize in the setWidth()/setHeight() call below, + // because sizeData has already been calculated and now contains an old + // effectivePreferredWidth/Height value. + requestedSize = newItemSize; + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item + << " (clampedMousePos=" << clampedMousePos + << " pressedHandlePos=" << pressedHandlePos + << " accumulated=" << accumulated + << " leftEdge=" << rightEdge + << " leftStop=" << leftStop + << " rightStop=" << rightStop + << " newHandlePos=" << newHandlePos + << " newItemSize=" << newItemSize << ")"; + } + } else if (index != m_fillIndex) { + // No handle is being dragged and we're not the fill item, + // so set our preferred size as we normally would. + requestedSize = horizontal + ? sizeData.effectivePreferredWidth : sizeData.effectivePreferredHeight; + } + + if (index != m_fillIndex) { + if (horizontal) { + item->setWidth(qBound( + sizeData.effectiveMinimumWidth, + requestedSize, + sizeData.effectiveMaximumWidth)); + item->setHeight(height); + } else { + item->setWidth(width); + item->setHeight(qBound( + sizeData.effectiveMinimumHeight, + requestedSize, + sizeData.effectiveMaximumHeight)); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item + << " (effective" + << " minW=" << sizeData.effectiveMinimumWidth + << ", minH=" << sizeData.effectiveMinimumHeight + << ", prfW=" << sizeData.effectivePreferredWidth + << ", prfH=" << sizeData.effectivePreferredHeight + << ", maxW=" << sizeData.effectiveMaximumWidth + << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + + // Keep track of how much space has been used so far. + if (horizontal) + usedWidth += item->width(); + else + usedHeight += item->height(); + } else if (indexBeingResizedDueToDrag != m_fillIndex) { + // The fill item is resized afterwards, outside of the loop. + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last"; + } + + // Also account for the size of the handle for this item (if any). + // We do this for the fill item too, which is why it's outside of the check above. + if (index < count - 1 && m_handle) { + QQuickItem *handleItem = m_handleItems.at(index); + // The handle for an item that's not visible will usually already be skipped + // with the item visibility check higher up, but if the view looks like this + // [ visible ] | [ visible (fill) ] | [ hidden ] + // ^ + // hidden + // and we're iterating over the second item (which is visible but has no handle), + // we need to add an extra check for it to avoid it still taking up space. + if (handleItem->isVisible()) { + if (horizontal) { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index + << ": handle takes up " << handleItem->width() << " width"; + usedWidth += handleItem->width(); + } else { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index + << ": handle takes up " << handleItem->height() << " height"; + usedHeight += handleItem->height(); + } + } else { + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": handle is not visible; skipping it"; + } + } + } +} + +/* + Resizes the fill item by giving it the remaining space + after all other items have been resized. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, + qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag) +{ + // Only bother resizing if it it's visible. Also, if it's being resized due to a drag, + // then we've already set its size in layoutResizeSplitItems(), so no need to do it here. + if (!fillItem->isVisible() || indexBeingResizedDueToDrag == m_fillIndex) { + qCDebug(qlcQQuickSplitView).nospace() << m_fillIndex << ": - fill item " << fillItem + << " is not visible or was already resized due to a drag;" + << " skipping it and its handles (if any)"; + return; + } + + const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); + const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached); + if (isHorizontal()) { + fillItem->setWidth(qBound( + fillSizeData.effectiveMinimumWidth, + width - usedWidth, + fillSizeData.effectiveMaximumWidth)); + fillItem->setHeight(height); + } else { + fillItem->setWidth(width); + fillItem->setHeight(qBound( + fillSizeData.effectiveMinimumHeight, + height - usedHeight, + fillSizeData.effectiveMaximumHeight)); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex + << ": resized split fill item " << fillItem << " (effective" + << " minW=" << fillSizeData.effectiveMinimumWidth + << ", minH=" << fillSizeData.effectiveMinimumHeight + << ", maxW=" << fillSizeData.effectiveMaximumWidth + << ", maxH=" << fillSizeData.effectiveMaximumHeight << ")"; +} + +/* + Positions items by laying them out in a row or column. + + Items that aren't visible are skipped. +*/ +void QQuickSplitViewPrivate::layoutPositionItems(const QQuickItem *fillItem) +{ + const bool horizontal = isHorizontal(); + const int count = contentModel->count(); + qreal usedWidth = 0; + qreal usedHeight = 0; + + for (int i = 0; i < count; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (!item->isVisible()) { + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": split item " << item + << " is not visible; skipping it and its handles (if any)"; + continue; + } + + // Position the item. + if (horizontal) { + item->setX(usedWidth); + item->setY(0); + } else { + item->setX(0); + item->setY(usedHeight); + } + + // Keep track of how much space has been used so far. + if (horizontal) + usedWidth += item->width(); + else + usedHeight += item->height(); + + if (Q_UNLIKELY(qlcQQuickSplitView().isDebugEnabled())) { + const QQuickItemPrivate *fillItemPrivate = QQuickItemPrivate::get(fillItem); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); + const auto sizeData = effectiveSizeData(fillItemPrivate, attached); + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned " + << (i == m_fillIndex ? "fill item " : "item ") << item << " (effective" + << " minW=" << sizeData.effectiveMinimumWidth + << ", minH=" << sizeData.effectiveMinimumHeight + << ", prfW=" << sizeData.effectivePreferredWidth + << ", prfH=" << sizeData.effectivePreferredHeight + << ", maxW=" << sizeData.effectiveMaximumWidth + << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + } + + // Position the handle for this item (if any). + if (i < count - 1 && m_handle) { + // Position the handle. + QQuickItem *handleItem = m_handleItems.at(i); + handleItem->setX(horizontal ? usedWidth : 0); + handleItem->setY(horizontal ? 0 : usedHeight); + + if (horizontal) + usedWidth += handleItem->width(); + else + usedHeight += handleItem->height(); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << i << ": positioned handle " << handleItem; + } + } +} + +void QQuickSplitViewPrivate::requestLayout() +{ + Q_Q(QQuickSplitView); + q->polish(); +} + +void QQuickSplitViewPrivate::layout() +{ + if (!componentComplete) + return; + + if (m_layingOut) + return; + + const int count = contentModel->count(); + if (count <= 0) + return; + + Q_ASSERT_X(m_fillIndex < count, Q_FUNC_INFO, qPrintable( + QString::fromLatin1("m_fillIndex is %1 but our count is %2").arg(m_fillIndex).arg(count))); + + Q_ASSERT_X(!m_handle || m_handleItems.size() == count - 1, Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "Expected %1 handle items, but there are %2").arg(count - 1).arg(m_handleItems.size()))); + + // We allow mouse events to instantly trigger layouts, whereas with e.g. + // attached properties being set, we require a delayed layout. + // To prevent recursive calls during mouse events, we need this guard. + QBoolBlocker guard(m_layingOut, true); + + const bool horizontal = isHorizontal(); + qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items" + << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func(); + + qreal usedWidth = 0; + qreal usedHeight = 0; + int indexBeingResizedDueToDrag = -1; + + qCDebug(qlcQQuickSplitView) << " resizing:"; + + // First, resize the items. We need to do this first because otherwise fill + // items would take up all of the remaining space as soon as they are encountered. + layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag); + + qCDebug(qlcQQuickSplitView).nospace() + << " - (remaining width=" << width - usedWidth + << " remaining height=" << height - usedHeight << ")"; + + // Give the fill item the remaining space. + QQuickItem *fillItem = qobject_cast<QQuickItem*>(contentModel->object(m_fillIndex)); + layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag); + + qCDebug(qlcQQuickSplitView) << " positioning:"; + + // Position the items. + layoutPositionItems(fillItem); + + qCDebug(qlcQQuickSplitView).nospace() << "finished layouting"; +} + +void QQuickSplitViewPrivate::createHandles() +{ + Q_ASSERT(m_handle); + // A handle only makes sense if there are two items on either side. + if (contentModel->count() <= 1) + return; + + // Create new handle items if there aren't enough. + const int count = contentModel->count() - 1; + qCDebug(qlcQQuickSplitView) << "creating" << count << "handles"; + m_handleItems.reserve(count); + for (int i = 0; i < count; ++i) + createHandleItem(i); +} + +void QQuickSplitViewPrivate::createHandleItem(int index) +{ + Q_Q(QQuickSplitView); + if (contentModel->count() <= 1) + return; + + qCDebug(qlcQQuickSplitView) << "- creating handle for split item at index" << index + << "from handle component" << m_handle; + + // If we don't use the correct context, it won't be possible to refer to + // the control's id from within the delegate. + QQmlContext *creationContext = m_handle->creationContext(); + // The component might not have been created in QML, in which case + // the creation context will be null and we have to create it ourselves. + if (!creationContext) + creationContext = qmlContext(q); + QQmlContext *context = new QQmlContext(creationContext, q); + context->setContextObject(q); + QQuickItem *item = qobject_cast<QQuickItem*>(m_handle->beginCreate(context)); + if (item) { + // Insert the item to our list of items *before* its parent is set to us, + // so that we can avoid it being added as a content item by checking + // if it is in the list in isContent(). + m_handleItems.insert(index, item); + + item->setParentItem(q); + + m_handle->completeCreate(); + resizeHandle(item); + } +} + +void QQuickSplitViewPrivate::removeExcessHandles() +{ + int excess = m_handleItems.size() - qMax(0, contentModel->count() - 1); + for (; excess > 0; --excess) { + QQuickItem *handleItem = m_handleItems.takeLast(); + delete handleItem; + } +} + +qreal QQuickSplitViewPrivate::accumulatedSize(int firstIndex, int lastIndex) const +{ + qreal size = 0.0; + const bool horizontal = isHorizontal(); + for (int i = firstIndex; i <= lastIndex; ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (item->isVisible()) { + if (i != m_fillIndex) { + size += horizontal ? item->width() : item->height(); + } else { + // If the fill item has a minimum size specified, we must respect it. + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) { + const QQuickSplitViewAttachedPrivate *attachedPrivate + = QQuickSplitViewAttachedPrivate::get(attached); + if (horizontal && attachedPrivate->m_isMinimumWidthSet) + size += attachedPrivate->m_minimumWidth; + else if (!horizontal && attachedPrivate->m_isMinimumHeightSet) + size += attachedPrivate->m_minimumHeight; + } + } + } + + // Only add the handle's width if there's actually a handle for this split item index. + if (i < lastIndex || lastIndex < contentModel->count() - 1) { + const QQuickItem *handleItem = m_handleItems.at(i); + if (handleItem->isVisible()) + size += horizontal ? handleItem->width() : handleItem->height(); + } + } + return size; +} + +qreal effectiveMinimumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMinimumWidthSet ? attachedPrivate->m_minimumWidth : 0; +} + +qreal effectiveMinimumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMinimumHeightSet ? attachedPrivate->m_minimumHeight : 0; +} + +qreal effectivePreferredWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate, + const QQuickItemPrivate *itemPrivate) +{ + return attachedPrivate && attachedPrivate->m_isPreferredWidthSet + ? attachedPrivate->m_preferredWidth : itemPrivate->implicitWidth; +} + +qreal effectivePreferredHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate, + const QQuickItemPrivate *itemPrivate) +{ + return attachedPrivate && attachedPrivate->m_isPreferredHeightSet + ? attachedPrivate->m_preferredHeight : itemPrivate->implicitHeight; +} + +qreal effectiveMaximumWidth(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMaximumWidthSet + ? attachedPrivate->m_maximumWidth : std::numeric_limits<qreal>::infinity(); +} + +qreal effectiveMaximumHeight(const QQuickSplitViewAttachedPrivate *attachedPrivate) +{ + return attachedPrivate && attachedPrivate->m_isMaximumHeightSet + ? attachedPrivate->m_maximumHeight : std::numeric_limits<qreal>::infinity(); +} + +// We don't just take an index, because the item and attached properties object +// will both be used outside of this function by calling code, so save some +// time by not accessing them twice. +QQuickSplitViewPrivate::EffectiveSizeData QQuickSplitViewPrivate::effectiveSizeData( + const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const +{ + EffectiveSizeData data; + const QQuickSplitViewAttachedPrivate *attachedPrivate = attached ? QQuickSplitViewAttachedPrivate::get(attached) : nullptr; + data.effectiveMinimumWidth = effectiveMinimumWidth(attachedPrivate); + data.effectiveMinimumHeight = effectiveMinimumHeight(attachedPrivate); + data.effectivePreferredWidth = effectivePreferredWidth(attachedPrivate, itemPrivate); + data.effectivePreferredHeight = effectivePreferredHeight(attachedPrivate, itemPrivate); + data.effectiveMaximumWidth = effectiveMaximumWidth(attachedPrivate); + data.effectiveMaximumHeight = effectiveMaximumHeight(attachedPrivate); + return data; +} + +int QQuickSplitViewPrivate::handleIndexForSplitIndex(int splitIndex) const +{ + // If it's the last item in the view, it doesn't have a handle, so use + // the handle for the previous item. + return splitIndex == contentModel->count() - 1 ? splitIndex - 1 : splitIndex; +} + +void QQuickSplitViewPrivate::destroyHandles() +{ + qDeleteAll(m_handleItems); + m_handleItems.clear(); +} + +void QQuickSplitViewPrivate::resizeHandle(QQuickItem *handleItem) +{ + const bool horizontal = isHorizontal(); + handleItem->setWidth(horizontal ? handleItem->implicitWidth() : width); + handleItem->setHeight(horizontal ? height : handleItem->implicitHeight()); +} + +void QQuickSplitViewPrivate::resizeHandles() +{ + for (QQuickItem *handleItem : m_handleItems) + resizeHandle(handleItem); +} + +void QQuickSplitViewPrivate::updateHandleVisibilities() +{ + // If this is the first item that is visible, we won't have any + // handles yet, because we don't create a handle if we only have one item. + if (m_handleItems.isEmpty()) + return; + + // If the visibility/children change makes any item the last (right/bottom-most) + // visible item, we don't want to display a handle for it either: + // [ visible (fill) ] | [ hidden ] | [ hidden ] + // ^ ^ + // hidden hidden + const int count = contentModel->count(); + int lastVisibleItemIndex = -1; + for (int i = count - 1; i >= 0; --i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + if (item->isVisible()) { + lastVisibleItemIndex = i; + break; + } + } + + for (int i = 0; i < count - 1; ++i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); + QQuickItem *handleItem = m_handleItems.at(i); + if (i != lastVisibleItemIndex) + handleItem->setVisible(item->isVisible()); + else + handleItem->setVisible(false); + qCDebug(qlcQQuickSplitView) << "set visible property of handle at index" + << i << "to" << handleItem->isVisible(); + } +} + +void QQuickSplitViewPrivate::setResizing(bool resizing) +{ + Q_Q(QQuickSplitView); + if (resizing == m_resizing) + return; + + m_resizing = resizing; + emit q->resizingChanged(); +} + +bool QQuickSplitViewPrivate::isHorizontal() const +{ + return m_orientation == Qt::Horizontal; +} + +QQuickItem *QQuickSplitViewPrivate::getContentItem() +{ + Q_Q(QQuickSplitView); + if (QQuickItem *item = QQuickContainerPrivate::getContentItem()) + return item; + + // TODO: why are several created? + return new QQuickContentItem(q); +} + +void QQuickSplitViewPrivate::handlePress(const QPointF &point) +{ + Q_Q(QQuickSplitView); + QQuickContainerPrivate::handlePress(point); + + QQuickItem *pressedItem = q->childAt(point.x(), point.y()); + const int pressedHandleIndex = m_handleItems.indexOf(pressedItem); + if (pressedHandleIndex != -1) { + m_pressedHandleIndex = pressedHandleIndex; + m_pressPos = point; + m_mousePos = point; + + const QQuickItem *leftOrTopItem = qobject_cast<QQuickItem*>(contentModel->object(m_pressedHandleIndex)); + const QQuickItem *rightOrBottomItem = qobject_cast<QQuickItem*>(contentModel->object(m_pressedHandleIndex + 1)); + const bool isHorizontal = m_orientation == Qt::Horizontal; + m_leftOrTopItemSizeBeforePress = isHorizontal ? leftOrTopItem->width() : leftOrTopItem->height(); + m_rightOrBottomItemSizeBeforePress = isHorizontal ? rightOrBottomItem->width() : rightOrBottomItem->height(); + m_handlePosBeforePress = pressedItem->position(); + + // Avoid e.g. Flickable stealing our drag if we're inside it. + q->setKeepMouseGrab(true); + + // Force the attached object to be created since we rely on it. + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedItem, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(true); + + setResizing(true); + + qCDebug(qlcQQuickSplitViewMouse).nospace() << "handled press -" + << " left/top index=" << m_pressedHandleIndex << "," + << " size before press=" << m_leftOrTopItemSizeBeforePress << "," + << " right/bottom index=" << m_pressedHandleIndex + 1 << "," + << " size before press=" << m_rightOrBottomItemSizeBeforePress; + } +} + +void QQuickSplitViewPrivate::handleMove(const QPointF &point) +{ + QQuickContainerPrivate::handleMove(point); + + if (m_pressedHandleIndex != -1) { + m_mousePos = point; + // Don't request layouts for input events because we want + // resizing to be as responsive and smooth as possible. + updatePolish(); + } +} + +void QQuickSplitViewPrivate::handleRelease(const QPointF &point) +{ + Q_Q(QQuickSplitView); + QQuickContainerPrivate::handleRelease(point); + + if (m_pressedHandleIndex != -1) { + QQuickItem *pressedHandle = m_handleItems.at(m_pressedHandleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(pressedHandle, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setPressed(false); + } + + setResizing(false); + + m_pressedHandleIndex = -1; + m_pressPos = QPointF(); + m_mousePos = QPointF(); + m_handlePosBeforePress = QPointF(); + m_leftOrTopItemSizeBeforePress = 0.0; + m_rightOrBottomItemSizeBeforePress = 0.0; + q->setKeepMouseGrab(false); +} + +void QQuickSplitViewPrivate::itemVisibilityChanged(QQuickItem *item) +{ + const int itemIndex = contentModel->indexOf(item, nullptr); + Q_ASSERT(itemIndex != -1); + + qCDebug(qlcQQuickSplitView) << "visible property of split item" + << item << "at index" << itemIndex << "changed to" << item->isVisible(); + + // The visibility of an item just changed, so we need to update the visibility + // of the corresponding handle (if one exists). + + const int handleIndex = handleIndexForSplitIndex(itemIndex); + QQuickItem *handleItem = m_handleItems.at(handleIndex); + handleItem->setVisible(item->isVisible()); + + qCDebug(qlcQQuickSplitView) << "set visible property of handle item" + << handleItem << "at index" << handleIndex << "to" << item->isVisible(); + + updateHandleVisibilities(); + updateFillIndex(); + requestLayout(); +} + +void QQuickSplitViewPrivate::itemImplicitWidthChanged(QQuickItem *) +{ + requestLayout(); +} + +void QQuickSplitViewPrivate::itemImplicitHeightChanged(QQuickItem *) +{ + requestLayout(); +} + +void QQuickSplitViewPrivate::updatePolish() +{ + layout(); +} + +QQuickSplitViewPrivate *QQuickSplitViewPrivate::get(QQuickSplitView *splitView) +{ + return splitView->d_func(); +} + +QQuickSplitView::QQuickSplitView(QQuickItem *parent) + : QQuickContainer(*(new QQuickSplitViewPrivate), parent) +{ + Q_D(QQuickSplitView); + d->changeTypes |= QQuickItemPrivate::Visibility; + + setAcceptedMouseButtons(Qt::LeftButton); +} + +QQuickSplitView::QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent) + : QQuickContainer(dd, parent) +{ + Q_D(QQuickSplitView); + d->changeTypes |= QQuickItemPrivate::Visibility; + + setAcceptedMouseButtons(Qt::LeftButton); +} + +QQuickSplitView::~QQuickSplitView() +{ + Q_D(QQuickSplitView); + for (int i = 0; i < d->contentModel->count(); ++i) { + QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); + d->removeImplicitSizeListener(item); + } +} + +/*! + \qmlproperty enumeration QtQuick.Controls::SplitView::orientation + + This property holds the orientation of the SplitView. + + The orientation determines how the split items are laid out: + + Possible values: + \value Qt.Horizontal The items are laid out horizontally (default). + \value Qt.Vertical The items are laid out vertically. +*/ +Qt::Orientation QQuickSplitView::orientation() const +{ + Q_D(const QQuickSplitView); + return d->m_orientation; +} + +void QQuickSplitView::setOrientation(Qt::Orientation orientation) +{ + Q_D(QQuickSplitView); + if (orientation == d->m_orientation) + return; + + d->m_orientation = orientation; + d->resizeHandles(); + d->requestLayout(); + emit orientationChanged(); +} + +/*! + \qmlproperty bool QtQuick.Controls::SplitView::resizing + \readonly + + This property is \c true when the user is resizing + split items by dragging on the splitter handles. +*/ +bool QQuickSplitView::isResizing() const +{ + Q_D(const QQuickSplitView); + return d->m_resizing; +} + +/*! + \qmlproperty Component QtQuick.Controls::SplitView::handle + + This property holds the handle component. + + An instance of this component will be instantiated \c {count - 1} + times, as long as \l count is greater than than \c {1}. + + The following table explains how each handle will be resized + depending on the orientation of the split view: + + \table + \header + \li Orientation + \li Handle Width + \li Handle Height + \row + \li \c Qt.Horizontal + \li \c implicitWidth + \li The \l height of the SplitView. + \row + \li \c Qt.Vertical + \li The \l width of the SplitView. + \li \c implicitHeight + \endtable + + \sa {Customizing SplitView} +*/ +QQmlComponent *QQuickSplitView::handle() +{ + Q_D(const QQuickSplitView); + return d->m_handle; +} + +void QQuickSplitView::setHandle(QQmlComponent *handle) +{ + Q_D(QQuickSplitView); + if (handle == d->m_handle) + return; + + qCDebug(qlcQQuickSplitView) << "setting handle" << handle; + + if (d->m_handle) + d->destroyHandles(); + + d->m_handle = handle; + + if (d->m_handle) + d->createHandles(); + + d->requestLayout(); + + emit handleChanged(); +} + +bool QQuickSplitView::isContent(QQuickItem *item) const +{ + Q_D(const QQuickSplitView); + if (!qmlContext(item)) + return false; + + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return false; + + return !d->m_handleItems.contains(item); +} + +QQuickSplitViewAttached *QQuickSplitView::qmlAttachedProperties(QObject *object) +{ + return new QQuickSplitViewAttached(object); +} + +/*! + \qmlmethod var QtQuick.Controls::SplitView::saveState() + + Saves the preferred sizes of split items into a byte array and returns it. + + \sa {Serializing SplitView's State}, restoreState() +*/ +QVariant QQuickSplitView::saveState() +{ + Q_D(QQuickSplitView); + qCDebug(qlcQQuickSplitViewState) << "saving state for split items in" << this; + + // Save the preferred sizes of each split item. + QCborArray cborArray; + for (int i = 0; i < d->contentModel->count(); ++i) { + const QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(i)); + const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + // Don't serialise stuff if we don't need to. If a split item was given a preferred + // size in QML or it was dragged, it will have an attached object and either + // m_isPreferredWidthSet or m_isPreferredHeightSet (or both) will be true, + // so items without these can be skipped. We write the index of each item + // that has data so that we know which item to set it on when restoring. + if (!attached) + continue; + + const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); + if (!attachedPrivate->m_isPreferredWidthSet && !attachedPrivate->m_isPreferredHeightSet) + continue; + + QCborMap cborMap; + cborMap[QLatin1String("index")] = i; + if (attachedPrivate->m_isPreferredWidthSet) { + cborMap[QLatin1String("preferredWidth")] = static_cast<double>(attachedPrivate->m_preferredWidth); + + qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredWidth of " + << attachedPrivate->m_preferredWidth << " for split item at index " << i; + } + if (attachedPrivate->m_isPreferredHeightSet) { + cborMap[QLatin1String("preferredHeight")] = static_cast<double>(attachedPrivate->m_preferredHeight); + + qCDebug(qlcQQuickSplitViewState).nospace() << "- wrote preferredHeight of " + << attachedPrivate->m_preferredHeight << " for split item at index " << i; + } + + cborArray.append(cborMap); + } + + const QByteArray byteArray = cborArray.toCborValue().toCbor(); + qCDebug(qlcQQuickSplitViewState) << "the resulting byte array is:" << byteArray; + return QVariant(byteArray); +} + +/*! + \qmlmethod bool QtQuick.Controls::SplitView::restoreState(state) + + Reads the preferred sizes from \a state and applies them to the split items. + + Returns \c true if the state was successfully restored, otherwise \c false. + + \sa {Serializing SplitView's State}, saveState() +*/ +bool QQuickSplitView::restoreState(const QVariant &state) +{ + const QByteArray cborByteArray = state.toByteArray(); + Q_D(QQuickSplitView); + if (cborByteArray.isEmpty()) + return false; + + QCborParserError parserError; + const QCborValue cborValue(QCborValue::fromCbor(cborByteArray, &parserError)); + if (parserError.error != QCborError::NoError) { + qmlWarning(this) << "Error reading SplitView state:" << parserError.errorString(); + return false; + } + + qCDebug(qlcQQuickSplitViewState) << "restoring state for split items of" << this + << "from the following string:" << state; + + const QCborArray cborArray(cborValue.toArray()); + const int ourCount = d->contentModel->count(); + // This could conceivably happen if items were removed from the SplitView since the state was last saved. + if (cborArray.size() > ourCount) { + qmlWarning(this) << "Error reading SplitView state: expected " + << ourCount << " or less split items but got " << cborArray.size(); + return false; + } + + for (auto it = cborArray.constBegin(); it != cborArray.constEnd(); ++it) { + QCborMap cborMap(it->toMap()); + const int splitItemIndex = cborMap.value(QLatin1String("index")).toInteger(); + const bool isPreferredWidthSet = cborMap.contains(QLatin1String("preferredWidth")); + const bool isPreferredHeightSet = cborMap.contains(QLatin1String("preferredWidth")); + + QQuickItem *item = qobject_cast<QQuickItem*>(d->contentModel->object(splitItemIndex)); + // If the split item does not have a preferred size specified in QML, it could still have + // been resized via dragging before it was saved. In this case, it won't have an + // attached object upon application startup, so we create it. + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + if (isPreferredWidthSet) { + const qreal preferredWidth = cborMap.value(QLatin1String("preferredWidth")).toDouble(); + attached->setPreferredWidth(preferredWidth); + } + if (isPreferredHeightSet) { + const qreal preferredHeight = cborMap.value(QLatin1String("preferredHeight")).toDouble(); + attached->setPreferredHeight(preferredHeight); + } + + const QQuickSplitViewAttachedPrivate *attachedPrivate = QQuickSplitViewAttachedPrivate::get(attached); + qCDebug(qlcQQuickSplitViewState).nospace() + << "- restored the following state for split item at index " << splitItemIndex + << ": preferredWidthSet=" << attachedPrivate->m_isPreferredWidthSet + << " preferredWidth=" << attachedPrivate->m_preferredWidth + << " preferredHeightSet=" << attachedPrivate->m_isPreferredHeightSet + << " preferredHeight=" << attachedPrivate->m_preferredHeight; + } + + return true; +} + +void QQuickSplitView::componentComplete() +{ + Q_D(QQuickSplitView); + QQuickControl::componentComplete(); + d->resizeHandles(); + d->updateFillIndex(); + d->updatePolish(); +} + +void QQuickSplitView::hoverMoveEvent(QHoverEvent *event) +{ + Q_D(QQuickSplitView); + QQuickContainer::hoverMoveEvent(event); + + QQuickItem *hoveredItem = childAt(event->pos().x(), event->pos().y()); + if (!hoveredItem) { + // No handle is hovered. + if (d->m_hoveredHandleIndex != -1) { + // The previously-hovered handle is no longer hovered. + QQuickItem *oldHoveredHandle = d->m_handleItems.at(d->m_hoveredHandleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(oldHoveredHandle, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setHovered(false); + } + + qCDebug(qlcQQuickSplitViewMouse) << "handle item at index" << d->m_hoveredHandleIndex << "is no longer hovered"; + + d->m_hoveredHandleIndex = -1; + +#if QT_CONFIG(cursor) + setCursor(Qt::ArrowCursor); +#endif + } else { + // A child item of ours is hovered. + + // First, clear the hovered flag of any previously-hovered handle. + if (d->m_hoveredHandleIndex != -1) { + QQuickItem *oldHoveredHandle = d->m_handleItems.at(d->m_hoveredHandleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(oldHoveredHandle, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setHovered(false); + } + + // Now check if the newly hovered item is actually a handle. + const int hoveredHandleIndex = d->m_handleItems.indexOf(hoveredItem); + if (hoveredHandleIndex == -1) + return; + + // It's a handle, so it's now hovered. + d->m_hoveredHandleIndex = hoveredHandleIndex; + + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(hoveredItem, true)); + QQuickSplitHandleAttachedPrivate::get(handleAttached)->setHovered(true); + +#if QT_CONFIG(cursor) + setCursor(d->m_orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor); +#endif + + qCDebug(qlcQQuickSplitViewMouse) << "handle item at index" << d->m_hoveredHandleIndex << "is now hovered"; + } +} + +void QQuickSplitView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) +{ + Q_D(QQuickSplitView); + QQuickControl::geometryChanged(newGeometry, oldGeometry); + d->resizeHandles(); + d->requestLayout(); +} + +void QQuickSplitView::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + const int count = d->contentModel->count(); + qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " added at index " << index + << "; there are now " << count << " items"; + + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) + QQuickSplitViewAttachedPrivate::get(attached)->setView(this); + + // Only need to add handles if we have more than one split item. + if (count > 1) { + // If the item was added at the end, it shouldn't get a handle; + // the handle always goes to the split item on the left. + d->createHandleItem(index < count - 1 ? index : index - 1); + } + + d->addImplicitSizeListener(item); + + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +void QQuickSplitView::itemMoved(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + qCDebug(qlcQQuickSplitView) << "split item" << item << "moved to index" << index; + + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +void QQuickSplitView::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickSplitView); + if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + qCDebug(qlcQQuickSplitView).nospace() << "split item " << item << " removed from index " << index + << "; there are now " << d->contentModel->count() << " items"; + + // Clear hovered/pressed handle if there are any. + if (d->m_hoveredHandleIndex != -1 || d->m_pressedHandleIndex != -1) { + const int handleIndex = d->m_hoveredHandleIndex != -1 ? d->m_hoveredHandleIndex : d->m_pressedHandleIndex; + QQuickItem *itemHandle = d->m_handleItems.at(handleIndex); + QQuickSplitHandleAttached *handleAttached = qobject_cast<QQuickSplitHandleAttached*>( + qmlAttachedPropertiesObject<QQuickSplitHandleAttached>(itemHandle, false)); + if (handleAttached) { + auto handleAttachedPrivate = QQuickSplitHandleAttachedPrivate::get(handleAttached); + handleAttachedPrivate->setHovered(false); + handleAttachedPrivate->setPressed(false); + } + + setKeepMouseGrab(false); + d->m_hoveredHandleIndex = -1; + d->m_pressedHandleIndex = -1; + } + + // Unset any attached properties since the item is no longer owned by us. + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + if (attached) + QQuickSplitViewAttachedPrivate::get(attached)->setView(this); + + d->removeImplicitSizeListener(item); + + d->removeExcessHandles(); + d->updateHandleVisibilities(); + d->updateFillIndex(); + d->requestLayout(); +} + +#if QT_CONFIG(accessibility) +QAccessible::Role QQuickSplitView::accessibleRole() const +{ + return QAccessible::Pane; +} +#endif + +QQuickSplitViewAttached::QQuickSplitViewAttached(QObject *parent) + : QObject(*(new QQuickSplitViewAttachedPrivate), parent) +{ + Q_D(QQuickSplitViewAttached); + QQuickItem *item = qobject_cast<QQuickItem *>(parent); + if (!item || QQuickItemPrivate::get(item)->isTransparentForPositioner()) + return; + + d->m_splitItem = item; + + // Child items get added to SplitView's contentItem, so we have to ensure + // that exists first before trying to set m_splitView. + // Apparently, in some cases it's normal for the parent item + // to not exist until shortly after this constructor has run. + if (!item->parentItem()) + return; + + // This will get hit when attached SplitView properties are imperatively set + // on an item that previously had none set, for example. + QQuickSplitView *splitView = qobject_cast<QQuickSplitView*>(item->parentItem()->parentItem()); + if (!splitView) { + qmlWarning(parent) << "SplitView: attached properties must be accessed through a direct child of SplitView"; + return; + } + + d->setView(splitView); +} + +/*! + \qmlattachedproperty SplitView QtQuick.Controls::SplitView::view + + This attached property holds the split view of the item it is + attached to, or \c null if the item is not in a split view. +*/ +QQuickSplitView *QQuickSplitViewAttached::view() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_splitView; +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::minimumWidth + + This attached property controls the minimum width of the split item. + The \l preferredWidth is bound within the \l minimumWidth and + \l maximumWidth. A split item cannot be dragged to be smaller than + its \c minimumWidth. + + The default value is \c 0. To reset this property to its default value, + set it to \c undefined. + + \sa maximumWidth, preferredWidth, fillWidth, minimumHeight +*/ +qreal QQuickSplitViewAttached::minimumWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_minimumWidth; +} + +void QQuickSplitViewAttached::setMinimumWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMinimumWidthSet = true; + if (qFuzzyCompare(width, d->m_minimumWidth)) + return; + + d->m_minimumWidth = width; + d->requestLayoutView(); + emit minimumWidthChanged(); +} + +void QQuickSplitViewAttached::resetMinimumWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMinimumWidth = effectiveMinimumWidth(d); + + d->m_isMinimumWidthSet = false; + d->m_minimumWidth = -1; + + const qreal newEffectiveMinimumWidth = effectiveMinimumWidth(d); + if (qFuzzyCompare(newEffectiveMinimumWidth, oldEffectiveMinimumWidth)) + return; + + d->requestLayoutView(); + emit minimumWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::minimumHeight + + This attached property controls the minimum height of the split item. + The \l preferredHeight is bound within the \l minimumHeight and + \l maximumHeight. A split item cannot be dragged to be smaller than + its \c minimumHeight. + + The default value is \c 0. To reset this property to its default value, + set it to \c undefined. + + \sa maximumHeight, preferredHeight, fillHeight, minimumWidth +*/ +qreal QQuickSplitViewAttached::minimumHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_minimumHeight; +} + +void QQuickSplitViewAttached::setMinimumHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMinimumHeightSet = true; + if (qFuzzyCompare(height, d->m_minimumHeight)) + return; + + d->m_minimumHeight = height; + d->requestLayoutView(); + emit minimumHeightChanged(); +} + +void QQuickSplitViewAttached::resetMinimumHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMinimumHeight = effectiveMinimumHeight(d); + + d->m_isMinimumHeightSet = false; + d->m_minimumHeight = -1; + + const qreal newEffectiveMinimumHeight = effectiveMinimumHeight(d); + if (qFuzzyCompare(newEffectiveMinimumHeight, oldEffectiveMinimumHeight)) + return; + + d->requestLayoutView(); + emit minimumHeightChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::preferredWidth + + This attached property controls the preferred width of the split item. The + preferred width will be used as the size of the item, and will be bound + within the \l minimumWidth and \l maximumWidth. If the preferred width + is not set, the item's \l {Item::}{implicitWidth} will be used. + + When a split item is resized, the preferredWidth will be set in order + to keep track of the new size. + + By default, this property is not set, and therefore + \l {Item::}{implicitWidth} will be used instead. To reset this property to + its default value, set it to \c undefined. + + \note Do not set the \l width property of a split item, as it will be + overwritten upon each layout of the SplitView. + + \sa minimumWidth, maximumWidth, fillWidth, preferredHeight +*/ +qreal QQuickSplitViewAttached::preferredWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_preferredWidth; +} + +void QQuickSplitViewAttached::setPreferredWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isPreferredWidthSet = true; + // Make sure that we clear this flag now, before we emit the change signals + // which could cause another setter to be called. + auto splitViewPrivate = QQuickSplitViewPrivate::get(d->m_splitView); + const bool ignoreNextLayoutRequest = splitViewPrivate->m_ignoreNextLayoutRequest; + splitViewPrivate->m_ignoreNextLayoutRequest = false; + + if (qFuzzyCompare(width, d->m_preferredWidth)) + return; + + d->m_preferredWidth = width; + + if (!ignoreNextLayoutRequest) { + // We are currently in the middle of performing a layout, and the user (not our internal code) + // changed the preferred width of one of the split items, so request another layout. + d->requestLayoutView(); + } + + emit preferredWidthChanged(); +} + +void QQuickSplitViewAttached::resetPreferredWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectivePreferredWidth = effectivePreferredWidth( + d, QQuickItemPrivate::get(d->m_splitItem)); + + d->m_isPreferredWidthSet = false; + d->m_preferredWidth = -1; + + const qreal newEffectivePreferredWidth = effectivePreferredWidth( + d, QQuickItemPrivate::get(d->m_splitItem)); + if (qFuzzyCompare(newEffectivePreferredWidth, oldEffectivePreferredWidth)) + return; + + d->requestLayoutView(); + emit preferredWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::preferredHeight + + This attached property controls the preferred height of the split item. The + preferred height will be used as the size of the item, and will be bound + within the \l minimumHeight and \l maximumHeight. If the preferred height + is not set, the item's \l {Item::}{implicitHeight} will be used. + + When a split item is resized, the preferredHeight will be set in order + to keep track of the new size. + + By default, this property is not set, and therefore + \l {Item::}{implicitHeight} will be used instead. To reset this property to + its default value, set it to \c undefined. + + \note Do not set the \l height property of a split item, as it will be + overwritten upon each layout of the SplitView. + + \sa minimumHeight, maximumHeight, fillHeight, preferredWidth +*/ +qreal QQuickSplitViewAttached::preferredHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_preferredHeight; +} + +void QQuickSplitViewAttached::setPreferredHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isPreferredHeightSet = true; + // Make sure that we clear this flag now, before we emit the change signals + // which could cause another setter to be called. + auto splitViewPrivate = QQuickSplitViewPrivate::get(d->m_splitView); + const bool ignoreNextLayoutRequest = splitViewPrivate->m_ignoreNextLayoutRequest; + splitViewPrivate->m_ignoreNextLayoutRequest = false; + + if (qFuzzyCompare(height, d->m_preferredHeight)) + return; + + d->m_preferredHeight = height; + + if (!ignoreNextLayoutRequest) { + // We are currently in the middle of performing a layout, and the user (not our internal code) + // changed the preferred height of one of the split items, so request another layout. + d->requestLayoutView(); + } + + emit preferredHeightChanged(); +} + +void QQuickSplitViewAttached::resetPreferredHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectivePreferredHeight = effectivePreferredHeight( + d, QQuickItemPrivate::get(d->m_splitItem)); + + d->m_isPreferredHeightSet = false; + d->m_preferredHeight = -1; + + const qreal newEffectivePreferredHeight = effectivePreferredHeight( + d, QQuickItemPrivate::get(d->m_splitItem)); + if (qFuzzyCompare(newEffectivePreferredHeight, oldEffectivePreferredHeight)) + return; + + d->requestLayoutView(); + emit preferredHeightChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::maximumWidth + + This attached property controls the maximum width of the split item. + The \l preferredWidth is bound within the \l minimumWidth and + \l maximumWidth. A split item cannot be dragged to be larger than + its \c maximumWidth. + + The default value is \c Infinity. To reset this property to its default + value, set it to \c undefined. + + \sa minimumWidth, preferredWidth, fillWidth, maximumHeight +*/ +qreal QQuickSplitViewAttached::maximumWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_maximumWidth; +} + +void QQuickSplitViewAttached::setMaximumWidth(qreal width) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMaximumWidthSet = true; + if (qFuzzyCompare(width, d->m_maximumWidth)) + return; + + d->m_maximumWidth = width; + d->requestLayoutView(); + emit maximumWidthChanged(); +} + +void QQuickSplitViewAttached::resetMaximumWidth() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMaximumWidth = effectiveMaximumWidth(d); + + d->m_isMaximumWidthSet = false; + d->m_maximumWidth = -1; + + const qreal newEffectiveMaximumWidth = effectiveMaximumWidth(d); + if (qFuzzyCompare(newEffectiveMaximumWidth, oldEffectiveMaximumWidth)) + return; + + d->requestLayoutView(); + emit maximumWidthChanged(); +} + +/*! + \qmlattachedproperty real QtQuick.Controls::SplitView::maximumHeight + + This attached property controls the maximum height of the split item. + The \l preferredHeight is bound within the \l minimumHeight and + \l maximumHeight. A split item cannot be dragged to be larger than + its \c maximumHeight. + + The default value is \c Infinity. To reset this property to its default + value, set it to \c undefined. + + \sa minimumHeight, preferredHeight, fillHeight, maximumWidth +*/ +qreal QQuickSplitViewAttached::maximumHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_maximumHeight; +} + +void QQuickSplitViewAttached::setMaximumHeight(qreal height) +{ + Q_D(QQuickSplitViewAttached); + d->m_isMaximumHeightSet = true; + if (qFuzzyCompare(height, d->m_maximumHeight)) + return; + + d->m_maximumHeight = height; + d->requestLayoutView(); + emit maximumHeightChanged(); +} + +void QQuickSplitViewAttached::resetMaximumHeight() +{ + Q_D(QQuickSplitViewAttached); + const qreal oldEffectiveMaximumHeight = effectiveMaximumHeight(d); + + d->m_isMaximumHeightSet = false; + d->m_maximumHeight = -1; + + const qreal newEffectiveMaximumHeight = effectiveMaximumHeight(d); + if (qFuzzyCompare(newEffectiveMaximumHeight, oldEffectiveMaximumHeight)) + return; + + d->requestLayoutView(); + emit maximumHeightChanged(); +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitView::fillWidth + + This attached property controls whether the item takes the remaining space + in the split view after all other items have been laid out. + + By default, the last visible child of the split view will have this set, + but it can be changed by explicitly setting \c fillWidth to \c true on + another item. + + The width of a split item with \c fillWidth set to \c true is still + restricted within its \l minimumWidth and \l maximumWidth. + + \sa minimumWidth, preferredWidth, maximumWidth, fillHeight +*/ +bool QQuickSplitViewAttached::fillWidth() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_fillWidth; +} + +void QQuickSplitViewAttached::setFillWidth(bool fill) +{ + Q_D(QQuickSplitViewAttached); + d->m_isFillWidthSet = true; + if (fill == d->m_fillWidth) + return; + + d->m_fillWidth = fill; + if (d->m_splitView && d->m_splitView->orientation() == Qt::Horizontal) + QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); + d->requestLayoutView(); + emit fillWidthChanged(); +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitView::fillHeight + + This attached property controls whether the item takes the remaining space + in the split view after all other items have been laid out. + + By default, the last visible child of the split view will have this set, + but it can be changed by explicitly setting \c fillHeight to \c true on + another item. + + The height of a split item with \c fillHeight set to \c true is still + restricted within its \l minimumHeight and \l maximumHeight. + + \sa minimumHeight, preferredHeight, maximumHeight, fillWidth +*/ +bool QQuickSplitViewAttached::fillHeight() const +{ + Q_D(const QQuickSplitViewAttached); + return d->m_fillHeight; +} + +void QQuickSplitViewAttached::setFillHeight(bool fill) +{ + Q_D(QQuickSplitViewAttached); + d->m_isFillHeightSet = true; + if (fill == d->m_fillHeight) + return; + + d->m_fillHeight = fill; + if (d->m_splitView && d->m_splitView->orientation() == Qt::Vertical) + QQuickSplitViewPrivate::get(d->m_splitView)->updateFillIndex(); + d->requestLayoutView(); + emit fillHeightChanged(); +} + +QQuickSplitViewAttachedPrivate::QQuickSplitViewAttachedPrivate() + : m_fillWidth(false) + , m_fillHeight(false) + , m_isFillWidthSet(false) + , m_isFillHeightSet(false) + , m_isMinimumWidthSet(false) + , m_isMinimumHeightSet(false) + , m_isPreferredWidthSet(false) + , m_isPreferredHeightSet(false) + , m_isMaximumWidthSet(false) + , m_isMaximumHeightSet(false) + , 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()) +{ +} + +void QQuickSplitViewAttachedPrivate::setView(QQuickSplitView *newView) +{ + Q_Q(QQuickSplitViewAttached); + if (newView == m_splitView) + return; + + m_splitView = newView; + qCDebug(qlcQQuickSplitView) << "set SplitView" << newView << "on attached object" << this; + emit q->viewChanged(); +} + +void QQuickSplitViewAttachedPrivate::requestLayoutView() +{ + if (m_splitView) + QQuickSplitViewPrivate::get(m_splitView)->requestLayout(); +} + +QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(QQuickSplitViewAttached *attached) +{ + return attached->d_func(); +} + +const QQuickSplitViewAttachedPrivate *QQuickSplitViewAttachedPrivate::get(const QQuickSplitViewAttached *attached) +{ + return attached->d_func(); +} + +QQuickSplitHandleAttachedPrivate::QQuickSplitHandleAttachedPrivate() + : m_hovered(false) + , m_pressed(false) +{ +} + +void QQuickSplitHandleAttachedPrivate::setHovered(bool hovered) +{ + Q_Q(QQuickSplitHandleAttached); + if (hovered == m_hovered) + return; + + m_hovered = hovered; + emit q->hoveredChanged(); +} + +void QQuickSplitHandleAttachedPrivate::setPressed(bool pressed) +{ + Q_Q(QQuickSplitHandleAttached); + if (pressed == m_pressed) + return; + + m_pressed = pressed; + emit q->pressedChanged(); +} + +QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(QQuickSplitHandleAttached *attached) +{ + return attached->d_func(); +} + +const QQuickSplitHandleAttachedPrivate *QQuickSplitHandleAttachedPrivate::get(const QQuickSplitHandleAttached *attached) +{ + return attached->d_func(); +} + +QQuickSplitHandleAttached::QQuickSplitHandleAttached(QObject *parent) + : QObject(*(new QQuickSplitViewAttachedPrivate), parent) +{ +} + +/*! + \qmltype SplitHandle + \inherits QtObject + \instantiates QQuickSplitHandleAttached + \inqmlmodule QtQuick.Controls + \since 5.12 + \brief Provides attached properties for SplitView handles + + SplitHandle provides attached properties for \l SplitView handles. + + For split items themselves, use the attached \l SplitView properties. + + \sa SplitView +*/ + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitHandle::hovered + + This attached property holds whether the split handle is hovered. + + \sa pressed +*/ +bool QQuickSplitHandleAttached::isHovered() const +{ + Q_D(const QQuickSplitHandleAttached); + return d->m_hovered; +} + +/*! + \qmlattachedproperty bool QtQuick.Controls::SplitHandle::pressed + + This attached property holds whether the split handle is pressed. + + \sa hovered +*/ +bool QQuickSplitHandleAttached::isPressed() const +{ + Q_D(const QQuickSplitHandleAttached); + return d->m_pressed; +} + +QQuickSplitHandleAttached *QQuickSplitHandleAttached::qmlAttachedProperties(QObject *object) +{ + return new QQuickSplitHandleAttached(object); +} + +QT_END_NAMESPACE + +#include "moc_qquicksplitview_p.cpp" diff --git a/src/quicktemplates2/qquicksplitview_p.h b/src/quicktemplates2/qquicksplitview_p.h new file mode 100644 index 00000000..99001615 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview_p.h @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSPLITVIEW_P_H +#define QQUICKSPLITVIEW_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p.h> +#include <QtQml/qqmllist.h> + +QT_BEGIN_NAMESPACE + +class QQuickSplitViewPrivate; +class QQuickSplitViewAttached; +class QQuickSplitViewAttachedPrivate; +class QQuickSplitHandleAttached; +class QQuickSplitHandleAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitView : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) + Q_PROPERTY(bool resizing READ isResizing NOTIFY resizingChanged) + Q_PROPERTY(QQmlComponent *handle READ handle WRITE setHandle NOTIFY handleChanged FINAL) + +public: + explicit QQuickSplitView(QQuickItem *parent = nullptr); + ~QQuickSplitView() override; + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + + bool isResizing() const; + + QQmlComponent *handle(); + void setHandle(QQmlComponent *handle); + + bool isContent(QQuickItem *item) const override; + + static QQuickSplitViewAttached *qmlAttachedProperties(QObject *object); + + // Based on the same code in QMainWindow. + enum VersionMarkers { + VersionMarker = 0xff + }; + Q_INVOKABLE QVariant saveState(); + Q_INVOKABLE bool restoreState(const QVariant &state); + +Q_SIGNALS: + void orientationChanged(); + void resizingChanged(); + void handleChanged(); + +protected: + QQuickSplitView(QQuickSplitViewPrivate &dd, QQuickItem *parent); + + void componentComplete() override; + void hoverMoveEvent(QHoverEvent *event) override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + + void itemAdded(int index, QQuickItem *item) override; + void itemMoved(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + +#if QT_CONFIG(accessibility) + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickSplitView) + Q_DECLARE_PRIVATE(QQuickSplitView) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickSplitView *view READ view NOTIFY viewChanged FINAL) + Q_PROPERTY(qreal minimumWidth READ minimumWidth WRITE setMinimumWidth + RESET resetMinimumWidth NOTIFY minimumWidthChanged FINAL) + Q_PROPERTY(qreal minimumHeight READ minimumHeight WRITE setMinimumHeight + RESET resetMinimumHeight NOTIFY minimumHeightChanged FINAL) + Q_PROPERTY(qreal preferredWidth READ preferredWidth WRITE setPreferredWidth + RESET resetPreferredWidth NOTIFY preferredWidthChanged FINAL) + Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight + RESET resetPreferredHeight NOTIFY preferredHeightChanged FINAL) + Q_PROPERTY(qreal maximumWidth READ maximumWidth WRITE setMaximumWidth + RESET resetMaximumWidth NOTIFY maximumWidthChanged FINAL) + Q_PROPERTY(qreal maximumHeight READ maximumHeight WRITE setMaximumHeight + RESET resetMaximumHeight NOTIFY maximumHeightChanged FINAL) + Q_PROPERTY(bool fillHeight READ fillHeight WRITE setFillHeight NOTIFY fillHeightChanged FINAL) + Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged FINAL) + +public: + explicit QQuickSplitViewAttached(QObject *parent = nullptr); + + QQuickSplitView *view() const; + + qreal minimumWidth() const; + void setMinimumWidth(qreal width); + void resetMinimumWidth(); + + qreal minimumHeight() const; + void setMinimumHeight(qreal height); + void resetMinimumHeight(); + + qreal preferredWidth() const; + void setPreferredWidth(qreal width); + void resetPreferredWidth(); + + qreal preferredHeight() const; + void setPreferredHeight(qreal height); + void resetPreferredHeight(); + + qreal maximumWidth() const; + void setMaximumWidth(qreal width); + void resetMaximumWidth(); + + qreal maximumHeight() const; + void setMaximumHeight(qreal height); + void resetMaximumHeight(); + + bool fillWidth() const; + void setFillWidth(bool fill); + + bool fillHeight() const; + void setFillHeight(bool fill); + +Q_SIGNALS: + void viewChanged(); + void minimumWidthChanged(); + void minimumHeightChanged(); + void preferredWidthChanged(); + void preferredHeightChanged(); + void maximumWidthChanged(); + void maximumHeightChanged(); + void fillWidthChanged(); + void fillHeightChanged(); + +private: + Q_DISABLE_COPY(QQuickSplitViewAttached) + Q_DECLARE_PRIVATE(QQuickSplitViewAttached) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitHandleAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL) + Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) + +public: + explicit QQuickSplitHandleAttached(QObject *parent = nullptr); + + bool isHovered() const; + bool isPressed() const; + + static QQuickSplitHandleAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void hoveredChanged(); + void pressedChanged(); + +private: + Q_DISABLE_COPY(QQuickSplitHandleAttached) + Q_DECLARE_PRIVATE(QQuickSplitHandleAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickSplitView) +QML_DECLARE_TYPEINFO(QQuickSplitView, QML_HAS_ATTACHED_PROPERTIES) + +QML_DECLARE_TYPE(QQuickSplitHandleAttached) +QML_DECLARE_TYPEINFO(QQuickSplitHandleAttached, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKSPLITVIEW_P_H diff --git a/src/quicktemplates2/qquicksplitview_p_p.h b/src/quicktemplates2/qquicksplitview_p_p.h new file mode 100644 index 00000000..5d71d461 --- /dev/null +++ b/src/quicktemplates2/qquicksplitview_p_p.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQUICKSPLITVIEW_P_P_H +#define QQUICKSPLITVIEW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuickTemplates2/private/qquickcontainer_p_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickSplitView; +class QQuickSplitViewAttached; +class QQuickSplitHandleAttached; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitView) + +public: + void updateFillIndex(); + void layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag); + void layoutResizeFillItem(QQuickItem *fillItem, qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag); + void layoutPositionItems(const QQuickItem *fillItem); + void requestLayout(); + void layout(); + void createHandles(); + void createHandleItem(int index); + void removeExcessHandles(); + void destroyHandles(); + void resizeHandle(QQuickItem *handleItem); + void resizeHandles(); + void updateHandleVisibilities(); + void setResizing(bool resizing); + + bool isHorizontal() const; + qreal accumulatedSize(int firstIndex, int lastIndex) const; + + struct EffectiveSizeData { + qreal effectiveMinimumWidth; + qreal effectiveMinimumHeight; + qreal effectivePreferredWidth; + qreal effectivePreferredHeight; + qreal effectiveMaximumWidth; + qreal effectiveMaximumHeight; + }; + + EffectiveSizeData effectiveSizeData(const QQuickItemPrivate *itemPrivate, + const QQuickSplitViewAttached *attached) const; + + int handleIndexForSplitIndex(int splitIndex) const; + + QQuickItem *getContentItem() override; + void handlePress(const QPointF &point) override; + void handleMove(const QPointF &point) override; + void handleRelease(const QPointF &point) override; + + void itemVisibilityChanged(QQuickItem *item) override; + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void updatePolish() override; + + static QQuickSplitViewPrivate *get(QQuickSplitView *splitView); + + Qt::Orientation m_orientation = Qt::Horizontal; + QQmlComponent *m_handle = nullptr; + QVector<QQuickItem*> m_handleItems; + int m_hoveredHandleIndex = -1; + int m_pressedHandleIndex = -1; + QPointF m_pressPos; + QPointF m_mousePos; + QPointF m_handlePosBeforePress; + qreal m_leftOrTopItemSizeBeforePress = 0.0; + qreal m_rightOrBottomItemSizeBeforePress = 0.0; + int m_fillIndex = -1; + bool m_layingOut = false; + bool m_ignoreNextLayoutRequest = false; + bool m_resizing = false; +}; + +class QQuickSplitViewAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitViewAttached) + +public: + QQuickSplitViewAttachedPrivate(); + + void setView(QQuickSplitView *newView); + void requestLayoutView(); + + static QQuickSplitViewAttachedPrivate *get(QQuickSplitViewAttached *attached); + static const QQuickSplitViewAttachedPrivate *get(const QQuickSplitViewAttached *attached); + + QQuickItem *m_splitItem = nullptr; + QQuickSplitView *m_splitView = nullptr; + + unsigned m_fillWidth : 1; + unsigned m_fillHeight : 1; + unsigned m_isFillWidthSet : 1; + unsigned m_isFillHeightSet : 1; + unsigned m_isMinimumWidthSet : 1; + unsigned m_isMinimumHeightSet : 1; + unsigned m_isPreferredWidthSet : 1; + unsigned m_isPreferredHeightSet : 1; + unsigned m_isMaximumWidthSet : 1; + unsigned m_isMaximumHeightSet : 1; + qreal m_minimumWidth; + qreal m_minimumHeight; + qreal m_preferredWidth; + qreal m_preferredHeight; + qreal m_maximumWidth; + qreal m_maximumHeight; +}; + +class QQuickSplitHandleAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickSplitHandleAttached) + +public: + QQuickSplitHandleAttachedPrivate(); + + void setHovered(bool hovered); + void setPressed(bool pressed); + + static QQuickSplitHandleAttachedPrivate *get(QQuickSplitHandleAttached *attached); + static const QQuickSplitHandleAttachedPrivate *get(const QQuickSplitHandleAttached *attached); + + unsigned m_hovered : 1; + unsigned m_pressed : 1; +}; + +QT_END_NAMESPACE + +#endif // QQUICKSPLITVIEW_P_P_H diff --git a/src/quicktemplates2/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp index 25710231..8b702c60 100644 --- a/src/quicktemplates2/qquicktumbler.cpp +++ b/src/quicktemplates2/qquicktumbler.cpp @@ -36,6 +36,7 @@ #include "qquicktumbler_p.h" +#include <QtCore/qloggingcategory.h> #include <QtGui/qpa/qplatformtheme.h> #include <QtQml/qqmlinfo.h> #include <QtQuick/private/qquickflickable_p.h> @@ -44,6 +45,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcTumbler, "qt.quick.controls.tumbler") + /*! \qmltype Tumbler \inherits Control @@ -190,11 +193,22 @@ void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged() // If the user set currentIndex in the onModelChanged handler, // we have to respect that currentIndex by ignoring changes in the view // until the model has finished being set. + qCDebug(lcTumbler).nospace() << "view currentIndex changed to " + << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)")) + << ", but we're ignoring it because one or more of the following conditions are true:" + << "\n- !view: " << !view + << "\n- ignoreCurrentIndexChanges: " << ignoreCurrentIndexChanges + << "\n- currentIndexSetDuringModelChange: " << currentIndexSetDuringModelChange; return; } const int oldCurrentIndex = currentIndex; currentIndex = view->property("currentIndex").toInt(); + + qCDebug(lcTumbler).nospace() << "view currentIndex changed to " + << (view ? view->property("currentIndex").toString() : QStringLiteral("unknown index (no view)")) + << ", our old currentIndex was " << oldCurrentIndex; + if (oldCurrentIndex != currentIndex) emit q->currentIndexChanged(); } @@ -202,6 +216,7 @@ void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged() void QQuickTumblerPrivate::_q_onViewCountChanged() { Q_Q(QQuickTumbler); + qCDebug(lcTumbler) << "view count changed - ignoring signals?" << ignoreSignals; if (ignoreSignals) return; @@ -513,10 +528,12 @@ void QQuickTumbler::geometryChanged(const QRectF &newGeometry, const QRectF &old void QQuickTumbler::componentComplete() { Q_D(QQuickTumbler); + qCDebug(lcTumbler) << "componentComplete()"; QQuickControl::componentComplete(); if (!d->view) { // Force the view to be created. + qCDebug(lcTumbler) << "emitting wrapChanged() to force view to be created"; emit wrapChanged(); // Determine the type of view for attached properties, etc. d->setupViewData(d->contentItem); @@ -532,6 +549,8 @@ void QQuickTumbler::componentComplete() d->_q_updateItemHeights(); d->_q_updateItemWidths(); d->_q_onViewCountChanged(); + + qCDebug(lcTumbler) << "componentComplete() is done"; } void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) @@ -659,18 +678,29 @@ void QQuickTumblerPrivate::syncCurrentIndex() void QQuickTumblerPrivate::setPendingCurrentIndex(int index) { + qCDebug(lcTumbler) << "setting pendingCurrentIndex to" << index; pendingCurrentIndex = index; } +QString QQuickTumblerPrivate::propertyChangeReasonToString( + QQuickTumblerPrivate::PropertyChangeReason changeReason) +{ + return changeReason == UserChange ? QStringLiteral("UserChange") : QStringLiteral("InternalChange"); +} + void QQuickTumblerPrivate::setCurrentIndex(int newCurrentIndex, QQuickTumblerPrivate::PropertyChangeReason changeReason) { Q_Q(QQuickTumbler); + qCDebug(lcTumbler).nospace() << "setting currentIndex to " << newCurrentIndex + << ", old currentIndex was " << currentIndex + << ", changeReason is " << propertyChangeReasonToString(changeReason); if (newCurrentIndex == currentIndex || newCurrentIndex < -1) return; if (!q->isComponentComplete()) { // Views can't set currentIndex until they're ready. + qCDebug(lcTumbler) << "we're not complete; setting pendingCurrentIndex instead"; setPendingCurrentIndex(newCurrentIndex); return; } @@ -680,6 +710,7 @@ void QQuickTumblerPrivate::setCurrentIndex(int newCurrentIndex, // the model is in the process of being set and the user has set // the currentIndex in onModelChanged. We have to queue the currentIndex // change until we're ready. + qCDebug(lcTumbler) << "a model is being set; setting pendingCurrentIndex instead"; setPendingCurrentIndex(newCurrentIndex); return; } @@ -717,11 +748,16 @@ void QQuickTumblerPrivate::setCurrentIndex(int newCurrentIndex, currentIndex = newCurrentIndex; emit q->currentIndexChanged(); } + + qCDebug(lcTumbler) << "view's currentIndex is now" << view->property("currentIndex").toInt() + << "and ours is" << currentIndex; } } void QQuickTumblerPrivate::setCount(int newCount) { + qCDebug(lcTumbler).nospace() << "setting count to " << newCount + << ", old count was " << count; if (newCount == count) return; @@ -743,6 +779,7 @@ void QQuickTumblerPrivate::setWrapBasedOnCount() void QQuickTumblerPrivate::setWrap(bool shouldWrap, bool isExplicit) { + qCDebug(lcTumbler) << "setting wrap to" << shouldWrap << "- exlicit?" << isExplicit; if (isExplicit) explicitWrap = true; diff --git a/src/quicktemplates2/qquicktumbler_p_p.h b/src/quicktemplates2/qquicktumbler_p_p.h index 75c6cd1b..049ab8a1 100644 --- a/src/quicktemplates2/qquicktumbler_p_p.h +++ b/src/quicktemplates2/qquicktumbler_p_p.h @@ -111,6 +111,8 @@ public: InternalChange }; + static QString propertyChangeReasonToString(PropertyChangeReason changeReason); + void setCurrentIndex(int newCurrentIndex, PropertyChangeReason changeReason = InternalChange); void setCount(int newCount); void setWrapBasedOnCount(); diff --git a/src/quicktemplates2/quicktemplates2.pri b/src/quicktemplates2/quicktemplates2.pri index 33bc47ca..c145c20f 100644 --- a/src/quicktemplates2/quicktemplates2.pri +++ b/src/quicktemplates2/quicktemplates2.pri @@ -73,6 +73,7 @@ HEADERS += \ $$PWD/qquickshortcutcontext_p_p.h \ $$PWD/qquickslider_p.h \ $$PWD/qquickspinbox_p.h \ + $$PWD/qquicksplitview_p.h \ $$PWD/qquickstackelement_p_p.h \ $$PWD/qquickstacktransition_p_p.h \ $$PWD/qquickstackview_p.h \ @@ -149,6 +150,7 @@ SOURCES += \ $$PWD/qquickshortcutcontext.cpp \ $$PWD/qquickslider.cpp \ $$PWD/qquickspinbox.cpp \ + $$PWD/qquicksplitview.cpp \ $$PWD/qquickstackelement.cpp \ $$PWD/qquickstacktransition.cpp \ $$PWD/qquickstackview.cpp \ diff --git a/tests/auto/controls/data/splitview/fillItemInMiddle.qml b/tests/auto/controls/data/splitview/fillItemInMiddle.qml new file mode 100644 index 00000000..33aa5a8b --- /dev/null +++ b/tests/auto/controls/data/splitview/fillItemInMiddle.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.12 +import QtQuick.Controls 2.5 + +SplitView { + anchors.fill: parent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + + SplitView.fillWidth: true + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/controls/data/splitview/fillItemOnLeft.qml b/tests/auto/controls/data/splitview/fillItemOnLeft.qml new file mode 100644 index 00000000..ca20652b --- /dev/null +++ b/tests/auto/controls/data/splitview/fillItemOnLeft.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.12 +import QtQuick.Controls 2.5 + +SplitView { + anchors.fill: parent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + + SplitView.fillWidth: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/controls/data/splitview/fillItemOnTop.qml b/tests/auto/controls/data/splitview/fillItemOnTop.qml new file mode 100644 index 00000000..ea7aa2b6 --- /dev/null +++ b/tests/auto/controls/data/splitview/fillItemOnTop.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.12 +import QtQuick.Controls 2.5 + +SplitView { + anchors.fill: parent + orientation: Qt.Vertical + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + + SplitView.fillHeight: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/controls/data/tst_splitview.qml b/tests/auto/controls/data/tst_splitview.qml new file mode 100644 index 00000000..28c00179 --- /dev/null +++ b/tests/auto/controls/data/tst_splitview.qml @@ -0,0 +1,1923 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.12 +import QtQuick.Controls 2.5 +import QtQuick.Window 2.5 +import QtTest 1.0 +import Qt.labs.settings 1.0 + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "SplitView" + + function initTestCase() { + // For the serialization tests. + Qt.application.name = "qtquickcontrols2-splitview-auto-test" + Qt.application.domain = "org.qt-project" + Qt.application.organization = "Qt Project" + } + + function findHandles(splitView) { + var handles = [] + for (var i = 0; i < splitView.children.length; ++i) { + var child = splitView.children[i] + if (child.objectName.toLowerCase().indexOf("handle") !== -1) + handles.push(child) + } + return handles + } + + function compareSizes(control, expectedGeometries, context) { + if (context === undefined) + context = "" + else + context = " (" + context + ")" + + compare(control.count, Math.floor(expectedGeometries.length / 2) + 1, + "Mismatch in actual vs expected number of split items" + context) + + var handles = findHandles(control) + compare(handles.length, Math.floor(expectedGeometries.length / 2), + "Mismatch in actual vs expected number of handle items" + context) + + for (var i = 0, splitItemIndex = 0, handleItemIndex = 0; i < expectedGeometries.length; ++i) { + var item = null + var itemType = "" + var typeSpecificIndex = -1 + if (i % 2 == 0) { + item = control.itemAt(splitItemIndex) + itemType = "split item" + typeSpecificIndex = splitItemIndex + ++splitItemIndex + } else { + item = handles[handleItemIndex] + itemType = "handle item" + typeSpecificIndex = handleItemIndex + ++handleItemIndex + } + + verify(item, itemType + " at index " + typeSpecificIndex + " should not be null" + context) + + var expectedGeometry = expectedGeometries[i] + if (expectedGeometry.hasOwnProperty("hidden")) { + // It's geometry doesn't matter because it's hidden. + verify(!item.visible, itemType + " at index " + typeSpecificIndex + " should be hidden" + context) + continue + } + + // Note that the indices mentioned here account for handles; they do not + // match the indices reported by QQuickSplitView's logging categories. + compare(item.x, expectedGeometry.x, "Mismatch in actual vs expected x value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.y, expectedGeometry.y, "Mismatch in actual vs expected y value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.width, expectedGeometry.width, "Mismatch in actual vs expected width value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.height, expectedGeometry.height, "Mismatch in actual vs expected height value of " + + itemType + " at index " + typeSpecificIndex + context) + } + } + + property real defaultHorizontalHandleWidth: 10 + property real defaultVerticalHandleHeight: 10 + + + Component { + id: signalSpyComponent + SignalSpy {} + } + + Component { + id: handleComponent + Rectangle { + objectName: "handle" + implicitWidth: defaultHorizontalHandleWidth + implicitHeight: defaultVerticalHandleHeight + color: "#444" + } + } + + SplitView { + id: dummyHorizontalSplitView + handle: handleComponent + + Item { objectName: "dummyItem" } + Item { objectName: "dummyItem" } + } + + SplitView { + id: dummyVerticalSplitView + orientation: Qt.Vertical + handle: handleComponent + + Item { objectName: "dummyItem" } + Item { objectName: "dummyItem" } + } + + Component { + id: splitViewComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + } + } + + Component { + id: rectangleComponent + + Rectangle {} + } + + function test_addItemsAfterCompletion() { + var control = createTemporaryObject(splitViewComponent, testCase) + verify(control) + + var item0 = rectangleComponent.createObject(control, { implicitWidth: 25, color: "salmon" }) + verify(item0) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + // The last item fills the width by default, and since there is only one item... + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, testCase.height) + + var item1 = rectangleComponent.createObject(control, { implicitWidth: 25, color: "steelblue" }) + verify(item1) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + // Now that a second item has been added, the first item goes back to its preferred (implicit) width. + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, item0.implicitWidth) + compare(item0.height, testCase.height) + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, item0.implicitWidth) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, item0.implicitWidth + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - item0.implicitWidth - defaultHorizontalHandleWidth) + compare(item1.height, testCase.height) + } + + function test_addItemsWithNoSizeAfterCompletion() { + var control = createTemporaryObject(splitViewComponent, testCase) + verify(control) + + var item0 = rectangleComponent.createObject(control, { color: "salmon" }) + verify(item0) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, testCase.height) + + var item1 = rectangleComponent.createObject(control, { color: "steelblue" }) + verify(item1) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 0) + compare(item0.height, testCase.height) + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - defaultHorizontalHandleWidth) + compare(item1.height, testCase.height) + } + + Component { + id: threeZeroSizedItemsComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + } + Rectangle { + objectName: "navajowhite" + color: objectName + } + Rectangle { + objectName: "steelblue" + color: objectName + } + } + } + + function test_changeAttachedPropertiesAfterCompletion() { + var control = createTemporaryObject(threeZeroSizedItemsComponent, testCase) + verify(control) + + var item0 = control.itemAt(0) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 0) + compare(item0.height, testCase.height) + + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + + var item1 = control.itemAt(1) + compare(item1.x, defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + + var handle1 = handles[1] + compare(handle1.x, defaultHorizontalHandleWidth) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + + var item2 = control.itemAt(2) + compare(item2.x, defaultHorizontalHandleWidth * 2) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.preferredWidth = 25 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 25) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 25 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.minimumWidth = 50 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 50) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 50 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.preferredWidth = 100 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 100) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 100 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.maximumWidth = 75 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 75) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 75 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item1.SplitView.fillWidth = true + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 75) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 75 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - 75 - defaultHorizontalHandleWidth * 2) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, testCase.width) + compare(item2.y, 0) + compare(item2.width, 0) + compare(item2.height, testCase.height) + } + + function test_useAttachedPropertiesIncorrectly() { + var control = createTemporaryObject(splitViewComponent, testCase) + verify(control) + + var item = rectangleComponent.createObject(control, { implicitWidth: 25, color: "salmon" }) + verify(item) + + ignoreWarning(/.*SplitView: attached properties must be accessed through a direct child of SplitView/) + testCase.SplitView.fillWidth = true; + } + + function test_sizes_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + var data = [ + { + // When the combined size of items is too large, the non-fill items should just exceed + // the size of the SplitView, exactly as they would were they in a RowLayout, for example. + tag: "fillItemOnLeft", + expectedGeometries: [ + // We're the fill item, but since the combined implicitWidths + // of the other two items take up all the space, we get none. + { x: 0, y: 0, width: 0, height: splitViewHeight }, + // First handle. + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item does not fill, so its width should be unchanged. + { x: defaultHorizontalHandleWidth, y: 0, width: 200, height: splitViewHeight }, + // Second handle. + { x: 200 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + // The third item also gets its implicitWidth. + { x: 200 + defaultHorizontalHandleWidth * 2, y: 0, width: 200, height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "fillItemOnTop", + expectedGeometries: [ + // We're the fill item, but since the combined implicitHeights + // of the other two items take up all the space, we get none. + { x: 0, y: 0, width: splitViewWidth, height: 0 }, + // First handle. + { x: 0, y: 0, width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item does not fill, so its height should be unchanged. + { x: 0, y: defaultVerticalHandleHeight, width: splitViewWidth, height: 200 }, + // Second handle. + { x: 0, y: 200 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + // The third item also gets its implicitHeight. + { x: 0, y: 200 + defaultVerticalHandleHeight * 2, width: splitViewWidth, height: 200 } + ] + }, + { + tag: "fillItemInMiddle", + expectedGeometries: [ + // Our size is fixed. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item fills. + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - 200 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - 200 - defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The third item's size is also fixed. + { x: splitViewWidth - 200, y: 0, width: 200, height: splitViewHeight } + ] + } + ] + return data + } + + function test_sizes(data) { + var component = Qt.createComponent("splitview/" + data.tag + ".qml") + compare(component.status, Component.Ready, component.errorString()); + var control = createTemporaryObject(component, testCase, { "handle": handleComponent }) + verify(control) + + compareSizes(control, data.expectedGeometries) + } + + Component { + id: threeSizedItemsComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + } + } + + function test_resetAttachedProperties_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + var data = [ + { + tag: "resetMinimumWidth", + orientation: Qt.Horizontal, + // Set the minimumWidth to 50. It should be used instead of implicitWidth since it's greater than 25. + splitItemIndex: 0, + propertyName: "minimumWidth", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // minimumWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetMinimumHeight", + orientation: Qt.Vertical, + // Set the minimumHeight to 50. It should be used instead of implicitHeight since it's greater than 25. + splitItemIndex: 0, + propertyName: "minimumHeight", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 50 }, + { x: 0, y: 50, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "resetPreferredWidth", + orientation: Qt.Horizontal, + // Set the preferredWidth to 50; it should be used instead of implicitWidth. + splitItemIndex: 0, + propertyName: "preferredWidth", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // preferredWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetPreferredHeight", + orientation: Qt.Vertical, + // Set the preferredHeight to 50; it should be used instead of implicitHeight. + splitItemIndex: 0, + propertyName: "preferredHeight", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 50 }, + { x: 0, y: 50, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "resetMaximumWidth", + orientation: Qt.Horizontal, + // Set the maximumWidth to 15. It should be used instead of implicitWidth since it's less than 25. + splitItemIndex: 0, + propertyName: "maximumWidth", + propertyValue: 15, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 15, height: splitViewHeight }, + { x: 15, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 15 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 15 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 15 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 15 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // maximumWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetMaximumHeight", + orientation: Qt.Vertical, + // Set the preferredHeight to 15. It should be used instead of implicitHeight if it's not undefined. + splitItemIndex: 0, + propertyName: "maximumHeight", + propertyValue: 15, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 15 }, + { x: 0, y: 15, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 15 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 15 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 15 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 15 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + ] + return data; + } + + function test_resetAttachedProperties(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + var splitItem = control.itemAt(data.splitItemIndex) + splitItem.SplitView[data.propertyName] = data.propertyValue + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesBefore, "after setting attached property") + + splitItem.SplitView[data.propertyName] = undefined + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfter, "after resetting attached property") + } + + function test_orientation() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var item0 = control.itemAt(0) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, item0.implicitWidth) + compare(item0.height, testCase.height) + + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, item0.implicitWidth) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + + var item1 = control.itemAt(1) + compare(item1.x, item0.width + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, item1.implicitWidth) + compare(item1.height, testCase.height) + + var handle1 = handles[1] + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + + var item2 = control.itemAt(2) + compare(item2.x, item0.width + item1.width + defaultHorizontalHandleWidth * 2) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + control.orientation = Qt.Vertical + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, item0.implicitHeight) + handles = findHandles(control) + handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, item0.implicitHeight) + compare(handle0.width, testCase.width) + compare(handle0.height, defaultVerticalHandleHeight) + compare(item1.x, 0) + compare(item1.y, item0.height + defaultVerticalHandleHeight) + compare(item1.width, testCase.width) + compare(item1.height, item1.implicitHeight) + handle1 = handles[1] + compare(handle1.x, 0) + compare(handle1.y, item1.y + item1.height) + compare(handle1.width, testCase.width) + compare(handle1.height, defaultVerticalHandleHeight) + compare(item2.x, 0) + compare(item2.y, item0.height + item1.height + defaultVerticalHandleHeight * 2) + compare(item2.width, testCase.width) + compare(item2.height, testCase.height - item2.y) + } + + readonly property int splitViewMargins: 50 + + Component { + id: threeItemsMinSizeAndFillComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + SplitView.minimumWidth: 25 + SplitView.minimumHeight: 25 + SplitView.fillWidth: true + SplitView.fillHeight: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + } + } + + function test_dragHandle_data() { + var splitViewWidth = testCase.width - splitViewMargins * 2 + var splitViewHeight = testCase.height - splitViewMargins * 2 + var data = [ + { + tag: "fillThirdItemAndDragFirstHandlePastRightSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + // The index of the item that will fill. + fillIndex: 2, + // The index of the handle to be dragged. + handleIndex: 0, + // The position where the center of the handle will be. + newHandlePos: Qt.point(testCase.width + 20, testCase.height / 2), + // The expected geometry of each item managed by the SplitView before dragging the handle. + expectedGeometriesBeforeDrag: [ + // First item. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Third item (fills). + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // The expected geometry of each item managed by the SplitView after dragging the handle. + expectedGeometriesAfterDrag: [ + // The fill item is to the right of the handle at index 0, so the handle belongs + // to the left item: us. We should consume all of the fill item's width. + { x: 0, y: 0, width: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, + height: splitViewHeight }, + // First handle. + { x: splitViewWidth - defaultHorizontalHandleWidth * 2 - 100, + y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item does not fill, so its width should be unchanged. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth, + y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - defaultHorizontalHandleWidth, + y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The last item does fill, so it should lose all of its width. + { x: splitViewWidth, y: 0, width: 0, height: splitViewHeight } + ] + }, + { + tag: "fillThirdItemAndDragFirstHandlePastBottomSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 2, + handleIndex: 0, + newHandlePos: Qt.point(testCase.width / 2, testCase.height + 20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ], + // The expected geometry of each item managed by the SplitView after dragging the handle. + expectedGeometriesAfterDrag: [ + // The fill item is to the bottom of the handle at index 0, so the handle belongs + // to the top item: us. We should consume all of the fill item's width. + { x: 0, y: 0, width: splitViewWidth, + height: splitViewHeight - 100 - defaultVerticalHandleHeight * 2 }, + // First handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight * 2 - 100, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item does not fill, so its height should be unchanged. + { x: 0, y: splitViewWidth - 100 - defaultVerticalHandleHeight, + width: splitViewWidth, height: 100 }, + // Second handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The last item does fill, so it should lose all of its width. + { x: 0, y: splitViewHeight, width: splitViewWidth, height: 0 } + ] + }, + { + tag: "fillThirdItemAndDragSecondHandlePastLeftSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 2, + handleIndex: 1, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the right of the handle at index 1, so the handle belongs + // to the second item; our width should be unchanged. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item is the one being resized, and since we're dragging its handle + // to the left, its width should decrease. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 0, height: splitViewHeight }, + // Second handle. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + // The last item fills, so it should get the second item's lost width. + { x: 25 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "fillThirdItemAndDragSecondHandlePastTopSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 2, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width / 2, -20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the bottom of the handle at index 1, so the handle belongs + // to the second item; our height should be unchanged. + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + // First handle. + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item is the one being resized, and since we're dragging its handle + // to the top, its height should decrease. + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 0 }, + // Second handle. + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + // The last item fills, so it should get the second item's lost height. + { x: 0, y: 25 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - defaultVerticalHandleHeight * 2 } + ] + }, + { + // First item should start off empty and then eventually take up all of 3rd item's space + // as the handle is dragged past the right side. + tag: "fillFirstItemAndDragSecondHandlePastRightSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width + 20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: 200, height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the left of the handle at index 1, so the handle belongs + // to the third item. Since we're moving the handle to the right side of the + // SplitView, our width should grow as we consume the width of the third item. + { x: 0, y: 0, width: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + // First handle. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item's width remains unchanged. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The last item loses its width. + { x: splitViewWidth, y: 0, width: 0, height: splitViewHeight } + ] + }, + { + // First item should start off empty and then eventually take up all of 3rd item's space + // as the handle is dragged past the bottom side. + tag: "fillFirstItemAndDragSecondHandlePastBottomSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 0, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width / 2, testCase.height + 20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 0 }, + { x: 0, y: 0, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, height: 200 } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the top of the handle at index 1, so the handle belongs + // to the third item. Since we're moving the handle to the bottom side of the + // SplitView, our height should grow as we consume the height of the third item. + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight - 100 - defaultVerticalHandleHeight * 2 }, + // First handle. + { x: 0, y: splitViewHeight - 100 - defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item's width remains unchanged. + { x: 0, y: splitViewHeight - 100 - defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + // Second handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The last item loses its width. + { x: 0, y: splitViewHeight, width: splitViewHeight, height: 0 } + ] + }, + { + tag: "fillFirstItemAndDragFirstHandlePastLeftSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 0, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item's implicitWidth is 100, and ours is 200. The available width is 300, + // so both items get their implicit widths. + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ], + // Should be unchanged. + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ] + }, + { + tag: "fillFirstItemWithMinWidthAndDragFirstHandlePastLeftSide", + component: threeItemsMinSizeAndFillComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 0, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ], + // Should be unchanged. + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ] + } + ] + return data + } + + function test_dragHandle(data) { + var control = createTemporaryObject(data.component, testCase) + verify(control) + + control.orientation = data.orientation + + // Ensure that there is space to drag outside of the SplitView. + control.anchors.margins = splitViewMargins + + var fillItem = control.itemAt(data.fillIndex) + if (control.orientation === Qt.Horizontal) + fillItem.SplitView.fillWidth = true + else + fillItem.SplitView.fillHeight = true + + // Check the sizes of the items before the drag. + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesBeforeDrag, "before drag") + + // Drag the handle. + var handles = findHandles(control) + var targetHandle = handles[data.handleIndex] + mousePress(targetHandle) + verify(control.resizing) + var localPos = testCase.mapToItem(targetHandle, data.newHandlePos.x, data.newHandlePos.y) + mouseMove(targetHandle, localPos.x - targetHandle.width / 2, localPos.y - targetHandle.height / 2) + verify(control.resizing) + compareSizes(control, data.expectedGeometriesAfterDrag, "after drag move") + + // The geometries should remain unchanged after releasing. + mouseRelease(targetHandle, localPos.x - targetHandle.width / 2, localPos.y - targetHandle.height / 2, Qt.LeftButton) + verify(!control.resizing) + compareSizes(control, data.expectedGeometriesAfterDrag, "after drag release") + } + + function test_splitViewGeometryChanges_data() { + var defaultSplitViewWidth = testCase.width + var defaultSplitViewHeight = testCase.height + + var data = [ + { + tag: "growWidth", + orientation: Qt.Horizontal, + splitViewWidth: 800, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: defaultSplitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 800 - 25 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "growHeight", + orientation: Qt.Vertical, + splitViewHeight: 800, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 25 }, + { x: 0, y: 25, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: 800 - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "shrinkWidth", + orientation: Qt.Horizontal, + splitViewWidth: 200, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: defaultSplitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 200 - 25 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "shrinkHeight", + orientation: Qt.Vertical, + splitViewHeight: 200, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 25 }, + { x: 0, y: 25, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: 200 - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + ] + return data + } + + function test_splitViewGeometryChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "handle": handleComponent, "anchors.fill": undefined, "orientation": data.orientation }) + verify(control) + + if (data.hasOwnProperty("splitViewWidth")) + control.width = data.splitViewWidth + else + control.width = testCase.width + + if (data.hasOwnProperty("splitViewHeight")) + control.height = data.splitViewHeight + else + control.height = testCase.height + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_splitItemImplicitSizeChanges_data() { + var defaultSplitViewWidth = testCase.width + var defaultSplitViewHeight = testCase.height + + var data = [ + { + tag: "growImplicitWidth", + orientation: Qt.Horizontal, + splitItemImplicitWidth: 50, + expectedGeometries: [ + { x: 0, y: 0, width: 50, height: defaultSplitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultSplitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + tag: "growImplicitHeight", + orientation: Qt.Vertical, + splitItemImplicitHeight: 50, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 50 }, + { x: 0, y: 50, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: defaultSplitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ] + } + ] + return data + } + + // Tests that implicitWidth/Height changes in items are noticed by SplitView. + function test_splitItemImplicitSizeChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "handle": handleComponent, "orientation": data.orientation }) + verify(control) + + var firstItem = control.itemAt(0) + + if (data.hasOwnProperty("splitItemImplicitWidth")) + firstItem.implicitWidth = data.splitItemImplicitWidth + + if (data.hasOwnProperty("splitItemImplicitHeight")) + firstItem.implicitHeight = data.splitItemImplicitHeight + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + Component { + id: largerHandle + Rectangle { + objectName: "largerHandle" + implicitWidth: 20 + implicitHeight: 20 + color: "#444" + } + } + + Component { + id: smallerHandle + Rectangle { + objectName: "smallerHandle" + implicitWidth: 5 + implicitHeight: 5 + color: "#444" + } + } + + function test_handleChanges_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "growHandleWidth", + orientation: Qt.Horizontal, + handleComponent: largerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: 20, height: splitViewHeight }, + { x: 25 + 20, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + 20, y: 0, width: 20, height: splitViewHeight }, + { x: 25 + 100 + 20 * 2, y: 0, width: splitViewWidth - 25 - 100 - 20 * 2, + height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "growHandleHeight", + orientation: Qt.Vertical, + handleComponent: largerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: 20 }, + { x: 0, y: 25 + 20, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + 20, width: splitViewWidth, height: 20 }, + { x: 0, y: 25 + 100 + 20 * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - 20 * 2 } + ] + }, + { + tag: "shrinkHandleWidth", + orientation: Qt.Horizontal, + handleComponent: smallerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: 5, height: splitViewHeight }, + { x: 25 + 5, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + 5, y: 0, width: 5, height: splitViewHeight }, + { x: 25 + 100 + 5 * 2, y: 0, width: splitViewWidth - 25 - 100 - 5 * 2, + height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "shrinkHandleHeight", + orientation: Qt.Vertical, + handleComponent: smallerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: 5 }, + { x: 0, y: 25 + 5, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + 5, width: splitViewWidth, height: 5 }, + { x: 0, y: 25 + 100 + 5 * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - 5 * 2 } + ] + } + ] + return data + } + + function test_handleChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + control.handle = data.handleComponent + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_insertRemoveItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "insertItemAtHorizontalEnd", + orientation: Qt.Horizontal, + insertItemAtIndex: 3, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // This was the fill item originally, but since no fill item is explicitly + // specified, and we added an item to the right of it, it is no longer the fill item + // because it's no longer last. + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 200, height: splitViewHeight }, + // Handle for newly added item. + { x: 25 + 100 + 200 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Newly added item. + { x: 25 + 100 + 200 + defaultHorizontalHandleWidth * 3, y: 0, + width: splitViewWidth - 25 - 100 - 200 - defaultHorizontalHandleWidth * 3, + height: splitViewHeight }, + ] + }, + { + tag: "insertItemAtHorizontalBeginning", + orientation: Qt.Horizontal, + insertItemAtIndex: 0, + expectedGeometries: [ + // Newly added item. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 25, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth * 2, y: 0, width: 100, height: splitViewHeight }, + { x: 25 * 2 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Fill item doesn't change. + { x: 25 * 2 + 100 + defaultHorizontalHandleWidth * 3, y: 0, + width: splitViewWidth - 25 * 2 - 100 - defaultHorizontalHandleWidth * 3, + height: splitViewHeight }, + ] + }, + { + tag: "removeItemFromHorizontalEnd", + orientation: Qt.Horizontal, + removeItemAtIndex: 2, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ] + }, + { + tag: "removeItemFromHorizontalBeginning", + orientation: Qt.Horizontal, + removeItemAtIndex: 0, + expectedGeometries: [ + { x: 0, y: 0, width: 100, height: splitViewHeight }, + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 100 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ] + } + ] + return data + } + + Component { + id: smallRectComponent + + Rectangle { + objectName: "darkseagreen" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + } + + function test_insertRemoveItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + if (data.hasOwnProperty("removeItemAtIndex")) { + var itemToRemove = control.itemAt(data.removeItemAtIndex) + verify(itemToRemove) + + control.removeItem(itemToRemove) + } else if (data.hasOwnProperty("insertItemAtIndex")) { + var itemToAdd = smallRectComponent.createObject(control) + control.insertItem(data.insertItemAtIndex, itemToAdd) + } + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_removeAllItems() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + while (control.count > 0) + var itemToRemove = control.removeItem(0) + // Shouldn't crash. + } + + function test_hideItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideItemAtHorizontalEnd", + orientation: Qt.Horizontal, + hideIndices: [2], + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Last item should be hidden. + ] + }, + { + tag: "hideItemAtHorizontalBeginning", + orientation: Qt.Horizontal, + hideIndices: [0], + expectedGeometries: [ + { hidden: true }, // First item should be hidden. + { hidden: true }, // Handle for first item should be hidden. + { x: 0, y: 0, width: 100, height: splitViewHeight }, + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 100 - defaultHorizontalHandleWidth, height: splitViewHeight } + ] + }, + { + tag: "hideItemAtVerticalEnd", + orientation: Qt.Vertical, + hideIndices: [2], + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, + width: splitViewWidth, height: splitViewHeight - 25 - defaultVerticalHandleHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Last item should be hidden. + ] + }, + { + tag: "hideItemAtVerticalBeginning", + orientation: Qt.Vertical, + hideIndices: [0], + expectedGeometries: [ + { hidden: true }, // First item should be hidden. + { hidden: true }, // Handle for first item should be hidden. + { x: 0, y: 0, width: splitViewWidth, height: 100 }, + { x: 0, y: 100, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 100 + defaultVerticalHandleHeight, + width: splitViewWidth, height: splitViewHeight - 100 - defaultVerticalHandleHeight } + ] + }, + { + // No handles should be visible when there's only one item. + tag: "hideLastTwoHorizontalItems", + orientation: Qt.Horizontal, + hideIndices: [1, 2], + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight }, + { hidden: true }, // Handle for first item should be hidden. + { hidden: true }, // Second item should be hidden. + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ] + } + ] + return data + } + + function test_hideItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_hideAndShowItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideLastTwoHorizontalItems", + orientation: Qt.Horizontal, + hideIndices: [1, 2], + expectedGeometriesAfterHiding: [ + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight }, + { hidden: true }, // Handle for first item should be hidden. + { hidden: true }, // Second item should be hidden. + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ], + showIndices: [1], + expectedGeometriesAfterShowing: [ + // First item should be visible with its implicit size. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // Handle for first item should be visible. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item should be visible and fill. + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ] + } + ] + return data + } + + function test_hideAndShowItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterHiding, "after hiding") + + for (i = 0; i < data.showIndices.length; ++i) { + var itemToShow = control.itemAt(data.showIndices[i]) + verify(itemToShow) + itemToShow.visible = true + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterShowing, "after showing") + } + + function test_moveHiddenItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideSecondItemAndMoveItToFirst", + orientation: Qt.Horizontal, + hideIndices: [1], + moveFromIndex: 1, + moveToIndex: 0, + expectedGeometriesAfterMoving: [ + { hidden: true }, // First item (was second) should be hidden. + { hidden: true }, // Handle for first item should be hidden. + // Second item (was first) should get its implicit size. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ], + showIndices: [0], + expectedGeometriesAfterShowing: [ + // First item (was second) should be visible with its implicit size. + { x: 0, y: 0, width: 100, height: splitViewHeight }, + // Handle for first item (was second) should be visible. + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item (was first) should be visible with its implicit size. + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: 25, height: splitViewHeight }, + { x: 100 + 25 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + 25 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 100 - 25 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + } + ] + return data + } + + function test_moveHiddenItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + + control.moveItem(data.moveFromIndex, data.moveToIndex) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterMoving, "after moving") + + for (i = 0; i < data.showIndices.length; ++i) { + var itemToShow = control.itemAt(data.showIndices[i]) + verify(itemToShow) + itemToShow.visible = true + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterShowing, "after showing") + } + + Component { + id: flickableComponent + + Flickable { + anchors.fill: parent + anchors.margins: 100 + } + } + + function test_draggingHandleInFlickable() { + var flickable = createTemporaryObject(flickableComponent, testCase) + verify(flickable) + + var control = threeSizedItemsComponent.createObject(flickable.contentItem, + { "orientation": data.orientation }) + verify(control) + + control.anchors.fill = undefined + control.width = 400 + control.height = control.parent.height - 100 + flickable.contentWidth = control.width + flickable.contentHeight = control.height + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + var contentXSpy = signalSpyComponent.createObject(flickable, + { target: flickable, signalName: "contentXChanged" }) + verify(contentXSpy.valid) + var contentYSpy = signalSpyComponent.createObject(flickable, + { target: flickable, signalName: "contentYChanged" }) + verify(contentYSpy.valid) + + // Drag the first handle to the right; + // the flickable's contentX and contentY shouldn't change. + var firstItem = control.itemAt(0) + var firstItemOriginalWidth = firstItem.width + var handles = findHandles(control) + var firstHandle = handles[0] + // Add some vertical movement in there as well. + mouseDrag(firstHandle, firstHandle.width / 2, firstHandle.height / 2, 100, 50) + compare(contentXSpy.count, 0) + compare(contentYSpy.count, 0) + verify(firstItem.width > firstItemOriginalWidth) + + // Now do the same except vertically. + control.orientation = Qt.Vertical + control.width = control.parent.width - 100 + control.height = 400 + var firstItemOriginalHeight = firstItem.height + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + // Add some horizontal movement in there as well. + mouseDrag(firstHandle, firstHandle.width / 2, firstHandle.height / 2, 50, 100) + compare(contentXSpy.count, 0) + compare(contentYSpy.count, 0) + verify(firstItem.height > firstItemOriginalHeight) + } + + function test_hoveredPressed() { + if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) + skip("Mouse hovering not functional on offscreen/minimal platforms") + + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + control.anchors.margins = 50 + + var handles = findHandles(control) + var firstHandle = handles[0] + + var handleCenter = control.mapFromItem(firstHandle, firstHandle.width / 2, firstHandle.height / 2) + // Test fails if we don't do two moves for some reason... + mouseMove(control, handleCenter.x, handleCenter.y) + mouseMove(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mousePress(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(firstHandle.SplitHandle.pressed) + + mouseRelease(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mouseMove(control, 0, 0) + verify(!firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + } + + // Tests removing/adding/moving an item while it's pressed. + function test_modifyWhileHoveredPressed() { + if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) + skip("Mouse hovering not functional on offscreen/minimal platforms") + + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + control.anchors.margins = 50 + + var handles = findHandles(control) + var firstHandle = handles[0] + + // First, ensure that the handle is hovered + pressed. + var handleCenter = control.mapFromItem(firstHandle, firstHandle.width / 2, firstHandle.height / 2) + // Test fails if we don't do two moves for some reason... + mouseMove(control, handleCenter.x, handleCenter.y) + mouseMove(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mousePress(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(firstHandle.SplitHandle.pressed) + + // Then, remove it by removing the first item. + control.removeItem(0) + handles = findHandles(control) + firstHandle = null + compare(handles.length, 1) + + // No handles should be hovered/pressed. + for (var i = 0; i < handles.length; ++i) { + var handle = handles[i] + verify(!handle.SplitHandle.hovered, "handle at index " + i + " should not be hovered") + verify(!handle.SplitHandle.pressed, "handle at index " + i + " should not be hovered") + } + + mouseRelease(control, handleCenter.x, handleCenter.y) + } + + Component { + id: settingsComponent + Settings { + id: settings + } + } + + function test_saveAndRestoreState() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var lastItem = control.itemAt(2) + verify(lastItem) + lastItem.SplitView.preferredWidth = 123 + + // Save the state. + var settings = createTemporaryObject(settingsComponent, testCase) + verify(settings) + settings.setValue("splitView", control.saveState()) + + // Recreate the item to restore it to its "default" values. + control = createTemporaryObject(threeSizedItemsComponent, testCase) + lastItem = control.itemAt(2) + verify(lastItem) + compare(lastItem.SplitView.preferredWidth, -1) + + settings = createTemporaryObject(settingsComponent, testCase) + verify(settings) + + // Restore the state. + control.restoreState(settings.value("splitView")) + compare(lastItem.SplitView.preferredWidth, 123) + } + + function test_changePreferredSizeDuringLayout() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var firstItem = control.itemAt(0) + var secondItem = control.itemAt(1) + secondItem.widthChanged.connect(function() { + if (secondItem.width < 10) + firstItem.SplitView.preferredWidth = 50 + }) + + // Change the size of the item so that a layout happens, but + // make the size small enough that the item's onWidthChanged handler gets triggered. + // The onWidthChanged handler will set the preferredWidth of another item during the + // layout, so we need to make sure the assignment isn't lost since we return early in that case. + secondItem.implicitWidth = 5 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(secondItem.width, 5) + compare(firstItem.width, 50) + + // Now do the same for height. + control.orientation = Qt.Vertical + secondItem.heightChanged.connect(function() { + if (secondItem.height < 10) + firstItem.SplitView.preferredHeight = 50 + }) + // Get the polishes for the orientation out of the way so that they + // don't intefere with our results. + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + secondItem.implicitHeight = 5 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(secondItem.height, 5) + compare(firstItem.height, 50) + } + + // When the user drags a handle, we internally set preferredWidth/Height + // to reflect the new value. However, we also have to make sure that when + // we do so, it doesn't trigger a delayed layout. This is why we have + // m_ignoreNextDelayedLayoutRequest. This test checks that + // m_ignoreNextDelayedLayoutRequest doesn't interfere with any action from + // the user that results in a delayed layout. + function test_changePreferredSizeDuringLayoutWhileDraggingHandle() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var firstItem = control.itemAt(0) + var secondItem = control.itemAt(1) + firstItem.widthChanged.connect(function() { + if (firstItem.width === 0) + secondItem.SplitView.preferredWidth = 50 + }) + + // Start dragging the handle. + var handles = findHandles(control) + var targetHandle = handles[0] + mousePress(targetHandle) + verify(control.resizing) + var localPos = testCase.mapToItem(targetHandle, 15, testCase.height / 2) + + // Move the handle to the very left, so that the item's width becomes zero. + mouseMove(targetHandle, -100, targetHandle.height / 2) + verify(control.resizing) + compare(firstItem.width, 0) + compare(secondItem.SplitView.preferredWidth, 50) + compare(secondItem.width, 50) + mouseRelease(targetHandle, -100, targetHandle.height / 2, Qt.LeftButton) + verify(!control.resizing) + } +} diff --git a/tests/auto/controls/default/tst_default.cpp b/tests/auto/controls/default/tst_default.cpp index d21f13ad..4c575ce6 100644 --- a/tests/auto/controls/default/tst_default.cpp +++ b/tests/auto/controls/default/tst_default.cpp @@ -38,7 +38,6 @@ int main(int argc, char *argv[]) { - QTEST_ADD_GPU_BLACKLIST_SUPPORT QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); return quick_test_main(argc, argv, "tst_controls::Default", TST_CONTROLS_DATA); diff --git a/tests/auto/controls/fusion/tst_fusion.cpp b/tests/auto/controls/fusion/tst_fusion.cpp index 84f9c231..7528ccea 100644 --- a/tests/auto/controls/fusion/tst_fusion.cpp +++ b/tests/auto/controls/fusion/tst_fusion.cpp @@ -39,7 +39,6 @@ int main(int argc, char *argv[]) { - QTEST_ADD_GPU_BLACKLIST_SUPPORT QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); QQuickStyle::setStyle("Fusion"); diff --git a/tests/auto/controls/imagine/tst_imagine.cpp b/tests/auto/controls/imagine/tst_imagine.cpp index 9777c9c9..9db7ed4d 100644 --- a/tests/auto/controls/imagine/tst_imagine.cpp +++ b/tests/auto/controls/imagine/tst_imagine.cpp @@ -39,7 +39,6 @@ int main(int argc, char *argv[]) { - QTEST_ADD_GPU_BLACKLIST_SUPPORT QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); QQuickStyle::setStyle("Imagine"); diff --git a/tests/auto/controls/material/tst_material.cpp b/tests/auto/controls/material/tst_material.cpp index 7d1953c2..ae8bb3a1 100644 --- a/tests/auto/controls/material/tst_material.cpp +++ b/tests/auto/controls/material/tst_material.cpp @@ -39,7 +39,6 @@ int main(int argc, char *argv[]) { - QTEST_ADD_GPU_BLACKLIST_SUPPORT QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); QQuickStyle::setStyle("Material"); diff --git a/tests/auto/controls/universal/tst_universal.cpp b/tests/auto/controls/universal/tst_universal.cpp index d293dc4c..db5b560e 100644 --- a/tests/auto/controls/universal/tst_universal.cpp +++ b/tests/auto/controls/universal/tst_universal.cpp @@ -39,7 +39,6 @@ int main(int argc, char *argv[]) { - QTEST_ADD_GPU_BLACKLIST_SUPPORT QTEST_SET_MAIN_SOURCE_PATH qputenv("QML_NO_TOUCH_COMPRESSION", "1"); QQuickStyle::setStyle("Universal"); diff --git a/tests/auto/shared/qtest_quickcontrols.h b/tests/auto/shared/qtest_quickcontrols.h index 0ae74481..4a06c021 100644 --- a/tests/auto/shared/qtest_quickcontrols.h +++ b/tests/auto/shared/qtest_quickcontrols.h @@ -73,15 +73,11 @@ static int runTests(QObject *testObject, int argc, char *argv[]) } #define QTEST_QUICKCONTROLS_MAIN(TestCase) \ -QT_BEGIN_NAMESPACE \ -QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS \ -QT_END_NAMESPACE \ int main(int argc, char *argv[]) \ { \ qputenv("QML_NO_TOUCH_COMPRESSION", "1"); \ QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); \ QGuiApplication app(argc, argv); \ - QTEST_ADD_GPU_BLACKLIST_SUPPORT \ TestCase tc; \ QTEST_SET_MAIN_SOURCE_PATH \ return runTests(&tc, argc, argv); \ diff --git a/tests/manual/gifs/gifrecorder.cpp b/tests/manual/gifs/gifrecorder.cpp index a1ad6957..a7a5b9d6 100644 --- a/tests/manual/gifs/gifrecorder.cpp +++ b/tests/manual/gifs/gifrecorder.cpp @@ -47,7 +47,7 @@ \note The following programs must be installed if \c setHighQuality(true) is called: - \li \e avconv (sudo apt-get install libav-tools) + \li \e ffmpeg (sudo apt-get install ffmpeg) \li \e convert (sudo apt-get install imagemagick) \li \e gifsicle (sudo apt-get install gifsicle) @@ -142,29 +142,36 @@ QQuickWindow *GifRecorder::window() const } namespace { - void startProcess(QProcess &process, const QString &processName, const QString &args) + struct ProcessWaitResult { + bool success; + QString errorMessage; + }; + + ProcessWaitResult waitForProcessToStart(QProcess &process, const QString &processName, const QString &args) { qCDebug(lcGifRecorder) << "Starting" << processName << "with the following arguments:" << args; const QString command = processName + QLatin1Char(' ') + args; process.start(command); if (!process.waitForStarted(1000)) { - QString message = QString::fromLatin1("Could not launch %1 with the following arguments: %2\nError:\n%3"); - message = message.arg(processName).arg(args).arg(process.errorString()); - QFAIL(qPrintable(message)); - } else { - qCDebug(lcGifRecorder) << "Successfully started" << processName; + QString errorMessage = QString::fromLatin1("Could not launch %1 with the following arguments: %2\nError:\n%3"); + errorMessage = errorMessage.arg(processName).arg(args).arg(process.errorString()); + return { false, errorMessage }; } + + qCDebug(lcGifRecorder) << "Successfully started" << processName; + return { true, QString() }; } - void waitForProcessToFinish(QProcess &process, const QString &processName, int waitDuration) + ProcessWaitResult waitForProcessToFinish(QProcess &process, const QString &processName, int waitDuration) { if (!process.waitForFinished(waitDuration) || process.exitCode() != 0) { - QString message = QString::fromLatin1("%1 failed to finish (exit code %2): %3"); - message = message.arg(processName).arg(process.exitCode()).arg(process.errorString()); - QFAIL(qPrintable(message)); - } else { - qCDebug(lcGifRecorder) << processName << "finished"; + QString errorMessage = QString::fromLatin1("\"%1\" failed to finish (exit code %2): %3"); + errorMessage = errorMessage.arg(processName).arg(process.exitCode()).arg(process.errorString()); + return { false, errorMessage }; } + + qCDebug(lcGifRecorder) << processName << "finished"; + return { true, QString() }; } } @@ -222,7 +229,9 @@ void GifRecorder::start() connect(&mEventTimer, SIGNAL(timeout()), mWindow, SLOT(update())); mEventTimer.start(100); - startProcess(mByzanzProcess, byzanzProcessName, args); + const ProcessWaitResult result = waitForProcessToStart(mByzanzProcess, byzanzProcessName, args); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); } void GifRecorder::waitForFinish() @@ -252,20 +261,32 @@ void GifRecorder::waitForFinish() QSignalSpy spy(mWindow, SIGNAL(frameSwapped())); QVERIFY(spy.wait()); - QProcess avconvProcess; + // Start ffmpeg and send its output to imagemagick's convert command. + // Based on the example in the documentation for QProcess::setStandardOutputProcess(). + QProcess ffmpegProcess; QProcess convertProcess; - avconvProcess.setStandardOutputProcess(&convertProcess); + ffmpegProcess.setStandardOutputProcess(&convertProcess); - const QString avconvProcessName = QStringLiteral("avconv"); - const QString avconvArgs = QString::fromLatin1("-i %1 -r 20 -f image2pipe -vcodec ppm -").arg(mByzanzOutputFileName); - startProcess(avconvProcess, avconvProcessName, avconvArgs); + const QString ffmpegProcessName = QStringLiteral("ffmpeg"); + const QString ffmpegArgs = QString::fromLatin1("-i %1 -r 20 -f image2pipe -vcodec ppm -").arg(mByzanzOutputFileName); + ProcessWaitResult result = waitForProcessToStart(ffmpegProcess, ffmpegProcessName, ffmpegArgs); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); const QString convertProcessName = QStringLiteral("convert"); const QString convertArgs = QString::fromLatin1("-delay 5 -loop 0 - %1").arg(mGifFileName); - startProcess(convertProcess, convertProcessName, convertArgs); - waitForProcessToFinish(avconvProcess, avconvProcessName, waitDuration); - waitForProcessToFinish(convertProcess, convertProcessName, waitDuration); + result = waitForProcessToStart(convertProcess, convertProcessName, convertArgs); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); + + result = waitForProcessToFinish(ffmpegProcess, ffmpegProcessName, waitDuration); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); + // Conversion can take a bit longer, so double the wait time. + result = waitForProcessToFinish(convertProcess, convertProcessName, waitDuration * 2); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); const QString gifsicleProcessName = QStringLiteral("gifsicle"); const QString verbose = lcGifRecorder().isDebugEnabled() ? QStringLiteral("-V") : QString(); @@ -281,12 +302,15 @@ void GifRecorder::waitForFinish() QProcess gifsicleProcess; if (lcGifRecorder().isDebugEnabled()) gifsicleProcess.setProcessChannelMode(QProcess::ForwardedChannels); - startProcess(gifsicleProcess, gifsicleProcessName, gifsicleArgs); - waitForProcessToFinish(gifsicleProcess, gifsicleProcessName, waitDuration); - - if (QFile::exists(mByzanzOutputFileName)) { + result = waitForProcessToStart(gifsicleProcess, gifsicleProcessName, gifsicleArgs); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); + result = waitForProcessToFinish(gifsicleProcess, gifsicleProcessName, waitDuration); + if (!result.success) + QFAIL(qPrintable(result.errorMessage)); + + if (QFile::exists(mByzanzOutputFileName)) QVERIFY(QFile::remove(mByzanzOutputFileName)); - } } } diff --git a/tests/manual/gifs/tst_gifs.cpp b/tests/manual/gifs/tst_gifs.cpp index 2a7d55bd..d7d8f98d 100644 --- a/tests/manual/gifs/tst_gifs.cpp +++ b/tests/manual/gifs/tst_gifs.cpp @@ -733,8 +733,8 @@ void tst_Gifs::checkables() for (int i = 0; i < pressIndices.size(); ++i) { const int pressIndex = pressIndices.at(i); - const char *controlId = qPrintable(QString::fromLatin1("control%1").arg(pressIndex + 1)); - QQuickItem *control = window->property(controlId).value<QQuickItem*>(); + const QString controlId = QString::fromLatin1("control%1").arg(pressIndex + 1); + QQuickItem *control = window->property(qPrintable(controlId)).value<QQuickItem*>(); QVERIFY(control); const QPoint pos = control->mapToScene(QPointF(control->width() / 2, control->height() / 2)).toPoint(); diff --git a/tests/manual/testbench/controls/SplitView.qml b/tests/manual/testbench/controls/SplitView.qml new file mode 100644 index 00000000..7d534de4 --- /dev/null +++ b/tests/manual/testbench/controls/SplitView.qml @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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.12 +import QtQuick.Controls 2.5 + +QtObject { + property var supportedStates: [ + [] + ] + + property Component component: SplitView { + implicitWidth: 400 + implicitHeight: 100 + + Rectangle { + color: "salmon" + implicitWidth: 25 + implicitHeight: 25 + } + Rectangle { + color: "navajowhite" + implicitWidth: 100 + implicitHeight: 100 + } + Rectangle { + color: "steelblue" + implicitWidth: 200 + implicitHeight: 200 + } + } +} diff --git a/tests/manual/testbench/qml.qrc b/tests/manual/testbench/qml.qrc index a0927f35..743e6629 100644 --- a/tests/manual/testbench/qml.qrc +++ b/tests/manual/testbench/qml.qrc @@ -40,5 +40,6 @@ <file>controls/BusyIndicator.qml</file> <file>testbench.qml</file> <file>controls/MenuBar.qml</file> + <file>controls/SplitView.qml</file> </qresource> </RCC> |