diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp index 9bc6f2a9..19f5c78b 100644 --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -1,1391 +1,1393 @@ /* 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. */ #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 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 = QLatin1String("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()), tags(), tagsLister(new KCoreDirLister(q)) { 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, "All tags" , i18n("All tags").toUtf8().data() , 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; }; KBookmark KFilePlacesModel::bookmarkForUrl(const QUrl &searchUrl) const { KBookmarkGroup root = d->bookmarkManager->root(); KBookmark current = root.first(); while (!current.isNull()) { if (current.url() == searchUrl) { return current; } current = root.next(current); } return KBookmark(); } 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 I18NC_NOOP 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 second argument, the context, KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("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, I18NC_NOOP("KFile System Bookmarks", "Desktop"), QUrl::fromLocalFile(desktopFolder), QStringLiteral("user-desktop")); } const QString documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); if (QDir(documentsFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Documents"), QUrl::fromLocalFile(documentsFolder), QStringLiteral("folder-documents")); } const QString downloadFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); if (QDir(downloadFolder).exists()) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Downloads"), QUrl::fromLocalFile(downloadFolder), QStringLiteral("folder-downloads")); } KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Network"), QUrl(QStringLiteral("remote:/")), QStringLiteral("folder-network")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("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); } // Add a Recently Used entry if available (it comes from kio-extras) if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused")) && root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) { root.setMetaDataItem(QStringLiteral("withRecentlyUsed"), QStringLiteral("true")); KBookmark recentFilesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Recent Files"), QUrl(QStringLiteral("recentlyused:/files")), QStringLiteral("document-open-recent")); KBookmark recentDirectoriesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Recent Locations"), QUrl(QStringLiteral("recentlyused:/locations")), QStringLiteral("folder-open-recent")); setDefaultMetadataItemForGroup(RecentlySavedType); // Move The recently used bookmarks below the trash, making it the first element in the Recent group KBookmark trashBookmark = bookmarkForUrl(QUrl(QStringLiteral("trash:/"))); if (!trashBookmark.isNull()) { root.moveBookmark(recentFilesBookmark, trashBookmark); root.moveBookmark(recentDirectoriesBookmark, recentFilesBookmark); } d->bookmarkManager->save(); } // 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")); // don't add by default "Modified Today" and "Modified Yesterday" when recentlyused:/ is present if (root.metaDataItem(QStringLiteral("withRecentlyUsed")) != QLatin1String("true")) { KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Modified Today"), QUrl(QStringLiteral("timeline:/today")), QStringLiteral("go-jump-today")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Modified Yesterday"), QUrl(QStringLiteral("timeline:/yesterday")), QStringLiteral("view-calendar-day")); } KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Documents"), QUrl(QStringLiteral("search:/documents")), QStringLiteral("folder-text")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Images"), QUrl(QStringLiteral("search:/images")), QStringLiteral("folder-images")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("KFile System Bookmarks", "Audio"), QUrl(QStringLiteral("search:/audio")), QStringLiteral("folder-sound")); KFilePlacesItem::createSystemBookmark(d->bookmarkManager, I18NC_NOOP("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 == QLatin1String("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()); for (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(); + + emit q->reloaded(); } 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")); // 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) { 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()); KFilePlacesItem *item = nullptr; if (deviceAvailable) { item = new KFilePlacesItem(bookmarkManager, bookmark.address(), udi); if (!item->hasSupportedScheme(supportedSchemes)) { delete item; item = nullptr; } } else if (isSupportedScheme && isSupportedUrl && udi.isEmpty() && allowedHere) { // TODO: Update bookmark internal element item = new KFilePlacesItem(bookmarkManager, bookmark.address()); } if (item) { 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))); } } } bookmark = root.next(bookmark); } // Add bookmarks for the remaining devices, they were previously unknown for (const QString &udi : qAsConst(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); for (const QModelIndex &index : qAsConst(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(); for (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->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 2ee91219..6b6e83b6 100644 --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -1,375 +1,382 @@ /* 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, /// @see url() HiddenRole = 0x0741CAAC, /// @see isHidden() SetupNeededRole = 0x059A935D, /// @see setupNeeded() FixedDeviceRole = 0x332896C1, /// Whether the place is a fixed device (neither hotpluggable nor removable). CapacityBarRecommendedRole = 0x1548C5C4, /// Whether the place should have its free space displayed in a capacity bar. GroupRole = 0x0a5b64ee, ///< @since 5.40 /// The name of the group, for example "Remote" or "Devices". IconNameRole = 0x00a45c00, ///< @since 5.41 @see icon() GroupHiddenRole = 0x21a4b936 ///< @since 5.42 @see isGroupHidden() }; /// @since 5.42 enum GroupType { PlacesType, RemoteType, RecentlySavedType, SearchForType, DevicesType, RemovableDevicesType, UnknownType, TagsType ///< @since 5.54 }; 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; /** * @return The URL of the place at index @p index. */ QUrl url(const QModelIndex &index) const; /** * @return Whether the place at index @p index needs to be mounted before it can be used. */ bool setupNeeded(const QModelIndex &index) const; /** * @return The icon of the place at index @p index. */ QIcon icon(const QModelIndex &index) const; /** * @return The user-visible text of the place at index @p index. */ QString text(const QModelIndex &index) const; /** * @return Whether the place at index @p index is hidden or is inside an hidden group. */ bool isHidden(const QModelIndex &index) const; /** * @return Whether the group type @p type is hidden. * @since 5.42 */ bool isGroupHidden(const GroupType type) const; /** * @return Whether the group of the place at index @p index is hidden. * @since 5.42 */ bool isGroupHidden(const QModelIndex &index) const; /** * @return Whether the place at index @p index is a device handled by Solid. * @see deviceForIndex() */ bool isDevice(const QModelIndex &index) const; /** * @return The solid device of the place at index @p index, if it is a device. Otherwise a default Solid::Device() instance is returned. * @see isDevice() */ Solid::Device deviceForIndex(const QModelIndex &index) const; /** * @return The KBookmark instance of the place at index @p index. * If the index is not valid, a default KBookmark instance is returned. */ KBookmark bookmarkForIndex(const QModelIndex &index) const; /** * @return The KBookmark instance of the place with url @p searchUrl. * If the bookmark corresponding to searchUrl is not found, a default KBookmark instance is returned. * @since 5.63 */ KBookmark bookmarkForUrl(const QUrl &searchUrl) const; /** * @return The group type of the place at index @p index. * @since 5.42 */ GroupType groupType(const QModelIndex &index) const; /** * @return The list of model indexes that have @ type as their group type. * @see groupType() * @since 5.42 */ QModelIndexList groupIndexes(const GroupType type) const; /** * @return A QAction with a proper translated label that can be used to trigger the requestTeardown() * method for the place at index @p index. * @see requestTeardown() */ QAction *teardownActionForIndex(const QModelIndex &index) const; /** * @return A QAction with a proper translated label that can be used to trigger the requestEject() * method for the place at index @p index. * @see requestEject() */ QAction *ejectActionForIndex(const QModelIndex &index) const; /** * Unmounts the place at index @p index by triggering the teardown functionality of its Solid device. * @see deviceForIndex() */ void requestTeardown(const QModelIndex &index); /** * Ejects the place at index @p index by triggering the ejetc functionality of its Solid device. * @see deviceForIndex() */ void requestEject(const QModelIndex &index); /** * Mounts the place at index @p index by triggering the setup functionality of its Solid device. * @see deviceForIndex() */ void requestSetup(const QModelIndex &index); /** * Adds a new place to the model. * @param text The user-visible text for the place * @param url The URL of the place. It will be stored in its QUrl::FullyEncoded string format. * @param iconName The icon of the place * @param appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application. */ void addPlace(const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); /** * Adds a new place to the model. * @param text The user-visible text for the place * @param url The URL of the place. It will be stored in its QUrl::FullyEncoded string format. * @param iconName The icon of the place * @param appName If set as the value of QCoreApplication::applicationName(), will make the place visible only in this application. * @param after The index after which the new place will be added. */ void addPlace(const QString &text, const QUrl &url, const QString &iconName, const QString &appName, const QModelIndex &after); /** * Edits the place with index @p index. * @param text The new user-visible text for the place * @param url The new URL of the place * @param iconName The new icon of the place * @param appName The new application-local filter for the place (@see addPlace()). */ void editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); /** * Deletes the place with index @p index from the model. */ void removePlace(const QModelIndex &index) const; /** * Changes the visibility of the place with index @p index, but only if the place is not inside an hidden group. * @param hidden Whether the place should be hidden or visible. * @see isGroupHidden() */ void setPlaceHidden(const QModelIndex &index, bool hidden); /** * Changes the visibility of the group with type @p type. * @param hidden Whether the group should be hidden or visible. * @see isGroupHidden() * @since 5.42 */ void setGroupHidden(const GroupType type, bool hidden); /** * @brief Move place at @p itemRow to a position before @p row * @return Whether the place has been moved. * @since 5.41 */ bool movePlace(int itemRow, int row); /** * @return The number of hidden places in the model. * @see isHidden() */ 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: /** * @p message An error message explaining what went wrong. */ void errorMessage(const QString &message); /** * Emitted after the Solid setup ends. * @param success Whether the Solid setup has been successful. * @see requestSetup() */ void setupDone(const QModelIndex &index, bool success); /** * Emitted whenever the visibility of the group @p group changes. * @param hidden The new visibility of the group. * @see setGroupHidden() * @since 5.42 */ void groupHiddenChanged(KFilePlacesModel::GroupType group, bool hidden); + /** + * Called once the model has been reloaded + * + * @since 5.71 + */ + void reloaded(); + 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 diff --git a/src/filewidgets/kurlnavigatorplacesselector.cpp b/src/filewidgets/kurlnavigatorplacesselector.cpp index 103a5fc1..d6b1b05a 100644 --- a/src/filewidgets/kurlnavigatorplacesselector.cpp +++ b/src/filewidgets/kurlnavigatorplacesselector.cpp @@ -1,303 +1,299 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz (peter.penz@gmx.at) * * Copyright (C) 2007 by Kevin Ottens (ervin@kde.org) * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "kurlnavigatorplacesselector_p.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KDEPrivate { KUrlNavigatorPlacesSelector::KUrlNavigatorPlacesSelector(QWidget *parent, KFilePlacesModel *placesModel) : KUrlNavigatorButtonBase(parent), m_selectedItem(-1), m_placesModel(placesModel) { setFocusPolicy(Qt::NoFocus); m_placesMenu = new QMenu(this); m_placesMenu->installEventFilter(this); updateMenu(); - connect(m_placesModel, &QAbstractItemModel::rowsInserted, - this, &KUrlNavigatorPlacesSelector::updateMenu); - connect(m_placesModel, &QAbstractItemModel::rowsRemoved, - this, &KUrlNavigatorPlacesSelector::updateMenu); - connect(m_placesModel, &QAbstractItemModel::dataChanged, + connect(m_placesModel, &KFilePlacesModel::reloaded, this, &KUrlNavigatorPlacesSelector::updateMenu); connect(m_placesMenu, &QMenu::triggered, this, &KUrlNavigatorPlacesSelector::activatePlace); setMenu(m_placesMenu); setAcceptDrops(true); } KUrlNavigatorPlacesSelector::~KUrlNavigatorPlacesSelector() { } void KUrlNavigatorPlacesSelector::updateMenu() { m_placesMenu->clear(); // Submenus have to be deleted explicitly (QTBUG-11070) for(QObject *obj : QObjectList(m_placesMenu->children())) { delete qobject_cast(obj); // Noop for nullptr } updateSelection(m_selectedUrl); QString previousGroup; QMenu *subMenu = nullptr; const int rowCount = m_placesModel->rowCount(); for (int i = 0; i < rowCount; ++i) { QModelIndex index = m_placesModel->index(i, 0); if (m_placesModel->isHidden(index)) { continue; } QAction *placeAction = new QAction(m_placesModel->icon(index), m_placesModel->text(index), m_placesMenu); placeAction->setData(i); const QString &groupName = index.data(KFilePlacesModel::GroupRole).toString(); if (previousGroup.isEmpty()) { // Skip first group heading. previousGroup = groupName; } // Put all subsequent categories into a submenu. if (previousGroup != groupName) { QAction *subMenuAction = new QAction(groupName, m_placesMenu); subMenu = new QMenu(m_placesMenu); subMenu->installEventFilter(this); subMenuAction->setMenu(subMenu); m_placesMenu->addAction(subMenuAction); previousGroup = groupName; } if (subMenu) { subMenu->addAction(placeAction); } else { m_placesMenu->addAction(placeAction); } if (i == m_selectedItem) { setIcon(m_placesModel->icon(index)); } } updateTeardownAction(); } void KUrlNavigatorPlacesSelector::updateTeardownAction() { const QString teardownActionId = QStringLiteral("teardownAction"); const auto actions = m_placesMenu->actions(); for (QAction *action : actions) { if (action->data() == teardownActionId) { delete action; } } const QModelIndex index = m_placesModel->index(m_selectedItem, 0); QAction *teardown = m_placesModel->teardownActionForIndex(index); if (teardown) { QAction *separator = m_placesMenu->addSeparator(); separator->setData(teardownActionId); teardown->setParent(m_placesMenu); teardown->setData(teardownActionId); m_placesMenu->addAction(teardown); } } void KUrlNavigatorPlacesSelector::updateSelection(const QUrl &url) { const QModelIndex index = m_placesModel->closestItem(url); if (index.isValid()) { m_selectedItem = index.row(); m_selectedUrl = url; setIcon(m_placesModel->icon(index)); } else { m_selectedItem = -1; // No bookmark has been found which matches to the given Url. Show // a generic folder icon as pixmap for indication: setIcon(QIcon::fromTheme(QStringLiteral("folder"))); } updateTeardownAction(); } QUrl KUrlNavigatorPlacesSelector::selectedPlaceUrl() const { const QModelIndex index = m_placesModel->index(m_selectedItem, 0); return index.isValid() ? m_placesModel->url(index) : QUrl(); } QString KUrlNavigatorPlacesSelector::selectedPlaceText() const { const QModelIndex index = m_placesModel->index(m_selectedItem, 0); return index.isValid() ? m_placesModel->text(index) : QString(); } QSize KUrlNavigatorPlacesSelector::sizeHint() const { const int height = KUrlNavigatorButtonBase::sizeHint().height(); return QSize(height, height); } void KUrlNavigatorPlacesSelector::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); drawHoverBackground(&painter); // draw icon const QPixmap pixmap = icon().pixmap(QSize(22, 22).expandedTo(iconSize()), QIcon::Normal); style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, pixmap); } void KUrlNavigatorPlacesSelector::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { setDisplayHintEnabled(DraggedHint, true); event->acceptProposedAction(); update(); } } void KUrlNavigatorPlacesSelector::dragLeaveEvent(QDragLeaveEvent *event) { KUrlNavigatorButtonBase::dragLeaveEvent(event); setDisplayHintEnabled(DraggedHint, false); update(); } void KUrlNavigatorPlacesSelector::dropEvent(QDropEvent *event) { setDisplayHintEnabled(DraggedHint, false); update(); QMimeDatabase db; const QList urlList = KUrlMimeData::urlsFromMimeData(event->mimeData()); for (const QUrl &url : urlList) { QMimeType mimetype = db.mimeTypeForUrl(url); if (mimetype.inherits(QStringLiteral("inode/directory"))) { m_placesModel->addPlace(url.fileName(), url); } } } void KUrlNavigatorPlacesSelector::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::MiddleButton && geometry().contains(event->pos())) { emit tabRequested(KFilePlacesModel::convertedUrl(m_placesModel->url(m_placesModel->index(m_selectedItem, 0)))); event->accept(); return; } KUrlNavigatorButtonBase::mouseReleaseEvent(event); } void KUrlNavigatorPlacesSelector::activatePlace(QAction *action) { Q_ASSERT(action != nullptr); if (action->data().toString() == QLatin1String("teardownAction")) { QModelIndex index = m_placesModel->index(m_selectedItem, 0); m_placesModel->requestTeardown(index); return; } QModelIndex index = m_placesModel->index(action->data().toInt(), 0); m_lastClickedIndex = QPersistentModelIndex(); if (m_placesModel->setupNeeded(index)) { connect(m_placesModel, &KFilePlacesModel::setupDone, this, &KUrlNavigatorPlacesSelector::onStorageSetupDone); m_lastClickedIndex = index; m_placesModel->requestSetup(index); return; } else if (index.isValid()) { m_selectedItem = index.row(); setIcon(m_placesModel->icon(index)); updateTeardownAction(); emit placeActivated(KFilePlacesModel::convertedUrl(m_placesModel->url(index))); } } void KUrlNavigatorPlacesSelector::onStorageSetupDone(const QModelIndex &index, bool success) { if (m_lastClickedIndex == index) { if (success) { m_selectedItem = index.row(); setIcon(m_placesModel->icon(index)); updateTeardownAction(); emit placeActivated(KFilePlacesModel::convertedUrl(m_placesModel->url(index))); } m_lastClickedIndex = QPersistentModelIndex(); } } bool KUrlNavigatorPlacesSelector::eventFilter(QObject *watched, QEvent *event) { if (auto *menu = qobject_cast(watched)) { if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *me = static_cast(event); if (me->button() == Qt::MiddleButton) { if (QAction *action = menu->activeAction()) { m_placesMenu->close(); // always close top menu QModelIndex index = m_placesModel->index(action->data().toInt(), 0); emit tabRequested(KFilePlacesModel::convertedUrl(m_placesModel->url(index))); return true; } } } } return KUrlNavigatorButtonBase::eventFilter(watched, event); } } // namespace KDEPrivate #include "moc_kurlnavigatorplacesselector_p.cpp"