Change in kio[master]: New job: KIO::DropJob *KIO::drop(QDropEvent* ev, QUrl destUrl).
David Faure (Code Review)
noreply at kde.org
Mon Dec 15 09:38:55 UTC 2014
David Faure has uploaded a new change for review.
https://gerrit.vesnicky.cesnet.cz/r/241
Change subject: New job: KIO::DropJob *KIO::drop(QDropEvent* ev, QUrl destUrl).
......................................................................
New job: KIO::DropJob *KIO::drop(QDropEvent* ev, QUrl destUrl).
Takes care of the Move/Copy/Link/[app/plugin actions]/Cancel popup,
dropping onto executables and desktop files,
as well as dropping non-URL data (using KIO::paste internally)
This is a full-featured rewrite of KonqOperations::doDrop, with cleaner
code, a proper KIO::Job API, the popup menu is now async, and
the code is now fully unittested.
Change-Id: I9717f8a9e68a750696f2a54adfa828dead9a31f3
---
M autotests/CMakeLists.txt
A autotests/dropjobtest.cpp
M src/core/global.h
M src/core/job_error.cpp
M src/widgets/CMakeLists.txt
A src/widgets/dndpopupmenuplugin.cpp
A src/widgets/dndpopupmenuplugin.h
A src/widgets/dropjob.cpp
A src/widgets/dropjob.h
A src/widgets/kiodndpopupmenuplugin.desktop
M src/widgets/pastejob.cpp
M src/widgets/pastejob.h
A src/widgets/pastejob_p.h
13 files changed, 1,229 insertions(+), 47 deletions(-)
git pull ssh://gerrit.vesnicky.cesnet.cz:29418/kio refs/changes/41/241/1
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 38e01d7..c667bb9 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -44,6 +44,7 @@
ecm_add_tests(
clipboardupdatertest.cpp
+ dropjobtest.cpp
krununittest.cpp
kdirlistertest.cpp
kdirmodeltest.cpp
diff --git a/autotests/dropjobtest.cpp b/autotests/dropjobtest.cpp
new file mode 100644
index 0000000..e8f586f
--- /dev/null
+++ b/autotests/dropjobtest.cpp
@@ -0,0 +1,479 @@
+/* This file is part of the KDE project
+ Copyright (C) 2014 David Faure <faure at kde.org>
+
+ This library is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2 of the License or ( at
+ your option ) version 3 or, at the discretion of KDE e.V. ( which shall
+ act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest.h>
+#include <QSignalSpy>
+#include <QDir>
+#include <QMenu>
+#include <QMimeData>
+#include <QStandardPaths>
+#include <QTemporaryDir>
+
+#include "kiotesthelper.h"
+
+#include <KIO/DropJob>
+#include <KIO/StatJob>
+#include <KIO/CopyJob>
+#include <KIO/DeleteJob>
+#include <KConfigGroup>
+#include <KDesktopFile>
+#include <KFileItemListProperties>
+
+Q_DECLARE_METATYPE(Qt::KeyboardModifiers)
+Q_DECLARE_METATYPE(Qt::DropAction)
+Q_DECLARE_METATYPE(Qt::DropActions)
+Q_DECLARE_METATYPE(KFileItemListProperties)
+
+#ifndef Q_OS_WIN
+void initLocale()
+{
+ setenv("LC_ALL", "en_US.utf-8", 1);
+}
+Q_CONSTRUCTOR_FUNCTION(initLocale)
+#endif
+
+class JobSpy : public QObject
+{
+ Q_OBJECT
+public:
+ JobSpy(KIO::Job *job)
+ : QObject(0),
+ m_spy(job, SIGNAL(result(KJob *))),
+ m_error(0)
+ {
+ connect(job, &KJob::result, this, [this](KJob* job) { m_error = job->error(); });
+ }
+ // like job->exec(), but with a timeout (to avoid being stuck with a popup grabbing mouse and keyboard...)
+ bool waitForResult() {
+ // implementation taken from QTRY_COMPARE, to move the QVERIFY to the caller
+ if (m_spy.isEmpty()) {
+ QTest::qWait(0);
+ }
+ for (int i = 0; i < 5000 && m_spy.isEmpty(); i += 50) {
+ QTest::qWait(50);
+ }
+ return !m_spy.isEmpty();
+ }
+ int error() const { return m_error; }
+
+private:
+ QSignalSpy m_spy;
+ int m_error;
+};
+
+class DropJobTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase()
+ {
+ QStandardPaths::setTestModeEnabled(true);
+ setenv("KIOSLAVE_ENABLE_TESTMODE", "1", 1); // ensure the ioslaves call QStandardPaths::setTestModeEnabled too
+
+ // To avoid a runtime dependency on klauncher
+ qputenv("KDE_FORK_SLAVES", "yes");
+
+ const QString trashDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QString::fromLatin1("/Trash");
+ QDir(trashDir).removeRecursively();
+
+ QVERIFY(m_tempDir.isValid());
+ QVERIFY(m_nonWritableTempDir.isValid());
+ QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner|QFile::ReadUser|QFile::ExeOwner|QFile::ExeUser));
+ m_srcDir = m_tempDir.path();
+
+ m_srcFile = m_srcDir + "/srcfile";
+ m_srcLink = m_srcDir + "/link";
+ }
+
+ void cleanupTestCase()
+ {
+ QVERIFY(QFile(m_nonWritableTempDir.path()).setPermissions(QFile::ReadOwner|QFile::ReadUser|QFile::WriteOwner|QFile::WriteUser|QFile::ExeOwner|QFile::ExeUser));
+ }
+
+ // Before every test method, ensure the test file m_srcFile exists
+ void init()
+ {
+ if (QFile::exists(m_srcFile)) {
+ QVERIFY(QFileInfo(m_srcFile).isWritable());
+ } else {
+ QFile srcFile(m_srcFile);
+ QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString()));
+ srcFile.write("Hello world\n");
+ }
+#ifndef Q_OS_WIN
+ if (!QFile::exists(m_srcLink)) {
+ QVERIFY(QFile(m_srcFile).link(m_srcLink));
+ QVERIFY(QFileInfo(m_srcLink).isSymLink());
+ }
+#endif
+ QVERIFY(QFileInfo(m_srcFile).isWritable());
+ m_mimeData.setUrls(QList<QUrl>() << QUrl::fromLocalFile(m_srcFile));
+ }
+
+ void shouldDropToDesktopFile()
+ {
+ // Given an executable application desktop file and a source file
+ const QString desktopPath = m_srcDir + "/target.desktop";
+ KDesktopFile desktopFile(desktopPath);
+ KConfigGroup desktopGroup = desktopFile.desktopGroup();
+ desktopGroup.writeEntry("Type", "Application");
+ desktopGroup.writeEntry("StartupNotify", "false");
+#ifdef Q_OS_WIN
+ desktopGroup.writeEntry("Exec", "copy.exe %f %d/dest");
+#else
+ desktopGroup.writeEntry("Exec", "cp %f %d/dest");
+#endif
+ desktopFile.sync();
+ QFile file(desktopPath);
+ file.setPermissions(file.permissions() | QFile::ExeOwner | QFile::ExeUser);
+
+ // When dropping the source file onto the desktop file
+ QUrl destUrl = QUrl::fromLocalFile(desktopPath);
+ QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
+ KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ QSignalSpy spy(job, SIGNAL(itemCreated(QUrl)));
+
+ // Then the application is run with the source file as argument
+ // (in this example, it copies the source file to "dest")
+ QVERIFY2(job->exec(), qPrintable(job->errorString()));
+ QCOMPARE(spy.count(), 0);
+ const QString dest = m_srcDir + "/dest";
+ QTRY_VERIFY(QFile::exists(dest));
+
+ QVERIFY(QFile::remove(desktopPath));
+ QVERIFY(QFile::remove(dest));
+ }
+
+ void shouldDropToDirectory_data()
+ {
+ QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
+ QTest::addColumn<Qt::DropAction>("dropAction"); // Qt's dnd support sets it from the modifiers, we fake it here
+ QTest::addColumn<QString>("srcFile");
+ QTest::addColumn<QString>("dest"); // empty for a temp dir
+ QTest::addColumn<int>("expectedError");
+ QTest::addColumn<bool>("shouldSourceStillExist");
+
+ QTest::newRow("Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcFile << QString()
+ << 0 << true;
+ QTest::newRow("Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcFile << QString()
+ << 0 << false;
+ QTest::newRow("Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcFile << QString()
+ << 0 << true;
+ QTest::newRow("DropOnItself") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcDir << m_srcDir
+ << int(KIO::ERR_DROP_ON_ITSELF) << true;
+ QTest::newRow("DropDirOnFile") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcDir << m_srcFile
+ << int(KIO::ERR_ACCESS_DENIED) << true;
+ QTest::newRow("NonWritableDest") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcFile << m_nonWritableTempDir.path()
+ << int(KIO::ERR_WRITE_ACCESS_DENIED) << true;
+ }
+
+ void shouldDropToDirectory()
+ {
+ QFETCH(Qt::KeyboardModifiers, modifiers);
+ QFETCH(Qt::DropAction, dropAction);
+ QFETCH(QString, srcFile);
+ QFETCH(QString, dest);
+ QFETCH(int, expectedError);
+ QFETCH(bool, shouldSourceStillExist);
+
+ // Given a directory and a source file
+ QTemporaryDir tempDestDir;
+ QVERIFY(tempDestDir.isValid());
+ if (dest.isEmpty()) {
+ dest = tempDestDir.path();
+ }
+
+ // When dropping the source file onto the directory
+ const QUrl destUrl = QUrl::fromLocalFile(dest);
+ m_mimeData.setUrls(QList<QUrl>() << QUrl::fromLocalFile(srcFile));
+ QDropEvent dropEvent(QPoint(10, 10), dropAction, &m_mimeData, Qt::LeftButton, modifiers);
+ KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ job->setUiDelegateExtension(0);
+ JobSpy jobSpy(job);
+ QSignalSpy spy(job, SIGNAL(itemCreated(QUrl)));
+
+ // Then the file is copied
+ QVERIFY(jobSpy.waitForResult());
+ QCOMPARE(jobSpy.error(), expectedError);
+ if (expectedError == 0) {
+ const QString destFile = dest + "/srcfile";
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.at(0).at(0).value<QUrl>(), QUrl::fromLocalFile(destFile));
+ QVERIFY(QFile::exists(destFile));
+ QCOMPARE(QFile::exists(m_srcFile), shouldSourceStillExist);
+ if (dropAction == Qt::LinkAction) {
+ QVERIFY(QFileInfo(destFile).isSymLink());
+ }
+ }
+ }
+
+ void shouldDropToTrash_data()
+ {
+ QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
+ QTest::addColumn<Qt::DropAction>("dropAction"); // Qt's dnd support sets it from the modifiers, we fake it here
+ QTest::addColumn<QString>("srcFile");
+
+ QTest::newRow("Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcFile;
+ QTest::newRow("Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcFile;
+ QTest::newRow("Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcFile;
+ QTest::newRow("NoModifiers") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcFile;
+#ifndef Q_OS_WIN
+ QTest::newRow("Link_Ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier) << Qt::CopyAction << m_srcLink;
+ QTest::newRow("Link_Shift") << Qt::KeyboardModifiers(Qt::ShiftModifier) << Qt::MoveAction << m_srcLink;
+ QTest::newRow("Link_Ctrl_Shift") << Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier) << Qt::LinkAction << m_srcLink;
+ QTest::newRow("Link_NoModifiers") << Qt::KeyboardModifiers() << Qt::CopyAction << m_srcLink;
+#endif
+ }
+
+ void shouldDropToTrash()
+ {
+ // Given a source file
+ QFETCH(Qt::KeyboardModifiers, modifiers);
+ QFETCH(Qt::DropAction, dropAction);
+ QFETCH(QString, srcFile);
+ const bool isLink = QFileInfo(srcFile).isSymLink();
+
+ // When dropping it into the trash, with <modifiers> pressed
+ m_mimeData.setUrls(QList<QUrl>() << QUrl::fromLocalFile(srcFile));
+ QDropEvent dropEvent(QPoint(10, 10), dropAction, &m_mimeData, Qt::LeftButton, modifiers);
+ KIO::DropJob *job = KIO::drop(&dropEvent, QUrl("trash:/"), KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ QSignalSpy spy(job, SIGNAL(itemCreated(QUrl)));
+
+ // Then a confirmation dialog should appear
+ PredefinedAnswerJobUiDelegate extension;
+ extension.m_deleteResult = true;
+ job->setUiDelegateExtension(&extension);
+
+ // and the file should be moved to the trash, no matter what the modifiers are
+ QVERIFY2(job->exec(), qPrintable(job->errorString()));
+ QCOMPARE(extension.m_askDeleteCalled, 1);
+ QCOMPARE(spy.count(), 1);
+ const QUrl trashUrl = spy.at(0).at(0).value<QUrl>();
+ QCOMPARE(trashUrl.scheme(), QString("trash"));
+ KIO::StatJob *statJob = KIO::stat(trashUrl, KIO::HideProgressInfo);
+ QVERIFY(statJob->exec());
+ if (isLink) {
+ QVERIFY(statJob->statResult().isLink());
+ }
+
+ // clean up
+ KIO::DeleteJob *delJob = KIO::del(trashUrl, KIO::HideProgressInfo);
+ QVERIFY2(delJob->exec(), qPrintable(delJob->errorString()));
+ }
+
+ void shouldDropFromTrash()
+ {
+ // Given a file in the trash
+ const QFile::Permissions origPerms = QFileInfo(m_srcFile).permissions();
+ QVERIFY(QFileInfo(m_srcFile).isWritable());
+ KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(m_srcFile), QUrl("trash:/"));
+ QSignalSpy copyingDoneSpy(copyJob, SIGNAL(copyingDone(KIO::Job*, QUrl, QUrl, QDateTime, bool, bool)));
+ QVERIFY(copyJob->exec());
+ const QUrl trashUrl = copyingDoneSpy.at(0).at(2).value<QUrl>();
+ QVERIFY(trashUrl.isValid());
+ QVERIFY(!QFile::exists(m_srcFile));
+
+ // When dropping the trashed file into a local dir, without modifiers
+ m_mimeData.setUrls(QList<QUrl>() << trashUrl);
+ QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
+ KIO::DropJob *job = KIO::drop(&dropEvent, QUrl::fromLocalFile(m_srcDir), KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ QSignalSpy spy(job, SIGNAL(itemCreated(QUrl)));
+
+ // Then the file should be moved, without a popup. No point in copying out of the trash, or linking to it.
+ QVERIFY2(job->exec(), qPrintable(job->errorString()));
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.at(0).at(0).value<QUrl>(), QUrl::fromLocalFile(m_srcFile));
+ QVERIFY(QFile::exists(m_srcFile));
+ QCOMPARE(int(QFileInfo(m_srcFile).permissions()), int(origPerms));
+ QVERIFY(QFileInfo(m_srcFile).isWritable());
+ KIO::StatJob *statJob = KIO::stat(trashUrl, KIO::HideProgressInfo);
+ QVERIFY(!statJob->exec());
+ QVERIFY(QFileInfo(m_srcFile).isWritable());
+ }
+
+ void shouldDropTrashRootWithoutMovingAllTrashedFiles() // #319660
+ {
+ // Given some stuff in the trash
+ const QUrl trashUrl("trash:/");
+ KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(m_srcFile), trashUrl);
+ QVERIFY(copyJob->exec());
+ // and an empty destination directory
+ QTemporaryDir tempDestDir;
+ QVERIFY(tempDestDir.isValid());
+ const QUrl destUrl = QUrl::fromLocalFile(tempDestDir.path());
+
+ // When dropping a link / icon of the trash...
+ m_mimeData.setUrls(QList<QUrl>() << trashUrl);
+ QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
+ KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ QVERIFY2(job->exec(), qPrintable(job->errorString()));
+
+ // Then a full move shouldn't happen, just a link
+ const QStringList items = QDir(tempDestDir.path()).entryList();
+ QVERIFY2(!items.contains("srcfile"), qPrintable(items.join(',')));
+ QVERIFY2(items.contains("trash:" + QChar(0x2044) + ".desktop"), qPrintable(items.join(',')));
+ }
+
+ void shouldDropToDirectoryWithPopup_data()
+ {
+ QTest::addColumn<QString>("dest"); // empty for a temp dir
+ QTest::addColumn<Qt::DropActions>("offeredActions");
+ QTest::addColumn<int>("triggerActionNumber");
+ QTest::addColumn<int>("expectedError");
+ QTest::addColumn<Qt::DropAction>("expectedDropAction");
+ QTest::addColumn<bool>("shouldSourceStillExist");
+
+ const Qt::DropActions threeActions = Qt::MoveAction | Qt::CopyAction | Qt::LinkAction;
+ const Qt::DropActions copyAndLink = Qt::CopyAction | Qt::LinkAction;
+ QTest::newRow("Move") << QString() << threeActions << 0 << 0 << Qt::MoveAction << false;
+ QTest::newRow("Copy") << QString() << threeActions << 1 << 0 << Qt::CopyAction << true;
+ QTest::newRow("Link") << QString() << threeActions << 2 << 0 << Qt::LinkAction << true;
+ QTest::newRow("SameDestCopy") << m_srcDir << copyAndLink << 0 << int(KIO::ERR_IDENTICAL_FILES) << Qt::CopyAction << true;
+ QTest::newRow("SameDestLink") << m_srcDir << copyAndLink << 1 << int(KIO::ERR_FILE_ALREADY_EXIST) << Qt::LinkAction << true;
+ }
+
+ void shouldDropToDirectoryWithPopup()
+ {
+ QFETCH(QString, dest);
+ QFETCH(Qt::DropActions, offeredActions);
+ QFETCH(int, triggerActionNumber);
+ QFETCH(int, expectedError);
+ QFETCH(Qt::DropAction, expectedDropAction);
+ QFETCH(bool, shouldSourceStillExist);
+
+ // Given a directory and a source file
+ QTemporaryDir tempDestDir;
+ QVERIFY(tempDestDir.isValid());
+ if (dest.isEmpty()) {
+ dest = tempDestDir.path();
+ }
+
+ // When dropping the source file onto the directory
+ QUrl destUrl = QUrl::fromLocalFile(dest);
+ QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
+ KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
+ job->setUiDelegate(0);
+ job->setUiDelegateExtension(0); // no rename dialog
+ JobSpy jobSpy(job);
+ qRegisterMetaType<KFileItemListProperties>();
+ QSignalSpy spyShow(job, SIGNAL(popupMenuAboutToShow(KFileItemListProperties)));
+ QVERIFY(spyShow.isValid());
+
+ // Then a popup should appear, with the expected available actions
+ QVERIFY(spyShow.wait());
+ QTRY_VERIFY(findPopup());
+ QMenu *popup = findPopup();
+ QCOMPARE(int(popupDropActions(popup)), int(offeredActions));
+
+ // And when selecting action number <triggerActionNumber>
+ QAction *action = popup->actions().at(triggerActionNumber);
+ QVERIFY(action);
+ QCOMPARE(int(action->data().value<Qt::DropAction>()), int(expectedDropAction));
+ const QRect actionGeom = popup->actionGeometry(action);
+ QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center());
+
+ // Then the job should finish, and the chosen action should happen.
+ QVERIFY(jobSpy.waitForResult());
+ QCOMPARE(jobSpy.error(), expectedError);
+ if (expectedError == 0) {
+ const QString destFile = dest + "/srcfile";
+ QVERIFY(QFile::exists(destFile));
+ QCOMPARE(QFile::exists(m_srcFile), shouldSourceStillExist);
+ if (expectedDropAction == Qt::LinkAction) {
+ QVERIFY(QFileInfo(destFile).isSymLink());
+ }
+ }
+ QTRY_VERIFY(!findPopup()); // flush deferred delete, so we don't get this popup again in findPopup
+ }
+
+ void shouldAddApplicationActionsToPopup()
+ {
+ // Given a directory and a source file
+ QTemporaryDir tempDestDir;
+ QVERIFY(tempDestDir.isValid());
+ const QUrl destUrl = QUrl::fromLocalFile(tempDestDir.path());
+
+ // When dropping the source file onto the directory
+ QDropEvent dropEvent(QPoint(10, 10), Qt::CopyAction /*unused*/, &m_mimeData, Qt::LeftButton, Qt::NoModifier);
+ KIO::DropJob *job = KIO::drop(&dropEvent, destUrl, KIO::HideProgressInfo);
+ QAction appAction1("action1", this);
+ QAction appAction2("action2", this);
+ QList<QAction *> appActions; appActions << &appAction1 << &appAction2;
+ job->setUiDelegate(0);
+ job->setApplicationActions(appActions);
+ JobSpy jobSpy(job);
+
+ // Then a popup should appear, with the expected available actions
+ QTRY_VERIFY(findPopup());
+ QMenu *popup = findPopup();
+ const QList<QAction *> actions = popup->actions();
+ QVERIFY(actions.contains(&appAction1));
+ QVERIFY(actions.contains(&appAction2));
+ QVERIFY(actions.at(actions.indexOf(&appAction1) - 1)->isSeparator());
+ QVERIFY(actions.at(actions.indexOf(&appAction2) + 1)->isSeparator());
+
+ // And when selecting action appAction1
+ const QRect actionGeom = popup->actionGeometry(&appAction1);
+ QTest::mouseClick(popup, Qt::LeftButton, Qt::NoModifier, actionGeom.center());
+
+ // Then the menu should hide and the job terminate (without doing any copying)
+ QVERIFY(jobSpy.waitForResult());
+ QCOMPARE(jobSpy.error(), 0);
+ const QString destFile = tempDestDir.path() + "/srcfile";
+ QVERIFY(!QFile::exists(destFile));
+ }
+
+private:
+ static QMenu *findPopup() {
+ Q_FOREACH (QWidget *widget, qApp->topLevelWidgets()) {
+ if (QMenu* menu = qobject_cast<QMenu*>(widget)) {
+ return menu;
+ }
+ }
+ return Q_NULLPTR;
+ }
+ static Qt::DropActions popupDropActions(QMenu *menu) {
+ Qt::DropActions actions;
+ Q_FOREACH (QAction *action, menu->actions()) {
+ const QVariant userData = action->data();
+ if (userData.isValid()) {
+ actions |= userData.value<Qt::DropAction>();
+ }
+ }
+ return actions;
+ }
+ QMimeData m_mimeData; // contains m_srcFile
+ QTemporaryDir m_tempDir;
+ QString m_srcDir;
+ QString m_srcFile;
+ QString m_srcLink;
+ QTemporaryDir m_nonWritableTempDir;
+};
+
+QTEST_MAIN(DropJobTest)
+
+#include "dropjobtest.moc"
+
diff --git a/src/core/global.h b/src/core/global.h
index a2426a3..d2c4b6a 100644
--- a/src/core/global.h
+++ b/src/core/global.h
@@ -241,7 +241,8 @@
ERR_CANNOT_SEEK = KJob::UserDefinedError + 66,
ERR_CANNOT_SETTIME = KJob::UserDefinedError + 67, // Emitted by setModificationTime
ERR_CANNOT_CHOWN = KJob::UserDefinedError + 68,
- ERR_POST_NO_SIZE = KJob::UserDefinedError + 69
+ ERR_POST_NO_SIZE = KJob::UserDefinedError + 69,
+ ERR_DROP_ON_ITSELF = KJob::UserDefinedError + 70 //< from KIO::DropJob, @since 5.6
};
/**
diff --git a/src/core/job_error.cpp b/src/core/job_error.cpp
index 6a5c35a..68fd77f 100644
--- a/src/core/job_error.cpp
+++ b/src/core/job_error.cpp
@@ -232,6 +232,9 @@
case KIO::ERR_POST_NO_SIZE:
result = i18n("The required content size information was not provided for a POST operation.");
break;
+ case KIO::ERR_DROP_ON_ITSELF:
+ result = i18n("A file or folder cannot be dropped onto itself");
+ break;
default:
result = i18n("Unknown error code %1\n%2\nPlease send a full bug report at http://bugs.kde.org.", errorCode, errorText);
break;
@@ -1018,6 +1021,13 @@
solutions << i18n("Choose a different filename for the destination file.");
break;
+ case KIO::ERR_DROP_ON_ITSELF:
+ errorName = i18n("File or Folder dropped onto itself");
+ description = i18n("The operation could not be completed because the "
+ "source and destination file or folder are the same.");
+ solutions << i18n("Drop the item into a different file or folder.");
+ break;
+
// We assume that the slave has all the details
case KIO::ERR_SLAVE_DEFINED:
errorName.clear();
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index 8695895..4b3a288 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -44,6 +44,7 @@
kshellcompletion.cpp
kurlcompletion.cpp
kurifilter.cpp
+ dropjob.cpp
pastejob.cpp
previewjob.cpp
renamedialog.cpp
@@ -56,6 +57,7 @@
kdirlister.cpp
kdirmodel.cpp
executablefileopendialog.cpp
+ dndpopupmenuplugin.cpp
)
if (WIN32)
list(APPEND kiowidgets_SRCS
@@ -145,6 +147,8 @@
SslUi
ThumbSequenceCreator
ThumbCreator
+ DropJob
+ DndPopupMenuPlugin
PasteJob
PreviewJob
RenameDialog
@@ -179,6 +183,7 @@
kfileitemactionplugin.desktop
kpropertiesdialogplugin.desktop
kurifilterplugin.desktop
+ kiodndpopupmenuplugin.desktop
DESTINATION ${SERVICETYPES_INSTALL_DIR} )
include(ECMGeneratePriFile)
diff --git a/src/widgets/dndpopupmenuplugin.cpp b/src/widgets/dndpopupmenuplugin.cpp
new file mode 100644
index 0000000..5ee834f
--- /dev/null
+++ b/src/widgets/dndpopupmenuplugin.cpp
@@ -0,0 +1,30 @@
+/* This file is part of the KDE project
+ Copyright 2009 Harald Hvaal <haraldhv at stud.ntnu.no>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) version 3.
+
+ 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 "dndpopupmenuplugin.h"
+
+using namespace KIO;
+
+DndPopupMenuPlugin::DndPopupMenuPlugin(QObject* parent)
+ : QObject(parent)
+{
+}
+
+DndPopupMenuPlugin::~DndPopupMenuPlugin()
+{
+}
diff --git a/src/widgets/dndpopupmenuplugin.h b/src/widgets/dndpopupmenuplugin.h
new file mode 100644
index 0000000..6364295
--- /dev/null
+++ b/src/widgets/dndpopupmenuplugin.h
@@ -0,0 +1,67 @@
+/* This file is part of the KDE project
+ Copyright 2009 Harald Hvaal <haraldhv at stud.ntnu.no>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) version 3.
+
+ 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 _KIO_DNDPOPUPMENUPLUGIN_H_
+#define _KIO_DNDPOPUPMENUPLUGIN_H_
+
+#include "kiowidgets_export.h"
+#include <QObject>
+
+class QMenu;
+class KFileItemListProperties;
+class QUrl;
+class QAction;
+
+namespace KIO
+{
+/**
+ * Base class for drag and drop popup menus
+ *
+ * This can be used for adding dynamic menu items to the normal copy/move/link
+ * here menu appearing in dolphin/konqueror. In the setup method you may check
+ * the properties of the dropped files, and if applicable, append your own
+ * QAction that the user may trigger in the menu.
+ *
+ * @author Harald Hvaal <metellius at gmail.com>
+ * @since 5.6
+ */
+class KIOWIDGETS_EXPORT DndPopupMenuPlugin : public QObject
+{
+ Q_OBJECT
+public:
+
+ /**
+ * Constructor.
+ */
+ DndPopupMenuPlugin(QObject* parent);
+ virtual ~DndPopupMenuPlugin();
+
+ /**
+ * Implement the setup method in the plugin in order to create actions
+ * in the given actionCollection and add it to the menu using menu->addAction().
+ *
+ * @param popupMenuInfo all the information about the source URLs being dropped
+ * @param destination the URL to where the file(s) were dropped
+ * @return a QList with the QActions that will be plugged into the menu.
+ */
+ virtual QList<QAction *> setup(const KFileItemListProperties& popupMenuInfo,
+ const QUrl& destination) = 0;
+};
+
+}
+
+#endif
diff --git a/src/widgets/dropjob.cpp b/src/widgets/dropjob.cpp
new file mode 100644
index 0000000..7f33be4
--- /dev/null
+++ b/src/widgets/dropjob.cpp
@@ -0,0 +1,450 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2014 David Faure <faure at kde.org>
+
+ This library is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2 of the License or (at
+ your option) version 3 or, at the discretion of KDE e.V. (which shall
+ act as a proxy as in section 14 of the GPLv3), any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "dropjob.h"
+
+#include "job_p.h"
+#include "pastejob.h"
+#include "pastejob_p.h"
+#include "jobuidelegate.h"
+#include "jobuidelegateextension.h"
+
+#include <KConfigGroup>
+#include <KCoreDirLister>
+#include <KDesktopFile>
+#include <KIO/CopyJob>
+#include <KIO/DndPopupMenuPlugin>
+#include <KIO/FileUndoManager>
+#include <KFileItem>
+#include <KFileItemListProperties>
+#include <KJobWidgets>
+#include <KLocalizedString>
+#include <KMimeTypeTrader>
+#include <KProtocolManager>
+#include <KToolInvocation>
+#include <KUrlMimeData>
+
+#include <QApplication>
+#include <QDebug>
+#include <QDropEvent>
+#include <QFileInfo>
+#include <QMenu>
+#include <QMimeData>
+#include <QProcess>
+#include <QTimer>
+
+using namespace KIO;
+
+Q_DECLARE_METATYPE(Qt::DropAction);
+
+class KIO::DropJobPrivate : public KIO::JobPrivate
+{
+public:
+ DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags)
+ : JobPrivate(),
+ // Extract everything from the dropevent, since it will be deleted before the job starts
+ m_mimeData(dropEvent->mimeData()),
+ m_urls(KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls, &m_metaData)),
+ m_dropAction(dropEvent->dropAction()),
+ m_globalPos(QCursor::pos()), // record mouse pos at time of drop
+ m_keyboardModifiers(dropEvent->keyboardModifiers()),
+ m_destUrl(destUrl),
+ m_destItem(KCoreDirLister::cachedItemForUrl(destUrl)),
+ m_flags(flags | KIO::HideProgressInfo)
+ {
+ // Check for the drop of a bookmark -> we want a Link action
+ if (m_mimeData->hasFormat("application/x-xbel")) {
+ m_keyboardModifiers |= Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier);
+ m_dropAction = Qt::LinkAction;
+ }
+ if (m_destItem.isNull() && m_destUrl.isLocalFile()) {
+ m_destItem = KFileItem(m_destUrl);
+ }
+ }
+
+ bool destIsDirectory() const {
+ if (!m_destItem.isNull()) {
+ return m_destItem.isDir();
+ }
+ // We support local dir, remote dir, local desktop file, local executable.
+ // So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not.
+ return true;
+ }
+ void handleCopyToDirectory();
+ void handleDropToDesktopFile();
+ void handleDropToExecutable();
+ int determineDropAction();
+ void fillPopupMenu(QMenu *popup);
+ void addPluginActions(QMenu *popup, const KFileItemListProperties &itemProps);
+ void doCopyToDirectory();
+
+ const QMimeData *m_mimeData;
+ const QList<QUrl> m_urls;
+ QMap<QString, QString> m_metaData;
+ Qt::DropAction m_dropAction;
+ QPoint m_globalPos;
+ Qt::KeyboardModifiers m_keyboardModifiers;
+ QUrl m_destUrl;
+ KFileItem m_destItem; // null for remote URLs not found in the dirlister cache
+ const JobFlags m_flags;
+ QList<QAction *> m_appActions;
+ QList<QAction *> m_pluginActions;
+
+ Q_DECLARE_PUBLIC(DropJob)
+
+ void slotStart();
+ void slotCopyingDone(KIO::Job*, const QUrl &, const QUrl &to) {
+ emit q_func()->itemCreated(to);
+ }
+ void slotCopyingLinkDone(KIO::Job*, const QUrl &, const QString &, const QUrl &to) {
+ emit q_func()->itemCreated(to);
+ }
+ void slotTriggered(QAction *);
+
+ static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags) {
+ DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, flags));
+ job->setUiDelegate(KIO::createDefaultJobUiDelegate());
+ if (!(flags & HideProgressInfo)) {
+ KIO::getJobTracker()->registerJob(job);
+ }
+ return job;
+ }
+
+};
+
+DropJob::DropJob(DropJobPrivate &dd)
+ : Job(dd)
+{
+ QTimer::singleShot(0, this, SLOT(slotStart()));
+}
+
+DropJob::~DropJob()
+{
+}
+
+void DropJobPrivate::slotStart()
+{
+ Q_Q(DropJob);
+ if (!m_urls.isEmpty()) {
+ if (destIsDirectory()) {
+ handleCopyToDirectory();
+ } else { // local file
+ const QString destFile = m_destUrl.toLocalFile();
+ if (KDesktopFile::isDesktopFile(destFile)) {
+ handleDropToDesktopFile();
+ } else if (QFileInfo(destFile).isExecutable()) {
+ handleDropToExecutable();
+ } else {
+ // should not happen, if KDirModel::flags is correct
+ q->setError(KIO::ERR_ACCESS_DENIED);
+ q->emitResult();
+ }
+ }
+ return;
+ }
+
+ // Dropping raw data
+ KIO::PasteJob *job = KIO::paste(m_mimeData, m_destUrl, KIO::HideProgressInfo);
+ job->d_func()->m_clipboard = false;
+ QObject::connect(job, &KIO::PasteJob::itemCreated, q, &KIO::DropJob::itemCreated);
+ q->addSubjob(job);
+}
+
+// Input: m_dropAction as set by Qt at the time of the drop event
+// Output: m_dropAction possibly modified
+// Returns a KIO error code, in case of error.
+int DropJobPrivate::determineDropAction()
+{
+ Q_Q(DropJob);
+
+ if (!KProtocolManager::supportsWriting(m_destUrl)) {
+ return KIO::ERR_CANNOT_WRITE;
+ }
+ if (!m_destItem.isNull() && !m_destItem.isWritable()) {
+ return KIO::ERR_WRITE_ACCESS_DENIED;
+ }
+
+ bool allItemsAreFromTrash = true;
+ bool containsTrashRoot = false;
+ foreach (const QUrl &url, m_urls) {
+ const bool local = url.isLocalFile();
+ //if (local && KDesktopFile::isDesktopFile(url.toLocalFile()))
+ // isDesktopFile = true;
+ if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) {
+ if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
+ containsTrashRoot = true;
+ }
+ } else {
+ allItemsAreFromTrash = false;
+ }
+ if (url.matches(m_destUrl, QUrl::StripTrailingSlash)) {
+ return KIO::ERR_DROP_ON_ITSELF;
+ }
+ }
+
+ const bool trashing = m_destUrl.scheme() == QLatin1String("trash");
+ if (trashing) {
+ m_dropAction = Qt::MoveAction;
+ if (!q->uiDelegateExtension()->askDeleteConfirmation(m_urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
+ return KIO::ERR_USER_CANCELED;
+ }
+ return 0; // ok
+ }
+ if (containsTrashRoot) {
+ // Dropping a link to the trash: don't move the full contents, just make a link (#319660)
+ m_dropAction = Qt::LinkAction;
+ return 0; // ok
+ }
+ if (allItemsAreFromTrash) {
+ // No point in asking copy/move/link when using dragging from the trash, just move the file out.
+ m_dropAction = Qt::MoveAction;
+ return 0; // ok
+ }
+ if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
+ // Qt determined m_dropAction from the modifiers already
+ return 0; // ok
+ }
+
+ // We need to ask the user with a popup menu. Let the caller know.
+ return KIO::ERR_UNKNOWN;
+}
+
+void DropJobPrivate::fillPopupMenu(QMenu *popup)
+{
+ Q_Q(DropJob);
+
+ // Check what the source can do
+ // TODO: Determining the mimetype of the source URLs is difficult for remote URLs,
+ // we would need to KIO::stat each URL in turn, asynchronously....
+ KFileItemList fileItems;
+ foreach (const QUrl &url, m_urls) {
+ fileItems.append(KFileItem(url));
+ }
+ const KFileItemListProperties itemProps(fileItems);
+
+ emit q->popupMenuAboutToShow(fileItems);
+
+ const bool sReading = itemProps.supportsReading();
+ const bool sDeleting = itemProps.supportsDeleting();
+ const bool sMoving = itemProps.supportsMoving();
+
+ QString seq = QKeySequence(Qt::ShiftModifier).toString();
+ Q_ASSERT(seq.endsWith('+'));
+ seq.chop(1); // chop superfluous '+'
+ QAction* popupMoveAction = new QAction(i18n("&Move Here") + '\t' + seq, popup);
+ popupMoveAction->setIcon(QIcon::fromTheme("go-jump"));
+ popupMoveAction->setData(QVariant::fromValue(Qt::MoveAction));
+ seq = QKeySequence(Qt::ControlModifier).toString();
+ Q_ASSERT(seq.endsWith('+'));
+ seq.chop(1);
+ QAction* popupCopyAction = new QAction(i18n("&Copy Here") + '\t' + seq, popup);
+ popupCopyAction->setIcon(QIcon::fromTheme("edit-copy"));
+ popupCopyAction->setData(QVariant::fromValue(Qt::CopyAction));
+ seq = QKeySequence(Qt::ControlModifier + Qt::ShiftModifier).toString();
+ Q_ASSERT(seq.endsWith('+'));
+ seq.chop(1);
+ QAction* popupLinkAction = new QAction(i18n("&Link Here") + '\t' + seq, popup);
+ popupLinkAction->setIcon(QIcon::fromTheme("edit-link"));
+ popupLinkAction->setData(QVariant::fromValue(Qt::LinkAction));
+ QAction* popupCancelAction = new QAction(i18n("C&ancel") + '\t' + QKeySequence(Qt::Key_Escape).toString(), popup);
+ popupCancelAction->setIcon(QIcon::fromTheme("process-stop"));
+
+ if (sMoving || (sReading && sDeleting)) {
+ bool equalDestination = true;
+ foreach (const QUrl &src, m_urls) {
+ if (!m_destUrl.matches(src.adjusted(QUrl::RemoveFilename), QUrl::StripTrailingSlash)) {
+ equalDestination = false;
+ break;
+ }
+ }
+
+ if (!equalDestination) {
+ popup->addAction(popupMoveAction);
+ }
+ }
+
+ if (sReading) {
+ popup->addAction(popupCopyAction);
+ }
+
+ popup->addAction(popupLinkAction);
+
+ addPluginActions(popup, itemProps);
+
+ popup->addSeparator();
+ popup->addAction(popupCancelAction);
+}
+
+void DropJobPrivate::addPluginActions(QMenu *popup, const KFileItemListProperties &itemProps)
+{
+ Q_Q(DropJob);
+
+ const QString commonMimeType = itemProps.mimeType().isEmpty() ? QString("application/octet-stream") : itemProps.mimeType();
+ const KService::List plugin_offers = KMimeTypeTrader::self()->query(commonMimeType, "KIO/DndPopupMenu/Plugin", "exist Library");
+ foreach (const KService::Ptr &service, plugin_offers) {
+ //qDebug() << service->name() << service->library();
+ KIO::DndPopupMenuPlugin *plugin = service->createInstance<KIO::DndPopupMenuPlugin>(popup);
+ if (plugin) {
+ m_pluginActions += plugin->setup(itemProps, m_destUrl);
+ }
+ }
+
+ if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) {
+ popup->addSeparator();
+ popup->addActions(m_appActions);
+ popup->addActions(m_pluginActions);
+ }
+
+}
+
+void DropJob::setApplicationActions(const QList<QAction *> &actions)
+{
+ Q_D(DropJob);
+ d->m_appActions = actions;
+}
+
+void DropJobPrivate::slotTriggered(QAction *action)
+{
+ Q_Q(DropJob);
+ if (m_appActions.contains(action) || m_pluginActions.contains(action)) {
+ q->emitResult();
+ return;
+ }
+ const QVariant data = action->data();
+ if (!data.canConvert<Qt::DropAction>()) {
+ q->setError(KIO::ERR_USER_CANCELED);
+ q->emitResult();
+ return;
+ }
+ m_dropAction = data.value<Qt::DropAction>();
+ doCopyToDirectory();
+}
+
+void DropJobPrivate::handleCopyToDirectory()
+{
+ Q_Q(DropJob);
+
+ if (int error = determineDropAction()) {
+ if (error == KIO::ERR_UNKNOWN) {
+ QMenu *menu = new QMenu(KJobWidgets::window(q));
+ QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
+ fillPopupMenu(menu);
+ // can't use new-style connect here
+ QObject::connect(menu, SIGNAL(triggered(QAction*)), q, SLOT(slotTriggered(QAction*)));
+ menu->popup(m_globalPos);
+ } else {
+ q->setError(error);
+ q->emitResult();
+ }
+ } else {
+ doCopyToDirectory();
+ }
+}
+
+void DropJobPrivate::doCopyToDirectory()
+{
+ Q_Q(DropJob);
+ KIO::CopyJob * job = 0;
+ switch (m_dropAction) {
+ case Qt::MoveAction:
+ job = KIO::move(m_urls, m_destUrl, m_flags);
+ KIO::FileUndoManager::self()->recordJob(
+ m_destUrl.scheme() == QLatin1String("trash") ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move,
+ m_urls, m_destUrl, job);
+ break;
+ case Qt::CopyAction:
+ job = KIO::copy(m_urls, m_destUrl, m_flags);
+ KIO::FileUndoManager::self()->recordCopyJob(job);
+ break;
+ case Qt::LinkAction:
+ job = KIO::link(m_urls, m_destUrl, m_flags);
+ KIO::FileUndoManager::self()->recordCopyJob(job);
+ break;
+ default:
+ qWarning() << "Unknown drop action" << (int)m_dropAction;
+ q->setError(KIO::ERR_UNSUPPORTED_ACTION);
+ q->emitResult();
+ return;
+ }
+ Q_ASSERT(job);
+ job->setMetaData(m_metaData);
+ QObject::connect(job, SIGNAL(copyingDone(KIO::Job*, QUrl, QUrl, QDateTime, bool, bool)), q, SLOT(slotCopyingDone(KIO::Job*, QUrl, QUrl)));
+ QObject::connect(job, SIGNAL(copyingLinkDone(KIO::Job*, QUrl, QString, QUrl)), q, SLOT(slotCopyingLinkDone(KIO::Job*, QUrl, QString, QUrl)));
+ q->addSubjob(job);
+}
+
+void DropJobPrivate::handleDropToDesktopFile()
+{
+ Q_Q(DropJob);
+ const QString destFile = m_destUrl.toLocalFile();
+ const KDesktopFile desktopFile(destFile);
+ const KConfigGroup desktopGroup = desktopFile.desktopGroup();
+ if (desktopFile.hasApplicationType()) {
+ // Drop to application -> start app with urls as argument
+ QString error;
+ if (KToolInvocation::startServiceByDesktopPath(destFile, QUrl::toStringList(m_urls), &error) > 0) {
+ q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS);
+ q->setErrorText(error);
+ }
+ q->emitResult();
+ } else if (desktopFile.hasLinkType() && desktopGroup.hasKey("URL")) {
+ // Drop to link -> adjust destination directory
+ m_destUrl = QUrl::fromUserInput(desktopGroup.readPathEntry("URL", QString()));
+ handleCopyToDirectory();
+ } else {
+ if (desktopFile.hasDeviceType()) {
+ qWarning() << "Not re-implemented; please email kde-frameworks-devel at kde.org if you need this.";
+ // take code from libkonq's old konq_operations.cpp
+ // for now, fallback
+ }
+ // Some other kind of .desktop file (service, servicetype...)
+ q->setError(KIO::ERR_UNSUPPORTED_ACTION);
+ q->emitResult();
+ }
+}
+
+void DropJobPrivate::handleDropToExecutable()
+{
+ // Launch executable for each of the files
+ QStringList args;
+ Q_FOREACH(const QUrl &url, m_urls) {
+ args << url.toLocalFile(); // assume local files
+ }
+ //qDebug() << "starting" << m_destUrl.toLocalFile() << "with" << lst.count() << "arguments";
+ QProcess::startDetached(m_destUrl.toLocalFile(), args);
+}
+
+void DropJob::slotResult(KJob *job)
+{
+ Q_D(DropJob);
+ if (job->error()) {
+ KIO::Job::slotResult(job); // will set the error and emit result(this)
+ return;
+ }
+ removeSubjob(job);
+ emitResult();
+}
+
+DropJob * KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags)
+{
+ return DropJobPrivate::newJob(dropEvent, destUrl, flags);
+}
+
+#include "moc_dropjob.cpp"
diff --git a/src/widgets/dropjob.h b/src/widgets/dropjob.h
new file mode 100644
index 0000000..db67a90
--- /dev/null
+++ b/src/widgets/dropjob.h
@@ -0,0 +1,114 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2014 David Faure <faure at kde.org>
+
+ This library is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2 of the License or ( at
+ your option ) version 3 or, at the discretion of KDE e.V. ( which shall
+ act as a proxy as in section 14 of the GPLv3 ), any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef DROPJOB_H
+#define DROPJOB_H
+
+#include <QUrl>
+
+#include "kiowidgets_export.h"
+#include <kio/job_base.h>
+
+class QAction;
+class QDropEvent;
+class QMimeData;
+class KFileItemListProperties;
+
+namespace KIO
+{
+
+class DropJobPrivate;
+
+/**
+ * A KIO job that handles dropping into a file-manager-like view.
+ * @see KIO::drop
+ * @since 5.6
+ */
+class KIOWIDGETS_EXPORT DropJob : public Job
+{
+ Q_OBJECT
+
+public:
+ virtual ~DropJob();
+
+ /**
+ * Allows the application to set additional actions in the drop popup menu.
+ * For instance, the application handling the desktop might want to add
+ * "set as wallpaper" if the dropped url is an image file.
+ * This can be called upfront, or for convenience, when popupMenuAboutToShow is emitted.
+ */
+ void setApplicationActions(const QList<QAction *> &actions);
+
+Q_SIGNALS:
+ /**
+ * Signals that a file or directory was created.
+ */
+ void itemCreated(const QUrl &url);
+
+ /**
+ * Signals that the popup menu is about to be shown.
+ * Applications can use the information provided about the dropped URLs
+ * (e.g. the mimetype) to decide whether to call setApplicationActions.
+ * @param itemProps properties of the dropped items
+ */
+ void popupMenuAboutToShow(const KFileItemListProperties &itemProps);
+
+protected Q_SLOTS:
+ void slotResult(KJob *job) Q_DECL_OVERRIDE;
+
+protected:
+ DropJob(DropJobPrivate &dd);
+
+private:
+ Q_DECLARE_PRIVATE(DropJob)
+ Q_PRIVATE_SLOT(d_func(), void slotStart());
+ Q_PRIVATE_SLOT(d_func(), void slotCopyingDone(KIO::Job*, const QUrl &, const QUrl &to));
+ Q_PRIVATE_SLOT(d_func(), void slotCopyingLinkDone(KIO::Job*, const QUrl &, const QString &, const QUrl &to));
+ Q_PRIVATE_SLOT(d_func(), void slotTriggered(QAction*));
+};
+
+/**
+ * Drops the clipboard contents.
+ *
+ * If the mime data contains URLs, a popup appears to choose between
+ * Move, Copy, Link and Cancel
+ * which is then implemented by the job, using KIO::move, KIO::copy or KIO::link
+ * Additional actions provided by the application or by plugins can be shown in the popup.
+ *
+ * If the mime data contains other data than URLs, it is saved into a file after asking
+ * the user to choose a filename and the preferred data format.
+ *
+ * This job takes care of recording the subjob in the FileUndoManager, and emits
+ * itemCreated for every file or directory being created, so that the view can select
+ * these items.
+ *
+ * @param dropEvent the drop event, from which the job will extract mimeData, dropAction, etc.
+ The application should take care of calling dropEvent->acceptProposedAction().
+ * @param destUrl The URL of the target file or directory
+ * @param flags passed to the sub job
+ *
+ * @return A pointer to the job handling the operation.
+ * @since 5.4
+ */
+KIOCORE_EXPORT DropJob *drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags = DefaultFlags);
+
+}
+
+#endif
diff --git a/src/widgets/kiodndpopupmenuplugin.desktop b/src/widgets/kiodndpopupmenuplugin.desktop
new file mode 100644
index 0000000..5d4d277
--- /dev/null
+++ b/src/widgets/kiodndpopupmenuplugin.desktop
@@ -0,0 +1,4 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=KIO/DndPopupMenu/Plugin
+Comment=Plugin for the KIO Drag-and-drop Popup Menu
diff --git a/src/widgets/pastejob.cpp b/src/widgets/pastejob.cpp
index 7ad93da..3f08d61 100644
--- a/src/widgets/pastejob.cpp
+++ b/src/widgets/pastejob.cpp
@@ -18,8 +18,7 @@
*/
#include "pastejob.h"
-
-#include "job_p.h"
+#include "pastejob_p.h"
#include "paste.h"
@@ -39,41 +38,6 @@
extern KIO::Job *pasteMimeDataImpl(const QMimeData *mimeData, const QUrl &destUrl,
const QString &dialogText, QWidget *widget,
bool clipboard);
-
-class KIO::PasteJobPrivate : public KIO::JobPrivate
-{
-public:
- PasteJobPrivate(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags, bool clipboard)
- : JobPrivate(),
- m_mimeData(mimeData),
- m_destDir(destDir),
- m_flags(flags),
- m_clipboard(clipboard)
- {
- }
-
- const QMimeData *m_mimeData;
- QUrl m_destDir;
- JobFlags m_flags;
- bool m_clipboard;
-
- Q_DECLARE_PUBLIC(PasteJob)
-
- void slotStart();
- void slotCopyingDone(KIO::Job*, const QUrl &, const QUrl &to) { emit q_func()->itemCreated(to); }
- void slotCopyingLinkDone(KIO::Job*, const QUrl &, const QString &, const QUrl &to) { emit q_func()->itemCreated(to); }
-
- static inline PasteJob *newJob(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags, bool clipboard)
- {
- PasteJob *job = new PasteJob(*new PasteJobPrivate(mimeData, destDir, flags, clipboard));
- job->setUiDelegate(KIO::createDefaultJobUiDelegate());
- if (!(flags & HideProgressInfo)) {
- KIO::getJobTracker()->registerJob(job);
- }
- return job;
- }
-
-};
PasteJob::PasteJob(PasteJobPrivate &dd)
: Job(dd)
@@ -137,15 +101,7 @@
PasteJob * KIO::paste(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags)
{
- return PasteJobPrivate::newJob(mimeData, destDir, flags, true);
+ return PasteJobPrivate::newJob(mimeData, destDir, flags);
}
-
-/*
- To be called from the drop job directly.
-PasteJob * KIO::drop(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags)
-{
- return PasteJobPrivate::newJob(mimeData, destDir, flags, false);
-}
-*/
#include "moc_pastejob.cpp"
diff --git a/src/widgets/pastejob.h b/src/widgets/pastejob.h
index 026e152..2d08929 100644
--- a/src/widgets/pastejob.h
+++ b/src/widgets/pastejob.h
@@ -31,6 +31,7 @@
{
class PasteJobPrivate;
+class DropJobPrivate;
/**
* A KIO job that handles pasting the clipboard contents.
*
@@ -61,6 +62,7 @@
PasteJob(PasteJobPrivate &dd);
private:
+ friend class KIO::DropJobPrivate;
Q_DECLARE_PRIVATE(PasteJob)
Q_PRIVATE_SLOT(d_func(), void slotStart());
Q_PRIVATE_SLOT(d_func(), void slotCopyingDone(KIO::Job*, const QUrl &, const QUrl &to));
diff --git a/src/widgets/pastejob_p.h b/src/widgets/pastejob_p.h
new file mode 100644
index 0000000..41e63fa
--- /dev/null
+++ b/src/widgets/pastejob_p.h
@@ -0,0 +1,63 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2014 David Faure <faure at kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef PASTEJOB_P_H
+#define PASTEJOB_P_H
+
+#include <job_p.h>
+
+namespace KIO { class DropJobPrivate; }
+
+class KIO::PasteJobPrivate : public KIO::JobPrivate
+{
+public:
+ PasteJobPrivate(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags)
+ : JobPrivate(),
+ m_mimeData(mimeData),
+ m_destDir(destDir),
+ m_flags(flags),
+ m_clipboard(true) // set to false by DropJob
+ {
+ }
+
+ friend class KIO::DropJobPrivate;
+
+ const QMimeData *m_mimeData;
+ QUrl m_destDir;
+ JobFlags m_flags;
+ bool m_clipboard;
+
+ Q_DECLARE_PUBLIC(PasteJob)
+
+ void slotStart();
+ void slotCopyingDone(KIO::Job*, const QUrl &, const QUrl &to) { emit q_func()->itemCreated(to); }
+ void slotCopyingLinkDone(KIO::Job*, const QUrl &, const QString &, const QUrl &to) { emit q_func()->itemCreated(to); }
+
+ static inline PasteJob *newJob(const QMimeData *mimeData, const QUrl &destDir, JobFlags flags)
+ {
+ PasteJob *job = new PasteJob(*new PasteJobPrivate(mimeData, destDir, flags));
+ job->setUiDelegate(KIO::createDefaultJobUiDelegate());
+ if (!(flags & HideProgressInfo)) {
+ KIO::getJobTracker()->registerJob(job);
+ }
+ return job;
+ }
+};
+
+#endif
--
To view, visit https://gerrit.vesnicky.cesnet.cz/r/241
To unsubscribe, visit https://gerrit.vesnicky.cesnet.cz/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I9717f8a9e68a750696f2a54adfa828dead9a31f3
Gerrit-PatchSet: 1
Gerrit-Project: kio
Gerrit-Branch: master
Gerrit-Owner: David Faure <faure at kde.org>
Gerrit-Reviewer: Sysadmin Testing Account <null at kde.org>
More information about the Kde-frameworks-devel
mailing list