diff --git a/containments/desktop/package/contents/config/main.xml b/containments/desktop/package/contents/config/main.xml
index 8356d283b..2dc6a4eac 100644
--- a/containments/desktop/package/contents/config/main.xml
+++ b/containments/desktop/package/contents/config/main.xml
@@ -1,148 +1,148 @@
folder
false
true
desktop:/
1
0
0
false
0
false
true
false
true
true
true
-
- imagethumbnail,jpegthumbnail
+
+
0
4
2
white
true
*
0
all/all
true
true
true
true
diff --git a/containments/desktop/plugins/folder/foldermodel.cpp b/containments/desktop/plugins/folder/foldermodel.cpp
index b730551d5..0e05275dd 100644
--- a/containments/desktop/plugins/folder/foldermodel.cpp
+++ b/containments/desktop/plugins/folder/foldermodel.cpp
@@ -1,1998 +1,2007 @@
/***************************************************************************
* Copyright (C) 2006 David Faure *
* Copyright (C) 2008 Fredrik Höglund *
* Copyright (C) 2008 Rafael Fernández López *
* Copyright (C) 2011 Marco Martin *
* Copyright (C) 2014 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "foldermodel.h"
#include "itemviewadapter.h"
#include "positioner.h"
#include "screenmapper.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
#include
#include
Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel")
DirLister::DirLister(QObject *parent) : KDirLister(parent)
{
}
DirLister:: ~DirLister()
{
}
void DirLister::handleError(KIO::Job *job)
{
if (!autoErrorHandlingEnabled()) {
emit error(job->errorString());
return;
}
KDirLister::handleError(job);
}
FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent),
m_dirWatch(nullptr),
m_dragInProgress(false),
m_urlChangedWhileDragging(false),
m_dropTargetPositionsCleanup(new QTimer(this)),
m_previewGenerator(nullptr),
m_viewAdapter(nullptr),
m_actionCollection(this),
m_newMenu(nullptr),
m_fileItemActions(nullptr),
m_usedByContainment(false),
m_locked(true),
m_sortMode(0),
m_sortDesc(false),
m_sortDirsFirst(true),
m_parseDesktopFiles(false),
m_previews(false),
m_filterMode(NoFilter),
m_filterPatternMatchAll(true),
m_screenMapper(ScreenMapper::instance()),
m_complete(false)
{
//needed to pass the job around with qml
qmlRegisterType();
DirLister *dirLister = new DirLister(this);
dirLister->setDelayedMimeTypes(true);
dirLister->setAutoErrorHandlingEnabled(false, nullptr);
connect(dirLister, &DirLister::error, this, &FolderModel::dirListFailed);
connect(dirLister, &KCoreDirLister::itemsDeleted, this, &FolderModel::evictFromIsDirCache);
connect(dirLister, &KCoreDirLister::started, this, std::bind(&FolderModel::setStatus, this, Status::Listing));
void (KCoreDirLister::*myCompletedSignal)() = &KCoreDirLister::completed;
QObject::connect(dirLister, myCompletedSignal, this, [this] {
setStatus(Status::Ready);
emit listingCompleted();
});
void (KCoreDirLister::*myCanceledSignal)() = &KCoreDirLister::canceled;
QObject::connect(dirLister, myCanceledSignal, this, [this] {
setStatus(Status::Canceled);
emit listingCanceled();
});
m_dirModel = new KDirModel(this);
m_dirModel->setDirLister(dirLister);
m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable);
/*
* position dropped items at the desired target position
* delay this via queued connection, such that the row is available and can be mapped
* when we emit the move request
*/
connect(this, &QAbstractItemModel::rowsInserted,
this, [this](const QModelIndex &parent, int first, int last) {
for (int i = first; i <= last; ++i) {
const auto idx = index(i, 0, parent);
const auto url = itemForIndex(idx).url();
auto it = m_dropTargetPositions.find(url.fileName());
if (it != m_dropTargetPositions.end()) {
const auto pos = it.value();
m_dropTargetPositions.erase(it);
setSortMode(-1);
emit move(pos.x(), pos.y(), {url});
}
}
});
/*
* Dropped files may not actually show up as new files, e.g. when we overwrite
* an existing file. Or files that fail to be listed by the dirLister, or...
* To ensure we don't grow the map indefinitely, clean it up periodically.
* The cleanup timer is (re)started whenever we modify the map. We use a quite
* high interval of 10s. This should ensure, that we don't accidentally wipe
* the mapping when we actually still want to use it. Since the time between
* adding an entry in the map and it showing up in the model should be
* small, this should rarely, if ever happen.
*/
m_dropTargetPositionsCleanup->setInterval(10000);
m_dropTargetPositionsCleanup->setSingleShot(true);
connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() {
if (!m_dropTargetPositions.isEmpty()) {
qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions;
m_dropTargetPositions.clear();
}
});
m_selectionModel = new QItemSelectionModel(this, this);
connect(m_selectionModel, &QItemSelectionModel::selectionChanged,
this, &FolderModel::selectionChanged);
setSourceModel(m_dirModel);
setSortLocaleAware(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setDynamicSortFilter(true);
sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
createActions();
}
FolderModel::~FolderModel()
{
if (m_usedByContainment) {
// disconnect so we don't handle signals from the screen mapper when
// removeScreen is called
m_screenMapper->disconnect(this);
m_screenMapper->removeScreen(m_screen, resolvedUrl());
}
}
QHash< int, QByteArray > FolderModel::roleNames() const
{
return staticRoleNames();
}
QHash< int, QByteArray > FolderModel::staticRoleNames()
{
QHash roleNames;
roleNames[Qt::DisplayRole] = "display";
roleNames[Qt::DecorationRole] = "decoration";
roleNames[BlankRole] = "blank";
roleNames[OverlaysRole] = "overlays";
roleNames[SelectedRole] = "selected";
roleNames[IsDirRole] = "isDir";
roleNames[IsLinkRole] = "isLink";
roleNames[IsHiddenRole] = "isHidden";
roleNames[UrlRole] = "url";
roleNames[LinkDestinationUrl] = "linkDestinationUrl";
roleNames[SizeRole] = "size";
roleNames[TypeRole] = "type";
return roleNames;
}
void FolderModel::classBegin()
{
}
void FolderModel::componentComplete()
{
m_complete = true;
invalidate();
}
void FolderModel::invalidateIfComplete()
{
if (!m_complete) {
return;
}
invalidate();
}
void FolderModel::invalidateFilterIfComplete()
{
if (!m_complete) {
return;
}
invalidateFilter();
}
void FolderModel::newFileMenuItemCreated(const QUrl &url)
{
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal);
m_dropTargetPositions.insert(url.fileName(), m_menuPosition);
m_menuPosition = {};
m_dropTargetPositionsCleanup->start();
}
}
QString FolderModel::url() const
{
return m_url;
}
void FolderModel::setUrl(const QString& url)
{
const QUrl &resolvedNewUrl = resolve(url);
if (url == m_url) {
m_dirModel->dirLister()->updateDirectory(resolvedNewUrl);
return;
}
const auto oldUrl = resolvedUrl();
beginResetModel();
m_url = url;
m_isDirCache.clear();
m_dirModel->dirLister()->openUrl(resolvedNewUrl);
clearDragImages();
m_dragIndexes.clear();
endResetModel();
emit urlChanged();
emit resolvedUrlChanged();
m_errorString.clear();
emit errorStringChanged();
if (m_dirWatch) {
delete m_dirWatch;
m_dirWatch = nullptr;
}
if (resolvedNewUrl.isValid()) {
m_dirWatch = new KDirWatch(this);
connect(m_dirWatch, &KDirWatch::created, this, &FolderModel::iconNameChanged);
connect(m_dirWatch, &KDirWatch::dirty, this, &FolderModel::iconNameChanged);
m_dirWatch->addFile(resolvedNewUrl.toLocalFile() + QLatin1String("/.directory"));
}
if (m_dragInProgress) {
m_urlChangedWhileDragging = true;
}
emit iconNameChanged();
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
m_screenMapper->removeScreen(m_screen, oldUrl);
m_screenMapper->addScreen(m_screen, resolvedUrl());
}
}
QUrl FolderModel::resolvedUrl() const
{
return m_dirModel->dirLister()->url();
}
QUrl FolderModel::resolve(const QString& url)
{
QUrl resolvedUrl;
if (url.startsWith(QLatin1Char('~'))) {
resolvedUrl = QUrl::fromLocalFile(KShell::tildeExpand(url));
} else {
resolvedUrl = QUrl::fromUserInput(url);
}
return resolvedUrl;
}
QString FolderModel::iconName() const
{
const KFileItem rootItem(m_dirModel->dirLister()->url());
if (!rootItem.isFinalIconKnown()) {
rootItem.determineMimeType();
}
return rootItem.iconName();
}
FolderModel::Status FolderModel::status() const
{
return m_status;
}
void FolderModel::setStatus(Status status)
{
if (m_status != status) {
m_status = status;
emit statusChanged();
}
}
QString FolderModel::errorString() const
{
return m_errorString;
}
bool FolderModel::dragging() const
{
return m_dragInProgress;
}
bool FolderModel::usedByContainment() const
{
return m_usedByContainment;
}
void FolderModel::setUsedByContainment(bool used)
{
if (m_usedByContainment != used) {
m_usedByContainment = used;
QAction *action = m_actionCollection.action(QStringLiteral("refresh"));
if (action) {
action->setText(m_usedByContainment ? i18n("&Refresh Desktop") : i18n("&Refresh View"));
action->setIcon(m_usedByContainment ? QIcon::fromTheme(QStringLiteral("user-desktop")) : QIcon::fromTheme(QStringLiteral("view-refresh")));
}
m_screenMapper->disconnect(this);
connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete);
connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete);
emit usedByContainmentChanged();
}
}
bool FolderModel::locked() const
{
return m_locked;
}
void FolderModel::setLocked(bool locked)
{
if (m_locked != locked) {
m_locked = locked;
emit lockedChanged();
}
}
void FolderModel::dirListFailed(const QString& error)
{
m_errorString = error;
emit errorStringChanged();
}
int FolderModel::sortMode() const
{
return m_sortMode;
}
void FolderModel::setSortMode(int mode)
{
if (m_sortMode != mode) {
m_sortMode = mode;
if (mode == -1 /* Unsorted */) {
setDynamicSortFilter(false);
} else {
invalidateIfComplete();
sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
setDynamicSortFilter(true);
}
emit sortModeChanged();
}
}
bool FolderModel::sortDesc() const
{
return m_sortDesc;
}
void FolderModel::setSortDesc(bool desc)
{
if (m_sortDesc != desc) {
m_sortDesc = desc;
if (m_sortMode != -1 /* Unsorted */) {
invalidateIfComplete();
sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
}
emit sortDescChanged();
}
}
bool FolderModel::sortDirsFirst() const
{
return m_sortDirsFirst;
}
void FolderModel::setSortDirsFirst(bool enable)
{
if (m_sortDirsFirst != enable) {
m_sortDirsFirst = enable;
if (m_sortMode != -1 /* Unsorted */) {
invalidateIfComplete();
sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
}
emit sortDirsFirstChanged();
}
}
bool FolderModel::parseDesktopFiles() const
{
return m_parseDesktopFiles;
}
void FolderModel::setParseDesktopFiles(bool enable)
{
if (m_parseDesktopFiles != enable) {
m_parseDesktopFiles = enable;
emit parseDesktopFilesChanged();
}
}
QObject* FolderModel::viewAdapter() const
{
return m_viewAdapter;
}
void FolderModel::setViewAdapter(QObject* adapter)
{
if (m_viewAdapter != adapter) {
KAbstractViewAdapter *abstractViewAdapter = dynamic_cast(adapter);
m_viewAdapter = abstractViewAdapter;
if (m_viewAdapter && !m_previewGenerator) {
m_previewGenerator = new KFilePreviewGenerator(abstractViewAdapter, this);
m_previewGenerator->setPreviewShown(m_previews);
- m_previewGenerator->setEnabledPlugins(m_previewPlugins);
+ m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins);
}
emit viewAdapterChanged();
}
}
bool FolderModel::previews() const
{
return m_previews;
}
void FolderModel::setPreviews(bool previews)
{
if (m_previews != previews) {
m_previews = previews;
if (m_previewGenerator) {
m_previewGenerator->setPreviewShown(m_previews);
}
emit previewsChanged();
}
}
QStringList FolderModel::previewPlugins() const
{
return m_previewPlugins;
}
void FolderModel::setPreviewPlugins(const QStringList& previewPlugins)
{
- if (m_previewPlugins != previewPlugins) {
- m_previewPlugins = previewPlugins;
+ QStringList effectivePlugins = previewPlugins;
+ if (effectivePlugins.isEmpty()) {
+ effectivePlugins = KIO::PreviewJob::defaultPlugins();
+ }
+
+ if (m_effectivePreviewPlugins != effectivePlugins) {
+ m_effectivePreviewPlugins = effectivePlugins;
if (m_previewGenerator) {
m_previewGenerator->setPreviewShown(false);
- m_previewGenerator->setEnabledPlugins(m_previewPlugins);
+ m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins);
m_previewGenerator->setPreviewShown(true);
}
+ }
+ if (m_previewPlugins != previewPlugins) {
+ m_previewPlugins = previewPlugins;
emit previewPluginsChanged();
}
}
int FolderModel::filterMode() const
{
return m_filterMode;
}
void FolderModel::setFilterMode(int filterMode)
{
if (m_filterMode != (FilterMode)filterMode) {
m_filterMode = (FilterMode)filterMode;
invalidateFilterIfComplete();
emit filterModeChanged();
}
}
QString FolderModel::filterPattern() const
{
return m_filterPattern;
}
void FolderModel::setFilterPattern(const QString &pattern)
{
if (m_filterPattern == pattern) {
return;
}
m_filterPattern = pattern;
m_filterPatternMatchAll = (pattern == QLatin1String("*"));
const QStringList patterns = pattern.split(QLatin1Char(' '));
m_regExps.clear();
m_regExps.reserve(patterns.count());
foreach (const QString &pattern, patterns) {
QRegExp rx(pattern);
rx.setPatternSyntax(QRegExp::Wildcard);
rx.setCaseSensitivity(Qt::CaseInsensitive);
m_regExps.append(rx);
}
invalidateFilterIfComplete();
emit filterPatternChanged();
}
QStringList FolderModel::filterMimeTypes() const
{
return m_mimeSet.toList();
}
void FolderModel::setFilterMimeTypes(const QStringList &mimeList)
{
const QSet &set = QSet::fromList(mimeList);
if (m_mimeSet != set) {
m_mimeSet = set;
invalidateFilterIfComplete();
emit filterMimeTypesChanged();
}
}
void FolderModel::setScreen(int screen)
{
if (m_screen == screen)
return;
m_screen = screen;
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
m_screenMapper->addScreen(screen, resolvedUrl());
}
emit screenChanged();
}
KFileItem FolderModel::rootItem() const
{
return m_dirModel->dirLister()->rootItem();
}
void FolderModel::up()
{
const QUrl &up = KIO::upUrl(resolvedUrl());
if (up.isValid()) {
setUrl(up.toString());
}
}
void FolderModel::cd(int row)
{
if (row < 0) {
return;
}
const QModelIndex idx = index(row, 0);
bool isDir = data(idx, IsDirRole).toBool();
if (isDir) {
const KFileItem item = itemForIndex(idx);
if (m_parseDesktopFiles && item.isDesktopFile()) {
const KDesktopFile file(item.targetUrl().path());
if (file.hasLinkType()) {
setUrl(file.readUrl());
}
} else {
setUrl(item.targetUrl().toString());
}
}
}
void FolderModel::run(int row)
{
if (row < 0) {
return;
}
KFileItem item = itemForIndex(index(row, 0));
QUrl url(item.targetUrl());
// FIXME TODO: This can go once we depend on a KIO w/ fe1f50caaf2.
if (url.scheme().isEmpty()) {
url.setScheme(QStringLiteral("file"));
}
KRun *run = new KRun(url, nullptr);
// On desktop:/ we want to be able to run .desktop files right away,
// otherwise ask for security reasons. We also don't use the targetUrl()
// from above since we don't want the resolved /home/foo/Desktop URL.
run->setShowScriptExecutionPrompt(item.url().scheme() != QLatin1String("desktop")
|| item.url().adjusted(QUrl::RemoveFilename).path() != QLatin1String("/"));
}
void FolderModel::runSelected()
{
if (!m_selectionModel->hasSelection()) {
return;
}
if (m_selectionModel->selectedIndexes().count() == 1) {
run(m_selectionModel->selectedIndexes().constFirst().row());
return;
}
KFileItemActions fileItemActions(this);
KFileItemList items;
foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) {
// Skip over directories.
if (!index.data(IsDirRole).toBool()) {
items << itemForIndex(index);
}
}
fileItemActions.runPreferredApplications(items, QString());
}
void FolderModel::rename(int row, const QString& name)
{
if (row < 0) {
return;
}
QModelIndex idx = index(row, 0);
m_dirModel->setData(mapToSource(idx), name, Qt::EditRole);
}
int FolderModel::fileExtensionBoundary(int row)
{
const QModelIndex idx = index(row, 0);
const QString &name = data(idx, Qt::DisplayRole).toString();
int boundary = name.length();
if (data(idx, IsDirRole).toBool()) {
return boundary;
}
QMimeDatabase db;
const QString &ext = db.suffixForFileName(name);
if (ext.isEmpty()) {
boundary = name.lastIndexOf(QLatin1Char('.'));
if (boundary < 1) {
boundary = name.length();
}
} else {
boundary -= ext.length() + 1;
}
return boundary;
}
bool FolderModel::hasSelection() const
{
return m_selectionModel->hasSelection();
}
bool FolderModel::isSelected(int row)
{
if (row < 0) {
return false;
}
return m_selectionModel->isSelected(index(row, 0));
}
void FolderModel::setSelected(int row)
{
if (row < 0) {
return;
}
m_selectionModel->select(index(row, 0), QItemSelectionModel::Select);
}
void FolderModel::toggleSelected(int row)
{
if (row < 0) {
return;
}
m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle);
}
void FolderModel::setRangeSelected(int anchor, int to)
{
if (anchor < 0 || to < 0) {
return;
}
QItemSelection selection(index(anchor, 0), index(to, 0));
m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
}
void FolderModel::updateSelection(const QVariantList &rows, bool toggle)
{
QItemSelection newSelection;
int iRow = -1;
foreach (const QVariant &row, rows) {
iRow = row.toInt();
if (iRow < 0) {
return;
}
const QModelIndex &idx = index(iRow, 0);
newSelection.select(idx, idx);
}
if (toggle) {
QItemSelection pinnedSelection = m_pinnedSelection;
pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle);
m_selectionModel->select(pinnedSelection, QItemSelectionModel::ClearAndSelect);
} else {
m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect);
}
}
void FolderModel::clearSelection()
{
if (m_selectionModel->hasSelection()) {
m_selectionModel->clear();
}
}
void FolderModel::pinSelection()
{
m_pinnedSelection = m_selectionModel->selection();
}
void FolderModel::unpinSelection()
{
m_pinnedSelection = QItemSelection();
}
void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image)
{
if (row < 0) {
return;
}
delete m_dragImages.take(row);
DragImage *dragImage = new DragImage();
dragImage->row = row;
dragImage->rect = QRect(x, y, width, height);
dragImage->image = image.value();
dragImage->blank = false;
m_dragImages.insert(row, dragImage);
}
void FolderModel::clearDragImages()
{
qDeleteAll(m_dragImages);
m_dragImages.clear();
}
void FolderModel::setDragHotSpotScrollOffset(int x, int y)
{
m_dragHotSpotScrollOffset.setX(x);
m_dragHotSpotScrollOffset.setY(y);
}
QPoint FolderModel::dragCursorOffset(int row)
{
DragImage *image = m_dragImages.value(row);
if (!image) {
return QPoint(0, 0);
}
return image->cursorOffset;
}
void FolderModel::addDragImage(QDrag *drag, int x, int y)
{
if (!drag || m_dragImages.isEmpty()) {
return;
}
QRegion region;
foreach (DragImage *image, m_dragImages) {
image->blank = isBlank(image->row);
image->rect.translate(-m_dragHotSpotScrollOffset.x(), -m_dragHotSpotScrollOffset.y());
if (!image->blank && !image->image.isNull()) {
region = region.united(image->rect);
}
}
QRect rect = region.boundingRect();
QPoint offset = rect.topLeft();
rect.translate(-offset.x(), -offset.y());
QImage dragImage(rect.size(), QImage::Format_RGBA8888);
dragImage.fill(Qt::transparent);
QPainter painter(&dragImage);
QPoint pos;
foreach (DragImage *image, m_dragImages) {
if (!image->blank && !image->image.isNull()) {
pos = image->rect.translated(-offset.x(), -offset.y()).topLeft();
image->cursorOffset.setX(pos.x() - (x - offset.x()));
image->cursorOffset.setY(pos.y() - (y - offset.y()));
painter.drawImage(pos, image->image);
}
// FIXME HACK: Operate on copy.
image->rect.translate(m_dragHotSpotScrollOffset.x(), m_dragHotSpotScrollOffset.y());
}
drag->setPixmap(QPixmap::fromImage(dragImage));
drag->setHotSpot(QPoint(x - offset.x(), y - offset.y()));
}
void FolderModel::dragSelected(int x, int y)
{
if (m_dragInProgress) {
return;
}
m_dragInProgress = true;
emit draggingChanged();
m_urlChangedWhileDragging = false;
// Avoid starting a drag synchronously in a mouse handler or interferes with
// child event filtering in parent items (and thus e.g. press-and-hold hand-
// ling in a containment).
QMetaObject::invokeMethod(this, "dragSelectedInternal", Qt::QueuedConnection,
Q_ARG(int, x),
Q_ARG(int, y));
}
void FolderModel::dragSelectedInternal(int x, int y)
{
if (!m_viewAdapter || !m_selectionModel->hasSelection()) {
m_dragInProgress = false;
emit draggingChanged();
return;
}
ItemViewAdapter *adapter = qobject_cast(m_viewAdapter);
QQuickItem *item = qobject_cast(adapter->adapterView());
QDrag *drag = new QDrag(item);
addDragImage(drag, x, y);
m_dragIndexes = m_selectionModel->selectedIndexes();
qSort(m_dragIndexes.begin(), m_dragIndexes.end());
// TODO: Optimize to emit contiguous groups.
emit dataChanged(m_dragIndexes.first(), m_dragIndexes.last(), QVector() << BlankRole);
QModelIndexList sourceDragIndexes;
sourceDragIndexes.reserve(m_dragIndexes.count());
foreach (const QModelIndex &index, m_dragIndexes) {
sourceDragIndexes.append(mapToSource(index));
}
drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes));
// Due to spring-loading (aka auto-expand), the URL might change
// while the drag is in-flight - in that case we don't want to
// unnecessarily emit dataChanged() for (possibly invalid) indices
// after it ends.
const QUrl currentUrl(m_dirModel->dirLister()->url());
item->grabMouse();
drag->exec(supportedDragActions());
item->ungrabMouse();
m_dragInProgress = false;
emit draggingChanged();
m_urlChangedWhileDragging = false;
if (m_dirModel->dirLister()->url() == currentUrl) {
const QModelIndex first(m_dragIndexes.first());
const QModelIndex last(m_dragIndexes.last());
m_dragIndexes.clear();
// TODO: Optimize to emit contiguous groups.
emit dataChanged(first, last, QVector() << BlankRole);
}
}
static bool isDropBetweenSharedViews(const QList &urls, const QUrl &folderUrl)
{
for (const auto &url : urls) {
if (folderUrl.adjusted(QUrl::StripTrailingSlash)
!= url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)) {
return false;
}
}
return true;
}
void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row)
{
QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value());
if (!mimeData) {
return;
}
QModelIndex idx;
KFileItem item;
if (row > -1 && row < rowCount()) {
idx = index(row, 0);
item = itemForIndex(idx);
}
QUrl dropTargetUrl;
// So we get to run mostLocalUrl() over the current URL.
if (item.isNull()) {
item = rootItem();
}
if (item.isNull()) {
dropTargetUrl = m_dirModel->dirLister()->url();
} else if (m_parseDesktopFiles && item.isDesktopFile()) {
const KDesktopFile file(item.targetUrl().path());
if (file.hasLinkType()) {
dropTargetUrl = QUrl(file.readUrl());
} else {
dropTargetUrl = item.mostLocalUrl();
}
} else {
dropTargetUrl = item.mostLocalUrl();
}
auto dropTargetFolderUrl = dropTargetUrl;
if (dropTargetFolderUrl.fileName() == QLatin1String(".")) {
// the target URL for desktop:/ is e.g. 'file://home/user/Desktop/.'
dropTargetFolderUrl = dropTargetFolderUrl.adjusted(QUrl::RemoveFilename);
}
// use dropTargetUrl to resolve desktop:/ to the actual file location which is also used by the mime data
/* QMimeData operates on local URLs, but the dir lister and thus screen mapper and positioner may
* use a fancy scheme like desktop:/ instead. Ensure we always use the latter to properly map URLs,
* i.e. go from file:///home/user/Desktop/file to desktop:/file
*/
auto mappableUrl = [this, dropTargetFolderUrl](const QUrl &url) -> QUrl {
if (dropTargetFolderUrl != m_dirModel->dirLister()->url()) {
QString mappedUrl = url.toString();
const auto local = dropTargetFolderUrl.toString();
const auto internal = m_dirModel->dirLister()->url().toString();
if (mappedUrl.startsWith(local)) {
mappedUrl.replace(0, local.size(), internal);
}
return ScreenMapper::stringToUrl(mappedUrl);
}
return url;
};
const int x = dropEvent->property("x").toInt();
const int y = dropEvent->property("y").toInt();
const QPoint dropPos = {x, y};
if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) {
if (m_locked || mimeData->urls().isEmpty()) {
return;
}
setSortMode(-1);
for (const auto &url : mimeData->urls()) {
m_dropTargetPositions.insert(url.fileName(), dropPos);
m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal);
m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url));
}
emit move(x, y, mimeData->urls());
return;
}
if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) &&
mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) {
const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service"));
const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path"));
QDBusMessage message =
QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath,
QStringLiteral("org.kde.ark.DndExtract"),
QStringLiteral("extractSelectedFilesTo"));
message.setArguments({dropTargetUrl.toDisplayString(QUrl::PreferLocalFile)});
QDBusConnection::sessionBus().call(message, QDBus::NoBlock);
return;
}
if (idx.isValid() && !(flags(idx) & Qt::ItemIsDropEnabled)) {
return;
}
// Catch drops from a Task Manager and convert to usable URL.
if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) {
QList urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))};
mimeData->setUrls(urls);
}
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
if (isDropBetweenSharedViews(mimeData->urls(), dropTargetFolderUrl)) {
setSortMode(-1);
for (const auto &url : mimeData->urls()) {
m_dropTargetPositions.insert(url.fileName(), dropPos);
m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal);
m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url));
}
m_dropTargetPositionsCleanup->start();
return;
}
}
Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt());
Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt());
Qt::MouseButtons buttons(dropEvent->property("buttons").toInt());
Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt());
auto pos = target->mapToScene(dropPos).toPoint();
pos = target->window()->mapToGlobal(pos);
QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers);
ev.setDropAction(proposedAction);
KIO::DropJob *dropJob = KIO::drop(&ev, dropTargetUrl);
dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true);
// The QMimeData we extract from the DropArea's drop event is deleted as soon as this method
// ends but we need to keep a copy for when popupMenuAboutToShow fires.
QMimeData *mimeCopy = new QMimeData();
for (const QString &format : mimeData->formats()) {
mimeCopy->setData(format, mimeData->data(format));
}
connect(dropJob, &KIO::DropJob::popupMenuAboutToShow, this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) {
emit popupMenuAboutToShow(dropJob, mimeCopy, x, y);
mimeCopy->deleteLater();
});
/*
* Position files that come from a drag'n'drop event at the drop event
* target position. To do so, we first listen to copy job to figure out
* the target URL. Then we store the position of this drop event in the
* hash and eventually trigger a move request when we get notified about
* the new file event from the source model.
*/
connect(dropJob, &KIO::DropJob::copyJobStarted, this, [this, dropPos, dropTargetUrl](KIO::CopyJob* copyJob) {
auto map = [this, dropPos, dropTargetUrl](const QUrl &targetUrl) {
m_dropTargetPositions.insert(targetUrl.fileName(), dropPos);
m_dropTargetPositionsCleanup->start();
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
// assign a screen for the item before the copy is actually done, so
// filterAcceptsRow doesn't assign the default screen to it
QUrl url = resolvedUrl();
// if the folderview's folder is a standard path, just use the targetUrl for mapping
if (targetUrl.toString().startsWith(url.toString())) {
m_screenMapper->addMapping(targetUrl, m_screen, ScreenMapper::DelayedSignal);
} else if (targetUrl.toString().startsWith(dropTargetUrl.toString())) {
// if the folderview's folder is a special path, like desktop:// , we need to convert
// the targetUrl file:// path to a desktop:/ path for mapping
auto destPath = dropTargetUrl.path();
auto filePath = targetUrl.path();
if (filePath.startsWith(destPath)) {
url.setPath(filePath.remove(0, destPath.length()));
m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal);
}
}
}
};
// remember drop target position for target URL and forget about the source URL
connect(copyJob, &KIO::CopyJob::copyingDone,
this, [this, map](KIO::Job *, const QUrl &, const QUrl &targetUrl, const QDateTime &, bool, bool) {
map(targetUrl);
});
connect(copyJob, &KIO::CopyJob::copyingLinkDone,
this, [this, map](KIO::Job *, const QUrl &, const QString &, const QUrl &targetUrl) {
map(targetUrl);
});
});
}
void FolderModel::dropCwd(QObject* dropEvent)
{
QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value());
if (!mimeData) {
return;
}
if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) &&
mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) {
const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service"));
const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path"));
QDBusMessage message =
QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath,
QStringLiteral("org.kde.ark.DndExtract"),
QStringLiteral("extractSelectedFilesTo"));
message.setArguments(QVariantList() << m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile).toString());
QDBusConnection::sessionBus().call(message, QDBus::NoBlock);
} else {
Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt());
Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt());
Qt::MouseButtons buttons(dropEvent->property("buttons").toInt());
Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt());
QDropEvent ev(QPoint(), possibleActions, mimeData, buttons, modifiers);
ev.setDropAction(proposedAction);
KIO::DropJob *dropJob = KIO::drop(&ev, m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile));
dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true);
}
}
void FolderModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
QModelIndexList indices = selected.indexes();
indices.append(deselected.indexes());
QVector roles;
roles.append(SelectedRole);
foreach(const QModelIndex &index, indices) {
emit dataChanged(index, index, roles);
}
if (!m_selectionModel->hasSelection()) {
clearDragImages();
} else {
foreach (const QModelIndex &idx, deselected.indexes()) {
delete m_dragImages.take(idx.row());
}
}
}
bool FolderModel::isBlank(int row) const
{
if (row < 0) {
return true;
}
return data(index(row, 0), BlankRole).toBool();
}
QVariant FolderModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (role == BlankRole) {
return m_dragIndexes.contains(index);
} else if (role == OverlaysRole) {
const KFileItem item = itemForIndex(index);
return item.overlays();
} else if (role == SelectedRole) {
return m_selectionModel->isSelected(index);
} else if (role == IsDirRole) {
return isDir(mapToSource(index), m_dirModel);
} else if (role == IsLinkRole) {
const KFileItem item = itemForIndex(index);
return item.isLink();
} else if (role == IsHiddenRole) {
const KFileItem item = itemForIndex(index);
return item.isHidden();
} else if (role == UrlRole) {
return itemForIndex(index).url();
} else if (role == LinkDestinationUrl) {
const KFileItem item = itemForIndex(index);
if (m_parseDesktopFiles && item.isDesktopFile()) {
const KDesktopFile file(item.targetUrl().path());
if (file.hasLinkType()) {
return file.readUrl();
}
}
return item.targetUrl();
} else if (role == SizeRole) {
bool isDir = data(index, IsDirRole).toBool();
if (!isDir) {
return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 1)), Qt::DisplayRole);
}
} else if (role == TypeRole) {
return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 6)), Qt::DisplayRole);
} else if (role == FileNameRole) {
return itemForIndex(index).url().fileName();
}
return QSortFilterProxyModel::data(index, role);
}
int FolderModel::indexForUrl(const QUrl& url) const
{
return mapFromSource(m_dirModel->indexForUrl(url)).row();
}
KFileItem FolderModel::itemForIndex(const QModelIndex &index) const
{
return m_dirModel->itemForIndex(mapToSource(index));
}
bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const
{
KFileItem item = dirModel->itemForIndex(index);
if (item.isDir()) {
return true;
}
auto it = m_isDirCache.constFind(item.url());
if (it != m_isDirCache.constEnd()) {
return *it;
}
if (m_parseDesktopFiles && item.isDesktopFile()) {
// Check if the desktop file is a link to a directory
KDesktopFile file(item.targetUrl().path());
if (!file.hasLinkType()) {
return false;
}
const QUrl url(file.readUrl());
// Check if we already have a running StatJob for this URL.
if (m_isDirJobs.contains(item.url())) {
return false;
}
if (KProtocolInfo::protocolClass(url.scheme()) != QStringLiteral(":local")) {
return false;
}
KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo);
job->setProperty("org.kde.plasma.folder_url", item.url());
job->setSide(KIO::StatJob::SourceSide);
job->setDetails(0);
connect(job, &KJob::result, this, &FolderModel::statResult);
m_isDirJobs.insert(item.url(), job);
}
return false;
}
void FolderModel::statResult(KJob *job)
{
KIO::StatJob *statJob = static_cast(job);
const QUrl &url = statJob->property("org.kde.plasma.folder_url").toUrl();
const QModelIndex &idx = index(indexForUrl(url), 0);
if (idx.isValid() && statJob->error() == KJob::NoError) {
m_isDirCache[url] = statJob->statResult().isDir();
emit dataChanged(idx, idx, QVector() << IsDirRole);
}
m_isDirJobs.remove(url);
}
void FolderModel::evictFromIsDirCache(const KFileItemList& items)
{
foreach (const KFileItem &item, items) {
m_screenMapper->removeFromMap(item.url());
m_isDirCache.remove(item.url());
}
}
bool FolderModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
const KDirModel *dirModel = static_cast(sourceModel());
if (m_sortDirsFirst || left.column() == KDirModel::Size) {
bool leftIsDir = isDir(left, dirModel);
bool rightIsDir = isDir(right, dirModel);
if (leftIsDir && !rightIsDir) {
return (sortOrder() == Qt::AscendingOrder);
}
if (!leftIsDir && rightIsDir) {
return (sortOrder() == Qt::DescendingOrder);
}
}
const KFileItem leftItem = dirModel->data(left, KDirModel::FileItemRole).value();
const KFileItem rightItem = dirModel->data(right, KDirModel::FileItemRole).value();
const int column = left.column();
int result = 0;
switch (column) {
case KDirModel::Size: {
if (isDir(left, dirModel) && isDir(right, dirModel)) {
const int leftChildCount = dirModel->data(left, KDirModel::ChildCountRole).toInt();
const int rightChildCount = dirModel->data(right, KDirModel::ChildCountRole).toInt();
if (leftChildCount < rightChildCount)
result = -1;
else if (leftChildCount > rightChildCount)
result = +1;
} else {
const KIO::filesize_t leftSize = leftItem.size();
const KIO::filesize_t rightSize = rightItem.size();
if (leftSize < rightSize)
result = -1;
else if (leftSize > rightSize)
result = +1;
}
break;
}
case KDirModel::ModifiedTime: {
const QDateTime leftTime = leftItem.time(KFileItem::ModificationTime);
const QDateTime rightTime = rightItem.time(KFileItem::ModificationTime);
if (leftTime < rightTime)
result = -1;
else if (leftTime > rightTime)
result = +1;
break;
}
case KDirModel::Type:
result = QString::compare(dirModel->data(left, Qt::DisplayRole).toString(),
dirModel->data(right, Qt::DisplayRole).toString());
break;
default:
break;
}
if (result != 0)
return result < 0;
QCollator collator;
result = collator.compare(leftItem.text(), rightItem.text());
if (result != 0)
return result < 0;
result = collator.compare(leftItem.name(), rightItem.name());
if (result != 0)
return result < 0;
return QString::compare(leftItem.url().url(), rightItem.url().url(), Qt::CaseSensitive);
}
Qt::DropActions FolderModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction;
}
inline bool FolderModel::matchMimeType(const KFileItem &item) const
{
if (m_mimeSet.isEmpty()) {
return false;
}
if (m_mimeSet.contains(QStringLiteral("all/all")) || m_mimeSet.contains(QStringLiteral("all/allfiles"))) {
return true;
}
const QString mimeType = item.determineMimeType().name();
return m_mimeSet.contains(mimeType);
}
inline bool FolderModel::matchPattern(const KFileItem &item) const
{
if (m_filterPatternMatchAll) {
return true;
}
const QString name = item.name();
QListIterator i(m_regExps);
while (i.hasNext()) {
if (i.next().exactMatch(name)) {
return true;
}
}
return false;
}
bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const KDirModel *dirModel = static_cast(sourceModel());
const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent));
if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
const QUrl url = item.url();
const int screen = m_screenMapper->screenForItem(url);
// don't do anything if the folderview is not associated with a screen
if (m_screen != -1) {
if (screen == -1) {
// The item is not associated with a screen, probably because this is the first
// time we see it or the folderview was previously used as a regular applet.
// Associated with this folderview if the view is on the first available screen
if (m_screen == m_screenMapper->firstAvailableScreen(resolvedUrl())) {
m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal);
} else {
return false;
}
} else if (m_screen != screen) {
// the item belongs to a different screen, filter it out
return false;
}
}
}
if (m_filterMode == NoFilter) {
return true;
}
if (m_filterMode == FilterShowMatches) {
return (matchPattern(item) && matchMimeType(item));
} else {
return !(matchPattern(item) && matchMimeType(item));
}
}
void FolderModel::createActions()
{
KIO::FileUndoManager *manager = KIO::FileUndoManager::self();
QAction *cut = KStandardAction::cut(this, &FolderModel::cut, this);
QAction *copy = KStandardAction::copy(this, &FolderModel::copy, this);
QAction *undo = KStandardAction::undo(manager, &KIO::FileUndoManager::undo, this);
undo->setEnabled(manager->undoAvailable());
undo->setShortcutContext(Qt::WidgetShortcut);
connect(manager, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool)));
connect(manager, &KIO::FileUndoManager::undoTextChanged, this, &FolderModel::undoTextChanged);
QAction *paste = KStandardAction::paste(this, &FolderModel::paste, this);
QAction *pasteTo = KStandardAction::paste(this, &FolderModel::pasteTo, this);
QAction *refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Refresh View"), this);
refresh->setShortcut(QKeySequence(QKeySequence::Refresh));
connect(refresh, &QAction::triggered, this, &FolderModel::refresh);
QAction *rename = KStandardAction::renameFile(this, &FolderModel::requestRename, this);
QAction *trash = KStandardAction::moveToTrash(this, &FolderModel::moveSelectedToTrash, this);
QAction *emptyTrash = new QAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("&Empty Trash Bin"), this);
connect(emptyTrash, &QAction::triggered, this, &FolderModel::emptyTrashBin);
QAction *restoreFromTrash = new QAction(i18nc("Restore from trash", "Restore"), this);
connect(restoreFromTrash, &QAction::triggered, this, &FolderModel::restoreSelectedFromTrash);
QAction *del = KStandardAction::deleteFile(this, &FolderModel::deleteSelected, this);
QAction *actOpen = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("&Open"), this);
connect(actOpen, &QAction::triggered, this, &FolderModel::openSelected);
m_actionCollection.addAction(QStringLiteral("open"), actOpen);
m_actionCollection.addAction(QStringLiteral("cut"), cut);
m_actionCollection.addAction(QStringLiteral("undo"), undo);
m_actionCollection.addAction(QStringLiteral("copy"), copy);
m_actionCollection.addAction(QStringLiteral("paste"), paste);
m_actionCollection.addAction(QStringLiteral("pasteto"), pasteTo);
m_actionCollection.addAction(QStringLiteral("refresh"), refresh);
m_actionCollection.addAction(QStringLiteral("rename"), rename);
m_actionCollection.addAction(QStringLiteral("trash"), trash);
m_actionCollection.addAction(QStringLiteral("del"), del);
m_actionCollection.addAction(QStringLiteral("restoreFromTrash"), restoreFromTrash);
m_actionCollection.addAction(QStringLiteral("emptyTrash"), emptyTrash);
m_newMenu = new KNewFileMenu(&m_actionCollection, QStringLiteral("newMenu"), QApplication::desktop());
m_newMenu->setModal(false);
connect(m_newMenu, &KNewFileMenu::directoryCreated, this, &FolderModel::newFileMenuItemCreated);
connect(m_newMenu, &KNewFileMenu::fileCreated, this, &FolderModel::newFileMenuItemCreated);
m_copyToMenu = new KFileCopyToMenu(nullptr);
}
QAction* FolderModel::action(const QString &name) const
{
return m_actionCollection.action(name);
}
QObject* FolderModel::newMenu() const
{
return m_newMenu->menu();
}
void FolderModel::updateActions()
{
if (m_newMenu) {
m_newMenu->checkUpToDate();
m_newMenu->setPopupFiles(m_dirModel->dirLister()->url());
// we need to set here as well, when the menu is shown via AppletInterface::eventFilter
m_menuPosition = QCursor::pos();
}
const bool isTrash = (resolvedUrl().scheme() == QLatin1String("trash"));
QAction *emptyTrash = m_actionCollection.action(QStringLiteral("emptyTrash"));
if (emptyTrash) {
if (isTrash) {
emptyTrash->setVisible(true);
emptyTrash->setEnabled(!isTrashEmpty());
} else {
emptyTrash->setVisible(false);
}
}
if (QAction *restoreFromTrash = m_actionCollection.action(QStringLiteral("restoreFromTrash"))) {
restoreFromTrash->setVisible(isTrash);
}
QAction *paste = m_actionCollection.action(QStringLiteral("paste"));
if (paste) {
bool enable = false;
const QString pasteText = KIO::pasteActionText(QApplication::clipboard()->mimeData(),
&enable, rootItem());
if (enable) {
paste->setText(pasteText);
paste->setEnabled(true);
} else {
paste->setText(i18n("&Paste"));
paste->setEnabled(false);
}
QAction* pasteTo = m_actionCollection.action(QStringLiteral("pasteto"));
if (pasteTo) {
pasteTo->setEnabled(paste->isEnabled());
pasteTo->setText(paste->text());
}
}
}
void FolderModel::openContextMenu(QQuickItem *visualParent, Qt::KeyboardModifiers modifiers)
{
QModelIndexList indexes = m_selectionModel->selectedIndexes();
if (m_usedByContainment && !KAuthorized::authorize(QStringLiteral("action/kdesktop_rmb"))) {
return;
}
updateActions();
QMenu *menu = new QMenu();
if (!m_fileItemActions) {
m_fileItemActions = new KFileItemActions(this);
m_fileItemActions->setParentWidget(QApplication::desktop());
}
if (indexes.isEmpty()) {
menu->addAction(m_actionCollection.action(QStringLiteral("newMenu")));
menu->addSeparator();
menu->addAction(m_actionCollection.action(QStringLiteral("paste")));
menu->addAction(m_actionCollection.action(QStringLiteral("undo")));
menu->addAction(m_actionCollection.action(QStringLiteral("refresh")));
menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash")));
menu->addSeparator();
KFileItemListProperties itemProperties(KFileItemList() << rootItem());
m_fileItemActions->setItemListProperties(itemProperties);
menu->addAction(m_fileItemActions->preferredOpenWithAction(QString()));
} else {
KFileItemList items;
QList urls;
bool hasRemoteFiles = false;
bool isTrashLink = false;
items.reserve(indexes.count());
urls.reserve(indexes.count());
foreach (const QModelIndex &index, indexes) {
KFileItem item = itemForIndex(index);
if (!item.isNull()) {
hasRemoteFiles |= item.localPath().isEmpty();
items.append(item);
urls.append(item.url());
}
}
KFileItemListProperties itemProperties(items);
// Check if we're showing the menu for the trash link
if (items.count() == 1 && items.at(0).isDesktopFile()) {
KDesktopFile file(items.at(0).localPath());
if (file.hasLinkType() && file.readUrl() == QLatin1String("trash:/")) {
isTrashLink = true;
}
}
// Start adding the actions:
menu->addAction(m_actionCollection.action(QStringLiteral("open")));
menu->addSeparator();
if (itemProperties.supportsDeleting()) {
menu->addAction(m_actionCollection.action(QStringLiteral("cut")));
}
menu->addAction(m_actionCollection.action(QStringLiteral("copy")));
if (itemProperties.isDirectory() && itemProperties.supportsWriting()) {
menu->addAction(m_actionCollection.action(QStringLiteral("pasteto")));
}
menu->addAction(m_actionCollection.action(QStringLiteral("rename")));
menu->addAction(m_actionCollection.action(QStringLiteral("restoreFromTrash")));
KSharedConfig::Ptr globalConfig = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
KConfigGroup cg(globalConfig, "KDE");
bool showDeleteCommand = cg.readEntry("ShowDeleteCommand", false);
// When we're showing the menu for the trash link, offer "Empty Trash" instead
// of the "Move to Trash" action.
if (isTrashLink) {
QAction *emptyTrashAction = m_actionCollection.action(QStringLiteral("emptyTrash"));
if (emptyTrashAction) {
// We explicitly force the action visible here, as it relies on the KFileItemList
// we collected above. In updateActions() we don't have it and since this is always
// called before we open the menu, it would correct visibility again when opening
// the context menu for other items later.
emptyTrashAction->setVisible(true);
emptyTrashAction->setEnabled(!isTrashEmpty());
menu->addAction(emptyTrashAction);
}
} else {
if (!modifiers.testFlag(Qt::ShiftModifier) && !hasRemoteFiles && itemProperties.supportsMoving()) {
menu->addAction(m_actionCollection.action(QStringLiteral("trash")));
} else {
showDeleteCommand = true;
}
}
if (showDeleteCommand && itemProperties.supportsDeleting()) {
menu->addAction(m_actionCollection.action(QStringLiteral("del")));
}
// "Open with" actions
m_fileItemActions->setItemListProperties(itemProperties);
m_fileItemActions->addOpenWithActionsTo(menu);
// Service actions
m_fileItemActions->addServiceActionsTo(menu);
menu->addSeparator();
// Plugin actions
m_fileItemActions->addPluginActionsTo(menu);
// Copy To, Move To
KSharedConfig::Ptr dolphin = KSharedConfig::openConfig(QStringLiteral("dolphinrc"));
if (KConfigGroup(dolphin, "General").readEntry("ShowCopyMoveMenu", false)) {
m_copyToMenu->setUrls(urls);
m_copyToMenu->setReadOnly(!itemProperties.supportsMoving());
m_copyToMenu->addActionsTo(menu);
menu->addSeparator();
}
// Properties
if (KPropertiesDialog::canDisplay(items)) {
QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Properties"), menu);
QObject::connect(act, &QAction::triggered, this, &FolderModel::openPropertiesDialog);
menu->addAction(act);
}
}
if (visualParent) {
m_menuPosition = visualParent->mapToGlobal(QPointF(0, visualParent->height())).toPoint();
} else {
m_menuPosition = QCursor::pos();
}
menu->winId(); //force surface creation before ensurePolish call in menu::Popup which happens before show
menu->popup(m_menuPosition);
connect(menu, &QMenu::aboutToHide, [menu]() { menu->deleteLater(); });
}
void FolderModel::openPropertiesDialog()
{
const QModelIndexList indexes = m_selectionModel->selectedIndexes();
if (indexes.isEmpty()) {
return;
}
KFileItemList items;
items.reserve(indexes.count());
for (const QModelIndex &index : indexes) {
KFileItem item = itemForIndex(index);
if (!item.isNull()) {
items.append(item);
}
}
if (!KPropertiesDialog::canDisplay(items)) {
return;
}
KPropertiesDialog::showDialog(items, nullptr, false /*non modal*/);
}
void FolderModel::linkHere(const QUrl &sourceUrl)
{
KIO::CopyJob *job = KIO::link(sourceUrl, m_dirModel->dirLister()->url());
KIO::FileUndoManager::self()->recordCopyJob(job);
}
QList FolderModel::selectedUrls() const
{
const auto indexes = m_selectionModel->selectedIndexes();
QList urls;
urls.reserve(indexes.count());
for (const QModelIndex &index : indexes) {
urls.append(itemForIndex(index).url());
}
return urls;
}
void FolderModel::copy()
{
if (!m_selectionModel->hasSelection()) {
return;
}
QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes());
QApplication::clipboard()->setMimeData(mimeData);
}
void FolderModel::cut()
{
if (!m_selectionModel->hasSelection()) {
return;
}
QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes());
KIO::setClipboardDataCut(mimeData, true);
QApplication::clipboard()->setMimeData(mimeData);
}
void FolderModel::paste()
{
KIO::paste(QApplication::clipboard()->mimeData(), m_dirModel->dirLister()->url());
}
void FolderModel::pasteTo()
{
const QList urls = selectedUrls();
Q_ASSERT(urls.count() == 1);
KIO::paste(QApplication::clipboard()->mimeData(), urls.first());
}
void FolderModel::refresh()
{
m_errorString.clear();
emit errorStringChanged();
m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url());
}
QObject *FolderModel::appletInterface() const
{
return m_appletInterface;
}
void FolderModel::setAppletInterface(QObject *appletInterface)
{
if (m_appletInterface != appletInterface) {
Q_ASSERT(!m_appletInterface);
m_appletInterface = appletInterface;
if (appletInterface) {
Plasma::Applet *applet = appletInterface->property("_plasma_applet").value();
if (applet) {
Plasma::Containment *containment = applet->containment();
if (containment) {
Plasma::Corona *corona = containment->corona();
if (corona) {
m_screenMapper->setCorona(corona);
}
setScreen(containment->screen());
connect(containment, &Plasma::Containment::screenChanged, this, &FolderModel::setScreen);
}
}
}
emit appletInterfaceChanged();
}
}
void FolderModel::moveSelectedToTrash()
{
if (!m_selectionModel->hasSelection()) {
return;
}
const QList urls = selectedUrls();
KIO::JobUiDelegate uiDelegate;
if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
KIO::Job* job = KIO::trash(urls);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl(QStringLiteral("trash:/")), job);
}
}
void FolderModel::deleteSelected()
{
if (!m_selectionModel->hasSelection()) {
return;
}
const QList urls = selectedUrls();
KIO::JobUiDelegate uiDelegate;
if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
KIO::Job* job = KIO::del(urls);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
}
}
void FolderModel::openSelected()
{
if (!m_selectionModel->hasSelection()) {
return;
}
const QList urls = selectedUrls();
for (const QUrl &url : urls) {
(void) new KRun(url, nullptr);
}
}
void FolderModel::undo()
{
if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) {
// trigger() doesn't check enabled and would crash if invoked nonetheless.
if (action->isEnabled()) {
action->trigger();
}
}
}
void FolderModel::emptyTrashBin()
{
KIO::JobUiDelegate uiDelegate;
uiDelegate.setWindow(QApplication::desktop());
if (uiDelegate.askDeleteConfirmation(QList(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) {
KIO::Job* job = KIO::emptyTrash();
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
}
}
void FolderModel::restoreSelectedFromTrash()
{
if (!m_selectionModel->hasSelection()) {
return;
}
const auto &urls = selectedUrls();
KIO::RestoreJob *job = KIO::restoreFromTrash(urls);
job->uiDelegate()->setAutoErrorHandlingEnabled(true);
}
bool FolderModel::isTrashEmpty()
{
KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
return trashConfig.group("Status").readEntry("Empty", true);
}
void FolderModel::undoTextChanged(const QString &text)
{
if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) {
action->setText(text);
}
}
diff --git a/containments/desktop/plugins/folder/foldermodel.h b/containments/desktop/plugins/folder/foldermodel.h
index 77417f055..c087cedfd 100644
--- a/containments/desktop/plugins/folder/foldermodel.h
+++ b/containments/desktop/plugins/folder/foldermodel.h
@@ -1,359 +1,363 @@
/***************************************************************************
* Copyright (C) 2008 Fredrik Höglund *
* Copyright (C) 2011 Marco Martin *
* Copyright (C) 2014 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#ifndef FOLDERMODEL_H
#define FOLDERMODEL_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "folderplugin_private_export.h"
class QDrag;
class QItemSelectionModel;
class QQuickItem;
class KFileCopyToMenu;
class KActionCollection;
class KDirModel;
class KDirWatch;
class KFileItem;
class KFileItemActions;
class KJob;
class KNewFileMenu;
namespace KIO {
class DropJob;
class StatJob;
}
class ScreenMapper;
class DirLister : public KDirLister
{
Q_OBJECT
public:
explicit DirLister(QObject *parent = nullptr);
~DirLister();
Q_SIGNALS:
void error(const QString &string);
protected:
void handleError(KIO::Job *job) override;
};
class FOLDERPLUGIN_TESTS_EXPORT FolderModel : public QSortFilterProxyModel, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged)
Q_PROPERTY(QUrl resolvedUrl READ resolvedUrl NOTIFY resolvedUrlChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged)
Q_PROPERTY(bool usedByContainment READ usedByContainment WRITE setUsedByContainment NOTIFY usedByContainmentChanged)
Q_PROPERTY(bool locked READ locked WRITE setLocked NOTIFY lockedChanged)
Q_PROPERTY(int sortMode READ sortMode WRITE setSortMode NOTIFY sortModeChanged)
Q_PROPERTY(bool sortDesc READ sortDesc WRITE setSortDesc NOTIFY sortDescChanged)
Q_PROPERTY(bool sortDirsFirst READ sortDirsFirst WRITE setSortDirsFirst NOTIFY sortDirsFirstChanged)
Q_PROPERTY(bool parseDesktopFiles READ parseDesktopFiles WRITE setParseDesktopFiles NOTIFY parseDesktopFilesChanged)
Q_PROPERTY(QObject* viewAdapter READ viewAdapter WRITE setViewAdapter NOTIFY viewAdapterChanged)
Q_PROPERTY(bool previews READ previews WRITE setPreviews NOTIFY previewsChanged)
Q_PROPERTY(QStringList previewPlugins READ previewPlugins WRITE setPreviewPlugins NOTIFY previewPluginsChanged)
Q_PROPERTY(int filterMode READ filterMode WRITE setFilterMode NOTIFY filterModeChanged)
Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged)
Q_PROPERTY(QStringList filterMimeTypes READ filterMimeTypes WRITE setFilterMimeTypes NOTIFY filterMimeTypesChanged)
Q_PROPERTY(QObject* newMenu READ newMenu CONSTANT)
Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged);
public:
enum DataRole {
BlankRole = Qt::UserRole + 1,
OverlaysRole,
SelectedRole,
IsDirRole,
IsLinkRole,
IsHiddenRole,
UrlRole,
LinkDestinationUrl,
SizeRole,
TypeRole,
FileNameRole
};
enum FilterMode {
NoFilter = 0,
FilterShowMatches,
FilterHideMatches
};
enum Status {
None,
Ready,
Listing,
Canceled
};
Q_ENUM(Status)
explicit FolderModel(QObject *parent = nullptr);
~FolderModel();
QHash roleNames() const override;
static QHash staticRoleNames();
void classBegin() override;
void componentComplete() override;
QString url() const;
void setUrl(const QString &url);
QString iconName() const;
QUrl resolvedUrl() const;
Q_INVOKABLE QUrl resolve(const QString& url);
Status status() const;
QString errorString() const;
bool dragging() const;
bool usedByContainment() const;
void setUsedByContainment(bool used);
bool locked() const;
void setLocked(bool locked);
int sortMode() const;
void setSortMode(int mode);
bool sortDesc() const;
void setSortDesc(bool desc);
bool sortDirsFirst() const;
void setSortDirsFirst(bool enable);
bool parseDesktopFiles() const;
void setParseDesktopFiles(bool enable);
QObject* viewAdapter() const;
void setViewAdapter(QObject *adapter);
bool previews() const;
void setPreviews(bool previews);
QStringList previewPlugins() const;
void setPreviewPlugins(const QStringList &previewPlugins);
int filterMode() const;
void setFilterMode(int filterMode);
QString filterPattern() const;
void setFilterPattern(const QString &pattern);
QStringList filterMimeTypes() const;
void setFilterMimeTypes(const QStringList &mimeList);
QObject *appletInterface() const;
void setAppletInterface(QObject *appletInterface);
KFileItem rootItem() const;
Q_INVOKABLE void up();
Q_INVOKABLE void cd(int row);
Q_INVOKABLE void run(int row);
Q_INVOKABLE void runSelected();
Q_INVOKABLE void rename(int row, const QString &name);
Q_INVOKABLE int fileExtensionBoundary(int row);
Q_INVOKABLE bool hasSelection() const;
Q_INVOKABLE bool isSelected(int row);
Q_INVOKABLE void setSelected(int row);
Q_INVOKABLE void toggleSelected(int row);
Q_INVOKABLE void setRangeSelected(int anchor, int to);
Q_INVOKABLE void updateSelection(const QVariantList &rows, bool toggle);
Q_INVOKABLE void clearSelection();
Q_INVOKABLE void pinSelection();
Q_INVOKABLE void unpinSelection();
Q_INVOKABLE void addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image);
Q_INVOKABLE void clearDragImages();
Q_INVOKABLE void setDragHotSpotScrollOffset(int x, int y); // FIXME TODO: Propify.
Q_INVOKABLE QPoint dragCursorOffset(int row);
Q_INVOKABLE void dragSelected(int x, int y);
Q_INVOKABLE void drop(QQuickItem *target, QObject *dropEvent, int row);
Q_INVOKABLE void dropCwd(QObject *dropEvent);
Q_INVOKABLE bool isBlank(int row) const;
Q_INVOKABLE QAction* action(const QString& name) const;
QObject* newMenu() const;
Q_INVOKABLE void updateActions();
Q_INVOKABLE void openContextMenu(QQuickItem *visualParent = nullptr,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
Q_INVOKABLE void linkHere(const QUrl &sourceUrl);
Q_INVOKABLE void openPropertiesDialog();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int indexForUrl(const QUrl &url) const;
KFileItem itemForIndex(const QModelIndex &index) const;
bool isDir(const QModelIndex &index, const KDirModel *dirModel) const;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
Qt::DropActions supportedDragActions() const override;
Q_INVOKABLE void paste();
Q_INVOKABLE void copy();
Q_INVOKABLE void cut();
Q_INVOKABLE void deleteSelected();
Q_INVOKABLE void openSelected();
Q_INVOKABLE void undo();
Q_INVOKABLE void refresh();
void setScreen(int screen);
Q_SIGNALS:
void urlChanged() const;
void listingCompleted() const;
void listingCanceled() const;
void iconNameChanged() const;
void resolvedUrlChanged() const;
void statusChanged() const;
void errorStringChanged() const;
void draggingChanged() const;
void usedByContainmentChanged() const;
void lockedChanged() const;
void sortModeChanged() const;
void sortDescChanged() const;
void sortDirsFirstChanged() const;
void parseDesktopFilesChanged() const;
void viewAdapterChanged();
void previewsChanged() const;
void previewPluginsChanged() const;
void filterModeChanged() const;
void filterPatternChanged() const;
void filterMimeTypesChanged() const;
void screenChanged() const;
void appletInterfaceChanged() const;
void requestRename() const;
void move(int x, int y, QList urls);
void popupMenuAboutToShow(KIO::DropJob *dropJob, QMimeData *mimeData, int x, int y);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool matchMimeType(const KFileItem &item) const;
bool matchPattern(const KFileItem &item) const;
private Q_SLOTS:
void dragSelectedInternal(int x, int y);
void dirListFailed(const QString &error);
void statResult(KJob *job);
void evictFromIsDirCache(const KFileItemList &items);
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
void pasteTo();
void moveSelectedToTrash();
void emptyTrashBin();
void restoreSelectedFromTrash();
void undoTextChanged(const QString &text);
void invalidateIfComplete();
void invalidateFilterIfComplete();
void newFileMenuItemCreated(const QUrl &url);
private:
struct DragImage {
int row;
QRect rect;
QPoint cursorOffset;
QImage image;
bool blank;
};
void createActions();
void updatePasteAction();
void addDragImage(QDrag *drag, int x, int y);
void setStatus(Status status);
static bool isTrashEmpty();
QList selectedUrls() const;
KDirModel *m_dirModel;
KDirWatch *m_dirWatch;
QString m_url;
QHash m_isDirCache;
mutable QHash m_isDirJobs;
QItemSelectionModel *m_selectionModel;
QItemSelection m_pinnedSelection;
QModelIndexList m_dragIndexes;
QHash m_dragImages;
QPoint m_dragHotSpotScrollOffset;
bool m_dragInProgress;
bool m_urlChangedWhileDragging;
// target filename to target position of a drop event, note that this deliberately
// is not using the URL to easily support desktop:/ URL schemes
QHash m_dropTargetPositions;
QTimer *m_dropTargetPositionsCleanup;
QPointer m_previewGenerator;
QPointer m_viewAdapter;
KActionCollection m_actionCollection;
KNewFileMenu *m_newMenu;
KFileItemActions *m_fileItemActions;
KFileCopyToMenu *m_copyToMenu;
Status m_status = Status::None;
QString m_errorString;
bool m_usedByContainment;
bool m_locked;
int m_sortMode; // FIXME TODO: Enumify.
bool m_sortDesc;
bool m_sortDirsFirst;
bool m_parseDesktopFiles;
bool m_previews;
+ // An empty previewPlugin list means use default.
+ // We don't want to leak that fact to the QML side, however, so the property stays empty
+ // and internally we operate on effectivePreviewPlugins instead.
QStringList m_previewPlugins;
+ QStringList m_effectivePreviewPlugins;
FilterMode m_filterMode;
QString m_filterPattern;
bool m_filterPatternMatchAll;
QSet m_mimeSet;
QList m_regExps;
int m_screen = -1;
ScreenMapper *m_screenMapper = nullptr;
QObject *m_appletInterface = nullptr;
bool m_complete;
QPoint m_menuPosition;
};
#endif
diff --git a/containments/desktop/plugins/folder/previewpluginsmodel.cpp b/containments/desktop/plugins/folder/previewpluginsmodel.cpp
index 03d929e35..f6eb009af 100644
--- a/containments/desktop/plugins/folder/previewpluginsmodel.cpp
+++ b/containments/desktop/plugins/folder/previewpluginsmodel.cpp
@@ -1,118 +1,137 @@
/***************************************************************************
* Copyright (C) 2008 Fredrik Höglund *
* Copyright (C) 2014 by Eike Hein *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "previewpluginsmodel.h"
#include
+#include
+
+#include
+
static bool lessThan(const KService::Ptr &a, const KService::Ptr &b)
{
return QString::localeAwareCompare(a->name(), b->name()) < 0;
}
PreviewPluginsModel::PreviewPluginsModel(QObject *parent)
: QAbstractListModel(parent)
{
m_plugins = KServiceTypeTrader::self()->query(QStringLiteral("ThumbCreator"));
qStableSort(m_plugins.begin(), m_plugins.end(), lessThan);
m_checkedRows = QVector(m_plugins.size(), false);
}
PreviewPluginsModel::~PreviewPluginsModel()
{
}
QHash PreviewPluginsModel::roleNames() const
{
return {
{ Qt::DisplayRole, "display" },
{ Qt::CheckStateRole, "checked" }
};
}
QVariant PreviewPluginsModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_plugins.size()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return m_plugins.at(index.row())->name();
case Qt::CheckStateRole:
return m_checkedRows.at(index.row()) ? Qt::Checked : Qt::Unchecked;
}
return QVariant();
}
bool PreviewPluginsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= m_plugins.size()) {
return false;
}
if (role == Qt::CheckStateRole) {
m_checkedRows[index.row()] = value.toBool();
emit dataChanged(index, index, {role});
return true;
}
return false;
}
int PreviewPluginsModel::indexOfPlugin(const QString &name) const
{
for (int i = 0; i < m_plugins.size(); i++) {
if (m_plugins.at(i)->desktopEntryName() == name) {
return i;
}
}
return -1;
}
void PreviewPluginsModel::setCheckedPlugins(const QStringList &list)
{
+ QStringList plugins = list;
+ if (plugins.isEmpty()) {
+ plugins = KIO::PreviewJob::defaultPlugins();
+ }
+
m_checkedRows = QVector(m_plugins.size(), false);
- foreach (const QString &name, list) {
+ for (const QString &name : plugins) {
const int row = indexOfPlugin(name);
if (row != -1) {
m_checkedRows[row] = true;
}
}
emit dataChanged(index(0, 0), index(m_plugins.size() - 1, 0), {Qt::CheckStateRole});
emit checkedPluginsChanged();
}
QStringList PreviewPluginsModel::checkedPlugins() const
{
QStringList list;
for (int i = 0; i < m_checkedRows.size(); ++i) {
if (m_checkedRows.at(i)) {
list.append(m_plugins.at(i)->desktopEntryName());
}
}
+
+ const QStringList defaultPlugins = KIO::PreviewJob::defaultPlugins();
+
+ // If the list of checked plugins is the default set, return an empty list
+ // which denotes the default set.
+ // This ensures newly installed thumbnails are always automatically enabled.
+ if (std::is_permutation(list.constBegin(), list.constEnd(), defaultPlugins.begin())) {
+ return QStringList();
+ }
+
return list;
}