diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -71,6 +71,9 @@ void testEnableBaloo(); void testRemoteUrls_data(); void testRemoteUrls(); + void testRefresh(); + void testConvertedUrl_data(); + void testConvertedUrl(); private: QStringList placesUrls() const; @@ -823,6 +826,74 @@ m_places->removePlace(index); } +void KFilePlacesModelTest::testRefresh() +{ + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + KBookmark homePlace = root.first(); + const QModelIndex homePlaceIndex = m_places->index(0, 0); + + QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); + + // modify bookmark + homePlace.setFullText("Test change the text"); + QVERIFY(m_places->text(homePlaceIndex) != homePlace.fullText()); + + // reload bookmark data + m_places->refresh(); + QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText()); +} + +void KFilePlacesModelTest::testConvertedUrl_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedScheme"); + QTest::addColumn("expectedUrl"); + + // places + QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) + << QStringLiteral("file") + << QUrl::fromLocalFile(QDir::homePath()); + + // baloo -search + const QString jsonQuery(QStringLiteral("{\"dayFilter\": 0,\ + \"monthFilter\": 0, \ + \"yearFilter\": 0, \ + \"type\": [ \"Document\"]}")); + QUrl url; + url.setScheme(QStringLiteral("baloosearch")); + QUrlQuery urlQuery; + urlQuery.addQueryItem(QStringLiteral("json"), jsonQuery.simplified()); + url.setQuery(urlQuery); + + QTest::newRow("Baloo - Documents") << QUrl("search:/documents") + << QStringLiteral("baloosearch") + << url; + + // baloo - timeline + const QDate lasMonthDate = QDate::currentDate().addMonths(-1); + QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") + << QStringLiteral("timeline") + << QUrl(QString("timeline:/%1-%2").arg(lasMonthDate.year()).arg(lasMonthDate.month())); + + // devices + QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") + << QStringLiteral("file") + << QUrl("file:///media/floppy0"); +} + +void KFilePlacesModelTest::testConvertedUrl() +{ + QFETCH(QUrl, url); + QFETCH(QString, expectedScheme); + QFETCH(QUrl, expectedUrl); + + const QUrl convertedUrl = KFilePlacesModel::convertedUrl(url); + + QCOMPARE(expectedScheme, convertedUrl.scheme()); + QCOMPARE(expectedUrl, convertedUrl); +} + QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/src/filewidgets/kfileplacesitem.cpp b/src/filewidgets/kfileplacesitem.cpp --- a/src/filewidgets/kfileplacesitem.cpp +++ b/src/filewidgets/kfileplacesitem.cpp @@ -202,6 +202,8 @@ return false; case KFilePlacesModel::HiddenRole: return b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); + case KFilePlacesModel::IconNameRole: + return iconNameForBookmark(b); default: return QVariant(); } @@ -253,6 +255,9 @@ case KFilePlacesModel::CapacityBarRecommendedRole: return m_isAccessible && !m_isCdrom; + case KFilePlacesModel::IconNameRole: + return m_iconPath; + default: return QVariant(); } diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -48,7 +48,8 @@ SetupNeededRole = 0x059A935D, FixedDeviceRole = 0x332896C1, CapacityBarRecommendedRole = 0x1548C5C4, - GroupRole = 0x0a5b64ee + GroupRole = 0x0a5b64ee, + IconNameRole = 0x00a45c00 }; KFilePlacesModel(QObject *parent = nullptr); @@ -74,6 +75,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 'row' to a position before 'before' + * @since 5.41 + */ + bool movePlace(int row, int before); int hiddenCount() const; @@ -131,6 +137,23 @@ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE; + /** + * @brief Reload bookmark information + * @since 5.41 + */ + void refresh() const; + + /** + * @brief Converts the URL, which contains "virtual" URLs for system-items like + * "search:/documents" into a Query-URL that will be handled by + * the corresponding IO-slave. Virtual URLs for bookmarks are used to + * be independent from internal format changes. + * @param an url + * @return new url with dynamic data + * @since 5.41 + */ + static QUrl convertedUrl(const QUrl &url); + Q_SIGNALS: void errorMessage(const QString &message); void setupDone(const QModelIndex &index, bool success); diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -66,6 +67,84 @@ KConfigGroup basicSettings = config.group("Basic Settings"); return basicSettings.readEntry("Indexing-Enabled", true); } + + static QString timelineDateString(int year, int month, int day = 0) + { + const QString dateFormat("%1-%2"); + + QString date = dateFormat.arg(year).arg(month, 2, 10, QChar('0')); + if (day > 0) { + date += QString("-%1").arg(day, 2, 10, QChar('0')); + } + return date; + } + + static QUrl createTimelineUrl(const QUrl &url) + { + // based on dolphin urls + const QString timelinePrefix = QStringLiteral("timeline:") + QLatin1Char('/'); + QUrl timelineUrl; + + const QString path = url.toDisplayString(QUrl::PreferLocalFile); + if (path.endsWith(QLatin1String("/yesterday"))) { + const QDate date = QDate::currentDate().addDays(-1); + const int year = date.year(); + const int month = date.month(); + const int day = date.day(); + + timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + + '/' + timelineDateString(year, month, day)); + } else if (path.endsWith(QLatin1String("/thismonth"))) { + const QDate date = QDate::currentDate(); + timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); + } else if (path.endsWith(QLatin1String("/lastmonth"))) { + const QDate date = QDate::currentDate().addMonths(-1); + timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); + } else { + Q_ASSERT(path.endsWith(QLatin1String("/today"))); + timelineUrl = url; + } + + return timelineUrl; + } + + static QUrl searchUrlForType(const QString &type) + { + const QString jsonQuery(QStringLiteral("{\"dayFilter\": 0,\ + \"monthFilter\": 0, \ + \"yearFilter\": 0, \ + \"type\": [ \"%1\"]}")); + QUrl url; + url.setScheme(QStringLiteral("baloosearch")); + + QUrlQuery urlQuery; + urlQuery.addQueryItem(QStringLiteral("json"), jsonQuery.arg(type).simplified()); + url.setQuery(urlQuery); + + return url; + } + + static QUrl createSearchUrl(const QUrl &url) + { + QUrl searchUrl; + + const QString path = url.toDisplayString(QUrl::PreferLocalFile); + + if (path.endsWith(QLatin1String("/documents"))) { + searchUrl = searchUrlForType(QStringLiteral("Document")); + } else if (path.endsWith(QLatin1String("/images"))) { + searchUrl = searchUrlForType(QStringLiteral("Image")); + } else if (path.endsWith(QLatin1String("/audio"))) { + searchUrl = searchUrlForType(QStringLiteral("Audio")); + } else if (path.endsWith(QLatin1String("/videos"))) { + searchUrl = searchUrlForType(QStringLiteral("Video")); + } else { + qWarning() << "Invalid search url:" << url; + searchUrl = url; + } + + return searchUrl; + } } class KFilePlacesModel::Private @@ -278,12 +357,7 @@ } KFilePlacesItem *item = static_cast(index.internalPointer()); - - if (!item->isDevice()) { - return item->bookmark(); - } else { - return KBookmark(); - } + return item->bookmark(); } QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const @@ -629,52 +703,34 @@ } 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(); + movePlace(itemRow, row); + + } else if (data->hasFormat(QStringLiteral("text/uri-list"))) { + KBookmark afterBookmark; + + if (row == -1) { + // The dropped item is moved or added to the last position + + KFilePlacesItem *lastItem = d->items.last(); + afterBookmark = lastItem->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) { - return false; + } 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(); + } } - 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 const QList urls = KUrlMimeData::urlsFromMimeData(data); @@ -718,11 +774,28 @@ return false; } - d->reloadAndSignal(); + refresh(); return true; } +void KFilePlacesModel::refresh() const +{ + d->reloadAndSignal(); +} + +QUrl KFilePlacesModel::convertedUrl(const QUrl &url) +{ + QUrl newUrl = url; + if (url.scheme() == QLatin1String("timeline")) { + newUrl = createTimelineUrl(url); + } else if (url.scheme() == QLatin1String("search")) { + newUrl = createSearchUrl(url); + } + + return newUrl; +} + void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName) { @@ -745,7 +818,7 @@ d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark()); } - d->reloadAndSignal(); + refresh(); } void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const QUrl &url, @@ -767,13 +840,32 @@ return; } - bookmark.setFullText(text); - bookmark.setUrl(url); - bookmark.setIcon(iconName); - bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); + bool changed = false; + if (text != bookmark.fullText()) { + bookmark.setFullText(text); + changed = true; + } - d->reloadAndSignal(); - emit dataChanged(index, index); + if (url != bookmark.url()) { + bookmark.setUrl(url); + changed = true; + } + + if (iconName != bookmark.icon()) { + bookmark.setIcon(iconName); + changed = true; + } + + const QString onlyInApp = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); + if (appName != onlyInApp) { + bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); + changed = true; + } + + if (changed) { + refresh(); + emit dataChanged(index, index); + } } void KFilePlacesModel::removePlace(const QModelIndex &index) const @@ -795,7 +887,7 @@ } d->bookmarkManager->root().deleteBookmark(bookmark); - d->reloadAndSignal(); + refresh(); } void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden) @@ -814,10 +906,53 @@ bookmark.setMetaDataItem(QStringLiteral("IsHidden"), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); - d->reloadAndSignal(); + refresh(); emit dataChanged(index, index); } +bool KFilePlacesModel::movePlace(int row, int before) +{ + if ((row < 0) || (row >= rowCount())) { + // invalid row + return false; + } + + KBookmark afterBookmark; + if (before == -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 'before', ie after position 'row-1' + if (before > 0) { + KFilePlacesItem *afterItem = d->items[before - 1]; + afterBookmark = afterItem->bookmark(); + } + } + + before = before == -1 ? d->items.count() : before; + + if (row == before || row + 1 == before) { + return false; + } + KFilePlacesItem *item = d->items[row]; + KBookmark bookmark = item->bookmark(); + + beginMoveRows(QModelIndex(), row, row, QModelIndex(), before); + 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(row, row < before ? (before - 1) : before); + endMoveRows(); + + return true; +} + int KFilePlacesModel::hiddenCount() const { int rows = rowCount(); diff --git a/src/filewidgets/kfileplacesview.cpp b/src/filewidgets/kfileplacesview.cpp --- a/src/filewidgets/kfileplacesview.cpp +++ b/src/filewidgets/kfileplacesview.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include @@ -496,13 +495,6 @@ void fadeCapacityBar(const QModelIndex &index, FadeType fadeType); int sectionsCount() const; - // based on dolphin urls - QUrl convertUrl(const QUrl &url) const; - QUrl createTimelineUrl(const QUrl &url) const; - QUrl createSearchUrl(const QUrl &url) const; - QString timelineDateString(int year, int month, int day = 0) const; - QUrl searchUrlForType(const QString &type) const; - void _k_placeClicked(const QModelIndex &index); void _k_placeEntered(const QModelIndex &index); void _k_placeLeft(const QModelIndex &index); @@ -1094,7 +1086,7 @@ if (url.isValid()) { currentUrl = url; updateHiddenRows(); - emit q->urlChanged(convertUrl(url)); + emit q->urlChanged(KFilePlacesModel::convertedUrl(url)); if (showAll) { q->setShowAll(false); } @@ -1279,98 +1271,6 @@ return count; } -QUrl KFilePlacesView::Private::convertUrl(const QUrl &url) const -{ - QUrl newUrl = url; - if (url.scheme() == QLatin1String("timeline")) { - newUrl = createTimelineUrl(url); - } else if (url.scheme() == QLatin1String("search")) { - newUrl = createSearchUrl(url); - } - - return newUrl; -} - -QUrl KFilePlacesView::Private::createTimelineUrl(const QUrl &url) const -{ - // based on dolphin urls - const QString timelinePrefix = QStringLiteral("timeline:") + QLatin1Char('/'); - QUrl timelineUrl; - - const QString path = url.toDisplayString(QUrl::PreferLocalFile); - if (path.endsWith(QLatin1String("/yesterday"))) { - const QDate date = QDate::currentDate().addDays(-1); - const int year = date.year(); - const int month = date.month(); - const int day = date.day(); - - timelineUrl = QUrl(timelinePrefix + timelineDateString(year, month) + - '/' + timelineDateString(year, month, day)); - } else if (path.endsWith(QLatin1String("/thismonth"))) { - const QDate date = QDate::currentDate(); - timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); - } else if (path.endsWith(QLatin1String("/lastmonth"))) { - const QDate date = QDate::currentDate().addMonths(-1); - timelineUrl = QUrl(timelinePrefix + timelineDateString(date.year(), date.month())); - } else { - Q_ASSERT(path.endsWith(QLatin1String("/today"))); - timelineUrl = url; - } - - return timelineUrl; -} - -QString KFilePlacesView::Private::timelineDateString(int year, int month, int day) const -{ - const QString dateFormat("%1-%2"); - - QString date = dateFormat.arg(year).arg(month, 2, 10, QChar('0')); - if (day > 0) { - date += QString("-%1").arg(day, 2, 10, QChar('0')); - } - return date; -} - -QUrl KFilePlacesView::Private::createSearchUrl(const QUrl &url) const -{ - QUrl searchUrl; - - const QString path = url.toDisplayString(QUrl::PreferLocalFile); - - if (path.endsWith(QLatin1String("/documents"))) { - searchUrl = searchUrlForType(QStringLiteral("Document")); - } else if (path.endsWith(QLatin1String("/images"))) { - searchUrl = searchUrlForType(QStringLiteral("Image")); - } else if (path.endsWith(QLatin1String("/audio"))) { - searchUrl = searchUrlForType(QStringLiteral("Audio")); - } else if (path.endsWith(QLatin1String("/videos"))) { - searchUrl = searchUrlForType(QStringLiteral("Video")); - } else { - qWarning() << "Invalid search url:" << url; - searchUrl = url; - } - - return searchUrl; -} - - -QUrl KFilePlacesView::Private::searchUrlForType(const QString& type) const -{ - const QString jsonQuery(QStringLiteral("{\"dayFilter\": 0,\ - \"monthFilter\": 0, \ - \"yearFilter\": 0, \ - \"type\": [ \"%1\"]}")); - QUrl url; - url.setScheme(QStringLiteral("baloosearch")); - - QUrlQuery urlQuery; - urlQuery.addQueryItem(QStringLiteral("json"), jsonQuery.arg(type).simplified()); - url.setQuery(urlQuery); - - return url; -} - - void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) { KFilePlacesModel *placesModel = qobject_cast(q->model());