diff --git a/examples/gallerydata/contents/ui/gallery/ListViewGallery.qml b/examples/gallerydata/contents/ui/gallery/ListViewGallery.qml
--- a/examples/gallerydata/contents/ui/gallery/ListViewGallery.qml
+++ b/examples/gallerydata/contents/ui/gallery/ListViewGallery.qml
@@ -20,7 +20,7 @@
import QtQuick 2.4
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0 as Controls
-import org.kde.kirigami 2.4 as Kirigami
+import org.kde.kirigami 2.5 as Kirigami
Kirigami.ScrollablePage {
id: page
@@ -81,20 +81,23 @@
}
}
- ListView {
- Timer {
- id: refreshRequestTimer
- interval: 3000
- onTriggered: page.refreshing = false
- }
- model: 200
- delegate: Kirigami.SwipeListItem {
+ Component {
+ id: delegateComponent
+ Kirigami.SwipeListItem {
id: listItem
- contentItem: Controls.Label {
- height: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium)
- anchors.verticalCenter: parent.verticalCenter
- text: "Item " + modelData
- color: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeTextColor : listItem.textColor
+ contentItem: RowLayout {
+ Kirigami.ListItemDragHandle {
+ listItem: listItem
+ listView: mainList
+ onMoveRequested: listModel.move(oldIndex, newIndex, 1)
+ }
+
+ Controls.Label {
+ Layout.fillWidth: true
+ height: Math.max(implicitHeight, Kirigami.Units.iconSizes.smallMedium)
+ text: model.title
+ color: listItem.checked || (listItem.pressed && !listItem.checked && !listItem.sectionDelegate) ? listItem.activeTextColor : listItem.textColor
+ }
}
actions: [
Kirigami.Action {
@@ -109,4 +112,34 @@
}]
}
}
+ ListView {
+ id: mainList
+ Timer {
+ id: refreshRequestTimer
+ interval: 3000
+ onTriggered: page.refreshing = false
+ }
+ model: ListModel {
+ id: listModel
+
+ Component.onCompleted: {
+ for (var i = 0; i < 200; ++i) {
+ listModel.append({"title": "Item " + i,
+ "actions": [{text: "Action 1", icon: "document-decrypt"},
+ {text: "Action 2", icon: "mail-reply-sender"}]
+ })
+ }
+ }
+ }
+ moveDisplaced: Transition {
+ YAnimator {
+ duration: Kirigami.Units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ }
+ delegate: Kirigami.DelegateRecycler {
+ width: parent ? parent.width : implicitWidth
+ sourceComponent: delegateComponent
+ }
+ }
}
diff --git a/kirigami.qrc b/kirigami.qrc
--- a/kirigami.qrc
+++ b/kirigami.qrc
@@ -56,6 +56,7 @@
src/controls/BasicListItem.qml
src/controls/AbstractApplicationHeader.qml
src/controls/FormLayout.qml
+ src/controls/ListItemDragHandle.qml
src/styles/Material/AbstractListItem.qml
src/styles/Material/Theme.qml
src/styles/Material/SwipeListItem.qml
diff --git a/src/controls/ListItemDragHandle.qml b/src/controls/ListItemDragHandle.qml
new file mode 100644
--- /dev/null
+++ b/src/controls/ListItemDragHandle.qml
@@ -0,0 +1,193 @@
+/*
+* Copyright (C) 2018 by Marco Martin
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Library General Public License as
+* published by the Free Software Foundation; either version 2, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Library General Public License for more details
+*
+* You should have received a copy of the GNU Library General Public
+* License along with this program; if not, write to the
+* Free Software Foundation, Inc.,
+* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
+*/
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.4 as Kirigami
+
+/**
+ * Implements a drag handle supposed to be in items in ListViews to reorder items
+ * The ListView must visualize a model which supports item reordering,
+ * such as ListModel.move() or QAbstractItemModel instances with moveRows() correctly implemented.
+ * In order for ListItemDragHandle to work correctly, the listItem that is being dragged
+ * should not directly be the delegate of the ListView, but a child of it.
+ *
+ * It is recomended to use DelagateRecycler as base delegate like the following code:
+ * @code
+ * ...
+ * Component {
+ * id: delegateComponent
+ * Kirigami.AbstractListItem {
+ * id: listItem
+ * contentItem: RowLayout {
+ * Kirigami.ListItemDragHandle {
+ * listItem: listItem
+ * listView: mainList
+ * onMoveRequested: listModel.move(oldIndex, newIndex, 1)
+ * }
+ * Controls.Label {
+ * text: model.label
+ * }
+ * }
+ * }
+ * }
+ * ListView {
+ * id: mainList
+ *
+ * model: ListModel {
+ * id: listModel
+ * ListItem {
+ * lablel: "Item 1"
+ * }
+ * ListItem {
+ * lablel: "Item 2"
+ * }
+ * ListItem {
+ * lablel: "Item 3"
+ * }
+ * }
+ * //this is optional to make list items animated when reordered
+ * moveDisplaced: Transition {
+ * YAnimator {
+ * duration: Kirigami.Units.longDuration
+ * easing.type: Easing.InOutQuad
+ * }
+ * }
+ * delegate: Kirigami.DelegateRecycler {
+ * width: mainList.width
+ * sourceComponent: delegateComponent
+ * }
+ * }
+ * ...
+ * @endcode
+ *
+ * @inherits MouseArea
+ * @since 2.5
+ */
+MouseArea {
+ id: root
+
+ /**
+ * listItem: Item
+ * The id of the delegate that we want to drag around, which *must*
+ * be a child of the actual ListView's delegate
+ */
+ property Item listItem
+
+ /**
+ * listView: Listview
+ * The id of the ListView the delegates belong to.
+ */
+ property ListView listView
+
+ /**
+ * Emitted when the drag handle wants to move the item in the model
+ * The following example does the move in the case a ListModel is used
+ * @code
+ * onMoveRequested: listModel.move(oldIndex, newIndex, 1)
+ * @endcode
+ * @param oldIndex the index the item is currently at
+ * @param newIndex the index we want to move the item to
+ */
+ signal moveRequested(int oldIndex, int newIndex)
+
+ hoverEnabled: !Kirigami.Settings.tabletMode
+
+ drag {
+ target: listItem
+ axis: Drag.YAxis
+ minimumY: 0
+ maximumY: listView.height - listItem.height
+ }
+ Kirigami.Icon {
+ id: internal
+ source: "handle-sort"
+ property int startY
+ property int mouseDownY
+ property Item originalParent
+ property int autoScrollThreshold: listItem.height * 3
+ opacity: root.pressed || root.containsMouse ? 1 : 0.6
+
+ function arrangeItem() {
+ var newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(listItem, 0, 0).y + internal.mouseDownY);
+
+ if (Math.abs(listItem.y - internal.startY) > height && newIndex > -1 && newIndex != index) {
+ root.moveRequested(index, newIndex);
+ }
+ }
+
+ anchors.fill: parent
+ }
+ preventStealing: true
+ implicitWidth: Kirigami.Units.iconSizes.smallMedium
+ implicitHeight: implicitWidth
+
+
+ onPressed: {
+ internal.originalParent = listItem.parent;
+ listItem.parent = listView;
+ listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
+ internal.originalParent.z = 99;
+ internal.startY = listItem.y;
+ internal.mouseDownY = mouse.y;
+ }
+
+ onPositionChanged: {
+ internal.arrangeItem();
+
+ scrollTimer.interval = 500 * Math.max(0.1, (1-Math.max(internal.autoScrollThreshold - listItem.y, listItem.y - listView.height + internal.autoScrollThreshold + listItem.height) / internal.autoScrollThreshold));
+ scrollTimer.running = (listItem.y < internal.autoScrollThreshold ||
+ listItem.y > listView.height - internal.autoScrollThreshold);
+ }
+ onReleased: {
+ listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
+ listItem.parent = internal.originalParent;
+ dropAnimation.running = true;
+ scrollTimer.running = false;
+ }
+ onCanceled: released()
+ SequentialAnimation {
+ id: dropAnimation
+ YAnimator {
+ target: listItem
+ from: listItem.y
+ to: 0
+ duration: Kirigami.Units.longDuration
+ easing.type: Easing.InOutQuad
+ }
+ PropertyAction {
+ target: listItem.parent
+ property: "z"
+ value: 0
+ }
+ }
+ Timer {
+ id: scrollTimer
+ interval: 500
+ repeat: true
+ onTriggered: {
+ if (listItem.y < internal.autoScrollThreshold) {
+ listView.contentY = Math.max(0, listView.contentY - Kirigami.Units.gridUnit)
+ } else {
+ listView.contentY = Math.min(listView.contentHeight - listView.height, listView.contentY + Kirigami.Units.gridUnit)
+ }
+ internal.arrangeItem();
+ }
+ }
+}
diff --git a/src/controls/private/DefaultListItemBackground.qml b/src/controls/private/DefaultListItemBackground.qml
--- a/src/controls/private/DefaultListItemBackground.qml
+++ b/src/controls/private/DefaultListItemBackground.qml
@@ -38,22 +38,15 @@
ColorAnimation { duration: Units.longDuration }
}
- readonly property bool _firstElement: typeof(index) !== "undefined" && index == 0
+ readonly property bool __separatorVisible: listItem.separatorVisible
- on_FirstElementChanged: {
- if (_firstElement) {
+ on__SeparatorVisibleChanged: {
+ if (__separatorVisible) {
var newObject = Qt.createQmlObject('import QtQuick 2.0; import org.kde.kirigami 2.4; Separator {anchors {left: parent.left; right: parent.right; bottom: parent.top} visible: listItem.separatorVisible}',
background);
+ newObject = Qt.createQmlObject('import QtQuick 2.0; import org.kde.kirigami 2.4; Separator {anchors {left: parent.left; right: parent.right; bottom: parent.bottom} visible: listItem.separatorVisible}',
+ background);
}
}
-
- Separator {
- anchors {
- left: parent.left
- right: parent.right
- bottom: parent.bottom
- }
- visible: listItem.separatorVisible
- }
}
diff --git a/src/controls/templates/SwipeListItem.qml b/src/controls/templates/SwipeListItem.qml
--- a/src/controls/templates/SwipeListItem.qml
+++ b/src/controls/templates/SwipeListItem.qml
@@ -149,7 +149,15 @@
//TODO: a global "open" state
enabled: background.x !== 0
property bool indicateActiveFocus: listItem.pressed || Settings.tabletMode || listItem.activeFocus || (view ? view.activeFocus : false)
- property Flickable view: listItem.ListView.view || listItem.parent.ListView.view
+ property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || listItem.parent) : null)
+ onViewChanged: {
+ if (view && Settings.tabletMode && !behindItem.view.parent.parent._swipeFilter) {
+ var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
+ behindItem.view.parent.parent._swipeFilter = component.createObject(behindItem.view.parent.parent);
+ print("SSS"+behindItem.view.parent.parent._swipeFilter+internal.swipeFilterItem+" "+(behindItem.view && behindItem.view.parent && behindItem.view.parent.parent))
+ }
+ }
+
anchors {
fill: parent
}
@@ -339,17 +347,13 @@
}
Component.onCompleted: {
//this will happen only once
- if (Settings.tabletMode && !swipeFilterConnection.swipeFilterItem) {
- var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
- behindItem.view.parent.parent._swipeFilter = component.createObject(behindItem.view.parent.parent);
- }
listItem.contentItemChanged();
}
Connections {
target: Settings
onTabletModeChanged: {
if (Settings.tabletMode) {
- if (!swipeFilterConnection.swipeFilterItem) {
+ if (!internal.swipeFilterItem) {
var component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent);
}
@@ -363,20 +367,27 @@
}
}
}
+ QtObject {
+ id: internal
+ readonly property QtObject swipeFilterItem: (behindItem.view && behindItem.view.parent && behindItem.view.parent.parent) ? behindItem.view.parent.parent._swipeFilter : null
+
+ readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false
+ }
+
Connections {
id: swipeFilterConnection
- readonly property QtObject swipeFilterItem: (behindItem.view && behindItem.view && behindItem.view.parent && behindItem.view.parent.parent) ? behindItem.view.parent.parent._swipeFilter : null
- readonly property bool enabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem : false
- target: enabled ? swipeFilterItem : null
- onPeekChanged: listItem.background.x = -(listItem.background.width - listItem.background.height) * swipeFilterItem.peek
+
+ target: internal.edgeEnabled ? internal.swipeFilterItem : null
+ onPeekChanged: listItem.background.x = -(listItem.background.width - listItem.background.height) * internal.swipeFilterItem.peek
onCurrentItemChanged: {
- if (!enabled) {
+ if (!internal.edgeEnabled) {
positionAnimation.to = 0;
positionAnimation.from = background.x;
positionAnimation.running = true;
}
}
}
+
//END signal handlers
Accessible.role: Accessible.ListItem
diff --git a/src/delegaterecycler.h b/src/delegaterecycler.h
--- a/src/delegaterecycler.h
+++ b/src/delegaterecycler.h
@@ -72,9 +72,15 @@
Q_SIGNALS:
void sourceComponentChanged();
+private Q_SLOTS:
+ void syncIndex();
+ void syncModel();
+ void syncModelData();
+
private:
QPointer m_sourceComponent;
QPointer m_item;
+ QObject *m_propertiesTracker = nullptr;
bool m_updatingSize = false;
};
diff --git a/src/delegaterecycler.cpp b/src/delegaterecycler.cpp
--- a/src/delegaterecycler.cpp
+++ b/src/delegaterecycler.cpp
@@ -115,6 +115,24 @@
}
}
+void DelegateRecycler::syncIndex()
+{
+ QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+ ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex"));
+}
+
+void DelegateRecycler::syncModel()
+{
+ QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+ ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel"));
+}
+
+void DelegateRecycler::syncModelData()
+{
+ QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
+ ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData"));
+}
+
QQmlComponent *DelegateRecycler::sourceComponent() const
{
return m_sourceComponent;
@@ -129,6 +147,18 @@
if (m_sourceComponent == component) {
return;
}
+
+ if (!m_propertiesTracker) {
+ QQmlComponent *propertiesTrackerComponent = new QQmlComponent(qmlEngine(this), this);
+
+ propertiesTrackerComponent->setData(QByteArrayLiteral("import QtQuick 2.3\nQtObject{property int trackedIndex: index; property var trackedModel: typeof model != 'undefined' ? model : null; property var trackedModelData: typeof modelData != 'undefined' ? modelData : null}"), QUrl());
+ m_propertiesTracker = propertiesTrackerComponent->create(QQmlEngine::contextForObject(this));
+
+ connect(m_propertiesTracker, SIGNAL(trackedIndexChanged()), this, SLOT(syncIndex()));
+ connect(m_propertiesTracker, SIGNAL(trackedModelChanged()), this, SLOT(syncModel()));
+ connect(m_propertiesTracker, SIGNAL(trackedModelDataChanged()), this, SLOT(syncModelData()));
+ }
+
if (m_sourceComponent) {
if (m_item) {
disconnect(m_item.data(), &QQuickItem::implicitWidthChanged, this, &DelegateRecycler::updateHints);
@@ -156,24 +186,21 @@
}
}
- QQmlContext *myCtx = QQmlEngine::contextForObject(this);
- ctx->setContextProperty(QStringLiteral("model"), myCtx->contextProperty(QStringLiteral("model")));
- ctx->setContextProperty(QStringLiteral("modelData"), myCtx->contextProperty(QStringLiteral("modelData")));
- ctx->setContextProperty(QStringLiteral("index"), myCtx->contextProperty(QStringLiteral("index")));
+ ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel"));
+ ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData"));
+ ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex"));
QObject * obj = component->create(ctx);
m_item = qobject_cast(obj);
if (!m_item) {
obj->deleteLater();
}
} else {
- QQmlContext *myCtx = QQmlEngine::contextForObject(this);
QQmlContext *ctx = QQmlEngine::contextForObject(m_item)->parentContext();
- QObject *model = myCtx->contextProperty(QStringLiteral("model")).value();
- ctx->setContextProperty(QStringLiteral("model"), QVariant::fromValue(model));
- ctx->setContextProperty(QStringLiteral("modelData"), myCtx->contextProperty(QStringLiteral("modelData")));
- ctx->setContextProperty(QStringLiteral("index"), myCtx->contextProperty(QStringLiteral("index")));
+ ctx->setContextProperty(QStringLiteral("model"), m_propertiesTracker->property("trackedModel"));
+ ctx->setContextProperty(QStringLiteral("modelData"), m_propertiesTracker->property("trackedModelData"));
+ ctx->setContextProperty(QStringLiteral("index"), m_propertiesTracker->property("trackedIndex"));
}
if (m_item) {
@@ -194,7 +221,7 @@
void DelegateRecycler::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
- if (m_item && newGeometry != oldGeometry) {
+ if (m_item && newGeometry.size() != oldGeometry.size()) {
updateSize(true);
}
QQuickItem::geometryChanged(newGeometry, oldGeometry);
diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp
--- a/src/kirigamiplugin.cpp
+++ b/src/kirigamiplugin.cpp
@@ -166,9 +166,11 @@
qmlRegisterType(componentUrl(QStringLiteral("CardsLayout.qml")), uri, 2, 4, "CardsLayout");
qmlRegisterType(componentUrl(QStringLiteral("InlineMessage.qml")), uri, 2, 4, "InlineMessage");
qmlRegisterUncreatableType(uri, 2, 4, "MessageType", "Cannot create objects of type MessageType");
-
qmlRegisterType(uri, 2, 4, "DelegateRecycler");
+ //2.5
+ qmlRegisterType(componentUrl(QStringLiteral("ListItemDragHandle.qml")), uri, 2, 5, "ListItemDragHandle");
+
qmlProtectModule(uri, 2);
}
diff --git a/src/qmldir b/src/qmldir
--- a/src/qmldir
+++ b/src/qmldir
@@ -42,3 +42,5 @@
CardsListView 2.4 CardsListView.qml
CardsGridView 2.4 CardsGridView.qml
InlineMessage 2.4 InlineMessage.qml
+ListItemDragHandle 2.5 ListItemDragHandle.qml
+
diff --git a/src/styles/Material/SwipeListItem.qml b/src/styles/Material/SwipeListItem.qml
--- a/src/styles/Material/SwipeListItem.qml
+++ b/src/styles/Material/SwipeListItem.qml
@@ -60,7 +60,6 @@
}
}
background: DefaultListItemBackground {
- clip: true
//TODO: this will have to reuse QQC2.1 Ripple
Rectangle {
id: ripple