Index: autotests/kfileplacesmodeltest.cpp =================================================================== --- autotests/kfileplacesmodeltest.cpp +++ autotests/kfileplacesmodeltest.cpp @@ -68,6 +68,8 @@ void testDevicePlugging(); void testDragAndDrop(); void testDeviceSetupTeardown(); + void testPlaceGroupHidden(); + void testPlaceGroupHiddenVsPlaceChildShown(); private: QStringList placesUrls() const; @@ -84,7 +86,7 @@ static QString bookmarksFile() { - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel"); } void KFilePlacesModelTest::initTestCase() @@ -157,8 +159,9 @@ QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi) { - if (m_interfacesMap.contains(udi)) { - return m_interfacesMap[udi]; + QDBusInterface *interface = m_interfacesMap[udi]; + if (interface) { + return interface; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); @@ -749,6 +752,86 @@ QCOMPARE(args.at(1).toModelIndex().row(), 7); } +void KFilePlacesModelTest::testPlaceGroupHidden() +{ + // GIVEN + QCOMPARE(m_places->hiddenCount(), 0); + + QStringList urls; + urls << initalListOfPlaces() << initalListOfDevices() << initalListOfRemovableDevices(); + CHECK_PLACES_URLS(urls); + + QList args; + QVector indexesHidden; + QSignalSpy spy(m_places, &KFilePlacesModel::dataChanged); + + // 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(indexesHidden.count(), 4); + QCOMPARE(spy.count(), indexesHidden.size()); + QCOMPARE(m_places->hiddenCount(), indexesHidden.size()); + 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, &KFilePlacesModel::dataChanged); + + // 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(m_places->hiddenCount(), 0); + QCOMPARE(indexesShown.count(), 4); + QCOMPARE(spy2.count(), indexesShown.size()); + 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" Index: src/filewidgets/kfileplacesitem.cpp =================================================================== --- src/filewidgets/kfileplacesitem.cpp +++ src/filewidgets/kfileplacesitem.cpp @@ -18,7 +18,6 @@ */ #include "kfileplacesitem_p.h" -#include "kfileplacesmodel.h" #include #include @@ -102,23 +101,24 @@ 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: default: Q_UNREACHABLE(); break; @@ -142,34 +142,47 @@ } } -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 { KBookmark b = bookmark(); @@ -184,7 +197,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(); @@ -194,7 +207,7 @@ case KFilePlacesModel::SetupNeededRole: return false; case KFilePlacesModel::HiddenRole: - return b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); + return isHidden(); default: return QVariant(); } Index: src/filewidgets/kfileplacesitem_p.h =================================================================== --- src/filewidgets/kfileplacesitem_p.h +++ 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, Index: src/filewidgets/kfileplacesmodel.h =================================================================== --- src/filewidgets/kfileplacesmodel.h +++ src/filewidgets/kfileplacesmodel.h @@ -51,6 +51,15 @@ GroupRole = 0x0a5b64ee }; + enum GroupType { + PlacesType, + RecentlySavedType, + SearchForType, + DevicesType, + RemovableDeviceType, + UnknownType = 255 + }; + KFilePlacesModel(QObject *parent = nullptr); ~KFilePlacesModel(); @@ -59,9 +68,12 @@ 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 +86,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; Index: src/filewidgets/kfileplacesmodel.cpp =================================================================== --- src/filewidgets/kfileplacesmodel.cpp +++ src/filewidgets/kfileplacesmodel.cpp @@ -59,6 +59,28 @@ #include #include +typedef QHash KFilePlaceGroupHash; +KFilePlaceGroupHash _k_loadFilePlaceModelGroup(); +Q_GLOBAL_STATIC_WITH_ARGS(KFilePlaceGroupHash, s_groupTypeToStateName, (_k_loadFilePlaceModelGroup())) + +KFilePlaceGroupHash _k_loadFilePlaceModelGroup() +{ + QHash grpTypeToStateName = + { + { KFilePlacesModel::PlacesType, QStringLiteral("GroupState-Places-IsHidden") }, + { KFilePlacesModel::RecentlySavedType, QStringLiteral("GroupState-RecentlySaved-IsHidden") }, + { KFilePlacesModel::SearchForType, QStringLiteral("GroupState-SearchFor-IsHidden") }, + { KFilePlacesModel::DevicesType, QStringLiteral("GroupState-Devices-IsHidden") }, + { KFilePlacesModel::RemovableDeviceType, QStringLiteral("GroupState-RemovableDevices-IsHidden") } + }; + return grpTypeToStateName; +} + +//static const QHash s_groupTypeToStateName +//{ +// return *s_groupTypeToStateName; +//} + class KFilePlacesModel::Private { public: @@ -107,6 +129,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 +166,10 @@ QStringLiteral("Trash"), I18N_NOOP2("KFile System Bookmarks", "Trash"), QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash")); + root.setMetaDataItem(s_groupTypeToStateName->value(PlacesType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(s_groupTypeToStateName->value(DevicesType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(s_groupTypeToStateName->value(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 +206,9 @@ QStringLiteral("Videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"), QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); + root.setMetaDataItem(s_groupTypeToStateName->value(SearchForType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + root.setMetaDataItem(s_groupTypeToStateName->value(RecentlySavedType), (areGroupsHiddenByDefault ? QStringLiteral("true") : QStringLiteral("false"))); + d->bookmarkManager->save(); } @@ -234,7 +265,23 @@ bool KFilePlacesModel::isHidden(const QModelIndex &index) const { - return data(index, HiddenRole).toBool(); + return data(index, HiddenRole).toBool() || isPlaceGroupHidden(index); +} + +bool KFilePlacesModel::isPlaceGroupHidden(const GroupType type) const +{ + const QString hidden = d->bookmarkManager->root().metaDataItem(s_groupTypeToStateName->value(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()); + return isPlaceGroupHidden(item->groupType()); } bool KFilePlacesModel::isDevice(const QModelIndex &index) const @@ -278,6 +325,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 +534,20 @@ (scheme == QLatin1String("search"))); } +static const QHash groupStateHidden(const KBookmark rootBookmark) +{ + QHash groupStateHidden; + static const QHash grpTypeToStateName = *s_groupTypeToStateName; + + QHashIterator it(grpTypeToStateName); + while (it.hasNext()) { + it.next(); + bool isHidden = rootBookmark.metaDataItem(it.value()) == QStringLiteral("true") ? true : false; + groupStateHidden.insert(it.key(), isHidden); + } + return groupStateHidden; +} + QList KFilePlacesModel::Private::loadBookmarkList() { QList items; @@ -495,7 +566,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 +598,13 @@ items << item; } } + // we make sure an item within a hidden group remains hidden too + const QHash groupStates = groupStateHidden(root); + foreach (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 +876,38 @@ KFilePlacesItem *item = static_cast(index.internalPointer()); - KBookmark bookmark = item->bookmark(); - - if (bookmark.isNull()) { + if (item->bookmark().isNull() || item->isHidden() == hidden) { return; } - bookmark.setMetaDataItem(QStringLiteral("IsHidden"), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); + const QHash gpStateHidden = groupStateHidden(d->bookmarkManager->root()); + const bool hidingChildOnShownParent = hidden && !gpStateHidden.value(item->groupType()); + const bool showingChildOnShownParent = !hidden && !gpStateHidden.value(item->groupType()); + + if (hidingChildOnShownParent || showingChildOnShownParent) { + item->setHidden(hidden); + + d->reloadAndSignal(); + emit dataChanged(index, index); + } +} +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(s_groupTypeToStateName->value(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); d->reloadAndSignal(); - emit dataChanged(index, index); } int KFilePlacesModel::hiddenCount() const