diff --git a/src/filewidgets/kfileplacesitem.cpp b/src/filewidgets/kfileplacesitem.cpp index 934c06c1..246563b4 100644 --- a/src/filewidgets/kfileplacesitem.cpp +++ b/src/filewidgets/kfileplacesitem.cpp @@ -1,418 +1,435 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kfileplacesitem_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool isTrash(const KBookmark &bk) { return bk.url().toString() == QLatin1String("trash:/"); } KFilePlacesItem::KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi) : m_manager(manager), m_folderIsEmpty(true), m_isCdrom(false), m_isAccessible(false) { updateDeviceInfo(udi); setBookmark(m_manager->findByAddress(address)); if (udi.isEmpty() && m_bookmark.metaDataItem(QStringLiteral("ID")).isEmpty()) { m_bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId()); } else if (udi.isEmpty()) { if (isTrash(m_bookmark)) { KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig); const KConfigGroup group = cfg.group("Status"); m_folderIsEmpty = group.readEntry("Empty", true); } } } KFilePlacesItem::~KFilePlacesItem() { } QString KFilePlacesItem::id() const { if (isDevice()) { return bookmark().metaDataItem(QStringLiteral("UDI")); } else { return bookmark().metaDataItem(QStringLiteral("ID")); } } bool KFilePlacesItem::isDevice() const { return !bookmark().metaDataItem(QStringLiteral("UDI")).isEmpty(); } KBookmark KFilePlacesItem::bookmark() const { return m_bookmark; } void KFilePlacesItem::setBookmark(const KBookmark &bookmark) { m_bookmark = bookmark; updateDeviceInfo(m_bookmark.metaDataItem(QStringLiteral("UDI"))); if (bookmark.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true")) { // This context must stay as it is - the translated system bookmark names // are created with 'KFile System Bookmarks' as their context, so this // ensures the right string is picked from the catalog. // (coles, 13th May 2009) m_text = i18nc("KFile System Bookmarks", bookmark.text().toUtf8().data()); } else { m_text = bookmark.text(); } const KFilePlacesModel::GroupType type = groupType(); switch (type) { case KFilePlacesModel::PlacesType: m_groupName = i18nc("@item", "Places"); break; case KFilePlacesModel::RemoteType: m_groupName = i18nc("@item", "Remote"); break; case KFilePlacesModel::RecentlySavedType: m_groupName = i18nc("@item", "Recently Saved"); break; case KFilePlacesModel::SearchForType: m_groupName = i18nc("@item", "Search For"); break; case KFilePlacesModel::DevicesType: m_groupName = i18nc("@item", "Devices"); break; case KFilePlacesModel::RemovableDevicesType: m_groupName = i18nc("@item", "Removable Devices"); break; + case KFilePlacesModel::TagsType: + m_groupName = i18nc("@item", "Tags"); + break; default: Q_UNREACHABLE(); break; } } Solid::Device KFilePlacesItem::device() const { return m_device; } QVariant KFilePlacesItem::data(int role) const { if (role == KFilePlacesModel::GroupRole) { return QVariant(m_groupName); } else if (role != KFilePlacesModel::HiddenRole && role != Qt::BackgroundRole && isDevice()) { return deviceData(role); } else { return bookmarkData(role); } } KFilePlacesModel::GroupType KFilePlacesItem::groupType() const { if (!isDevice()) { const QString protocol = bookmark().url().scheme(); if (protocol == QLatin1String("timeline")) { return KFilePlacesModel::RecentlySavedType; } if (protocol.contains(QLatin1String("search"))) { return KFilePlacesModel::SearchForType; } if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) { return KFilePlacesModel::DevicesType; } + if (protocol == QLatin1String("tags")) { + return KFilePlacesModel::TagsType; + } + if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) { return KFilePlacesModel::RemoteType; } else { return KFilePlacesModel::PlacesType; } } if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) { return KFilePlacesModel::RemovableDevicesType; } else if (m_networkShare) { return KFilePlacesModel::RemoteType; } else { 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(); if (b.isNull()) { return QVariant(); } switch (role) { case Qt::DisplayRole: return m_text; case Qt::DecorationRole: return QIcon::fromTheme(iconNameForBookmark(b)); case Qt::BackgroundRole: if (isHidden()) { return QColor(Qt::lightGray); } else { return QVariant(); } case KFilePlacesModel::UrlRole: return b.url(); case KFilePlacesModel::SetupNeededRole: return false; case KFilePlacesModel::HiddenRole: return isHidden(); case KFilePlacesModel::IconNameRole: return iconNameForBookmark(b); default: return QVariant(); } } QVariant KFilePlacesItem::deviceData(int role) const { Solid::Device d = device(); if (d.isValid()) { switch (role) { case Qt::DisplayRole: return d.description(); case Qt::DecorationRole: return KDE::icon(m_iconPath, m_emblems); case KFilePlacesModel::UrlRole: if (m_access) { const QString path = m_access->filePath(); return path.isEmpty() ? QUrl() : QUrl::fromLocalFile(path); } else if (m_disc && (m_disc->availableContent() & Solid::OpticalDisc::Audio) != 0) { Solid::Block *block = d.as(); if (block) { QString device = block->device(); return QUrl(QStringLiteral("audiocd:/?device=%1").arg(device)); } // We failed to get the block device. Assume audiocd:/ can // figure it out, but cannot handle multiple disc drives. // See https://bugs.kde.org/show_bug.cgi?id=314544#c40 return QUrl(QStringLiteral("audiocd:/")); } else if (m_mtp) { return QUrl(QStringLiteral("mtp:udi=%1").arg(d.udi())); } else { return QVariant(); } case KFilePlacesModel::SetupNeededRole: if (m_access) { return !m_isAccessible; } else { return QVariant(); } case KFilePlacesModel::FixedDeviceRole: { if (m_drive != nullptr) { return !m_drive->isHotpluggable() && !m_drive->isRemovable(); } return true; } case KFilePlacesModel::CapacityBarRecommendedRole: return m_isAccessible && !m_isCdrom; case KFilePlacesModel::IconNameRole: return m_iconPath; default: return QVariant(); } } else { return QVariant(); } } KBookmark KFilePlacesItem::createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after) { KBookmarkGroup root = manager->root(); if (root.isNull()) { return KBookmark(); } QString empty_icon = iconName; if (url.toString() == QLatin1String("trash:/")) { if (empty_icon.endsWith(QLatin1String("-full"))) { empty_icon.chop(5); } else if (empty_icon.isEmpty()) { empty_icon = QStringLiteral("user-trash"); } } KBookmark bookmark = root.addBookmark(label, url, empty_icon); bookmark.setMetaDataItem(QStringLiteral("ID"), generateNewId()); if (after) { root.moveBookmark(bookmark, after->bookmark()); } return bookmark; } KBookmark KFilePlacesItem::createSystemBookmark(KBookmarkManager *manager, const QString &untranslatedLabel, const QString &translatedLabel, const QUrl &url, const QString &iconName) { Q_UNUSED(translatedLabel); // parameter is only necessary to force the caller // providing a translated string for the label KBookmark bookmark = createBookmark(manager, untranslatedLabel, url, iconName); if (!bookmark.isNull()) { bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); } return bookmark; } KBookmark KFilePlacesItem::createDeviceBookmark(KBookmarkManager *manager, const QString &udi) { KBookmarkGroup root = manager->root(); if (root.isNull()) { return KBookmark(); } KBookmark bookmark = root.createNewSeparator(); bookmark.setMetaDataItem(QStringLiteral("UDI"), udi); bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); return bookmark; } +KBookmark KFilePlacesItem::createTagBookmark(KBookmarkManager *manager, + const QString &tag) +{ + KBookmark bookmark = createSystemBookmark(manager, tag, tag, QUrl(QStringLiteral("tags:/") + tag), QStringLiteral("tag")); + bookmark.setMetaDataItem(QStringLiteral("tag"), tag); + bookmark.setMetaDataItem(QStringLiteral("isSystemItem"), QStringLiteral("true")); + + return bookmark; +} + QString KFilePlacesItem::generateNewId() { static int count = 0; // return QString::number(count++); return QString::number(QDateTime::currentDateTimeUtc().toTime_t()) + QLatin1Char('/') + QString::number(count++); // return QString::number(QDateTime::currentDateTime().toTime_t()) // + '/' + QString::number(qrand()); } bool KFilePlacesItem::updateDeviceInfo(const QString &udi) { if (m_device.udi() == udi) { return false; } if (m_access) { m_access->disconnect(this); } m_device = Solid::Device(udi); if (m_device.isValid()) { m_access = m_device.as(); m_volume = m_device.as(); m_disc = m_device.as(); m_mtp = m_device.as(); m_networkShare = m_device.as(); m_iconPath = m_device.icon(); m_emblems = m_device.emblems(); m_drive = nullptr; Solid::Device parentDevice = m_device; while (parentDevice.isValid() && !m_drive) { m_drive = parentDevice.as(); parentDevice = parentDevice.parent(); } if (m_access) { connect(m_access.data(), &Solid::StorageAccess::accessibilityChanged, this, &KFilePlacesItem::onAccessibilityChanged); onAccessibilityChanged(m_access->isAccessible()); } } else { m_access = nullptr; m_volume = nullptr; m_disc = nullptr; m_mtp = nullptr; m_drive = nullptr; m_networkShare = nullptr; m_iconPath.clear(); m_emblems.clear(); } return true; } void KFilePlacesItem::onAccessibilityChanged(bool isAccessible) { m_isAccessible = isAccessible; m_isCdrom = m_device.is() || m_device.parent().is(); m_emblems = m_device.emblems(); emit itemChanged(id()); } QString KFilePlacesItem::iconNameForBookmark(const KBookmark &bookmark) const { if (!m_folderIsEmpty && isTrash(bookmark)) { return bookmark.icon() + QLatin1String("-full"); } else { return bookmark.icon(); } } #include "moc_kfileplacesitem_p.cpp" diff --git a/src/filewidgets/kfileplacesitem_p.h b/src/filewidgets/kfileplacesitem_p.h index 206873ea..be6745b3 100644 --- a/src/filewidgets/kfileplacesitem_p.h +++ b/src/filewidgets/kfileplacesitem_p.h @@ -1,119 +1,122 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEPLACESITEM_P_H #define KFILEPLACESITEM_P_H #include #include #include #include #include #include #include #include "kfileplacesmodel.h" class KDirLister; namespace Solid { class StorageAccess; class StorageVolume; class StorageDrive; class NetworkShare; class OpticalDisc; class PortableMediaPlayer; } class KFilePlacesItem : public QObject { Q_OBJECT public: enum GroupType { PlacesType, RemoteType, RecentlySavedType, SearchForType, DevicesType, - RemovableDevicesType + RemovableDevicesType, + TagsType }; KFilePlacesItem(KBookmarkManager *manager, const QString &address, const QString &udi = QString()); ~KFilePlacesItem(); QString id() const; bool isDevice() const; KBookmark bookmark() const; void setBookmark(const KBookmark &bookmark); Solid::Device device() const; QVariant data(int role) const; KFilePlacesModel::GroupType groupType() const; bool isHidden() const; void setHidden(bool hide); static KBookmark createBookmark(KBookmarkManager *manager, const QString &label, const QUrl &url, const QString &iconName, KFilePlacesItem *after = nullptr); static KBookmark createSystemBookmark(KBookmarkManager *manager, const QString &untranslatedLabel, const QString &translatedLabel, const QUrl &url, const QString &iconName); static KBookmark createDeviceBookmark(KBookmarkManager *manager, const QString &udi); + static KBookmark createTagBookmark(KBookmarkManager *manager, + const QString &tag); Q_SIGNALS: void itemChanged(const QString &id); private Q_SLOTS: void onAccessibilityChanged(bool); private: QVariant bookmarkData(int role) const; QVariant deviceData(int role) const; QString iconNameForBookmark(const KBookmark &bookmark) const; static QString generateNewId(); bool updateDeviceInfo(const QString &udi); KBookmarkManager *m_manager; KBookmark m_bookmark; bool m_folderIsEmpty; bool m_isCdrom; bool m_isAccessible; QString m_text; Solid::Device m_device; QPointer m_access; QPointer m_volume; QPointer m_drive; QPointer m_disc; QPointer m_mtp; QPointer m_networkShare; QString m_iconPath; QStringList m_emblems; QString m_groupName; }; #endif diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp index 9d996486..01a10490 100644 --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -1,1282 +1,1358 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens Copyright (C) 2007 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // TODO: remove me #undef QT_NO_CAST_FROM_ASCII #include "kfileplacesmodel.h" #include "kfileplacesitem_p.h" #ifdef _WIN32_WCE #include "Windows.h" #include "WinBase.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include namespace { QString stateNameForGroupType(KFilePlacesModel::GroupType type) { switch (type) { case KFilePlacesModel::PlacesType: return QStringLiteral("GroupState-Places-IsHidden"); case KFilePlacesModel::RemoteType: return QStringLiteral("GroupState-Remote-IsHidden"); case KFilePlacesModel::RecentlySavedType: return QStringLiteral("GroupState-RecentlySaved-IsHidden"); case KFilePlacesModel::SearchForType: return QStringLiteral("GroupState-SearchFor-IsHidden"); case KFilePlacesModel::DevicesType: return QStringLiteral("GroupState-Devices-IsHidden"); case KFilePlacesModel::RemovableDevicesType: return QStringLiteral("GroupState-RemovableDevices-IsHidden"); + case KFilePlacesModel::TagsType: + return QStringLiteral("GroupState-Tags-IsHidden"); default: Q_UNREACHABLE(); } } static bool isFileIndexingEnabled() { KConfig config(QStringLiteral("baloofilerc")); 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 = QStringLiteral("%1-%2"); QString date = dateFormat.arg(year).arg(month, 2, 10, QLatin1Char('0')); if (day > 0) { date += QStringLiteral("-%1").arg(day, 2, 10, QLatin1Char('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) + QLatin1Char('/') + 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 createSearchUrl(const QUrl &url) { QUrl searchUrl = url; const QString path = url.toDisplayString(QUrl::PreferLocalFile); const QStringList validSearchPaths = { QStringLiteral("/documents"), QStringLiteral("/images"), QStringLiteral("/audio"), QStringLiteral("/videos") }; for (const QString &validPath : validSearchPaths) { if (path.endsWith(validPath)) { searchUrl.setScheme(QStringLiteral("baloosearch")); return searchUrl; } } qWarning() << "Invalid search url:" << url; return searchUrl; } } class Q_DECL_HIDDEN KFilePlacesModel::Private { public: explicit Private(KFilePlacesModel *self) : q(self), bookmarkManager(nullptr), - fileIndexingEnabled(isFileIndexingEnabled()) + fileIndexingEnabled(isFileIndexingEnabled()), + tags(), + tagsLister(new KCoreDirLister()) { + if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) { + connect(tagsLister, &KCoreDirLister::itemsAdded, q, [this](const QUrl&, const KFileItemList& items) { + + if(tags.isEmpty()) { + QList existingBookmarks; + + KBookmarkGroup root = bookmarkManager->root(); + KBookmark bookmark = root.first(); + + while (!bookmark.isNull()) { + existingBookmarks.append(bookmark.url()); + bookmark = root.next(bookmark); + } + + if (!existingBookmarks.contains(QUrl(tagsUrlBase))) { + KBookmark alltags = KFilePlacesItem::createSystemBookmark(bookmarkManager, QStringLiteral("All tags"), i18n("All tags"), QUrl(tagsUrlBase), QStringLiteral("tag")); + } + } + + for (const KFileItem &item: items) { + const QString name = item.name(); + + if (!tags.contains(name)) { + tags.append(name); + } + } + _k_reloadBookmarks(); + }); + + connect(tagsLister, &KCoreDirLister::itemsDeleted, q, [this](const KFileItemList& items) { + for (const KFileItem &item: items) { + tags.removeAll(item.name()); + } + _k_reloadBookmarks(); + }); + + tagsLister->openUrl(QUrl(tagsUrlBase), KCoreDirLister::OpenUrlFlag::Reload); + } } ~Private() { qDeleteAll(items); } KFilePlacesModel * const q; QList items; QVector availableDevices; QMap setupInProgress; QStringList supportedSchemes; Solid::Predicate predicate; KBookmarkManager *bookmarkManager; const bool fileIndexingEnabled; QString alternativeApplicationName; void reloadAndSignal(); QList loadBookmarkList(); int findNearestPosition(int source, int target); + QVector tags; + const QString tagsUrlBase = QStringLiteral("tags:/"); + KCoreDirLister* tagsLister; + void _k_initDeviceList(); void _k_deviceAdded(const QString &udi); void _k_deviceRemoved(const QString &udi); void _k_itemChanged(const QString &udi); void _k_reloadBookmarks(); void _k_storageSetupDone(Solid::ErrorType error, const QVariant &errorData); void _k_storageTeardownDone(Solid::ErrorType error, const QVariant &errorData); private: bool isBalooUrl(const QUrl &url) const; }; KFilePlacesModel::KFilePlacesModel(const QString &alternativeApplicationName, QObject *parent) : QAbstractItemModel(parent), d(new Private(this)) { const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/user-places.xbel"); d->bookmarkManager = KBookmarkManager::managerForExternalFile(file); d->alternativeApplicationName = alternativeApplicationName; // Let's put some places in there if it's empty. KBookmarkGroup root = d->bookmarkManager->root(); const auto setDefaultMetadataItemForGroup = [&root](KFilePlacesModel::GroupType type) { root.setMetaDataItem(stateNameForGroupType(type), QStringLiteral("false")); }; if (root.first().isNull() || !QFile::exists(file)) { // NOTE: The context for these I18N_NOOP2 calls has to be "KFile System Bookmarks". // The real i18nc call is made later, with this context, so the two must match. // // createSystemBookmark actually does nothing with its third argument, // but we have to give it something so the I18N_NOOP2 calls stay here for now. // // (coles, 13th May 2009) KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Home"), I18N_NOOP2("KFile System Bookmarks", "Home"), QUrl::fromLocalFile(QDir::homePath()), QStringLiteral("user-home")); // Some distros may not create various standard XDG folders by default // so check for their existence before adding bookmarks for them const QString desktopFolder = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); if (QDir(desktopFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Desktop"), I18N_NOOP2("KFile System Bookmarks", "Desktop"), QUrl::fromLocalFile(desktopFolder), QStringLiteral("user-desktop")); } const QString downloadFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); if (QDir(downloadFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Downloads"), I18N_NOOP2("KFile System Bookmarks", "Downloads"), QUrl::fromLocalFile(downloadFolder), QStringLiteral("folder-downloads")); } KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Network"), I18N_NOOP2("KFile System Bookmarks", "Network"), QUrl(QStringLiteral("remote:/")), QStringLiteral("folder-network")); #if defined(_WIN32_WCE) // adding drives const QString driveIcon = QStringLiteral("drive-harddisk"); foreach (const QFileInfo &info, QDir::drives()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, info.absoluteFilePath(), info.absoluteFilePath(), QUrl::fromLocalFile(info.absoluteFilePath()), driveIcon); } #elif !defined(Q_OS_WIN) KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Root"), I18N_NOOP2("KFile System Bookmarks", "Root"), QUrl::fromLocalFile(QStringLiteral("/")), QStringLiteral("folder-root")); #endif KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Trash"), I18N_NOOP2("KFile System Bookmarks", "Trash"), QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash")); setDefaultMetadataItemForGroup(PlacesType); setDefaultMetadataItemForGroup(RemoteType); setDefaultMetadataItemForGroup(DevicesType); setDefaultMetadataItemForGroup(RemovableDevicesType); + setDefaultMetadataItemForGroup(TagsType); // 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) d->bookmarkManager->saveAs(file); } // if baloo is enabled, add new urls even if the bookmark file is not empty if (d->fileIndexingEnabled && root.metaDataItem(QStringLiteral("withBaloo")) != QLatin1String("true")) { root.setMetaDataItem(QStringLiteral("withBaloo"), QStringLiteral("true")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Today"), I18N_NOOP2("KFile System Bookmarks", "Today"), QUrl(QStringLiteral("timeline:/today")), QStringLiteral("go-jump-today")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Yesterday"), I18N_NOOP2("KFile System Bookmarks", "Yesterday"), QUrl(QStringLiteral("timeline:/yesterday")), QStringLiteral("view-calendar-day")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Documents"), I18N_NOOP2("KFile System Bookmarks", "Documents"), QUrl(QStringLiteral("search:/documents")), QStringLiteral("folder-text")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Images"), I18N_NOOP2("KFile System Bookmarks", "Images"), QUrl(QStringLiteral("search:/images")), QStringLiteral("folder-images")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Audio Files"), I18N_NOOP2("KFile System Bookmarks", "Audio Files"), QUrl(QStringLiteral("search:/audio")), QStringLiteral("folder-sound")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, QStringLiteral("Videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"), QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); setDefaultMetadataItemForGroup(SearchForType); setDefaultMetadataItemForGroup(RecentlySavedType); d->bookmarkManager->save(); } QString predicate(QString::fromLatin1("[[[[ StorageVolume.ignored == false AND [ StorageVolume.usage == 'FileSystem' OR StorageVolume.usage == 'Encrypted' ]]" " OR " "[ IS StorageAccess AND StorageDrive.driveType == 'Floppy' ]]" " OR " "OpticalDisc.availableContent & 'Audio' ]" " OR " "StorageAccess.ignored == false ]")); if (KProtocolInfo::isKnownProtocol(QStringLiteral("mtp"))) { predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'mtp']"); } d->predicate = Solid::Predicate::fromString(predicate); Q_ASSERT(d->predicate.isValid()); connect(d->bookmarkManager, SIGNAL(changed(QString,QString)), this, SLOT(_k_reloadBookmarks())); connect(d->bookmarkManager, SIGNAL(bookmarksChanged(QString)), this, SLOT(_k_reloadBookmarks())); d->_k_reloadBookmarks(); QTimer::singleShot(0, this, SLOT(_k_initDeviceList())); } KFilePlacesModel::KFilePlacesModel(QObject *parent) : KFilePlacesModel({}, parent) { } KFilePlacesModel::~KFilePlacesModel() { delete d; } QUrl KFilePlacesModel::url(const QModelIndex &index) const { return data(index, UrlRole).toUrl(); } bool KFilePlacesModel::setupNeeded(const QModelIndex &index) const { return data(index, SetupNeededRole).toBool(); } QIcon KFilePlacesModel::icon(const QModelIndex &index) const { return data(index, Qt::DecorationRole).value(); } QString KFilePlacesModel::text(const QModelIndex &index) const { return data(index, Qt::DisplayRole).toString(); } bool KFilePlacesModel::isHidden(const QModelIndex &index) const { //Note: we do not want to show an index if its parent is hidden return data(index, HiddenRole).toBool() || isGroupHidden(index); } bool KFilePlacesModel::isGroupHidden(const GroupType type) const { const QString hidden = d->bookmarkManager->root().metaDataItem(stateNameForGroupType(type)); return hidden == QStringLiteral("true") ? true : false; } bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const { if (!index.isValid()) { return false; } KFilePlacesItem *item = static_cast(index.internalPointer()); return isGroupHidden(item->groupType()); } bool KFilePlacesModel::isDevice(const QModelIndex &index) const { if (!index.isValid()) { return false; } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->isDevice(); } Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const { if (!index.isValid()) { return Solid::Device(); } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return item->device(); } else { return Solid::Device(); } } KBookmark KFilePlacesModel::bookmarkForIndex(const QModelIndex &index) const { if (!index.isValid()) { return KBookmark(); } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->bookmark(); } KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const { if (!index.isValid()) { return UnknownType; } KFilePlacesItem *item = static_cast(index.internalPointer()); return item->groupType(); } QModelIndexList KFilePlacesModel::groupIndexes(const KFilePlacesModel::GroupType type) const { if (type == UnknownType) { return QModelIndexList(); } QModelIndexList indexes; const int rows = rowCount(); for (int row = 0; row < rows ; ++row) { const QModelIndex current = index(row, 0); if (groupType(current) == type) { indexes << current; } } return indexes; } QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } KFilePlacesItem *item = static_cast(index.internalPointer()); if (role == KFilePlacesModel::GroupHiddenRole) { return isGroupHidden(item->groupType()); } else { return item->data(role); } } QModelIndex KFilePlacesModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0 || row >= d->items.size()) { return QModelIndex(); } if (parent.isValid()) { return QModelIndex(); } return createIndex(row, column, d->items.at(row)); } QModelIndex KFilePlacesModel::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } int KFilePlacesModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } else { return d->items.size(); } } int KFilePlacesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) // We only know 1 piece of information for a particular entry return 1; } QModelIndex KFilePlacesModel::closestItem(const QUrl &url) const { int foundRow = -1; int maxLength = 0; // Search the item which is equal to the URL or at least is a parent URL. // If there are more than one possible item URL candidates, choose the item // which covers the bigger range of the URL. for (int row = 0; row < d->items.size(); ++row) { KFilePlacesItem *item = d->items[row]; const QUrl itemUrl(item->data(UrlRole).toUrl()); if (itemUrl.matches(url, QUrl::StripTrailingSlash) || itemUrl.isParentOf(url)) { const int length = itemUrl.toString().length(); if (length > maxLength) { foundRow = row; maxLength = length; } } } if (foundRow == -1) { return QModelIndex(); } else { return createIndex(foundRow, 0, d->items[foundRow]); } } void KFilePlacesModel::Private::_k_initDeviceList() { Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); connect(notifier, SIGNAL(deviceAdded(QString)), q, SLOT(_k_deviceAdded(QString))); connect(notifier, SIGNAL(deviceRemoved(QString)), q, SLOT(_k_deviceRemoved(QString))); const QList &deviceList = Solid::Device::listFromQuery(predicate); availableDevices.reserve(deviceList.size()); foreach (const Solid::Device &device, deviceList) { availableDevices << device.udi(); } _k_reloadBookmarks(); } void KFilePlacesModel::Private::_k_deviceAdded(const QString &udi) { Solid::Device d(udi); if (predicate.matches(d)) { availableDevices << udi; _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_deviceRemoved(const QString &udi) { auto it = std::find(availableDevices.begin(), availableDevices.end(), udi); if (it != availableDevices.end()) { availableDevices.erase(it); _k_reloadBookmarks(); } } void KFilePlacesModel::Private::_k_itemChanged(const QString &id) { for (int row = 0; row < items.size(); ++row) { if (items.at(row)->id() == id) { QModelIndex index = q->index(row, 0); emit q->dataChanged(index, index); } } } void KFilePlacesModel::Private::_k_reloadBookmarks() { QList currentItems = loadBookmarkList(); QList::Iterator it_i = items.begin(); QList::Iterator it_c = currentItems.begin(); QList::Iterator end_i = items.end(); QList::Iterator end_c = currentItems.end(); while (it_i != end_i || it_c != end_c) { if (it_i == end_i && it_c != end_c) { int row = items.count(); q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } else if (it_i != end_i && it_c == end_c) { int row = items.indexOf(*it_i); q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else if ((*it_i)->id() == (*it_c)->id()) { bool shouldEmit = !((*it_i)->bookmark() == (*it_c)->bookmark()); (*it_i)->setBookmark((*it_c)->bookmark()); if (shouldEmit) { int row = items.indexOf(*it_i); QModelIndex idx = q->index(row, 0); emit q->dataChanged(idx, idx); } ++it_i; ++it_c; } else if ((*it_i)->id() != (*it_c)->id()) { int row = items.indexOf(*it_i); if (it_i + 1 != end_i && (*(it_i + 1))->id() == (*it_c)->id()) { // if the next one matches, it's a remove q->beginRemoveRows(QModelIndex(), row, row); delete *it_i; it_i = items.erase(it_i); end_i = items.end(); end_c = currentItems.end(); q->endRemoveRows(); } else { q->beginInsertRows(QModelIndex(), row, row); it_i = items.insert(it_i, *it_c); ++it_i; it_c = currentItems.erase(it_c); end_i = items.end(); end_c = currentItems.end(); q->endInsertRows(); } } } qDeleteAll(currentItems); currentItems.clear(); } bool KFilePlacesModel::Private::isBalooUrl(const QUrl &url) const { const QString scheme = url.scheme(); return ((scheme == QLatin1String("timeline")) || (scheme == QLatin1String("search"))); } QList KFilePlacesModel::Private::loadBookmarkList() { QList items; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QVector devices = availableDevices; + QVector tagsList = tags; while (!bookmark.isNull()) { const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); const QUrl url = bookmark.url(); + const QString tag = bookmark.metaDataItem(QStringLiteral("tag")); if (!udi.isEmpty() || url.isValid()) { QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); - auto it = std::find(devices.begin(), devices.end(), udi); - bool deviceAvailable = (it != devices.end()); - if (deviceAvailable) { - devices.erase(it); - } - - bool allowedHere = appName.isEmpty() || - ((appName == QCoreApplication::instance()->applicationName()) || - (appName == alternativeApplicationName)); - bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true; - bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme()); - if (isSupportedScheme && ((isSupportedUrl && udi.isEmpty() && allowedHere) || deviceAvailable)) { - - KFilePlacesItem *item; + // If it's not a tag it's a device + if (tag.isEmpty()) { + auto it = std::find(devices.begin(), devices.end(), udi); + bool deviceAvailable = (it != devices.end()); if (deviceAvailable) { - item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); - // TODO: Update bookmark internal element - } else { - item = new KFilePlacesItem(bookmarkManager, bookmark.address()); + devices.erase(it); + } + + bool allowedHere = appName.isEmpty() || + ((appName == QCoreApplication::instance()->applicationName()) || + (appName == alternativeApplicationName)); + bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true; + bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme()); + + if (isSupportedScheme && ((isSupportedUrl && udi.isEmpty() && allowedHere) || deviceAvailable)) { + + KFilePlacesItem *item; + if (deviceAvailable) { + item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); + // TODO: Update bookmark internal element + } else { + item = new KFilePlacesItem(bookmarkManager, bookmark.address()); + } + connect(item, SIGNAL(itemChanged(QString)), + q, SLOT(_k_itemChanged(QString))); + + items << item; + } + } else { + auto it = std::find(tagsList.begin(), tagsList.end(), tag); + if (it != tagsList.end()) { + tagsList.removeAll(tag); + KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address()); + items << item; + connect(item, SIGNAL(itemChanged(QString)), + q, SLOT(_k_itemChanged(QString))); } - connect(item, SIGNAL(itemChanged(QString)), - q, SLOT(_k_itemChanged(QString))); - items << item; } } + bookmark = root.next(bookmark); } // Add bookmarks for the remaining devices, they were previously unknown foreach (const QString &udi, devices) { bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, udi); if (!bookmark.isNull()) { KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); connect(item, SIGNAL(itemChanged(QString)), q, SLOT(_k_itemChanged(QString))); // TODO: Update bookmark internal element items << item; } } + for (const QString& tag: tagsList) { + bookmark = KFilePlacesItem::createTagBookmark(bookmarkManager, tag); + if (!bookmark.isNull()) { + KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, + bookmark.address(), tag); + connect(item, SIGNAL(itemChanged(QString)), + q, SLOT(_k_itemChanged(QString))); + items << item; + } + } + // return a sorted list based on groups std::stable_sort(items.begin(), items.end(), [](KFilePlacesItem *itemA, KFilePlacesItem *itemB) { return (itemA->groupType() < itemB->groupType()); }); return items; } int KFilePlacesModel::Private::findNearestPosition(int source, int target) { const KFilePlacesItem *item = items.at(source); const KFilePlacesModel::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; } if (target > source) { // moving down, move it to the end of the group int groupFooter = source; while (items.at(groupFooter)->groupType() == groupType) { groupFooter++; // end of the list move it there if (groupFooter == items.count()) { break; } } target = groupFooter; } else { // moving up, move it to beginning of the group int groupHead = source; while (items.at(groupHead)->groupType() == groupType) { groupHead--; // beginning of the list move it there if (groupHead == 0) { break; } } target = groupHead; } return target; } void KFilePlacesModel::Private::reloadAndSignal() { bookmarkManager->emitChanged(bookmarkManager->root()); // ... we'll get relisted anyway } Qt::DropActions KFilePlacesModel::supportedDropActions() const { return Qt::ActionMask; } Qt::ItemFlags KFilePlacesModel::flags(const QModelIndex &index) const { Qt::ItemFlags res = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (index.isValid()) { res |= Qt::ItemIsDragEnabled; } if (!index.isValid()) { res |= Qt::ItemIsDropEnabled; } return res; } static QString _k_internalMimetype(const KFilePlacesModel *const self) { return QStringLiteral("application/x-kfileplacesmodel-") + QString::number(reinterpret_cast(self)); } QStringList KFilePlacesModel::mimeTypes() const { QStringList types; types << _k_internalMimetype(this) << QStringLiteral("text/uri-list"); return types; } QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const { QList urls; QByteArray itemData; QDataStream stream(&itemData, QIODevice::WriteOnly); foreach (const QModelIndex &index, indexes) { QUrl itemUrl = url(index); if (itemUrl.isValid()) { urls << itemUrl; } stream << index.row(); } QMimeData *mimeData = new QMimeData(); if (!urls.isEmpty()) { mimeData->setUrls(urls); } mimeData->setData(_k_internalMimetype(this), itemData); return mimeData; } bool KFilePlacesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) { return true; } if (column > 0) { return false; } if (row == -1 && parent.isValid()) { return false; // Don't allow to move an item onto another one, // too easy for the user to mess something up // If we really really want to allow copying files this way, // let's do it in the views to get the good old drop menu } 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; if (!movePlace(itemRow, row)) { return false; } } 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(); foreach (const QUrl &url, urls) { // TODO: use KIO::stat in order to get the UDS_DISPLAY_NAME too KIO::MimetypeJob *job = KIO::mimetype(url); QString mimeString; if (!job->exec()) { mimeString = QStringLiteral("unknown"); } else { mimeString = job->mimetype(); } QMimeType mimetype = db.mimeTypeForName(mimeString); if (!mimetype.isValid()) { qWarning() << "URL not added to Places as mimetype could not be determined!"; continue; } if (!mimetype.inherits(QStringLiteral("inode/directory"))) { // Only directories are allowed continue; } KFileItem item(url, mimetype.name(), S_IFDIR); KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, url.fileName(), url, item.iconName()); group.moveBookmark(bookmark, afterBookmark); afterBookmark = bookmark; } } else { // Oops, shouldn't happen thanks to mimeTypes() qWarning() << ": received wrong mimedata, " << data->formats(); return false; } 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) { addPlace(text, url, iconName, appName, QModelIndex()); } void KFilePlacesModel::addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after) { KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, text, url, iconName); if (!appName.isEmpty()) { bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName); } if (after.isValid()) { KFilePlacesItem *item = static_cast(after.internalPointer()); d->bookmarkManager->root().moveBookmark(bookmark, item->bookmark()); } refresh(); } void KFilePlacesModel::editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName, const QString &appName) { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return; } KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) { return; } bool changed = false; if (text != bookmark.fullText()) { bookmark.setFullText(text); changed = true; } 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 { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->isDevice()) { return; } KBookmark bookmark = item->bookmark(); if (bookmark.isNull()) { return; } d->bookmarkManager->root().deleteBookmark(bookmark); refresh(); } void KFilePlacesModel::setPlaceHidden(const QModelIndex &index, bool hidden) { if (!index.isValid()) { return; } KFilePlacesItem *item = static_cast(index.internalPointer()); if (item->bookmark().isNull() || item->isHidden() == hidden) { return; } const bool groupHidden = isGroupHidden(item->groupType()); const bool hidingChildOnShownParent = hidden && !groupHidden; const bool showingChildOnShownParent = !hidden && !groupHidden; if (hidingChildOnShownParent || showingChildOnShownParent) { item->setHidden(hidden); d->reloadAndSignal(); emit dataChanged(index, index); } } void KFilePlacesModel::setGroupHidden(const GroupType type, bool hidden) { if (isGroupHidden(type) == hidden) return; - d->bookmarkManager->root().setMetaDataItem(stateNameForGroupType(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); + d->bookmarkManager->root().setMetaDataItem(stateNameForGroupType(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); d->reloadAndSignal(); emit groupHiddenChanged(type, hidden); } 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(); int hidden = 0; for (int i = 0; i < rows; ++i) { if (isHidden(index(i, 0))) { hidden++; } } return hidden; } QAction *KFilePlacesModel::teardownActionForIndex(const QModelIndex &index) const { Solid::Device device = deviceForIndex(index); if (device.is() && device.as()->isAccessible()) { Solid::StorageDrive *drive = device.as(); if (drive == nullptr) { drive = device.parent().as(); } bool hotpluggable = false; bool removable = false; if (drive != nullptr) { hotpluggable = drive->isHotpluggable(); removable = drive->isRemovable(); } QString iconName; QString text; QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); if (device.is()) { text = i18n("&Release '%1'", label); } else if (removable || hotpluggable) { text = i18n("&Safely Remove '%1'", label); iconName = QStringLiteral("media-eject"); } else { text = i18n("&Unmount '%1'", label); iconName = QStringLiteral("media-eject"); } if (!iconName.isEmpty()) { return new QAction(QIcon::fromTheme(iconName), text, nullptr); } else { return new QAction(text, nullptr); } } return nullptr; } QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const { Solid::Device device = deviceForIndex(index); if (device.is()) { QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); QString text = i18n("&Eject '%1'", label); return new QAction(QIcon::fromTheme(QStringLiteral("media-eject")), text, nullptr); } return nullptr; } void KFilePlacesModel::requestTeardown(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::StorageAccess *access = device.as(); if (access != nullptr) { connect(access, SIGNAL(teardownDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); access->teardown(); } } void KFilePlacesModel::requestEject(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); Solid::OpticalDrive *drive = device.parent().as(); if (drive != nullptr) { connect(drive, SIGNAL(ejectDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageTeardownDone(Solid::ErrorType,QVariant))); drive->eject(); } else { QString label = data(index, Qt::DisplayRole).toString().replace(QLatin1Char('&'), QLatin1String("&&")); QString message = i18n("The device '%1' is not a disk and cannot be ejected.", label); emit errorMessage(message); } } void KFilePlacesModel::requestSetup(const QModelIndex &index) { Solid::Device device = deviceForIndex(index); if (device.is() && !d->setupInProgress.contains(device.as()) && !device.as()->isAccessible()) { Solid::StorageAccess *access = device.as(); d->setupInProgress[access] = index; connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), this, SLOT(_k_storageSetupDone(Solid::ErrorType,QVariant))); access->setup(); } } void KFilePlacesModel::Private::_k_storageSetupDone(Solid::ErrorType error, const QVariant &errorData) { QPersistentModelIndex index = setupInProgress.take(q->sender()); if (!index.isValid()) { return; } if (!error) { emit q->setupDone(index, true); } else { if (errorData.isValid()) { emit q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2", q->text(index), errorData.toString())); } else { emit q->errorMessage(i18n("An error occurred while accessing '%1'", q->text(index))); } emit q->setupDone(index, false); } } void KFilePlacesModel::Private::_k_storageTeardownDone(Solid::ErrorType error, const QVariant &errorData) { if (error && errorData.isValid()) { emit q->errorMessage(errorData.toString()); } } void KFilePlacesModel::setSupportedSchemes(const QStringList &schemes) { d->supportedSchemes = schemes; d->_k_reloadBookmarks(); } QStringList KFilePlacesModel::supportedSchemes() const { return d->supportedSchemes; } #include "moc_kfileplacesmodel.cpp" diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h index 38a69f79..4e2a6282 100644 --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -1,229 +1,231 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens Copyright (C) 2007 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILEPLACESMODEL_H #define KFILEPLACESMODEL_H #include "kiofilewidgets_export.h" #include #include #include #include class QMimeData; class QAction; /** * @class KFilePlacesModel kfileplacesmodel.h * * This class is a list view model. Each entry represents a "place" * where user can access files. Only relevant when * used with QListView or QTableView. */ class KIOFILEWIDGETS_EXPORT KFilePlacesModel : public QAbstractItemModel { Q_OBJECT public: enum AdditionalRoles { UrlRole = 0x069CD12B, HiddenRole = 0x0741CAAC, SetupNeededRole = 0x059A935D, FixedDeviceRole = 0x332896C1, CapacityBarRecommendedRole = 0x1548C5C4, GroupRole = 0x0a5b64ee, /// @since 5.41 IconNameRole = 0x00a45c00, GroupHiddenRole = 0x21a4b936 }; /// @since 5.42 enum GroupType { PlacesType, RemoteType, RecentlySavedType, SearchForType, DevicesType, RemovableDevicesType, - UnknownType + UnknownType, + /// @since 5.54 + TagsType }; explicit KFilePlacesModel(QObject *parent = nullptr); /** * @brief Construct a new KFilePlacesModel with an alternativeApplicationName * @param alternativeApplicationName This value will be used to filter bookmarks in addition to the actual application name * @param parent Parent object * @since 5.43 * @todo kf6: merge constructors */ KFilePlacesModel(const QString &alternativeApplicationName, QObject *parent = nullptr); ~KFilePlacesModel() override; 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; /// @since 5.42 bool isGroupHidden(const GroupType type) const; /// @since 5.42 bool isGroupHidden(const QModelIndex &index) const; bool isDevice(const QModelIndex &index) const; Solid::Device deviceForIndex(const QModelIndex &index) const; KBookmark bookmarkForIndex(const QModelIndex &index) const; /// @since 5.42 GroupType groupType(const QModelIndex &index) const; QModelIndexList groupIndexes(const GroupType type) const; QAction *teardownActionForIndex(const QModelIndex &index) const; QAction *ejectActionForIndex(const QModelIndex &index) const; void requestTeardown(const QModelIndex &index); void requestEject(const QModelIndex &index); void requestSetup(const QModelIndex &index); void addPlace(const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); void addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after); 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); /// @since 5.42 void setGroupHidden(const GroupType type, 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; /** * @brief Get a visible data based on Qt role for the given index. * Return the device information for the give index. * * @param index The QModelIndex which contains the row, column to fetch the data. * @param role The Interview data role(ex: Qt::DisplayRole). * * @return the data for the given index and role. */ QVariant data(const QModelIndex &index, int role) const override; /** * @brief Get the children model index for the given row and column. */ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; /** * @brief Get the parent QModelIndex for the given model child. */ QModelIndex parent(const QModelIndex &child) const override; /** * @brief Get the number of rows for a model index. */ int rowCount(const QModelIndex &parent = QModelIndex()) const override; /** * @brief Get the number of columns for a model index. */ int columnCount(const QModelIndex &parent = QModelIndex()) const override; /** * Returns the closest item for the URL \a url. * The closest item is defined as item which is equal to * the URL or at least is a parent URL. If there are more than * one possible parent URL candidates, the item which covers * the bigger range of the URL is returned. * * Example: the url is '/home/peter/Documents/Music'. * Available items are: * - /home/peter * - /home/peter/Documents * * The returned item will the one for '/home/peter/Documents'. */ QModelIndex closestItem(const QUrl &url) const; Qt::DropActions supportedDropActions() const override; Qt::ItemFlags flags(const QModelIndex &index) const override; QStringList mimeTypes() const override; QMimeData *mimeData(const QModelIndexList &indexes) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; /** * @brief Reload bookmark information * @since 5.41 */ void refresh() const; /** * @brief Converts the URL, which contains "virtual" URLs for system-items like * "timeline:/lastmonth" into a Query-URL "timeline:/2017-10" * 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 the converted URL, which can be handled by an ioslave * @since 5.41 */ static QUrl convertedUrl(const QUrl &url); /** * Set the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. Examples for * schemes are @c "file" or @c "ftp". * * @sa QFileDialog::setSupportedSchemes * @since 5.43 */ void setSupportedSchemes(const QStringList &schemes); /** * Returns the URL schemes that the file widget should allow navigating to. * * If the returned list is empty, all schemes are supported. * * @sa QFileDialog::supportedSchemes * @since 5.43 */ QStringList supportedSchemes() const; Q_SIGNALS: void errorMessage(const QString &message); void setupDone(const QModelIndex &index, bool success); void groupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden); private: Q_PRIVATE_SLOT(d, void _k_initDeviceList()) Q_PRIVATE_SLOT(d, void _k_deviceAdded(const QString &)) Q_PRIVATE_SLOT(d, void _k_deviceRemoved(const QString &)) Q_PRIVATE_SLOT(d, void _k_itemChanged(const QString &)) Q_PRIVATE_SLOT(d, void _k_reloadBookmarks()) Q_PRIVATE_SLOT(d, void _k_storageSetupDone(Solid::ErrorType, const QVariant &)) Q_PRIVATE_SLOT(d, void _k_storageTeardownDone(Solid::ErrorType, const QVariant &)) class Private; Private *const d; friend class Private; }; #endif