Commit e9488870 authored by Milian Wolff's avatar Milian Wolff Committed by Andras Mantia

FolderView: position files at drop event target position

Summary:
Remember the drop event target position and move the items once
they become available. This requires us to allow moving while no
drag image is being shown. Otherwise, we listen to the
copy job signals, which gets created by the drop job. This allows
us to map target filenames in their final form, i.e. after the user
potentially renamed the files to handle conflicts, to some desired
visual position. To stay on the safe side, we also periodically
cleanup the mapping after an idle timeout of 10s. This ensures we
don't grow the mapping with stale items. This is required to handle
errors in the file lister, or situations like overwriting an existing
file which would not trigger a rowAdded signal.

Reviewers: hein, amantia

Reviewed By: hein, amantia

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D8598
parent 88718b16
......@@ -40,6 +40,8 @@
#include <QPixmap>
#include <QQuickItem>
#include <QQuickWindow>
#include <QTimer>
#include <QLoggingCategory>
#include <qplatformdefs.h>
#include <KDirWatch>
......@@ -79,6 +81,8 @@
#include <sys/stat.h>
#include <unistd.h>
Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel")
DirLister::DirLister(QObject *parent) : KDirLister(parent)
{
}
......@@ -101,6 +105,7 @@ 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),
......@@ -142,6 +147,44 @@ FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent),
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(m_dirModel, &QAbstractItemModel::rowsInserted,
this, [this](const QModelIndex &parent, int first, int last) {
for (int i = first; i <= last; ++i) {
const auto index = m_dirModel->index(i, 0, parent);
const auto url = m_dirModel->itemForIndex(index).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});
}
}
}, Qt::QueuedConnection);
/*
* 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);
......@@ -792,7 +835,7 @@ QPoint FolderModel::dragCursorOffset(int row)
{
DragImage *image = m_dragImages.value(row);
if (!image) {
return QPoint(-1, -1);
return QPoint(0, 0);
}
return image->cursorOffset;
......@@ -924,6 +967,7 @@ void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row)
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()) {
......@@ -986,15 +1030,14 @@ void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row)
return;
}
QPoint pos = {x, y};
pos = target->mapToScene(pos).toPoint();
pos = target->window()->mapToGlobal(pos);
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);
......@@ -1008,10 +1051,33 @@ void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row)
mimeCopy->setData(format, mimeData->data(format));
}
connect(dropJob, static_cast<void(KIO::DropJob::*)(const KFileItemListProperties &)>(&KIO::DropJob::popupMenuAboutToShow), this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) {
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](KIO::CopyJob* copyJob) {
auto map = [this, dropPos](const QUrl &targetUrl) {
m_dropTargetPositions.insert(targetUrl.fileName(), dropPos);
m_dropTargetPositionsCleanup->start();
};
// 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)
......
......@@ -315,6 +315,10 @@ class FOLDERPLUGIN_TESTS_EXPORT FolderModel : public QSortFilterProxyModel
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<QString, QPoint> m_dropTargetPositions;
QTimer *m_dropTargetPositionsCleanup;
QPointer<KFilePreviewGenerator> m_previewGenerator;
QPointer<KAbstractViewAdapter> m_viewAdapter;
KActionCollection m_actionCollection;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment