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().
This commit is contained in:
Frank Reininghaus 2011-09-29 20:50:00 +02:00
parent 4384ba7f09
commit ab9ed5033f
8 changed files with 189 additions and 33 deletions

View file

@ -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<int> 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;

View file

@ -239,6 +239,7 @@ void KItemListSelectionManager::itemsInserted(const KItemRangeList& itemRanges)
if (!m_selectedItems.isEmpty()) {
const QSet<int> previous = m_selectedItems;
m_selectedItems.clear();
m_selectedItems.reserve(previous.count());
QSetIterator<int> 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<int> previous = m_selectedItems;
m_selectedItems.clear();
m_selectedItems.reserve(previous.count());
QSetIterator<int> 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<int>& movedToIndexes)
{
// Store the current selection (needed in the selectionChanged() signal)
const QSet<int> 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<int> previous = m_selectedItems;
m_selectedItems.clear();
m_selectedItems.reserve(previous.count());
QSetIterator<int> 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<int> selection = selectedItems();
if (selection != previousSelection) {
emit selectionChanged(selection, previousSelection);
}
}
#include "kitemlistselectionmanager.moc"

View file

@ -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<int>& movedToIndexes);
private:
int m_currentItem;

View file

@ -762,6 +762,15 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
}
}
void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList<int>& 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<QByteArray>& 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<int>)),
this, SLOT(slotItemsMoved(KItemRangeList,QList<int>)));
}
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<int>)),
this, SLOT(slotItemsMoved(KItemRangeList,QList<int>)));
}
onModelChanged(model, previous);

View file

@ -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<int>& movedToIndexes);
virtual void slotItemsChanged(const KItemRangeList& itemRanges,
const QSet<QByteArray>& roles);

View file

@ -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<int> movedToIndexes);
void itemsMoved(const KItemRange& itemRange, const QList<int>& movedToIndexes);
void itemsChanged(const KItemRangeList& itemRanges, const QSet<QByteArray>& roles);

View file

@ -29,6 +29,7 @@ namespace {
};
Q_DECLARE_METATYPE(KItemRangeList)
Q_DECLARE_METATYPE(QList<int>)
class KFileItemModelTest : public QObject
{
@ -65,6 +66,7 @@ private:
void KFileItemModelTest::init()
{
qRegisterMetaType<KItemRange>("KItemRange");
qRegisterMetaType<KItemRangeList>("KItemRangeList");
qRegisterMetaType<KFileItemList>("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<int>)));
// 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 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<int> >(), QList<int>() << 0 << 4 << 3 << 2 << 1);
// TODO: Sort by other roles; show/hide hidden files
}

View file

@ -297,14 +297,44 @@ namespace {
NoChange,
InsertItems,
RemoveItems,
MoveItems,
EndAnchoredSelection,
ToggleSelected
SetSelected
};
}
Q_DECLARE_METATYPE(QSet<int>);
Q_DECLARE_METATYPE(ChangeType);
Q_DECLARE_METATYPE(KItemRange);
Q_DECLARE_METATYPE(KItemRangeList);
Q_DECLARE_METATYPE(KItemListSelectionManager::SelectionMode);
Q_DECLARE_METATYPE(QList<int>);
/**
* 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<int>("current");
QTest::addColumn<QSet<int> >("expectedSelection");
QTest::addColumn<ChangeType>("changeType");
QTest::addColumn<KItemRangeList>("changedItems");
QTest::addColumn<QList<QVariant> >("data");
QTest::addColumn<QSet<int> >("finalSelection");
QTest::newRow("No change")
<< (QSet<int>() << 5 << 6)
<< 2 << 3
<< (QSet<int>() << 2 << 3 << 5 << 6)
<< NoChange << KItemRangeList()
<< NoChange
<< QList<QVariant>()
<< (QSet<int>() << 2 << 3 << 5 << 6);
QTest::newRow("Insert Items")
<< (QSet<int>() << 5 << 6)
<< 2 << 3
<< (QSet<int>() << 2 << 3 << 5 << 6)
<< InsertItems << (KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 2) << KItemRange(10, 5))
<< InsertItems
<< (QList<QVariant>() << QVariant::fromValue(KItemRangeList() << KItemRange(1, 1) << KItemRange(5, 2) << KItemRange(10, 5)))
<< (QSet<int>() << 3 << 4 << 8 << 9);
QTest::newRow("Remove Items")
<< (QSet<int>() << 5 << 6)
<< 2 << 3
<< (QSet<int>() << 2 << 3 << 5 << 6)
<< RemoveItems << (KItemRangeList() << KItemRange(1, 1) << KItemRange(3, 1) << KItemRange(10, 5))
<< RemoveItems
<< (QList<QVariant>() << QVariant::fromValue(KItemRangeList() << KItemRange(1, 1) << KItemRange(3, 1) << KItemRange(10, 5)))
<< (QSet<int>() << 1 << 2 << 3 << 4);
QTest::newRow("Empty Anchored Selection")
<< QSet<int>()
<< 2 << 2
<< QSet<int>()
<< EndAnchoredSelection << KItemRangeList()
<< EndAnchoredSelection
<< QList<QVariant>()
<< QSet<int>();
QTest::newRow("Toggle selection")
<< (QSet<int>() << 1 << 3 << 4)
<< 6 << 8
<< (QSet<int>() << 1 << 3 << 4 << 6 << 7 << 8)
<< ToggleSelected << (KItemRangeList() << KItemRange(0, 10))
<< SetSelected
<< (QList<QVariant>() << 0 << 10 << QVariant::fromValue(KItemListSelectionManager::Toggle))
<< (QSet<int>() << 0 << 2 << 5 << 9);
// Swap items 2, 3 and 4, 5
QTest::newRow("Move items")
<< (QSet<int>() << 0 << 1 << 2 << 3)
<< -1 << -1
<< (QSet<int>() << 0 << 1 << 2 << 3)
<< MoveItems
<< (QList<QVariant>() << QVariant::fromValue(KItemRange(2, 4))
<< QVariant::fromValue(QList<int>() << 4 << 5 << 2 << 3))
<< (QSet<int>() << 0 << 1 << 4 << 5);
// Revert sort order
QTest::newRow("Revert sort order")
<< (QSet<int>() << 0 << 1)
<< 3 << 4
<< (QSet<int>() << 0 << 1 << 3 << 4)
<< MoveItems
<< (QList<QVariant>() << QVariant::fromValue(KItemRange(0, 10))
<< QVariant::fromValue(QList<int>() << 9 << 8 << 7 << 6 << 5 << 4 << 3 << 2 << 1 << 0))
<< (QSet<int>() << 5 << 6 << 8 << 9);
}
void KItemListSelectionManagerTest::testChangeSelection()
@ -357,10 +412,10 @@ void KItemListSelectionManagerTest::testChangeSelection()
QFETCH(QSet<int>, initialSelection);
QFETCH(int, anchor);
QFETCH(int, current);
QFETCH(QSet<int> , expectedSelection);
QFETCH(QSet<int>, expectedSelection);
QFETCH(ChangeType, changeType);
QFETCH(KItemRangeList, changedItems);
QFETCH(QSet<int> , finalSelection);
QFETCH(QList<QVariant>, data);
QFETCH(QSet<int>, finalSelection);
QSignalSpy spySelectionChanged(m_selectionManager, SIGNAL(selectionChanged(QSet<int>,QSet<int>)));
@ -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<KItemRangeList>());
break;
case RemoveItems:
m_selectionManager->itemsRemoved(changedItems);
m_selectionManager->itemsRemoved(data.at(0).value<KItemRangeList>());
break;
case MoveItems:
m_selectionManager->itemsMoved(data.at(0).value<KItemRange>(),
data.at(1).value<QList<int> >());
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<int>(), // index
data.at(1).value<int>(), // count
data.at(2).value<KItemListSelectionManager::SelectionMode>());
break;
case NoChange:
break;