diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -68,6 +68,8 @@ void testDevicePlugging(); void testDragAndDrop(); void testDeviceSetupTeardown(); + void testPlaceGroupHidden(); + void testPlaceGroupHiddenVsPlaceChildShown(); private: QStringList placesUrls() const; @@ -743,6 +745,86 @@ QCOMPARE(args.at(1).toModelIndex().row(), 7); } +void KFilePlacesModelTest::testPlaceGroupHidden() +{ + // GIVEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(!m_places->isHidden(index)); + } + + QStringList urls; + urls << initalListOfPlaces() << initalListOfRemovableDevices() << initalListOfDevices(); + CHECK_PLACES_URLS(urls); + + QList args; + QVector indexesHidden; + QSignalSpy spy(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // WHEN + m_places->setPlaceGroupHidden(KFilePlacesModel::PlacesType, true); + + // THEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { + QVERIFY(m_places->isHidden(index)); + indexesHidden << index; + } + } + QCOMPARE(spy.count(), 4); + QCOMPARE(indexesHidden.count(), 4); + for (int i = 0; i< spy.count(); ++i) { + args = spy.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), indexesHidden.at(i)); + QCOMPARE(args.at(1).toModelIndex(), indexesHidden.at(i)); + } + // and GIVEN + QVector indexesShown; + QSignalSpy spy2(m_places, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // WHEN + m_places->setPlaceGroupHidden(KFilePlacesModel::PlacesType, false); + + // THEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { + QVERIFY(!m_places->isHidden(index)); + indexesShown << index; + } + } + QCOMPARE(spy2.count(), 4); + QCOMPARE(indexesShown.count(), 4); + for (int i = 0; i< spy2.count(); ++i) { + args = spy2.takeFirst(); + QCOMPARE(args.at(0).toModelIndex(), indexesShown.at(i)); + QCOMPARE(args.at(1).toModelIndex(), indexesShown.at(i)); + } +} + +void KFilePlacesModelTest::testPlaceGroupHiddenVsPlaceChildShown() +{ + // GIVEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(!m_places->isHidden(index)); + } + m_places->setPlaceGroupHidden(KFilePlacesModel::PlacesType, true); + + QModelIndex firstIndex = m_places->index(0,0); + const int amountOfPlaces = initalListOfPlaces().size(); + for (int row = 0; row < amountOfPlaces; ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(m_places->isHidden(index)); + } + // WHEN + m_places->setPlaceHidden(firstIndex, false); + + // THEN + QVERIFY(m_places->isHidden(firstIndex)); // a child cannot show against its parent state +} + 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 @@ -18,7 +18,6 @@ */ #include "kfileplacesitem_p.h" -#include "kfileplacesmodel.h" #include #include @@ -101,23 +100,26 @@ m_text = bookmark.text(); } - const GroupType type = groupType(); + const KFilePlacesModel::GroupType type = groupType(); switch (type) { - case PlacesType: + case KFilePlacesModel::PlacesType: m_groupName = i18nc("@item", "Places"); break; - case RecentlySavedType: + case KFilePlacesModel::RecentlySavedType: m_groupName = i18nc("@item", "Recently Saved"); break; - case SearchForType: + case KFilePlacesModel::SearchForType: m_groupName = i18nc("@item", "Search For"); break; - case DevicesType: + case KFilePlacesModel::DevicesType: m_groupName = i18nc("@item", "Devices"); break; - case RemovableDeviceType: + case KFilePlacesModel::RemovableDeviceType: m_groupName = i18nc("@item", "Removable Devices"); break; + case KFilePlacesModel::UnknownType: + Q_ASSERT(false); + break; default: Q_UNREACHABLE(); break; @@ -141,32 +143,45 @@ } } -KFilePlacesItem::GroupType KFilePlacesItem::groupType() const +KFilePlacesModel::GroupType KFilePlacesItem::groupType() const { if (!isDevice()) { const QString protocol = bookmark().url().scheme(); if (protocol == QLatin1String("timeline")) { - return RecentlySavedType; + return KFilePlacesModel::RecentlySavedType; } if (protocol.contains(QLatin1String("search"))) { - return SearchForType; + return KFilePlacesModel::SearchForType; } if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) { - return DevicesType; + return KFilePlacesModel::DevicesType; } - return PlacesType; + return KFilePlacesModel::PlacesType; } if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) { - return RemovableDeviceType; + return KFilePlacesModel::RemovableDeviceType; } else { - return DevicesType; + return KFilePlacesModel::DevicesType; + } +} + +bool KFilePlacesItem::isHidden() const +{ + return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); +} + +void KFilePlacesItem::setHidden(bool hide) +{ + if (m_bookmark.isNull() || isHidden() == hide) { + return; } + m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false")); } QVariant KFilePlacesItem::bookmarkData(int role) const @@ -183,7 +198,7 @@ case Qt::DecorationRole: return QIcon::fromTheme(iconNameForBookmark(b)); case Qt::BackgroundRole: - if (b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true")) { + if (isHidden()) { return QColor(Qt::lightGray); } else { return QVariant(); @@ -193,7 +208,7 @@ case KFilePlacesModel::SetupNeededRole: return false; case KFilePlacesModel::HiddenRole: - return b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); + return isHidden(); default: return QVariant(); } diff --git a/src/filewidgets/kfileplacesitem_p.h b/src/filewidgets/kfileplacesitem_p.h --- a/src/filewidgets/kfileplacesitem_p.h +++ b/src/filewidgets/kfileplacesitem_p.h @@ -27,6 +27,7 @@ #include #include #include +#include "kfileplacesmodel.h" class KDirLister; namespace Solid @@ -42,14 +43,6 @@ { Q_OBJECT public: - enum GroupType - { - PlacesType, - SearchForType, - RecentlySavedType, - DevicesType, - RemovableDeviceType - }; KFilePlacesItem(KBookmarkManager *manager, const QString &address, @@ -63,7 +56,9 @@ void setBookmark(const KBookmark &bookmark); Solid::Device device() const; QVariant data(int role) const; - GroupType groupType() const; + KFilePlacesModel::GroupType groupType() const; + bool isHidden() const; + void setHidden(bool hide); static KBookmark createBookmark(KBookmarkManager *manager, const QString &label, diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -51,17 +51,30 @@ GroupRole = 0x0a5b64ee }; + enum GroupType + { + PlacesType, + RecentlySavedType, + SearchForType, + DevicesType, + RemovableDeviceType, + UnknownType = 255 + }; + KFilePlacesModel(QObject *parent = nullptr); ~KFilePlacesModel(); QUrl url(const QModelIndex &index) const; bool setupNeeded(const QModelIndex &index) const; QIcon icon(const QModelIndex &index) const; QString text(const QModelIndex &index) const; bool isHidden(const QModelIndex &index) const; + bool isPlaceGroupHidden(const GroupType type) const; + bool isPlaceGroupHidden(const QModelIndex &index) const; bool isDevice(const QModelIndex &index) const; Solid::Device deviceForIndex(const QModelIndex &index) const; KBookmark bookmarkForIndex(const QModelIndex &index) const; + GroupType groupType(const QModelIndex &index) const; QAction *teardownActionForIndex(const QModelIndex &index) const; QAction *ejectActionForIndex(const QModelIndex &index) const; @@ -74,6 +87,7 @@ 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); + void setPlaceGroupHidden(const GroupType type, bool hidden); 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 @@ -59,6 +59,19 @@ #include #include +static const QHash groupStateNameToType() +{ + static const QHash gpStateNameToType = + { + { QStringLiteral("GroupState-Places-IsHidden"), KFilePlacesModel::PlacesType }, + { QStringLiteral("GroupState-RecentlySaved-IsHidden"), KFilePlacesModel::RecentlySavedType }, + { QStringLiteral("GroupState-SearchFor-IsHidden"), KFilePlacesModel::SearchForType }, + { QStringLiteral("GroupState-Devices-IsHidden"), KFilePlacesModel::DevicesType }, + { QStringLiteral("GroupState-RemovableDevices-IsHidden"), KFilePlacesModel::RemovableDeviceType } + }; + return gpStateNameToType; +} + class KFilePlacesModel::Private { public: @@ -107,6 +120,8 @@ const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; d->bookmarkManager = KBookmarkManager::managerForExternalFile(file); + const bool areGroupsHiddenByDefault = false; + // Let's put some places in there if it's empty. KBookmarkGroup root = d->bookmarkManager->root(); if (root.first().isNull() || !QFile::exists(file)) { @@ -142,6 +157,10 @@ QStringLiteral("Trash"), I18N_NOOP2("KFile System Bookmarks", "Trash"), QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash")); + root.setMetaDataItem(groupStateNameToType().key(PlacesType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(groupStateNameToType().key(DevicesType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(groupStateNameToType().key(RemovableDeviceType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists // will always return false, which opening/closing all the time the open/save dialog would case the // bookmarks to be added once each time, having lots of times each bookmark. (ereslibre) @@ -178,6 +197,9 @@ QStringLiteral("Videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"), QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); + root.setMetaDataItem(groupStateNameToType().key(SearchForType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(groupStateNameToType().key(RecentlySavedType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + d->bookmarkManager->save(); } @@ -237,6 +259,24 @@ return data(index, HiddenRole).toBool(); } +bool KFilePlacesModel::isPlaceGroupHidden(const GroupType type) const +{ + QString hidden = d->bookmarkManager->root().metaDataItem(groupStateNameToType().key(type)); + return hidden == QStringLiteral("true") ? true : false; +} + +bool KFilePlacesModel::isPlaceGroupHidden(const QModelIndex &index) const +{ + if (!index.isValid()) { + return false; + } + + KFilePlacesItem *item = static_cast(index.internalPointer()); + QString hidden = d->bookmarkManager->root().metaDataItem(groupStateNameToType().key(item->groupType())); + + return hidden == QStringLiteral("true") ? true : false; +} + bool KFilePlacesModel::isDevice(const QModelIndex &index) const { if (!index.isValid()) { @@ -278,6 +318,16 @@ } } +KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const +{ + if (!index.isValid()) { + return UnknownType; + } + + KFilePlacesItem *item = static_cast(index.internalPointer()); + return item->groupType(); +} + QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { @@ -477,6 +527,20 @@ (scheme == QLatin1String("search"))); } +static const QHash groupStateHidden(const KBookmark rootBookmark) +{ + QHash groupStateHidden; + static const QHash gpStateNameToType = groupStateNameToType(); + + QHashIterator it(gpStateNameToType); + while (it.hasNext()) { + it.next(); + bool isHidden = rootBookmark.metaDataItem(it.key()) == QStringLiteral("true") ? true : false; + groupStateHidden.insert(it.value(), isHidden); + } + return groupStateHidden; +} + QList KFilePlacesModel::Private::loadBookmarkList() { QList items; @@ -495,7 +559,7 @@ devices.erase(it); } - bool allowedHere = appName.isEmpty() || (appName == QCoreApplication::instance()->applicationName()); + bool allowedHere = appName.isEmpty() || (appName == QCoreApplication::instance()->applicationName()); bool allowBalooUrl = isBalooUrl(url) ? fileIndexingEnabled : true; if ((allowBalooUrl && udi.isEmpty() && allowedHere) || deviceAvailable) { @@ -527,6 +591,13 @@ items << item; } } + // we make sure an item within a hidden group remains hidden too + const QHash groupStates = groupStateHidden(root); + for (KFilePlacesItem *item : items) { + if (bool hasToBeHidden = groupStates.value(item->groupType())) { + item->setHidden(hasToBeHidden); + } + } // return a sorted list based on groups qStableSort(items.begin(), items.end(), @@ -798,16 +869,37 @@ KFilePlacesItem *item = static_cast(index.internalPointer()); - KBookmark bookmark = item->bookmark(); - - if (bookmark.isNull()) { + if (item->bookmark().isNull() || item->isHidden() == hidden) return; + + const QHash gpStateHidden = groupStateHidden(d->bookmarkManager->root()); + const bool hiddingChildOnShownParent = hidden && !gpStateHidden.value(item->groupType()); + const bool showingChildOnShownParent = !hidden && !gpStateHidden.value(item->groupType()); + + if (hiddingChildOnShownParent || showingChildOnShownParent) { + item->setHidden(hidden); + + d->reloadAndSignal(); + emit dataChanged(index, index); } +} - bookmark.setMetaDataItem(QStringLiteral("IsHidden"), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); +void KFilePlacesModel::setPlaceGroupHidden(const GroupType type, bool hidden) +{ + if (isPlaceGroupHidden(type) == hidden) + return; + // we bypass SetPlaceHidden since we are overwritting children states + for (int row = 0; row < rowCount(); ++row) { + const QModelIndex current = index(row, 0); + if (groupType(current) == type) { + KFilePlacesItem *item = static_cast(current.internalPointer()); + item->setHidden(hidden); + emit dataChanged(current, current); + } + } + d->bookmarkManager->root().setMetaDataItem(groupStateNameToType().key(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); d->reloadAndSignal(); - emit dataChanged(index, index); } int KFilePlacesModel::hiddenCount() const