diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -78,6 +78,7 @@ void testDataChangedSignal(); void testIconRole_data(); void testIconRole(); + void testMoveFunction(); private: QStringList placesUrls() const; @@ -963,6 +964,87 @@ QCOMPARE(index.data(KFilePlacesModel::IconNameRole).toString(), expectedIconName); } +void KFilePlacesModelTest::testMoveFunction() +{ + QList args; + QStringList urls = initialListOfUrls(); + QSignalSpy rowsMoved(m_places, &KFilePlacesModel::rowsMoved); + + // move item 0 to pos 2 + QVERIFY(m_places->movePlace(0, 3)); + urls.move(0, 2); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 0); // start + QCOMPARE(args.at(2).toInt(), 0); // end + QCOMPARE(args.at(4).toInt(), 3); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + // move it back + QVERIFY(m_places->movePlace(2, 0)); + urls.move(2, 0); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 2); // start + QCOMPARE(args.at(2).toInt(), 2); // end + QCOMPARE(args.at(4).toInt(), 0); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + // target position is greater than model rows + // will move to the end of the first group + QVERIFY(m_places->movePlace(0, 20)); + urls.move(0, 2); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 0); // start + QCOMPARE(args.at(2).toInt(), 0); // end + QCOMPARE(args.at(4).toInt(), 3); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + // move it back + QVERIFY(m_places->movePlace(2, 0)); + urls.move(2, 0); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 2); // start + QCOMPARE(args.at(2).toInt(), 2); // end + QCOMPARE(args.at(4).toInt(), 0); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + QVERIFY(m_places->movePlace(8, 6)); + urls.move(8, 6); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 8); // start + QCOMPARE(args.at(2).toInt(), 8); // end + QCOMPARE(args.at(4).toInt(), 6); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + // move it back + QVERIFY(m_places->movePlace(6, 9)); + urls.move(6, 8); + QTRY_COMPARE(rowsMoved.count(), 1); + args = rowsMoved.takeFirst(); + QCOMPARE(args.at(1).toInt(), 6); // start + QCOMPARE(args.at(2).toInt(), 6); // end + QCOMPARE(args.at(4).toInt(), 9); // row (destination) + QCOMPARE(placesUrls(), urls); + rowsMoved.clear(); + + //use a invalid start postion + QVERIFY(!m_places->movePlace(100, 20)); + QTRY_COMPARE(rowsMoved.count(), 0); + + //use same start and target position + QVERIFY(!m_places->movePlace(1, 1)); + QTRY_COMPARE(rowsMoved.count(), 0); +} + QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -76,6 +76,11 @@ void editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); void removePlace(const QModelIndex &index) const; void setPlaceHidden(const QModelIndex &index, bool hidden); + /** + * @brief Move place at @p itemRow to a position before @p row + * @since 5.41 + */ + bool movePlace(int itemRow, int row); int hiddenCount() const; diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -175,6 +175,7 @@ void reloadAndSignal(); QList loadBookmarkList(); + int findNearestPosition(int source, int target); void _k_initDeviceList(); void _k_deviceAdded(const QString &udi); @@ -619,6 +620,42 @@ return items; } +int KFilePlacesModel::Private::findNearestPosition(int source, int target) +{ + const KFilePlacesItem *item = items.at(source); + const KFilePlacesItem::GroupType groupType = item->groupType(); + int newTarget = qMin(target, items.count() - 1); + + // moving inside the same group is ok + if ((items.at(newTarget)->groupType() == groupType)) { + return target; + } + + int direction = (target - source); + if (direction > 0) { // moving down, move it to the end of the group + int newTarget = source; + while(items.at(newTarget)->groupType() == groupType) { + newTarget++; + // end of the list move it there + if (newTarget == items.count()) { + break; + } + } + target = newTarget; + } else { // moving up, move it to begining of the group + int newTarget = source; + while(items.at(newTarget)->groupType() == groupType) { + newTarget--; + // begining of the list move it there + if (newTarget == 0) { + break; + } + } + target = newTarget; + } + return target; +} + void KFilePlacesModel::Private::reloadAndSignal() { bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway @@ -702,54 +739,39 @@ // let's do it in the views to get the good old drop menu } - QMimeDatabase db; - KBookmark afterBookmark; - - if (row == -1) { - // The dropped item is moved or added to the last position - - KFilePlacesItem *lastItem = d->items.last(); - afterBookmark = lastItem->bookmark(); - - } else { - // The dropped item is moved or added before position 'row', ie after position 'row-1' - - if (row > 0) { - KFilePlacesItem *afterItem = d->items[row - 1]; - afterBookmark = afterItem->bookmark(); - } - } - if (data->hasFormat(_k_internalMimetype(this))) { // The operation is an internal move QByteArray itemData = data->data(_k_internalMimetype(this)); QDataStream stream(&itemData, QIODevice::ReadOnly); int itemRow; stream >> itemRow; - KFilePlacesItem *item = d->items[itemRow]; - KBookmark bookmark = item->bookmark(); - - int destRow = row == -1 ? d->items.count() : row; - // The item is not moved when the drop indicator is on either item edge - if (itemRow == destRow || itemRow + 1 == destRow) { + if (!movePlace(itemRow, row)) { return false; } - beginMoveRows(QModelIndex(), itemRow, itemRow, QModelIndex(), destRow); - d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark); - // Move item ourselves so that _k_reloadBookmarks() does not consider - // the move as a remove + insert. - // - // 2nd argument of QList::move() expects the final destination index, - // but 'row' is the value of the destination index before the moved - // item has been removed from its original position. That is why we - // adjust if necessary. - d->items.move(itemRow, itemRow < destRow ? (destRow - 1) : destRow); - endMoveRows(); } else if (data->hasFormat(QStringLiteral("text/uri-list"))) { // The operation is an add + + QMimeDatabase db; + KBookmark afterBookmark; + + if (row == -1) { + // The dropped item is moved or added to the last position + + KFilePlacesItem *lastItem = d->items.last(); + afterBookmark = lastItem->bookmark(); + + } else { + // The dropped item is moved or added before position 'row', ie after position 'row-1' + + if (row > 0) { + KFilePlacesItem *afterItem = d->items[row - 1]; + afterBookmark = afterItem->bookmark(); + } + } + const QList urls = KUrlMimeData::urlsFromMimeData(data); KBookmarkGroup group = d->bookmarkManager->root(); @@ -928,6 +950,61 @@ emit dataChanged(index, index); } +bool KFilePlacesModel::movePlace(int itemRow, int row) +{ + KBookmark afterBookmark; + + if ((itemRow < 0) || (itemRow >= d->items.count())) { + return false; + } + + if (row >= d->items.count()) { + row = -1; + } + + if (row == -1) { + // The dropped item is moved or added to the last position + + KFilePlacesItem *lastItem = d->items.last(); + afterBookmark = lastItem->bookmark(); + + } else { + // The dropped item is moved or added before position 'row', ie after position 'row-1' + + if (row > 0) { + KFilePlacesItem *afterItem = d->items[row - 1]; + afterBookmark = afterItem->bookmark(); + } + } + + KFilePlacesItem *item = d->items[itemRow]; + KBookmark bookmark = item->bookmark(); + + int destRow = row == -1 ? d->items.count() : row; + + // avoid move item away from its group + destRow = d->findNearestPosition(itemRow, destRow); + + // The item is not moved when the drop indicator is on either item edge + if (itemRow == destRow || itemRow + 1 == destRow) { + return false; + } + + beginMoveRows(QModelIndex(), itemRow, itemRow, QModelIndex(), destRow); + d->bookmarkManager->root().moveBookmark(bookmark, afterBookmark); + // Move item ourselves so that _k_reloadBookmarks() does not consider + // the move as a remove + insert. + // + // 2nd argument of QList::move() expects the final destination index, + // but 'row' is the value of the destination index before the moved + // item has been removed from its original position. That is why we + // adjust if necessary. + d->items.move(itemRow, itemRow < destRow ? (destRow - 1) : destRow); + endMoveRows(); + + return true; +} + int KFilePlacesModel::hiddenCount() const { int rows = rowCount();