Files
RedBear-OS/local/recipes/kde/kf6-kio/source/src/filewidgets/kfileplacesmodel.cpp
T
2026-04-14 10:51:06 +01:00

1574 lines
55 KiB
C++

/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kfileplacesmodel.h"
#include "kfileplacesitem_p.h"
#include "kfileplacesmodel_p.h"
#include <KCoreDirLister>
#include <KLazyLocalizedString>
#include <KListOpenFilesJob>
#include <KLocalizedString>
#include <commandlauncherjob.h>
#include <kfileitem.h>
#include <kio/statjob.h>
#include <kprotocolinfo.h>
#include <KBookmarkManager>
#include <KConfig>
#include <KConfigGroup>
#include <KUrlMimeData>
#include <solid/block.h>
#include <solid/devicenotifier.h>
#include <solid/opticaldisc.h>
#include <solid/opticaldrive.h>
#include <solid/portablemediaplayer.h>
#include <solid/predicate.h>
#include <solid/storageaccess.h>
#include <solid/storagedrive.h>
#include <solid/storagevolume.h>
#include <QAction>
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QMimeData>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <QTimer>
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(QStringLiteral("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 {
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;
}
}
KFilePlacesModelPrivate::KFilePlacesModelPrivate(KFilePlacesModel *qq)
: q(qq)
, fileIndexingEnabled(isFileIndexingEnabled())
, tagsLister(new KCoreDirLister(q))
{
if (KProtocolInfo::isKnownProtocol(QStringLiteral("tags"))) {
QObject::connect(tagsLister, &KCoreDirLister::itemsAdded, q, [this](const QUrl &, const KFileItemList &items) {
if (tags.isEmpty()) {
QList<QUrl> 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,
kli18nc("KFile System Bookmarks", "All tags").untranslatedText(),
QUrl(tagsUrlBase),
QStringLiteral("tag"));
}
}
for (const KFileItem &item : items) {
const QString name = item.name();
if (!tags.contains(name)) {
tags.append(name);
}
}
reloadBookmarks();
});
QObject::connect(tagsLister, &KCoreDirLister::itemsDeleted, q, [this](const KFileItemList &items) {
for (const KFileItem &item : items) {
tags.removeAll(item.name());
}
reloadBookmarks();
});
tagsLister->openUrl(QUrl(tagsUrlBase), KCoreDirLister::OpenUrlFlag::Reload);
}
}
QString KFilePlacesModelPrivate::ignoreMimeType()
{
return QStringLiteral("application/x-kfileplacesmodel-ignore");
}
QString KFilePlacesModelPrivate::internalMimeType(const KFilePlacesModel *model)
{
return QStringLiteral("application/x-kfileplacesmodel-") + QString::number(reinterpret_cast<qptrdiff>(model));
}
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();
}
static inline QString versionKey()
{
return QStringLiteral("kde_places_version");
}
KFilePlacesModel::KFilePlacesModel(QObject *parent)
: QAbstractItemModel(parent)
, d(new KFilePlacesModelPrivate(this))
{
const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/user-places.xbel");
d->bookmarkManager = new KBookmarkManager(file, this);
// 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"));
};
// Increase this version number and use the following logic to handle the update process for existing installations.
static const int s_currentVersion = 4;
const bool newFile = root.first().isNull() || !QFile::exists(file);
const int fileVersion = root.metaDataItem(versionKey()).toInt();
if (newFile || fileVersion < s_currentVersion) {
root.setMetaDataItem(versionKey(), QString::number(s_currentVersion));
const QList<QUrl> seenUrls = root.groupUrlList();
/* clang-format off */
auto createSystemBookmark =
[this, &seenUrls](const char *untranslatedLabel,
const QUrl &url,
const QString &iconName,
const KBookmark &after) {
if (!seenUrls.contains(url)) {
return KFilePlacesItem::createSystemBookmark(d->bookmarkManager, untranslatedLabel, url, iconName, after);
}
return KBookmark();
};
/* clang-format on */
if (fileVersion < 2) {
// NOTE: The context for these kli18nc calls has to be "KFile System Bookmarks".
// The real i18nc call is made later, with this context, so the two must match.
createSystemBookmark(kli18nc("KFile System Bookmarks", "Home").untranslatedText(),
QUrl::fromLocalFile(QDir::homePath()),
QStringLiteral("user-home"),
KBookmark());
// 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()) {
createSystemBookmark(kli18nc("KFile System Bookmarks", "Desktop").untranslatedText(),
QUrl::fromLocalFile(desktopFolder),
QStringLiteral("user-desktop"),
KBookmark());
}
const QString documentsFolder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
if (QDir(documentsFolder).exists()) {
createSystemBookmark(kli18nc("KFile System Bookmarks", "Documents").untranslatedText(),
QUrl::fromLocalFile(documentsFolder),
QStringLiteral("folder-documents"),
KBookmark());
}
const QString downloadFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
if (QDir(downloadFolder).exists()) {
createSystemBookmark(kli18nc("KFile System Bookmarks", "Downloads").untranslatedText(),
QUrl::fromLocalFile(downloadFolder),
QStringLiteral("folder-downloads"),
KBookmark());
}
createSystemBookmark(kli18nc("KFile System Bookmarks", "Network").untranslatedText(),
QUrl(QStringLiteral("remote:/")),
QStringLiteral("folder-network"),
KBookmark());
createSystemBookmark(kli18nc("KFile System Bookmarks", "Trash").untranslatedText(),
QUrl(QStringLiteral("trash:/")),
QStringLiteral("user-trash"),
KBookmark());
}
if (!newFile && fileVersion < 3) {
KBookmarkGroup rootGroup = d->bookmarkManager->root();
KBookmark bItem = rootGroup.first();
while (!bItem.isNull()) {
KBookmark nextbItem = rootGroup.next(bItem);
const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true");
if (isSystemItem) {
const QString text = bItem.fullText();
// Because of b8a4c2223453932202397d812a0c6b30c6186c70 we need to find the system bookmark named Audio Files
// and rename it to Audio, otherwise users are getting untranslated strings
if (text == QLatin1String("Audio Files")) {
bItem.setFullText(QStringLiteral("Audio"));
} else if (text == QLatin1String("Today")) {
// Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Today
// and rename it to Modified Today, otherwise users are getting untranslated strings
bItem.setFullText(QStringLiteral("Modified Today"));
} else if (text == QLatin1String("Yesterday")) {
// Because of 19feef732085b444515da3f6c66f3352bbcb1824 we need to find the system bookmark named Yesterday
// and rename it to Modified Yesterday, otherwise users are getting untranslated strings
bItem.setFullText(QStringLiteral("Modified Yesterday"));
} else if (text == QLatin1String("This Month")) {
// Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named This Month
// and remove it, otherwise users are getting untranslated strings
rootGroup.deleteBookmark(bItem);
} else if (text == QLatin1String("Last Month")) {
// Because of 7e1d2fb84546506c91684dd222c2485f0783848f we need to find the system bookmark named Last Month
// and remove it, otherwise users are getting untranslated strings
rootGroup.deleteBookmark(bItem);
}
}
bItem = nextbItem;
}
}
if (fileVersion < 4) {
auto findSystemBookmark = [this](const QString &untranslatedText) {
KBookmarkGroup root = d->bookmarkManager->root();
KBookmark bItem = root.first();
while (!bItem.isNull()) {
const bool isSystemItem = bItem.metaDataItem(QStringLiteral("isSystemItem")) == QLatin1String("true");
if (isSystemItem && bItem.fullText() == untranslatedText) {
return bItem;
}
bItem = root.next(bItem);
}
return KBookmark();
};
// This variable is used to insert the new bookmarks at the correct place starting after the "Downloads"
// bookmark. When the user already has some of the bookmarks set up manually, the createSystemBookmark()
// function returns an empty KBookmark so the following entries will be added at the end of the bookmark
// section to not mess with the users setup.
KBookmark after = findSystemBookmark(QLatin1String("Downloads"));
const QString musicFolder = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
if (QDir(musicFolder).exists()) {
after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Music").untranslatedText(),
QUrl::fromLocalFile(musicFolder),
QStringLiteral("folder-music"),
after);
}
const QString pictureFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
if (QDir(pictureFolder).exists()) {
after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Pictures").untranslatedText(),
QUrl::fromLocalFile(pictureFolder),
QStringLiteral("folder-pictures"),
after);
}
// Choosing the name "Videos" instead of "Movies", since that is how the folder
// is called normally on Linux: https://cgit.freedesktop.org/xdg/xdg-user-dirs/tree/user-dirs.defaults
const QString videoFolder = QStandardPaths::writableLocation(QStandardPaths::MoviesLocation);
if (QDir(videoFolder).exists()) {
after = createSystemBookmark(kli18nc("KFile System Bookmarks", "Videos").untranslatedText(),
QUrl::fromLocalFile(videoFolder),
QStringLiteral("folder-videos"),
after);
}
}
if (newFile) {
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 cause 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,
kli18nc("KFile System Bookmarks", "Recent Files").untranslatedText(),
QUrl(QStringLiteral("recentlyused:/files")),
QStringLiteral("document-open-recent"));
KBookmark recentDirectoriesBookmark = KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
kli18nc("KFile System Bookmarks", "Recent Locations").untranslatedText(),
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,
kli18nc("KFile System Bookmarks", "Modified Today").untranslatedText(),
QUrl(QStringLiteral("timeline:/today")),
QStringLiteral("go-jump-today"));
KFilePlacesItem::createSystemBookmark(d->bookmarkManager,
kli18nc("KFile System Bookmarks", "Modified Yesterday").untranslatedText(),
QUrl(QStringLiteral("timeline:/yesterday")),
QStringLiteral("view-calendar-day"));
}
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']");
}
if (KProtocolInfo::isKnownProtocol(QStringLiteral("afc"))) {
predicate = QLatin1Char('[') + predicate + QLatin1String(" OR PortableMediaPlayer.supportedProtocols == 'afc']");
}
d->predicate = Solid::Predicate::fromString(predicate);
Q_ASSERT(d->predicate.isValid());
connect(d->bookmarkManager, &KBookmarkManager::changed, this, [this]() {
d->reloadBookmarks();
});
d->reloadBookmarks();
QTimer::singleShot(0, this, [this]() {
d->initDeviceList();
});
}
KFilePlacesModel::~KFilePlacesModel() = default;
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<QIcon>();
}
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");
}
bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const
{
if (!index.isValid()) {
return false;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return isGroupHidden(item->groupType());
}
bool KFilePlacesModel::isDevice(const QModelIndex &index) const
{
if (!index.isValid()) {
return false;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return item->isDevice();
}
bool KFilePlacesModel::isTeardownAllowed(const QModelIndex &index) const
{
if (!index.isValid()) {
return false;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return item->isTeardownAllowed();
}
bool KFilePlacesModel::isEjectAllowed(const QModelIndex &index) const
{
if (!index.isValid()) {
return false;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return item->isEjectAllowed();
}
bool KFilePlacesModel::isTeardownOverlayRecommended(const QModelIndex &index) const
{
if (!index.isValid()) {
return false;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return item->isTeardownOverlayRecommended();
}
KFilePlacesModel::DeviceAccessibility KFilePlacesModel::deviceAccessibility(const QModelIndex &index) const
{
if (!index.isValid()) {
return KFilePlacesModel::Accessible;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(index.internalPointer());
return item->deviceAccessibility();
}
Solid::Device KFilePlacesModel::deviceForIndex(const QModelIndex &index) const
{
if (!index.isValid()) {
return Solid::Device();
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(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<KFilePlacesItem *>(index.internalPointer());
return item->bookmark();
}
KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const
{
if (!index.isValid()) {
return UnknownType;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(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<KFilePlacesItem *>(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();
}
QHash<int, QByteArray> KFilePlacesModel::roleNames() const
{
auto super = QAbstractItemModel::roleNames();
super[UrlRole] = "url";
super[HiddenRole] = "isHidden";
super[SetupNeededRole] = "isSetupNeeded";
super[FixedDeviceRole] = "isFixedDevice";
super[CapacityBarRecommendedRole] = "isCapacityBarRecommended";
super[GroupRole] = "group";
super[IconNameRole] = "iconName";
super[GroupHiddenRole] = "isGroupHidden";
super[TeardownAllowedRole] = "isTeardownAllowed";
super[EjectAllowedRole] = "isEjectAllowed";
super[TeardownOverlayRecommendedRole] = "isTeardownOverlayRecommended";
super[DeviceAccessibilityRole] = "deviceAccessibility";
return super;
}
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];
if (item->isHidden() || isGroupHidden(item->groupType())) {
continue;
}
const QUrl itemUrl = convertedUrl(item->data(UrlRole).toUrl());
if (itemUrl.matches(url, QUrl::StripTrailingSlash)
|| (itemUrl.isParentOf(url) && itemUrl.query() == url.query() && itemUrl.fragment() == url.fragment())) {
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 KFilePlacesModelPrivate::initDeviceList()
{
Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
QObject::connect(notifier, &Solid::DeviceNotifier::deviceAdded, q, [this](const QString &device) {
deviceAdded(device);
});
QObject::connect(notifier, &Solid::DeviceNotifier::deviceRemoved, q, [this](const QString &device) {
deviceRemoved(device);
});
availableDevices = Solid::Device::listFromQuery(predicate);
reloadBookmarks();
}
void KFilePlacesModelPrivate::deviceAdded(const QString &udi)
{
Solid::Device d(udi);
if (predicate.matches(d)) {
availableDevices << d;
reloadBookmarks();
}
}
void KFilePlacesModelPrivate::deviceRemoved(const QString &udi)
{
auto it = std::find_if(availableDevices.begin(), availableDevices.end(), [udi](const Solid::Device &device) {
return device.udi() == udi;
});
if (it != availableDevices.end()) {
availableDevices.erase(it);
reloadBookmarks();
}
}
void KFilePlacesModelPrivate::itemChanged(const QString &id, const QList<int> &roles)
{
for (int row = 0; row < items.size(); ++row) {
if (items.at(row)->id() == id) {
QModelIndex index = q->index(row, 0);
Q_EMIT q->dataChanged(index, index, roles);
}
}
}
void KFilePlacesModelPrivate::reloadBookmarks()
{
QList<KFilePlacesItem *> currentItems = loadBookmarkList();
QList<KFilePlacesItem *>::Iterator it_i = items.begin();
QList<KFilePlacesItem *>::Iterator it_c = currentItems.begin();
QList<KFilePlacesItem *>::Iterator end_i = items.end();
QList<KFilePlacesItem *>::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);
Q_EMIT q->dataChanged(idx, idx);
}
++it_i;
++it_c;
} else {
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();
Q_EMIT q->reloaded();
}
bool KFilePlacesModelPrivate::isBalooUrl(const QUrl &url) const
{
const QString scheme = url.scheme();
return ((scheme == QLatin1String("timeline")) || (scheme == QLatin1String("search")));
}
QList<KFilePlacesItem *> KFilePlacesModelPrivate::loadBookmarkList()
{
QList<KFilePlacesItem *> items;
KBookmarkGroup root = bookmarkManager->root();
KBookmark bookmark = root.first();
QList<Solid::Device> devices{availableDevices};
QList<QString> tagsList = tags;
while (!bookmark.isNull()) {
KFilePlacesItem *item = nullptr;
if (const QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); !udi.isEmpty()) {
const QString uuid = bookmark.metaDataItem(QStringLiteral("uuid"));
auto it = std::find_if(devices.begin(), devices.end(), [udi, uuid](const Solid::Device &device) {
if (!uuid.isEmpty()) {
auto storageVolume = device.as<Solid::StorageVolume>();
if (storageVolume && !storageVolume->uuid().isEmpty()) {
return storageVolume->uuid() == uuid;
}
}
return device.udi() == udi;
});
if (it != devices.end()) {
item = new KFilePlacesItem(bookmarkManager, bookmark.address(), it->udi(), q);
if (!item->hasSupportedScheme(supportedSchemes)) {
delete item;
item = nullptr;
}
devices.erase(it);
}
} else if (const QString tag = bookmark.metaDataItem(QStringLiteral("tag")); !tag.isEmpty()) {
auto it = std::find(tagsList.begin(), tagsList.end(), tag);
if (it != tagsList.end()) {
tagsList.erase(it);
item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q);
}
} else if (const QUrl url = bookmark.url(); url.isValid()) {
QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
bool allowedHere = appName.isEmpty() || appName == QCoreApplication::instance()->applicationName();
bool isSupportedUrl = isBalooUrl(url) ? fileIndexingEnabled : true;
bool isSupportedScheme = supportedSchemes.isEmpty() || supportedSchemes.contains(url.scheme());
if (isSupportedScheme && isSupportedUrl && allowedHere) {
// TODO: Update bookmark internal element
item = new KFilePlacesItem(bookmarkManager, bookmark.address(), QString(), q);
}
}
if (item) {
QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QList<int> &roles) {
itemChanged(id, roles);
});
items << item;
}
bookmark = root.next(bookmark);
}
// Add bookmarks for the remaining devices, they were previously unknown
for (const Solid::Device &device : std::as_const(devices)) {
bookmark = KFilePlacesItem::createDeviceBookmark(bookmarkManager, device);
if (!bookmark.isNull()) {
KFilePlacesItem *item = new KFilePlacesItem(bookmarkManager, bookmark.address(), device.udi(), q);
QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QList<int> &roles) {
itemChanged(id, roles);
});
// 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, q);
QObject::connect(item, &KFilePlacesItem::itemChanged, q, [this](const QString &id, const QList<int> &roles) {
itemChanged(id, roles);
});
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 KFilePlacesModelPrivate::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 KFilePlacesModelPrivate::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;
if (index.isValid()) {
res |= Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
if (!index.isValid()) {
res |= Qt::ItemIsDropEnabled;
}
return res;
}
QStringList KFilePlacesModel::mimeTypes() const
{
QStringList types;
types << KFilePlacesModelPrivate::internalMimeType(this) << QStringLiteral("text/uri-list");
return types;
}
QMimeData *KFilePlacesModel::mimeData(const QModelIndexList &indexes) const
{
QList<QUrl> urls;
QByteArray itemData;
QDataStream stream(&itemData, QIODevice::WriteOnly);
for (const QModelIndex &index : std::as_const(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(KFilePlacesModelPrivate::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(KFilePlacesModelPrivate::ignoreMimeType())) {
return false;
}
if (data->hasFormat(KFilePlacesModelPrivate::internalMimeType(this))) {
// The operation is an internal move
QByteArray itemData = data->data(KFilePlacesModelPrivate::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<QUrl> urls = KUrlMimeData::urlsFromMimeData(data);
KBookmarkGroup group = d->bookmarkManager->root();
for (const QUrl &url : urls) {
KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatBasic);
if (!job->exec()) {
Q_EMIT errorMessage(i18nc("Placeholder is error message", "Could not add to the Places panel: %1", job->errorString()));
continue;
}
KFileItem item(job->statResult(), url, true /*delayed mime types*/);
if (!item.isDir()) {
Q_EMIT errorMessage(i18n("Only folders can be added to the Places panel."));
continue;
}
KBookmark bookmark = KFilePlacesItem::createBookmark(d->bookmarkManager, item.text(), url, KIO::iconNameForUrl(url));
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<KFilePlacesItem *>(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<KFilePlacesItem *>(index.internalPointer());
if (item->isDevice()) {
return;
}
KBookmark bookmark = item->bookmark();
if (bookmark.isNull()) {
return;
}
QList<int> changedRoles;
bool changed = false;
if (text != bookmark.fullText()) {
bookmark.setFullText(text);
changed = true;
changedRoles << Qt::DisplayRole;
}
if (url != bookmark.url()) {
bookmark.setUrl(url);
changed = true;
changedRoles << KFilePlacesModel::UrlRole;
}
if (iconName != bookmark.icon()) {
bookmark.setIcon(iconName);
changed = true;
changedRoles << Qt::DecorationRole;
}
const QString onlyInApp = bookmark.metaDataItem(QStringLiteral("OnlyInApp"));
if (appName != onlyInApp) {
bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), appName);
changed = true;
}
if (changed) {
refresh();
Q_EMIT dataChanged(index, index, changedRoles);
}
}
void KFilePlacesModel::removePlace(const QModelIndex &index) const
{
if (!index.isValid()) {
return;
}
KFilePlacesItem *item = static_cast<KFilePlacesItem *>(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<KFilePlacesItem *>(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();
Q_EMIT dataChanged(index, index, {KFilePlacesModel::HiddenRole});
}
}
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();
Q_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 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);
QAction *action = nullptr;
if (device.is<Solid::StorageAccess>() && device.as<Solid::StorageAccess>()->isAccessible()) {
Solid::StorageDrive *drive = device.as<Solid::StorageDrive>();
if (drive == nullptr) {
drive = device.parent().as<Solid::StorageDrive>();
}
const bool teardownInProgress = deviceAccessibility(index) == KFilePlacesModel::TeardownInProgress;
bool hotpluggable = false;
bool removable = false;
if (drive != nullptr) {
hotpluggable = drive->isHotpluggable();
removable = drive->isRemovable();
}
QString iconName;
QString text;
if (device.is<Solid::OpticalDisc>()) {
if (teardownInProgress) {
text = i18nc("@action:inmenu", "Releasing…");
} else {
text = i18nc("@action:inmenu", "&Release");
}
} else if (removable || hotpluggable) {
if (teardownInProgress) {
text = i18nc("@action:inmenu", "Safely Removing…");
} else {
text = i18nc("@action:inmenu", "&Safely Remove");
}
iconName = QStringLiteral("media-eject");
} else {
if (teardownInProgress) {
text = i18nc("@action:inmenu", "Unmounting…");
} else {
text = i18nc("@action:inmenu", "&Unmount");
}
iconName = QStringLiteral("media-eject");
}
if (!iconName.isEmpty()) {
action = new QAction(QIcon::fromTheme(iconName), text, nullptr);
} else {
action = new QAction(text, nullptr);
}
if (teardownInProgress) {
action->setEnabled(false);
}
}
return action;
}
QAction *KFilePlacesModel::ejectActionForIndex(const QModelIndex &index) const
{
Solid::Device device = deviceForIndex(index);
if (device.is<Solid::OpticalDisc>()) {
QString text = i18nc("@action:inmenu", "&Eject");
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<Solid::StorageAccess>();
if (access != nullptr) {
d->teardownInProgress[access] = index;
const QString filePath = access->filePath();
connect(access, &Solid::StorageAccess::teardownDone, this, [this, access, filePath](Solid::ErrorType error, QVariant errorData) {
d->storageTeardownDone(filePath, error, errorData, access);
});
access->teardown();
}
}
void KFilePlacesModel::requestEject(const QModelIndex &index)
{
Solid::Device device = deviceForIndex(index);
Solid::OpticalDrive *drive = device.parent().as<Solid::OpticalDrive>();
if (drive != nullptr) {
d->teardownInProgress[drive] = index;
QString filePath;
Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
if (access) {
filePath = access->filePath();
}
connect(drive, &Solid::OpticalDrive::ejectDone, this, [this, filePath, drive](Solid::ErrorType error, QVariant errorData) {
d->storageTeardownDone(filePath, error, errorData, drive);
});
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);
Q_EMIT errorMessage(message);
}
}
void KFilePlacesModel::requestSetup(const QModelIndex &index)
{
Solid::Device device = deviceForIndex(index);
if (device.is<Solid::StorageAccess>() && !d->setupInProgress.contains(device.as<Solid::StorageAccess>())
&& !device.as<Solid::StorageAccess>()->isAccessible()) {
Solid::StorageAccess *access = device.as<Solid::StorageAccess>();
d->setupInProgress[access] = index;
connect(access, &Solid::StorageAccess::setupDone, this, [this, access](Solid::ErrorType error, QVariant errorData) {
d->storageSetupDone(error, errorData, access);
});
access->setup();
}
}
void KFilePlacesModelPrivate::storageSetupDone(Solid::ErrorType error, const QVariant &errorData, Solid::StorageAccess *sender)
{
QPersistentModelIndex index = setupInProgress.take(sender);
if (!index.isValid()) {
return;
}
if (!error) {
Q_EMIT q->setupDone(index, true);
} else {
if (errorData.isValid()) {
Q_EMIT q->errorMessage(i18n("An error occurred while accessing '%1', the system responded: %2", q->text(index), errorData.toString()));
} else {
Q_EMIT q->errorMessage(i18n("An error occurred while accessing '%1'", q->text(index)));
}
Q_EMIT q->setupDone(index, false);
}
}
void KFilePlacesModelPrivate::storageTeardownDone(const QString &filePath, Solid::ErrorType error, const QVariant &errorData, QObject *sender)
{
QPersistentModelIndex index = teardownInProgress.take(sender);
if (!index.isValid()) {
return;
}
if (error == Solid::ErrorType::DeviceBusy && !filePath.isEmpty()) {
auto *listOpenFilesJob = new KListOpenFilesJob(filePath);
QObject::connect(listOpenFilesJob, &KIO::Job::result, q, [this, index, error, errorData, listOpenFilesJob]() {
const auto blockingProcesses = listOpenFilesJob->processInfoList();
QStringList blockingApps;
blockingApps.reserve(blockingProcesses.count());
for (const auto &process : blockingProcesses) {
blockingApps << process.name();
}
Q_EMIT q->teardownDone(index, error, errorData);
if (blockingProcesses.isEmpty()) {
Q_EMIT q->errorMessage(i18n("One or more files on this device are open within an application."));
} else {
blockingApps.removeDuplicates();
Q_EMIT q->errorMessage(xi18np("One or more files on this device are opened in application <application>\"%2\"</application>.",
"One or more files on this device are opened in following applications: <application>%2</application>.",
blockingApps.count(),
blockingApps.join(i18nc("separator in list of apps blocking device unmount", ", "))));
}
});
listOpenFilesJob->start();
return;
}
Q_EMIT q->teardownDone(index, error, errorData);
if (error != Solid::ErrorType::NoError && error != Solid::ErrorType::UserCanceled) {
Q_EMIT q->errorMessage(errorData.toString());
}
}
void KFilePlacesModel::setSupportedSchemes(const QStringList &schemes)
{
d->supportedSchemes = schemes;
d->reloadBookmarks();
Q_EMIT supportedSchemesChanged();
}
QStringList KFilePlacesModel::supportedSchemes() const
{
return d->supportedSchemes;
}
namespace
{
QString partitionManagerPath()
{
static const QString path = QStandardPaths::findExecutable(QStringLiteral("partitionmanager"));
return path;
}
} // namespace
QAction *KFilePlacesModel::partitionActionForIndex(const QModelIndex &index) const
{
const auto device = deviceForIndex(index);
if (!device.is<Solid::Block>()) {
return nullptr;
}
// Not using kservice to find partitionmanager because we need to manually invoke it so we can pass the --device argument.
if (partitionManagerPath().isEmpty()) {
return nullptr;
}
auto action =
new QAction(QIcon::fromTheme(QStringLiteral("partitionmanager")), i18nc("@action:inmenu", "Reformat or Edit with Partition Manager"), nullptr);
connect(action, &QAction::triggered, this, [device] {
const auto block = device.as<Solid::Block>();
auto job = new KIO::CommandLauncherJob(partitionManagerPath(), {QStringLiteral("--device"), block->device()});
job->start();
});
return action;
}
#include "moc_kfileplacesmodel.cpp"