From ab9ed5033fa7f4825a8ea5af6438e1235696bbef Mon Sep 17 00:00:00 2001 From: Frank Reininghaus Date: Thu, 29 Sep 2011 20:50:00 +0200 Subject: [PATCH] Keep current item and selection when resorting, part 1 KFileItemModel now emits the itemsMoved signal when the model is resorted, and KItemListSelectionManager has a new function itemsMoved() which will be called indirectly when this signal is emitted. Unit tests for the new functionality are included. The following things are still needed to make the feature work: 1. KFileItemMdel::resortAllItems() should not emit itemsAdded/itemsRemoved any more. 2. KItemListView::itemsMoved() must update the view according to the changes in the model, and it must call KItemListSelectionManager::itemsMoved(). --- dolphin/src/kitemviews/kfileitemmodel.cpp | 20 +++- .../kitemviews/kitemlistselectionmanager.cpp | 46 ++++++++++ .../kitemviews/kitemlistselectionmanager.h | 1 + dolphin/src/kitemviews/kitemlistview.cpp | 13 +++ dolphin/src/kitemviews/kitemlistview.h | 1 + dolphin/src/kitemviews/kitemmodelbase.h | 17 ++-- dolphin/src/tests/kfileitemmodeltest.cpp | 33 ++++++- .../tests/kitemlistselectionmanagertest.cpp | 91 +++++++++++++++---- 8 files changed, 189 insertions(+), 33 deletions(-) diff --git a/dolphin/src/kitemviews/kfileitemmodel.cpp b/dolphin/src/kitemviews/kfileitemmodel.cpp index 7874d30253..0d6f603363 100644 --- a/dolphin/src/kitemviews/kfileitemmodel.cpp +++ b/dolphin/src/kitemviews/kfileitemmodel.cpp @@ -732,6 +732,8 @@ void KFileItemModel::resortAllItems() return; } + const KFileItemList oldSortedItems = m_sortedItems; + KFileItemList sortedItems = m_sortedItems; m_sortedItems.clear(); m_items.clear(); @@ -748,12 +750,28 @@ void KFileItemModel::resortAllItems() ++index; } + bool emitItemsMoved = false; + QList movedToIndexes; + movedToIndexes.reserve(sortedItems.count()); + for (int i = 0; i < itemCount; i++) { + const int newIndex = m_items.value(oldSortedItems.at(i).url()); + movedToIndexes.append(newIndex); + if (!emitItemsMoved && newIndex != i) { + emitItemsMoved = true; + } + } + + if (emitItemsMoved) { + // TODO: + // * Implement KItemListView::slotItemsMoved() (which should call KItemListSelectionManager::itemsMoved()) + // * Do not emit itemsRemoved()/itemsInserted() here. + emit itemsMoved(KItemRange(0, itemCount), movedToIndexes); + } emit itemsInserted(KItemRangeList() << KItemRange(0, itemCount)); } void KFileItemModel::removeExpandedItems() { - KFileItemList expandedItems; const int maxIndex = m_data.count() - 1; diff --git a/dolphin/src/kitemviews/kitemlistselectionmanager.cpp b/dolphin/src/kitemviews/kitemlistselectionmanager.cpp index 63306a3f26..131ee46e62 100644 --- a/dolphin/src/kitemviews/kitemlistselectionmanager.cpp +++ b/dolphin/src/kitemviews/kitemlistselectionmanager.cpp @@ -239,6 +239,7 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges) if (!m_selectedItems.isEmpty()) { const QSet previous = m_selectedItems; m_selectedItems.clear(); + m_selectedItems.reserve(previous.count()); QSetIterator it(previous); while (it.hasNext()) { const int index = it.next(); @@ -307,6 +308,7 @@ void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) if (!m_selectedItems.isEmpty()) { const QSet previous = m_selectedItems; m_selectedItems.clear(); + m_selectedItems.reserve(previous.count()); QSetIterator it(previous); while (it.hasNext()) { int index = it.next(); @@ -338,4 +340,48 @@ void KItemListSelectionManager::itemsRemoved(const KItemRangeList& itemRanges) } } +void KItemListSelectionManager::itemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) +{ + // Store the current selection (needed in the selectionChanged() signal) + const QSet previousSelection = selectedItems(); + + // Update the current item + if (m_currentItem >= itemRange.index && m_currentItem < itemRange.index + itemRange.count) { + const int previousCurrentItem = m_currentItem; + const int newCurrentItem = movedToIndexes.at(previousCurrentItem - itemRange.index); + + // Calling setCurrentItem would trigger the selectionChanged signal, but we want to + // emit it only once in this function -> change the current item manually and emit currentChanged + m_currentItem = newCurrentItem; + emit currentChanged(newCurrentItem, previousCurrentItem); + } + + // Update the anchor item + if (m_anchorItem >= itemRange.index && m_anchorItem < itemRange.index + itemRange.count) { + m_anchorItem = movedToIndexes.at(m_anchorItem - itemRange.index); + } + + // Update the selections + if (!m_selectedItems.isEmpty()) { + const QSet previous = m_selectedItems; + m_selectedItems.clear(); + m_selectedItems.reserve(previous.count()); + QSetIterator it(previous); + while (it.hasNext()) { + const int index = it.next(); + if (index >= itemRange.index && index < itemRange.index + itemRange.count) { + m_selectedItems.insert(movedToIndexes.at(index - itemRange.index)); + } + else { + m_selectedItems.insert(index); + } + } + } + + const QSet selection = selectedItems(); + if (selection != previousSelection) { + emit selectionChanged(selection, previousSelection); + } +} + #include "kitemlistselectionmanager.moc" diff --git a/dolphin/src/kitemviews/kitemlistselectionmanager.h b/dolphin/src/kitemviews/kitemlistselectionmanager.h index 8b3a121a6c..a8ef5ca2df 100644 --- a/dolphin/src/kitemviews/kitemlistselectionmanager.h +++ b/dolphin/src/kitemviews/kitemlistselectionmanager.h @@ -73,6 +73,7 @@ private: void setModel(KItemModelBase* model); void itemsInserted(const KItemRangeList& itemRanges); void itemsRemoved(const KItemRangeList& itemRanges); + void itemsMoved(const KItemRange& itemRange, const QList& movedToIndexes); private: int m_currentItem; diff --git a/dolphin/src/kitemviews/kitemlistview.cpp b/dolphin/src/kitemviews/kitemlistview.cpp index 988a45d409..63315b6658 100644 --- a/dolphin/src/kitemviews/kitemlistview.cpp +++ b/dolphin/src/kitemviews/kitemlistview.cpp @@ -762,6 +762,15 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } } +void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) +{ + // TODO: + // * Implement KItemListView::slotItemsMoved() (which should call KItemListSelectionManager::itemsMoved()) + // * Do not emit itemsRemoved()/itemsInserted() in KFileItemModel::resortAllItems() + Q_UNUSED(itemRange); + Q_UNUSED(movedToIndexes); +} + void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { @@ -1031,6 +1040,8 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsInserted(KItemRangeList))); disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), this, SLOT(slotItemsRemoved(KItemRangeList))); + disconnect(m_model, SIGNAL(itemsMoved(KItemRangeList,QList)), + this, SLOT(slotItemsMoved(KItemRangeList,QList))); } m_model = model; @@ -1044,6 +1055,8 @@ void KItemListView::setModel(KItemModelBase* model) this, SLOT(slotItemsInserted(KItemRangeList))); connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), this, SLOT(slotItemsRemoved(KItemRangeList))); + connect(m_model, SIGNAL(itemsMoved(KItemRangeList,QList)), + this, SLOT(slotItemsMoved(KItemRangeList,QList))); } onModelChanged(model, previous); diff --git a/dolphin/src/kitemviews/kitemlistview.h b/dolphin/src/kitemviews/kitemlistview.h index 73b736bf70..e49dbe48d6 100644 --- a/dolphin/src/kitemviews/kitemlistview.h +++ b/dolphin/src/kitemviews/kitemlistview.h @@ -246,6 +246,7 @@ protected: protected slots: virtual void slotItemsInserted(const KItemRangeList& itemRanges); virtual void slotItemsRemoved(const KItemRangeList& itemRanges); + virtual void slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes); virtual void slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles); diff --git a/dolphin/src/kitemviews/kitemmodelbase.h b/dolphin/src/kitemviews/kitemmodelbase.h index 742bc29155..763a02efd6 100644 --- a/dolphin/src/kitemviews/kitemmodelbase.h +++ b/dolphin/src/kitemviews/kitemmodelbase.h @@ -34,7 +34,7 @@ class QMimeData; struct KItemRange { - KItemRange(int index, int count); + KItemRange(int index = 0, int count = 0); int index; int count; @@ -180,20 +180,15 @@ signals: /** * Is emitted if one ore more items get moved. - * @param itemRanges Item-ranges that get moved to a new position. - * @param movedToIndexes New positions for each element of the item-ranges. + * @param itemRange Item-range that gets moved to a new position. + * @param movedToIndexes New positions for each element of the item-range. * * For example if the model has 10 items and the items 0 and 1 get exchanged * with the items 5 and 6 then the parameters look like this: - * - itemRanges: Contains two ranges. The first has the index 0 and a count of - * 2 and the second as the index 5 and a count of 2. - * - movedToIndexes: Contains the four values 5, 6, 0, 1 - * - * For the item-ranges it is assured that: - * - They don't overlap - * - The index of item-range n is smaller than the index of item-range n + 1. + * - itemRange: has the index 0 and a count of 7. + * - movedToIndexes: Contains the seven values 5, 6, 2, 3, 4, 0, 1 */ - void itemsMoved(const KItemRangeList& itemRanges, const QList movedToIndexes); + void itemsMoved(const KItemRange& itemRange, const QList& movedToIndexes); void itemsChanged(const KItemRangeList& itemRanges, const QSet& roles); diff --git a/dolphin/src/tests/kfileitemmodeltest.cpp b/dolphin/src/tests/kfileitemmodeltest.cpp index ea8c19c05a..f56ef90336 100644 --- a/dolphin/src/tests/kfileitemmodeltest.cpp +++ b/dolphin/src/tests/kfileitemmodeltest.cpp @@ -29,6 +29,7 @@ namespace { }; Q_DECLARE_METATYPE(KItemRangeList) +Q_DECLARE_METATYPE(QList) class KFileItemModelTest : public QObject { @@ -65,6 +66,7 @@ private: void KFileItemModelTest::init() { + qRegisterMetaType("KItemRange"); qRegisterMetaType("KItemRangeList"); qRegisterMetaType("KFileItemList"); @@ -355,23 +357,31 @@ void KFileItemModelTest::testSorting() //QVERIFY(!m_model->showHiddenFiles()); QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "d" << "e"); + QSignalSpy spyItemsMoved(m_model, SIGNAL(itemsMoved(KItemRange,QList))); + // Sort by Name, descending m_model->setSortOrder(Qt::DescendingOrder); QCOMPARE(m_model->sortRole(), QByteArray("name")); QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); QCOMPARE(itemsInModel(), QStringList() << "c" << "e" << "d" << "b" << "a"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 0 << 4 << 3 << 2 << 1); - // Sort by Date, decending + // Sort by Date, descending m_model->setSortRole("date"); QCOMPARE(m_model->sortRole(), QByteArray("date")); QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); QCOMPARE(itemsInModel(), QStringList() << "c" << "b" << "d" << "a" << "e"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 0 << 4 << 2 << 1 << 3); // Sort by Date, ascending m_model->setSortOrder(Qt::AscendingOrder); QCOMPARE(m_model->sortRole(), QByteArray("date")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QCOMPARE(itemsInModel(), QStringList() << "c" << "e" << "a" << "d" << "b"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 0 << 4 << 3 << 2 << 1); // Sort by Date, ascending, 'Sort Folders First' disabled m_model->setSortFoldersFirst(false); @@ -379,20 +389,33 @@ void KFileItemModelTest::testSorting() QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(!m_model->sortFoldersFirst()); QCOMPARE(itemsInModel(), QStringList() << "e" << "a" << "c" << "d" << "b"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 2 << 0 << 1 << 3 << 4); - // Default: Sort by Name, ascending, 'Sort Folders First' disabled + // Sort by Name, ascending, 'Sort Folders First' disabled m_model->setSortRole("name"); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(!m_model->sortFoldersFirst()); QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "d" << "e"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 4 << 0 << 2 << 3 << 1); - // Sort by Size, ascending, 'Sort Folders First' enabled + // Sort by Size, ascending, 'Sort Folders First' disabled m_model->setSortRole("size"); + QCOMPARE(m_model->sortRole(), QByteArray("size")); + QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); + QVERIFY(!m_model->sortFoldersFirst()); + QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "e" << "d"); + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 1 << 2 << 0 << 4 << 3); + + // In 'Sort by Size' mode, folders are always first -> changing 'Sort Folders First' does not resort the model m_model->setSortFoldersFirst(true); QCOMPARE(m_model->sortRole(), QByteArray("size")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(m_model->sortFoldersFirst()); QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "e" << "d"); + QCOMPARE(spyItemsMoved.count(), 0); // Sort by Size, descending, 'Sort Folders First' enabled m_model->setSortOrder(Qt::DescendingOrder); @@ -400,8 +423,8 @@ void KFileItemModelTest::testSorting() QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); QVERIFY(m_model->sortFoldersFirst()); QCOMPARE(itemsInModel(), QStringList() << "c" << "d" << "e" << "b" << "a"); - - // TODO: How shall the sorting by size be done if 'Sort Folders First' is disabled? + QCOMPARE(spyItemsMoved.count(), 1); + QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 0 << 4 << 3 << 2 << 1); // TODO: Sort by other roles; show/hide hidden files } diff --git a/dolphin/src/tests/kitemlistselectionmanagertest.cpp b/dolphin/src/tests/kitemlistselectionmanagertest.cpp index c270961770..2a3601bb0a 100644 --- a/dolphin/src/tests/kitemlistselectionmanagertest.cpp +++ b/dolphin/src/tests/kitemlistselectionmanagertest.cpp @@ -297,14 +297,44 @@ namespace { NoChange, InsertItems, RemoveItems, + MoveItems, EndAnchoredSelection, - ToggleSelected + SetSelected }; } Q_DECLARE_METATYPE(QSet); Q_DECLARE_METATYPE(ChangeType); +Q_DECLARE_METATYPE(KItemRange); Q_DECLARE_METATYPE(KItemRangeList); +Q_DECLARE_METATYPE(KItemListSelectionManager::SelectionMode); +Q_DECLARE_METATYPE(QList); + +/** + * The following function provides a generic way to test the selection functionality. + * + * The test is data-driven and takes the following arguments: + * + * \param initialSelection The selection at the beginning. + * \param anchor This item will be the anchor item. + * \param current This item will be the current item. + * \param expectedSelection Expected selection after anchor and current are set. + * \param changeType Type of the change that is done then: + * - NoChange + * - InsertItems -> data.at(0) provides the KItemRangeList. \sa KItemListSelectionManager::itemsInserted() + * - RemoveItems -> data.at(0) provides the KItemRangeList. \sa KItemListSelectionManager::itemsRemoved() + * - MoveItems -> data.at(0) provides the KItemRange containing the original indices, + * data.at(1) provides the list containing the new indices + * \sa KItemListSelectionManager::itemsMoved(), KItemModelBase::itemsMoved() + * - EndAnchoredSelection + * - SetSelected -> data.at(0) provides the index where the selection process starts, + * data.at(1) provides the number of indices to be selected, + * data.at(2) provides the selection mode. + * \sa KItemListSelectionManager::setSelected() + * \param data A list of QVariants which will be cast to the arguments needed for the chosen ChangeType (see above). + * \param finalSelection The expected final selection. + * + */ void KItemListSelectionManagerTest::testChangeSelection_data() { @@ -313,43 +343,68 @@ void KItemListSelectionManagerTest::testChangeSelection_data() QTest::addColumn("current"); QTest::addColumn >("expectedSelection"); QTest::addColumn("changeType"); - QTest::addColumn("changedItems"); + QTest::addColumn >("data"); QTest::addColumn >("finalSelection"); QTest::newRow("No change") << (QSet() << 5 << 6) << 2 << 3 << (QSet() << 2 << 3 << 5 << 6) - << NoChange << KItemRangeList() + << NoChange + << QList() << (QSet() << 2 << 3 << 5 << 6); QTest::newRow("Insert Items") << (QSet() << 5 << 6) << 2 << 3 << (QSet() << 2 << 3 << 5 << 6) - << InsertItems << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 2) << KItemRange(10, 5)) + << InsertItems + << (QList() << QVariant::fromValue(KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 2) << KItemRange(10, 5))) << (QSet() << 3 << 4 << 8 << 9); QTest::newRow("Remove Items") << (QSet() << 5 << 6) << 2 << 3 << (QSet() << 2 << 3 << 5 << 6) - << RemoveItems << (KItemRangeList() << KItemRange(1, 1) << KItemRange(3, 1) << KItemRange(10, 5)) + << RemoveItems + << (QList() << QVariant::fromValue(KItemRangeList() << KItemRange(1, 1) << KItemRange(3, 1) << KItemRange(10, 5))) << (QSet() << 1 << 2 << 3 << 4); QTest::newRow("Empty Anchored Selection") << QSet() << 2 << 2 << QSet() - << EndAnchoredSelection << KItemRangeList() + << EndAnchoredSelection + << QList() << QSet(); QTest::newRow("Toggle selection") << (QSet() << 1 << 3 << 4) << 6 << 8 << (QSet() << 1 << 3 << 4 << 6 << 7 << 8) - << ToggleSelected << (KItemRangeList() << KItemRange(0, 10)) + << SetSelected + << (QList() << 0 << 10 << QVariant::fromValue(KItemListSelectionManager::Toggle)) << (QSet() << 0 << 2 << 5 << 9); + + // Swap items 2, 3 and 4, 5 + QTest::newRow("Move items") + << (QSet() << 0 << 1 << 2 << 3) + << -1 << -1 + << (QSet() << 0 << 1 << 2 << 3) + << MoveItems + << (QList() << QVariant::fromValue(KItemRange(2, 4)) + << QVariant::fromValue(QList() << 4 << 5 << 2 << 3)) + << (QSet() << 0 << 1 << 4 << 5); + + // Revert sort order + QTest::newRow("Revert sort order") + << (QSet() << 0 << 1) + << 3 << 4 + << (QSet() << 0 << 1 << 3 << 4) + << MoveItems + << (QList() << QVariant::fromValue(KItemRange(0, 10)) + << QVariant::fromValue(QList() << 9 << 8 << 7 << 6 << 5 << 4 << 3 << 2 << 1 << 0)) + << (QSet() << 5 << 6 << 8 << 9); } void KItemListSelectionManagerTest::testChangeSelection() @@ -357,10 +412,10 @@ void KItemListSelectionManagerTest::testChangeSelection() QFETCH(QSet, initialSelection); QFETCH(int, anchor); QFETCH(int, current); - QFETCH(QSet , expectedSelection); + QFETCH(QSet, expectedSelection); QFETCH(ChangeType, changeType); - QFETCH(KItemRangeList, changedItems); - QFETCH(QSet , finalSelection); + QFETCH(QList, data); + QFETCH(QSet, finalSelection); QSignalSpy spySelectionChanged(m_selectionManager, SIGNAL(selectionChanged(QSet,QSet))); @@ -406,19 +461,23 @@ void KItemListSelectionManagerTest::testChangeSelection() // Change the model by inserting or removing items. switch (changeType) { case InsertItems: - m_selectionManager->itemsInserted(changedItems); + m_selectionManager->itemsInserted(data.at(0).value()); break; case RemoveItems: - m_selectionManager->itemsRemoved(changedItems); + m_selectionManager->itemsRemoved(data.at(0).value()); + break; + case MoveItems: + m_selectionManager->itemsMoved(data.at(0).value(), + data.at(1).value >()); break; case EndAnchoredSelection: m_selectionManager->endAnchoredSelection(); QVERIFY(!m_selectionManager->isAnchoredSelectionActive()); break; - case ToggleSelected: - foreach(const KItemRange& range, changedItems) { - m_selectionManager->setSelected(range.index, range.count, KItemListSelectionManager::Toggle); - } + case SetSelected: + m_selectionManager->setSelected(data.at(0).value(), // index + data.at(1).value(), // count + data.at(2).value()); break; case NoChange: break;