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;