aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMitch Curtis <mitch.curtis@qt.io>2016-05-27 13:28:55 +0200
committerJ-P Nurmi <jpnurmi@qt.io>2016-06-28 14:58:42 +0000
commit4a92e383039f759b198bfdfa8b4bf70cb35c9521 (patch)
tree8eab7ea2720350a5355fee3a99e880058e77d22b /src
parent31074d7767949832475bc7c9c643f80d97050ac4 (diff)
Tumbler: add wrap property
[ChangeLog][Tumbler] Added wrap property to control whether or not tumbler wraps when it reaches the top and bottom. Change-Id: I27c543d98f7bc574bc5dc882a130abe0dcc13cea Task-number: QTBUG-53587 Reviewed-by: J-P Nurmi <jpnurmi@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/imports/controls/Tumbler.qml27
-rw-r--r--src/imports/controls/doc/qtquickcontrols2.qdocconf1
-rw-r--r--src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-listView.qml46
-rw-r--r--src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-pathView.qml57
-rw-r--r--src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-timePicker.qml103
-rw-r--r--src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc9
-rw-r--r--src/imports/controls/material/Tumbler.qml23
-rw-r--r--src/imports/controls/plugins.qmltypes3
-rw-r--r--src/imports/controls/qtquickcontrols2plugin.cpp3
-rw-r--r--src/imports/controls/universal/Tumbler.qml23
-rw-r--r--src/imports/templates/qtquicktemplates2plugin.cpp1
-rw-r--r--src/quickcontrols2/qquicktumblerview.cpp238
-rw-r--r--src/quickcontrols2/qquicktumblerview_p.h108
-rw-r--r--src/quickcontrols2/quickcontrols2.pri6
-rw-r--r--src/quicktemplates2/qquicktumbler.cpp388
-rw-r--r--src/quicktemplates2/qquicktumbler_p.h6
16 files changed, 875 insertions, 167 deletions
diff --git a/src/imports/controls/Tumbler.qml b/src/imports/controls/Tumbler.qml
index 13bc0859..371c705f 100644
--- a/src/imports/controls/Tumbler.qml
+++ b/src/imports/controls/Tumbler.qml
@@ -35,15 +35,15 @@
****************************************************************************/
import QtQuick 2.6
-import QtQuick.Controls 2.0
-import QtQuick.Templates 2.0 as T
+import QtQuick.Controls 2.1
+import QtQuick.Controls.impl 2.1
+import QtQuick.Templates 2.1 as T
T.Tumbler {
id: control
implicitWidth: 60
implicitHeight: 200
- //! [delegate]
delegate: Text {
id: label
text: modelData
@@ -53,29 +53,20 @@ T.Tumbler {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
- //! [delegate]
- //! [contentItem]
- contentItem: PathView {
- id: pathView
+ contentItem: TumblerView {
+ id: tumblerView
model: control.model
delegate: control.delegate
- clip: true
- pathItemCount: control.visibleItemCount + 1
- preferredHighlightBegin: 0.5
- preferredHighlightEnd: 0.5
- dragMargin: width / 2
-
path: Path {
- startX: pathView.width / 2
- startY: -pathView.delegateHeight / 2
+ startX: tumblerView.width / 2
+ startY: -tumblerView.delegateHeight / 2
PathLine {
- x: pathView.width / 2
- y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2
+ x: tumblerView.width / 2
+ y: (control.visibleItemCount + 1) * tumblerView.delegateHeight - tumblerView.delegateHeight / 2
}
}
property real delegateHeight: control.availableHeight / control.visibleItemCount
}
- //! [contentItem]
}
diff --git a/src/imports/controls/doc/qtquickcontrols2.qdocconf b/src/imports/controls/doc/qtquickcontrols2.qdocconf
index 95c438a6..73f9bdfc 100644
--- a/src/imports/controls/doc/qtquickcontrols2.qdocconf
+++ b/src/imports/controls/doc/qtquickcontrols2.qdocconf
@@ -38,7 +38,6 @@ exampledirs += ../../../../examples/quickcontrols2 \
../ \
../../../quicktemplates2 \
../../calendar \
- ../../../../tests/auto/controls/data \
snippets
examplesinstallpath = quickcontrols2
diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-listView.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-listView.qml
new file mode 100644
index 00000000..ff3ecd81
--- /dev/null
+++ b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-listView.qml
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.1
+
+//! [contentItem]
+Tumbler {
+ id: tumbler
+
+ contentItem: ListView {
+ model: tumbler.model
+ delegate: tumbler.delegate
+
+ snapMode: ListView.SnapToItem
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ preferredHighlightBegin: height / 2 - (height / tumbler.visibleItemCount / 2)
+ preferredHighlightEnd: height / 2 + (height / tumbler.visibleItemCount / 2)
+ clip: true
+ }
+}
+//! [contentItem]
diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-pathView.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-pathView.qml
new file mode 100644
index 00000000..957112e0
--- /dev/null
+++ b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-pathView.qml
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.8
+import QtQuick.Controls 2.1
+
+//! [contentItem]
+Tumbler {
+ id: tumbler
+
+ contentItem: PathView {
+ id: pathView
+ model: tumbler.model
+ delegate: tumbler.delegate
+ clip: true
+ pathItemCount: tumbler.visibleItemCount + 1
+ preferredHighlightBegin: 0.5
+ preferredHighlightEnd: 0.5
+ dragMargin: width / 2
+
+ path: Path {
+ startX: pathView.width / 2
+ startY: -pathView.delegateHeight / 2
+ PathLine {
+ x: pathView.width / 2
+ y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2
+ }
+ }
+
+ property real delegateHeight: tumbler.availableHeight / tumbler.visibleItemCount
+ }
+}
+//! [contentItem]
diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-timePicker.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-timePicker.qml
new file mode 100644
index 00000000..e62238ca
--- /dev/null
+++ b/src/imports/controls/doc/snippets/qtquickcontrols2-tumbler-timePicker.qml
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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$
+**
+****************************************************************************/
+
+//! [tumbler]
+import QtQuick 2.8
+import QtQuick.Window 2.2
+import QtQuick.Controls 2.1
+
+Rectangle {
+ width: frame.implicitWidth + 10
+ height: frame.implicitHeight + 10
+
+ function formatText(count, modelData) {
+ var data = count === 12 ? modelData + 1 : modelData;
+ return data.toString().length < 2 ? "0" + data : data;
+ }
+
+ FontMetrics {
+ id: fontMetrics
+ }
+
+ Component {
+ id: delegateComponent
+
+ Label {
+ text: formatText(Tumbler.tumbler.count, modelData)
+ opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: fontMetrics.font.pixelSize * 1.25
+ }
+ }
+
+ Frame {
+ id: frame
+ padding: 0
+ anchors.centerIn: parent
+
+ Row {
+ id: row
+
+ Tumbler {
+ id: hoursTumbler
+ visibleItemCount: 5
+ model: 12
+ delegate: delegateComponent
+ }
+
+ Tumbler {
+ id: minutesTumbler
+ visibleItemCount: 5
+ model: 60
+ delegate: delegateComponent
+ }
+
+ Tumbler {
+ id: amPmTumbler
+ visibleItemCount: 5
+ wrap: false
+ model: ["AM", "PM"]
+ delegate: delegateComponent
+ }
+ }
+ }
+}
+//! [tumbler]
diff --git a/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc b/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc
index 559c89f0..a60969c0 100644
--- a/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc
+++ b/src/imports/controls/doc/src/qtquickcontrols2-customize.qdoc
@@ -697,4 +697,13 @@
\image qtquickcontrols2-tumbler-custom.png
\snippet qtquickcontrols2-tumbler-custom.qml file
+
+ If you want to define your own contentItem, use either a \l ListView or
+ \l PathView as the root item. For a wrapping Tumbler, use PathView:
+
+ \snippet qtquickcontrols2-tumbler-pathView.qml contentItem
+
+ For a non-wrapping Tumbler, use ListView:
+
+ \snippet qtquickcontrols2-tumbler-listView.qml contentItem
*/
diff --git a/src/imports/controls/material/Tumbler.qml b/src/imports/controls/material/Tumbler.qml
index 7e914319..3587ea42 100644
--- a/src/imports/controls/material/Tumbler.qml
+++ b/src/imports/controls/material/Tumbler.qml
@@ -35,8 +35,9 @@
****************************************************************************/
import QtQuick 2.6
-import QtQuick.Controls 2.0
-import QtQuick.Templates 2.0 as T
+import QtQuick.Controls 2.1
+import QtQuick.Controls.impl 2.1
+import QtQuick.Templates 2.1 as T
import QtQuick.Controls.Material 2.0
T.Tumbler {
@@ -54,22 +55,16 @@ T.Tumbler {
verticalAlignment: Text.AlignVCenter
}
- contentItem: PathView {
- id: pathView
+ contentItem: TumblerView {
+ id: tumblerView
model: control.model
delegate: control.delegate
- clip: true
- pathItemCount: control.visibleItemCount + 1
- preferredHighlightBegin: 0.5
- preferredHighlightEnd: 0.5
- dragMargin: width / 2
-
path: Path {
- startX: pathView.width / 2
- startY: -pathView.delegateHeight / 2
+ startX: tumblerView.width / 2
+ startY: -tumblerView.delegateHeight / 2
PathLine {
- x: pathView.width / 2
- y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2
+ x: tumblerView.width / 2
+ y: (control.visibleItemCount + 1) * tumblerView.delegateHeight - tumblerView.delegateHeight / 2
}
}
diff --git a/src/imports/controls/plugins.qmltypes b/src/imports/controls/plugins.qmltypes
index e661056c..c8cde812 100644
--- a/src/imports/controls/plugins.qmltypes
+++ b/src/imports/controls/plugins.qmltypes
@@ -2055,7 +2055,7 @@ Module {
name: "QQuickTumbler"
defaultProperty: "data"
prototype: "QQuickControl"
- exports: ["QtQuick.Templates/Tumbler 2.0"]
+ exports: ["QtQuick.Templates/Tumbler 2.1"]
exportMetaObjectRevisions: [0]
attachedType: "QQuickTumblerAttached"
Property { name: "model"; type: "QVariant" }
@@ -2064,6 +2064,7 @@ Module {
Property { name: "currentItem"; type: "QQuickItem"; isReadonly: true; isPointer: true }
Property { name: "delegate"; type: "QQmlComponent"; isPointer: true }
Property { name: "visibleItemCount"; type: "int" }
+ Property { name: "wrap"; type: "bool" }
}
Component {
name: "QQuickTumblerAttached"
diff --git a/src/imports/controls/qtquickcontrols2plugin.cpp b/src/imports/controls/qtquickcontrols2plugin.cpp
index 84c17267..4cfbd340 100644
--- a/src/imports/controls/qtquickcontrols2plugin.cpp
+++ b/src/imports/controls/qtquickcontrols2plugin.cpp
@@ -49,6 +49,7 @@
#include <QtQuickControls2/private/qquickstyleplugin_p.h>
#include <QtQuickControls2/private/qquickstyleselector_p.h>
#include <QtQuickControls2/private/qquickcolorimageprovider_p.h>
+#include <QtQuickControls2/private/qquicktumblerview_p.h>
#include "qquickbusyindicatorring_p.h"
#include "qquickdialring_p.h"
@@ -142,6 +143,7 @@ void QtQuickControls2Plugin::registerTypes(const char *uri)
qmlRegisterType(selector.select(QStringLiteral("Slider.qml")), uri, 2, 1, "Slider");
qmlRegisterType(selector.select(QStringLiteral("StackView.qml")), uri, 2, 1, "StackView");
qmlRegisterType(selector.select(QStringLiteral("SwipeView.qml")), uri, 2, 1, "SwipeView");
+ qmlRegisterType(selector.select(QStringLiteral("Tumbler.qml")), uri, 2, 1, "Tumbler");
}
void QtQuickControls2Plugin::initializeEngine(QQmlEngine *engine, const char *uri)
@@ -156,6 +158,7 @@ void QtQuickControls2Plugin::initializeEngine(QQmlEngine *engine, const char *ur
qmlRegisterType<QQuickProgressStrip>(import, 2, 0, "ProgressStrip");
qmlRegisterType<QQuickProgressAnimator>(import, 2, 0, "ProgressStripAnimator");
qmlRegisterType<QQuickDialRing>(import, 2, 0, "DialRing");
+ qmlRegisterType<QQuickTumblerView>(import, 2, 1, "TumblerView");
qmlRegisterType(typeUrl(QStringLiteral("CheckIndicator.qml")), import, 2, 0, "CheckIndicator");
qmlRegisterType(typeUrl(QStringLiteral("RadioIndicator.qml")), import, 2, 0, "RadioIndicator");
diff --git a/src/imports/controls/universal/Tumbler.qml b/src/imports/controls/universal/Tumbler.qml
index 7b134b84..ead3a9d3 100644
--- a/src/imports/controls/universal/Tumbler.qml
+++ b/src/imports/controls/universal/Tumbler.qml
@@ -35,9 +35,10 @@
****************************************************************************/
import QtQuick 2.6
-import QtQuick.Templates 2.0 as T
+import QtQuick.Templates 2.1 as T
import QtQuick.Controls.Universal 2.0
-import QtQuick.Controls 2.0
+import QtQuick.Controls 2.1
+import QtQuick.Controls.impl 2.1
T.Tumbler {
id: control
@@ -54,22 +55,16 @@ T.Tumbler {
verticalAlignment: Text.AlignVCenter
}
- contentItem: PathView {
- id: pathView
+ contentItem: TumblerView {
+ id: tumblerView
model: control.model
delegate: control.delegate
- clip: true
- pathItemCount: control.visibleItemCount + 1
- preferredHighlightBegin: 0.5
- preferredHighlightEnd: 0.5
- dragMargin: width / 2
-
path: Path {
- startX: pathView.width / 2
- startY: -pathView.delegateHeight / 2
+ startX: tumblerView.width / 2
+ startY: -tumblerView.delegateHeight / 2
PathLine {
- x: pathView.width / 2
- y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2
+ x: tumblerView.width / 2
+ y: (control.visibleItemCount + 1) * tumblerView.delegateHeight - tumblerView.delegateHeight / 2
}
}
diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp
index 07a4653f..28162e61 100644
--- a/src/imports/templates/qtquicktemplates2plugin.cpp
+++ b/src/imports/templates/qtquicktemplates2plugin.cpp
@@ -181,6 +181,7 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri)
qmlRegisterType<QQuickSlider, 1>(uri, 2, 1, "Slider");
qmlRegisterType<QQuickStackView, 1>(uri, 2, 1, "StackView");
qmlRegisterType<QQuickSwipeView, 1>(uri, 2, 1, "SwipeView");
+ qmlRegisterType<QQuickTumbler, 1>(uri, 2, 1, "Tumbler");
}
QT_END_NAMESPACE
diff --git a/src/quickcontrols2/qquicktumblerview.cpp b/src/quickcontrols2/qquicktumblerview.cpp
new file mode 100644
index 00000000..93906408
--- /dev/null
+++ b/src/quickcontrols2/qquicktumblerview.cpp
@@ -0,0 +1,238 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+#include "qquicktumblerview_p.h"
+
+#include <QtQml/private/qqmldelegatemodel_p.h>
+#include <QtQuick/private/qquickitem_p.h>
+#include <QtQuick/private/qquicklistview_p.h>
+#include <QtQuick/private/qquickpathview_p.h>
+
+#include <QtQuickTemplates2/private/qquicktumbler_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickTumblerView::QQuickTumblerView(QQuickItem *parent) :
+ QQuickItem(parent),
+ m_tumbler(nullptr),
+ m_delegate(nullptr),
+ m_pathView(nullptr),
+ m_listView(nullptr),
+ m_path(nullptr)
+{
+ // We don't call createView() here because we don't know what the wrap flag is set to
+ // yet, and we don't want to create a view that might never get used.
+}
+
+QVariant QQuickTumblerView::model() const
+{
+ return m_model;
+}
+
+void QQuickTumblerView::setModel(const QVariant &model)
+{
+ if (model == m_model)
+ return;
+
+ m_model = model;
+
+ if (m_pathView) {
+ m_pathView->setModel(m_model);
+ } else if (m_listView) {
+ // QQuickItemView::setModel() resets the current index,
+ // but if we're still creating the Tumbler, it should be maintained.
+ const int oldCurrentIndex = m_listView->currentIndex();
+ m_listView->setModel(m_model);
+ if (!isComponentComplete())
+ m_listView->setCurrentIndex(oldCurrentIndex);
+ }
+
+ emit modelChanged();
+}
+
+QQmlComponent *QQuickTumblerView::delegate() const
+{
+ return m_delegate;
+}
+
+void QQuickTumblerView::setDelegate(QQmlComponent *delegate)
+{
+ if (delegate == m_delegate)
+ return;
+
+ m_delegate = delegate;
+
+ if (m_pathView)
+ m_pathView->setDelegate(m_delegate);
+ else if (m_listView)
+ m_listView->setDelegate(m_delegate);
+
+ emit delegateChanged();
+}
+
+QQuickPath *QQuickTumblerView::path() const
+{
+ return m_path;
+}
+
+void QQuickTumblerView::setPath(QQuickPath *path)
+{
+ if (path == m_path)
+ return;
+
+ m_path = path;
+ emit pathChanged();
+}
+
+void QQuickTumblerView::createView()
+{
+ Q_ASSERT(m_tumbler);
+
+ if (m_tumbler->wrap()) {
+ if (m_listView) {
+ delete m_listView;
+ m_listView = nullptr;
+ }
+
+ if (!m_pathView) {
+ m_pathView = new QQuickPathView;
+ QQmlEngine::setContextForObject(m_pathView, qmlContext(this));
+ QQml_setParent_noEvent(m_pathView, this);
+ // QQuickPathView::setPathItemCount() resets the offset animation,
+ // so we just skip the animation while constructing the view.
+ const int oldHighlightMoveDuration = m_pathView->highlightMoveDuration();
+ m_pathView->setHighlightMoveDuration(0);
+ m_pathView->setParentItem(this);
+ m_pathView->setModel(m_model);
+ m_pathView->setPath(m_path);
+ m_pathView->setDelegate(m_delegate);
+ m_pathView->setPreferredHighlightBegin(0.5);
+ m_pathView->setPreferredHighlightEnd(0.5);
+ m_pathView->setClip(true);
+
+ // Give the view a size.
+ updateView();
+
+ m_pathView->setHighlightMoveDuration(oldHighlightMoveDuration);
+ }
+ } else {
+ if (m_pathView) {
+ delete m_pathView;
+ m_pathView = nullptr;
+ }
+
+ if (!m_listView) {
+ m_listView = new QQuickListView;
+ QQmlEngine::setContextForObject(m_listView, qmlContext(this));
+ QQml_setParent_noEvent(m_listView, this);
+ m_listView->setParentItem(this);
+ m_listView->setSnapMode(QQuickListView::SnapToItem);
+ m_listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
+ m_listView->setClip(true);
+ m_listView->setModel(m_model);
+ m_listView->setDelegate(m_delegate);
+
+ // Give the view a size.
+ updateView();
+ }
+ }
+}
+
+// Called whever the size or visibleItemCount changes.
+void QQuickTumblerView::updateView()
+{
+ QQuickItem *theView = view();
+ if (!theView)
+ return;
+
+ theView->setSize(QSizeF(width(), height()));
+
+ // Can be called in geometryChanged when it might not have a parent item yet.
+ if (!m_tumbler)
+ return;
+
+ // Set view-specific properties that have a dependency on the size, etc.
+ if (m_pathView) {
+ m_pathView->setPathItemCount(m_tumbler->visibleItemCount() + 1);
+ m_pathView->setDragMargin(width() / 2);
+ } else {
+ m_listView->setPreferredHighlightBegin(height() / 2 - (height() / m_tumbler->visibleItemCount() / 2));
+ m_listView->setPreferredHighlightEnd(height() / 2 + (height() / m_tumbler->visibleItemCount() / 2));
+ }
+}
+
+void QQuickTumblerView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ QQuickItem::geometryChanged(newGeometry, oldGeometry);
+ updateView();
+}
+
+void QQuickTumblerView::componentComplete()
+{
+ QQuickItem::componentComplete();
+ updateView();
+}
+
+void QQuickTumblerView::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
+{
+ QQuickItem::itemChange(change, data);
+
+ if (change == QQuickItem::ItemParentHasChanged && data.item) {
+ if (m_tumbler)
+ m_tumbler->disconnect(this);
+
+ m_tumbler = qobject_cast<QQuickTumbler*>(parentItem());
+
+ if (m_tumbler) {
+ // We assume that the parentChanged() signal of the tumbler will be emitted before its wrap property is set...
+ connect(m_tumbler, &QQuickTumbler::wrapChanged, this, &QQuickTumblerView::createView);
+ connect(m_tumbler, &QQuickTumbler::visibleItemCountChanged, this, &QQuickTumblerView::updateView);
+ }
+ }
+}
+
+QQuickItem *QQuickTumblerView::view()
+{
+ if (!m_tumbler)
+ return nullptr;
+
+ if (m_tumbler->wrap())
+ return m_pathView;
+
+ return m_listView;
+}
+
+QT_END_NAMESPACE
diff --git a/src/quickcontrols2/qquicktumblerview_p.h b/src/quickcontrols2/qquicktumblerview_p.h
new file mode 100644
index 00000000..dad913da
--- /dev/null
+++ b/src/quickcontrols2/qquicktumblerview_p.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 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$
+**
+****************************************************************************/
+
+#ifndef QQUICKTUMBLERVIEW_P_H
+#define QQUICKTUMBLERVIEW_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 <QQuickItem>
+#include <QtQuickControls2/private/qtquickcontrols2global_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQuickListView;
+class QQuickPath;
+class QQuickPathView;
+
+class QQuickTumbler;
+
+class Q_QUICKCONTROLS2_PRIVATE_EXPORT QQuickTumblerView : public QQuickItem
+{
+ Q_OBJECT
+ Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
+ Q_PROPERTY(QQuickPath *path READ path WRITE setPath NOTIFY pathChanged)
+
+public:
+ QQuickTumblerView(QQuickItem *parent = nullptr);
+
+ QVariant model() const;
+ void setModel(const QVariant &model);
+
+ QQmlComponent *delegate() const;
+ void setDelegate(QQmlComponent *delegate);
+
+ QQuickPath *path() const;
+ void setPath(QQuickPath *path);
+
+Q_SIGNALS:
+ void modelChanged();
+ void delegateChanged();
+ void pathChanged();
+
+protected:
+ void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
+ void componentComplete() override;
+ void itemChange(ItemChange change, const ItemChangeData &data) override;
+
+private:
+ QQuickItem *view();
+ void createView();
+ void updateView();
+
+ void wrapChange();
+
+ QQuickTumbler *m_tumbler;
+ QVariant m_model;
+ QQmlComponent *m_delegate;
+ QQuickPathView *m_pathView;
+ QQuickListView *m_listView;
+ QQuickPath *m_path;
+};
+
+QT_END_NAMESPACE
+
+#endif // TUMBLERVIEW_H
diff --git a/src/quickcontrols2/quickcontrols2.pri b/src/quickcontrols2/quickcontrols2.pri
index aaf01ed6..a86c9d12 100644
--- a/src/quickcontrols2/quickcontrols2.pri
+++ b/src/quickcontrols2/quickcontrols2.pri
@@ -6,7 +6,8 @@ HEADERS += \
$$PWD/qquickstyleplugin_p.h \
$$PWD/qquickstyleselector_p.h \
$$PWD/qquickstyleselector_p_p.h \
- $$PWD/qquickpaddedrectangle_p.h
+ $$PWD/qquickpaddedrectangle_p.h \
+ $$PWD/qquicktumblerview_p.h
SOURCES += \
$$PWD/qquickcolorimageprovider.cpp \
@@ -15,4 +16,5 @@ SOURCES += \
$$PWD/qquickstyleattached.cpp \
$$PWD/qquickstyleplugin.cpp \
$$PWD/qquickstyleselector.cpp \
- $$PWD/qquickpaddedrectangle.cpp
+ $$PWD/qquickpaddedrectangle.cpp \
+ $$PWD/qquicktumblerview.cpp
diff --git a/src/quicktemplates2/qquicktumbler.cpp b/src/quicktemplates2/qquicktumbler.cpp
index 580859ef..572f1a7e 100644
--- a/src/quicktemplates2/qquicktumbler.cpp
+++ b/src/quicktemplates2/qquicktumbler.cpp
@@ -57,13 +57,10 @@ QT_BEGIN_NAMESPACE
}
\endcode
- \section1 Non-wrapping Tumbler
+ By default, Tumbler wraps when it reaches the top and bottom. To achieve a
+ non-wrapping Tumbler, set the \l wrap property to \c false:
- The default contentItem of Tumbler is a \l PathView, which wraps when it
- reaches the top and bottom. To achieve a non-wrapping Tumbler, use ListView
- as the contentItem:
-
- \snippet tst_tumbler.qml contentItem
+ \snippet qtquickcontrols2-tumbler-timePicker.qml tumbler
\image qtquickcontrols2-tumbler-wrap.gif
@@ -77,7 +74,13 @@ class QQuickTumblerPrivate : public QQuickControlPrivate, public QQuickItemChang
public:
QQuickTumblerPrivate() :
delegate(nullptr),
- visibleItemCount(3)
+ visibleItemCount(3),
+ wrap(true),
+ view(nullptr),
+ viewContentItem(nullptr),
+ viewContentItemType(UnsupportedContentItemType),
+ currentIndex(-1),
+ ignoreCurrentIndexChanges(false)
{
}
@@ -85,70 +88,91 @@ public:
{
}
+ enum ContentItemType {
+ UnsupportedContentItemType,
+ PathViewContentItem,
+ ListViewContentItem
+ };
+
+ QQuickItem *determineViewType(QQuickItem *contentItem);
+ void resetViewData();
+ QList<QQuickItem *> viewContentItemChildItems() const;
+
+ static QQuickTumblerPrivate *get(QQuickTumbler *tumbler)
+ {
+ return tumbler->d_func();
+ }
+
QVariant model;
QQmlComponent *delegate;
int visibleItemCount;
+ bool wrap;
+ QQuickItem *view;
+ QQuickItem *viewContentItem;
+ ContentItemType viewContentItemType;
+ int currentIndex;
+ bool ignoreCurrentIndexChanges;
void _q_updateItemHeights();
void _q_updateItemWidths();
+ void _q_onViewCurrentIndexChanged();
+
+ void disconnectFromView();
+ void setupViewData(QQuickItem *newControlContentItem);
void itemChildAdded(QQuickItem *, QQuickItem *) override;
void itemChildRemoved(QQuickItem *, QQuickItem *) override;
};
-static QList<QQuickItem *> contentItemChildItems(QQuickItem *contentItem)
-{
- if (!contentItem)
- return QList<QQuickItem *>();
-
- // PathView has no contentItem property, but ListView does.
- QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(contentItem);
- return flickable ? flickable->contentItem()->childItems() : contentItem->childItems();
-}
-
namespace {
static inline qreal delegateHeight(const QQuickTumbler *tumbler)
{
return tumbler->availableHeight() / tumbler->visibleItemCount();
}
+}
- enum ContentItemType {
- UnsupportedContentItemType,
- PathViewContentItem,
- ListViewContentItem
- };
-
- static inline QQuickItem *actualContentItem(QQuickItem *rootContentItem, ContentItemType contentType)
- {
- if (contentType == PathViewContentItem)
- return rootContentItem;
- else if (contentType == ListViewContentItem)
- return qobject_cast<QQuickFlickable*>(rootContentItem)->contentItem();
-
- return nullptr;
+/*
+ Finds the contentItem of the view that is a child of the control's \a contentItem.
+ The type is stored in \a type.
+*/
+QQuickItem *QQuickTumblerPrivate::determineViewType(QQuickItem *contentItem)
+{
+ if (contentItem->inherits("QQuickPathView")) {
+ view = contentItem;
+ viewContentItem = contentItem;
+ viewContentItemType = PathViewContentItem;
+ return contentItem;
+ } else if (contentItem->inherits("QQuickListView")) {
+ view = contentItem;
+ viewContentItem = qobject_cast<QQuickFlickable*>(contentItem)->contentItem();
+ viewContentItemType = ListViewContentItem;
+ return contentItem;
+ } else {
+ const auto childItems = contentItem->childItems();
+ for (QQuickItem *childItem : childItems) {
+ QQuickItem *item = determineViewType(childItem);
+ if (item)
+ return item;
+ }
}
- static inline ContentItemType contentItemType(QQuickItem *rootContentItem)
- {
- if (rootContentItem->inherits("QQuickPathView"))
- return PathViewContentItem;
- else if (rootContentItem->inherits("QQuickListView"))
- return ListViewContentItem;
+ resetViewData();
+ return nullptr;
+}
- return UnsupportedContentItemType;
- }
+void QQuickTumblerPrivate::resetViewData()
+{
+ view = nullptr;
+ viewContentItem = nullptr;
+ viewContentItemType = UnsupportedContentItemType;
+}
- static inline ContentItemType contentItemTypeFromDelegate(QQuickItem *delegateItem)
- {
- if (delegateItem->parentItem()->inherits("QQuickPathView")) {
- return PathViewContentItem;
- } else if (delegateItem->parentItem()->parentItem()
- && delegateItem->parentItem()->parentItem()->inherits("QQuickListView")) {
- return ListViewContentItem;
- }
+QList<QQuickItem *> QQuickTumblerPrivate::viewContentItemChildItems() const
+{
+ if (!viewContentItem)
+ return QList<QQuickItem *>();
- return UnsupportedContentItemType;
- }
+ return viewContentItem->childItems();
}
void QQuickTumblerPrivate::_q_updateItemHeights()
@@ -157,7 +181,7 @@ void QQuickTumblerPrivate::_q_updateItemHeights()
// which doesn't affect them, only their getters.
Q_Q(const QQuickTumbler);
const qreal itemHeight = delegateHeight(q);
- const auto items = contentItemChildItems(contentItem);
+ const auto items = viewContentItemChildItems();
for (QQuickItem *childItem : items)
childItem->setHeight(itemHeight);
}
@@ -166,11 +190,25 @@ void QQuickTumblerPrivate::_q_updateItemWidths()
{
Q_Q(const QQuickTumbler);
const qreal availableWidth = q->availableWidth();
- const auto items = contentItemChildItems(contentItem);
+ const auto items = viewContentItemChildItems();
for (QQuickItem *childItem : items)
childItem->setWidth(availableWidth);
}
+void QQuickTumblerPrivate::_q_onViewCurrentIndexChanged()
+{
+ Q_Q(QQuickTumbler);
+ if (!ignoreCurrentIndexChanges) {
+ Q_ASSERT(view);
+ const int oldCurrentIndex = currentIndex;
+
+ currentIndex = view->property("currentIndex").toInt();
+
+ if (oldCurrentIndex != currentIndex)
+ emit q->currentIndexChanged();
+ }
+}
+
void QQuickTumblerPrivate::itemChildAdded(QQuickItem *, QQuickItem *)
{
_q_updateItemWidths();
@@ -196,6 +234,9 @@ QQuickTumbler::QQuickTumbler(QQuickItem *parent) :
QQuickTumbler::~QQuickTumbler()
{
+ Q_D(QQuickTumbler);
+ // Ensure that the item change listener is removed.
+ d->disconnectFromView();
}
/*!
@@ -228,7 +269,7 @@ void QQuickTumbler::setModel(const QVariant &model)
int QQuickTumbler::count() const
{
Q_D(const QQuickTumbler);
- return d->contentItem->property("count").toInt();
+ return d->view ? d->view->property("count").toInt() : 0;
}
/*!
@@ -239,13 +280,27 @@ int QQuickTumbler::count() const
int QQuickTumbler::currentIndex() const
{
Q_D(const QQuickTumbler);
- return d->contentItem ? d->contentItem->property("currentIndex").toInt() : -1;
+ return d->currentIndex;
}
void QQuickTumbler::setCurrentIndex(int currentIndex)
{
Q_D(QQuickTumbler);
- d->contentItem->setProperty("currentIndex", currentIndex);
+ if (currentIndex == d->currentIndex)
+ return;
+
+ d->currentIndex = currentIndex;
+
+ if (d->view) {
+ // The view might not have been created yet, as is the case
+ // if you create a Tumbler component and pass e.g. { currentIndex: 2 }
+ // to createObject().
+ d->ignoreCurrentIndexChanges = true;
+ d->view->setProperty("currentIndex", currentIndex);
+ d->ignoreCurrentIndexChanges = false;
+ }
+
+ emit currentIndexChanged();
}
/*!
@@ -257,7 +312,7 @@ void QQuickTumbler::setCurrentIndex(int currentIndex)
QQuickItem *QQuickTumbler::currentItem() const
{
Q_D(const QQuickTumbler);
- return d->contentItem ? d->contentItem->property("currentItem").value<QQuickItem*>() : nullptr;
+ return d->view ? d->view->property("currentItem").value<QQuickItem*>() : nullptr;
}
/*!
@@ -304,11 +359,61 @@ void QQuickTumbler::setVisibleItemCount(int visibleItemCount)
emit visibleItemCountChanged();
}
+/*!
+ \qmlproperty bool QtQuick.Controls::Tumbler::wrap
+ \since QtQuick.Controls 2.1
+
+ This property determines whether or not the tumbler wraps around when it
+ reaches the top or bottom.
+
+ It is recommended to set this property to \c false when \l count is less than
+ \l visibleItemCount, as it is simpler to interact with a non-wrapping Tumbler
+ when there are only a few items.
+*/
+bool QQuickTumbler::wrap() const
+{
+ Q_D(const QQuickTumbler);
+ return d->wrap;
+}
+
+void QQuickTumbler::setWrap(bool wrap)
+{
+ Q_D(QQuickTumbler);
+ if (isComponentComplete() && wrap == d->wrap)
+ return;
+
+ // Since we use the currentIndex of the contentItem directly, we must
+ // ensure that we keep track of the currentIndex so it doesn't get lost
+ // between view changes.
+ const int oldCurrentIndex = currentIndex();
+
+ d->disconnectFromView();
+
+ d->wrap = wrap;
+
+ // New views will set their currentIndex upon creation, which we'd otherwise
+ // take as the correct one, so we must ignore them.
+ d->ignoreCurrentIndexChanges = true;
+
+ // This will cause the view to be created if our contentItem is a TumblerView.
+ emit wrapChanged();
+
+ d->ignoreCurrentIndexChanges = false;
+
+ // The view should have been created now, so we can start determining its type, etc.
+ // If the delegates use attached properties, this will have already been called,
+ // in which case it will return early. If the delegate doesn't use attached properties,
+ // we need to call it here.
+ d->setupViewData(d->contentItem);
+
+ setCurrentIndex(oldCurrentIndex);
+}
+
QQuickTumblerAttached *QQuickTumbler::qmlAttachedProperties(QObject *object)
{
QQuickItem *delegateItem = qobject_cast<QQuickItem *>(object);
if (!delegateItem) {
- qWarning() << "Tumbler: attached properties of Tumbler must be accessed from within a delegate item";
+ qWarning() << "Tumbler: attached properties of Tumbler must be accessed through a delegate item";
return nullptr;
}
@@ -333,6 +438,16 @@ void QQuickTumbler::componentComplete()
QQuickControl::componentComplete();
d->_q_updateItemHeights();
d->_q_updateItemWidths();
+
+ if (!d->view) {
+ // We don't want to create a PathView or ListView until we're certain
+ // which one we need, and if wrap is not set, it will be the default.
+ // We can only know the final value of wrap when componentComplete() is called,
+ // so, if the view hasn't already been created, we cause it to be created here.
+ emit wrapChanged();
+ // Then, we determine the type of view for attached properties, etc.
+ d->setupViewData(d->contentItem);
+ }
}
void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
@@ -341,59 +456,88 @@ void QQuickTumbler::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
QQuickControl::contentItemChange(newItem, oldItem);
- // Since we use the currentIndex of the contentItem directly, we must
- // ensure that we keep track of the currentIndex so it doesn't get lost
- // between contentItem changes.
- const int previousCurrentIndex = currentIndex();
-
- if (oldItem) {
- disconnect(oldItem, SIGNAL(currentIndexChanged()), this, SIGNAL(currentIndexChanged()));
- disconnect(oldItem, SIGNAL(currentItemChanged()), this, SIGNAL(currentItemChanged()));
- disconnect(oldItem, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
-
- ContentItemType oldContentItemType = contentItemType(oldItem);
- QQuickItem *actualOldContentItem = actualContentItem(oldItem, oldContentItemType);
- QQuickItemPrivate *actualContentItemPrivate = QQuickItemPrivate::get(actualOldContentItem);
- actualContentItemPrivate->removeItemChangeListener(d, QQuickItemPrivate::Children);
- }
+ if (oldItem)
+ d->disconnectFromView();
if (newItem) {
- ContentItemType contentType = contentItemType(newItem);
- if (contentType == UnsupportedContentItemType) {
- qWarning() << "Tumbler: contentItems other than PathView and ListView are not supported";
- return;
+ // We wait until wrap is set to that we know which type of view to create.
+ // If we try to set up the view too early, we'll issue warnings about it not existing.
+ if (isComponentComplete()) {
+ // Make sure we use the new content item and not the current one, as that won't
+ // be changed until after contentItemChange() has finished.
+ d->setupViewData(newItem);
}
+ }
+}
- connect(newItem, SIGNAL(currentIndexChanged()), this, SIGNAL(currentIndexChanged()));
- connect(newItem, SIGNAL(currentItemChanged()), this, SIGNAL(currentItemChanged()));
- connect(newItem, SIGNAL(countChanged()), this, SIGNAL(countChanged()));
+void QQuickTumblerPrivate::disconnectFromView()
+{
+ Q_Q(QQuickTumbler);
+ if (!view && contentItem && !q->isComponentComplete()) {
+ // If a custom content item is declared, it can happen that
+ // the original contentItem exists without the view etc. having been
+ // determined yet, and then this is called when the custom content item
+ // is eventually set. In all other cases, a view should exist.
+ return;
+ }
- QQuickItem *actualNewContentItem = actualContentItem(newItem, contentType);
- QQuickItemPrivate *actualContentItemPrivate = QQuickItemPrivate::get(actualNewContentItem);
- actualContentItemPrivate->addItemChangeListener(d, QQuickItemPrivate::Children);
+ Q_ASSERT(view);
+ QObject::disconnect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
+ QObject::disconnect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
+ QObject::disconnect(view, SIGNAL(countChanged()), q, SIGNAL(countChanged()));
- // If the previous currentIndex is -1, it means we had no contentItem previously.
- if (previousCurrentIndex != -1) {
- // Can't call setCurrentIndex here, as contentItemChange() is
- // called *before* the contentItem is set.
- newItem->setProperty("currentIndex", previousCurrentIndex);
- }
+ QQuickItemPrivate *oldViewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
+ oldViewContentItemPrivate->removeItemChangeListener(this, QQuickItemPrivate::Children);
+
+ resetViewData();
+}
+
+void QQuickTumblerPrivate::setupViewData(QQuickItem *newControlContentItem)
+{
+ // Don't do anything if we've already set up.
+ if (view)
+ return;
+
+ determineViewType(newControlContentItem);
+
+ if (viewContentItemType == QQuickTumblerPrivate::UnsupportedContentItemType) {
+ qWarning() << "Tumbler: contentItem must contain either a PathView or a ListView";
+ return;
+ }
+
+ Q_Q(QQuickTumbler);
+ QObject::connect(view, SIGNAL(currentIndexChanged()), q, SLOT(_q_onViewCurrentIndexChanged()));
+ QObject::connect(view, SIGNAL(currentItemChanged()), q, SIGNAL(currentItemChanged()));
+ QObject::connect(view, SIGNAL(countChanged()), q, SIGNAL(countChanged()));
+
+ QQuickItemPrivate *viewContentItemPrivate = QQuickItemPrivate::get(viewContentItem);
+ viewContentItemPrivate->addItemChangeListener(this, QQuickItemPrivate::Children);
+
+ const int actualViewIndex = view->property("currentIndex").toInt();
+ if (actualViewIndex != currentIndex) {
+ ignoreCurrentIndexChanges = true;
+ view->setProperty("currentIndex", currentIndex);
+ ignoreCurrentIndexChanges = false;
+
+ // If we still couldn't set the currentIndex, it's probably out of bounds,
+ // in which case we must respect the actual currentIndex.
+ if (view->property("currentIndex").toInt() != currentIndex)
+ q->setCurrentIndex(actualViewIndex);
}
}
void QQuickTumbler::keyPressEvent(QKeyEvent *event)
{
- Q_D(QQuickTumbler);
-
QQuickControl::keyPressEvent(event);
- if (event->isAutoRepeat())
+ Q_D(QQuickTumbler);
+ if (event->isAutoRepeat() || !d->view)
return;
if (event->key() == Qt::Key_Up) {
- QMetaObject::invokeMethod(d->contentItem, "decrementCurrentIndex");
+ QMetaObject::invokeMethod(d->view, "decrementCurrentIndex");
} else if (event->key() == Qt::Key_Down) {
- QMetaObject::invokeMethod(d->contentItem, "incrementCurrentIndex");
+ QMetaObject::invokeMethod(d->view, "incrementCurrentIndex");
}
}
@@ -407,7 +551,7 @@ public:
displacement(0)
{
if (!delegateItem->parentItem()) {
- qWarning() << "Tumbler: attached properties must be accessed from within a delegate item that has a parent";
+ qWarning() << "Tumbler: attached properties must be accessed through a delegate item that has a parent";
return;
}
@@ -418,14 +562,12 @@ public:
}
index = indexContextProperty.toInt();
- const ContentItemType contentItemType = contentItemTypeFromDelegate(delegateItem);
- if (contentItemType == UnsupportedContentItemType)
- return;
- // ListView has an "additional" content item.
- tumbler = qobject_cast<QQuickTumbler* >(contentItemType == PathViewContentItem
- ? delegateItem->parentItem()->parentItem() : delegateItem->parentItem()->parentItem()->parentItem());
- Q_ASSERT(tumbler);
+ QQuickItem *parentItem = delegateItem;
+ while ((parentItem = parentItem->parentItem())) {
+ if ((tumbler = qobject_cast<QQuickTumbler*>(parentItem)))
+ break;
+ }
}
~QQuickTumblerAttachedPrivate() {
@@ -438,7 +580,7 @@ public:
void _q_calculateDisplacement();
// The Tumbler that contains the delegate. Required to calculated the displacement.
- QQuickTumbler *tumbler;
+ QPointer<QQuickTumbler> tumbler;
// The index of the delegate. Used to calculate the displacement.
int index;
// The displacement for our delegate.
@@ -476,32 +618,35 @@ void QQuickTumblerAttachedPrivate::_q_calculateDisplacement()
const int previousDisplacement = displacement;
displacement = 0;
- const int count = tumbler->count();
- // This can happen in tests, so it may happen in normal usage too.
- if (count == 0)
+ // Can happen if the attached properties are accessed on the wrong type of item or the tumbler was destroyed.
+ if (!tumbler)
return;
- ContentItemType contentType = contentItemType(tumbler->contentItem());
- if (contentType == UnsupportedContentItemType)
+ // Can happen if there is no ListView or PathView within the contentItem.
+ QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(tumbler);
+ if (!tumblerPrivate->viewContentItem)
return;
- qreal offset = 0;
+ const int count = tumbler->count();
+ // This can happen in tests, so it may happen in normal usage too.
+ if (count == 0)
+ return;
- if (contentType == PathViewContentItem) {
- offset = tumbler->contentItem()->property("offset").toReal();
+ if (tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem) {
+ const qreal offset = tumblerPrivate->view->property("offset").toReal();
displacement = count > 1 ? count - index - offset : 0;
// Don't add 1 if count <= visibleItemCount
const int visibleItems = tumbler->visibleItemCount();
- int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0);
+ const int halfVisibleItems = visibleItems / 2 + (visibleItems < count ? 1 : 0);
if (displacement > halfVisibleItems)
displacement -= count;
else if (displacement < -halfVisibleItems)
displacement += count;
} else {
- const qreal contentY = tumbler->contentItem()->property("contentY").toReal();
+ const qreal contentY = tumblerPrivate->view->property("contentY").toReal();
const qreal delegateH = delegateHeight(tumbler);
- const qreal preferredHighlightBegin = tumbler->contentItem()->property("preferredHighlightBegin").toReal();
+ const qreal preferredHighlightBegin = tumblerPrivate->view->property("preferredHighlightBegin").toReal();
// Tumbler's displacement goes from negative at the top to positive towards the bottom, so we must switch this around.
const qreal reverseDisplacement = (contentY + preferredHighlightBegin) / delegateH;
displacement = reverseDisplacement - index;
@@ -517,14 +662,23 @@ QQuickTumblerAttached::QQuickTumblerAttached(QQuickItem *delegateItem) :
{
Q_D(QQuickTumblerAttached);
if (d->tumbler) {
- QQuickItem *rootContentItem = d->tumbler->contentItem();
- const ContentItemType contentType = contentItemType(rootContentItem);
- QQuickItemPrivate *p = QQuickItemPrivate::get(actualContentItem(rootContentItem, contentType));
+ // When the Tumbler is completed, wrapChanged() is emitted to let QQuickTumblerView
+ // know that it can create the view. The view itself might instantiate delegates
+ // that use attached properties. At this point, setupViewData() hasn't been called yet
+ // (it's called on the next line in componentComplete()), so we call it here so that
+ // we have access to the view.
+ QQuickTumblerPrivate *tumblerPrivate = QQuickTumblerPrivate::get(d->tumbler);
+ tumblerPrivate->setupViewData(tumblerPrivate->contentItem);
+
+ if (!tumblerPrivate->viewContentItem)
+ return;
+
+ QQuickItemPrivate *p = QQuickItemPrivate::get(tumblerPrivate->viewContentItem);
p->addItemChangeListener(d, QQuickItemPrivate::Geometry | QQuickItemPrivate::Children);
- const char *contentItemSignal = contentType == PathViewContentItem
+ const char *contentItemSignal = tumblerPrivate->viewContentItemType == QQuickTumblerPrivate::PathViewContentItem
? SIGNAL(offsetChanged()) : SIGNAL(contentYChanged());
- connect(d->tumbler->contentItem(), contentItemSignal, this, SLOT(_q_calculateDisplacement()));
+ connect(tumblerPrivate->view, contentItemSignal, this, SLOT(_q_calculateDisplacement()));
d->_q_calculateDisplacement();
}
diff --git a/src/quicktemplates2/qquicktumbler_p.h b/src/quicktemplates2/qquicktumbler_p.h
index e28102e4..d129fe33 100644
--- a/src/quicktemplates2/qquicktumbler_p.h
+++ b/src/quicktemplates2/qquicktumbler_p.h
@@ -66,6 +66,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumbler : public QQuickControl
Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL)
Q_PROPERTY(int visibleItemCount READ visibleItemCount WRITE setVisibleItemCount NOTIFY visibleItemCountChanged FINAL)
+ Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL REVISION 1)
public:
explicit QQuickTumbler(QQuickItem *parent = nullptr);
@@ -86,6 +87,9 @@ public:
int visibleItemCount() const;
void setVisibleItemCount(int visibleItemCount);
+ bool wrap() const;
+ void setWrap(bool wrap);
+
static QQuickTumblerAttached *qmlAttachedProperties(QObject *object);
Q_SIGNALS:
@@ -95,6 +99,7 @@ Q_SIGNALS:
void currentItemChanged();
void delegateChanged();
void visibleItemCountChanged();
+ Q_REVISION(1) void wrapChanged();
protected:
void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override;
@@ -108,6 +113,7 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_updateItemWidths())
Q_PRIVATE_SLOT(d_func(), void _q_updateItemHeights())
+ Q_PRIVATE_SLOT(d_func(), void _q_onViewCurrentIndexChanged())
};
class QQuickTumblerAttachedPrivate;