Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,696 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "dropjob.h"
|
||||
|
||||
#include "job_p.h"
|
||||
#include "jobuidelegate.h"
|
||||
#include "jobuidelegateextension.h"
|
||||
#include "kio_widgets_debug.h"
|
||||
#include "pastejob.h"
|
||||
#include "pastejob_p.h"
|
||||
|
||||
#include <KConfigGroup>
|
||||
#include <KCoreDirLister>
|
||||
#include <KDesktopFile>
|
||||
#include <KFileItem>
|
||||
#include <KFileItemListProperties>
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#include <KIO/CopyJob>
|
||||
#include <KIO/DndPopupMenuPlugin>
|
||||
#include <KIO/FileUndoManager>
|
||||
#include <KJobWidgets>
|
||||
#include <KJobWindows>
|
||||
#include <KLocalizedString>
|
||||
#include <KPluginFactory>
|
||||
#include <KPluginMetaData>
|
||||
#include <KProtocolManager>
|
||||
#include <KService>
|
||||
#include <KUrlMimeData>
|
||||
|
||||
#ifdef WITH_QTDBUS
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusPendingCall>
|
||||
#endif
|
||||
|
||||
#include <QDropEvent>
|
||||
#include <QFileInfo>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
using namespace KIO;
|
||||
|
||||
Q_DECLARE_METATYPE(Qt::DropAction)
|
||||
|
||||
namespace KIO
|
||||
{
|
||||
class DropMenu;
|
||||
}
|
||||
|
||||
class KIO::DropMenu : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DropMenu(QWidget *parent = nullptr);
|
||||
~DropMenu() override;
|
||||
|
||||
void addCancelAction();
|
||||
void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions);
|
||||
|
||||
private:
|
||||
QList<QAction *> m_appActions;
|
||||
QList<QAction *> m_pluginActions;
|
||||
QAction *m_lastSeparator;
|
||||
QAction *m_extraActionsSeparator;
|
||||
QAction *m_cancelAction;
|
||||
};
|
||||
|
||||
static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashService = //
|
||||
QStringLiteral("application/x-kde-ark-dndextract-service");
|
||||
static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath = //
|
||||
QStringLiteral("application/x-kde-ark-dndextract-path");
|
||||
|
||||
class KIO::DropJobPrivate : public KIO::JobPrivate
|
||||
{
|
||||
public:
|
||||
DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
|
||||
: JobPrivate()
|
||||
, m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts
|
||||
, m_urls(KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls, &m_metaData))
|
||||
, m_dropAction(dropEvent->dropAction())
|
||||
, m_relativePos(dropEvent->position().toPoint())
|
||||
, m_keyboardModifiers(dropEvent->modifiers())
|
||||
, m_hasArkFormat(m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)
|
||||
&& m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath))
|
||||
, m_destUrl(destUrl)
|
||||
, m_destItem(KCoreDirLister::cachedItemForUrl(destUrl))
|
||||
, m_flags(flags)
|
||||
, m_dropjobFlags(dropjobFlags)
|
||||
, m_triggered(false)
|
||||
{
|
||||
// Check for the drop of a bookmark -> we want a Link action
|
||||
if (m_mimeData->hasFormat(QStringLiteral("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);
|
||||
}
|
||||
|
||||
if (m_hasArkFormat) {
|
||||
m_remoteArkDBusClient = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService));
|
||||
m_remoteArkDBusPath = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath));
|
||||
}
|
||||
|
||||
if (!(m_flags & KIO::NoPrivilegeExecution)) {
|
||||
m_privilegeExecutionEnabled = true;
|
||||
switch (m_dropAction) {
|
||||
case Qt::CopyAction:
|
||||
m_operationType = Copy;
|
||||
break;
|
||||
case Qt::MoveAction:
|
||||
m_operationType = Move;
|
||||
break;
|
||||
case Qt::LinkAction:
|
||||
m_operationType = Symlink;
|
||||
break;
|
||||
default:
|
||||
m_operationType = Other;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 slotDropActionDetermined(int error);
|
||||
void handleDropToDesktopFile();
|
||||
void handleDropToExecutable();
|
||||
void fillPopupMenu(KIO::DropMenu *popup);
|
||||
void addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps);
|
||||
void doCopyToDirectory();
|
||||
|
||||
QWindow *transientParent();
|
||||
|
||||
QPointer<const QMimeData> m_mimeData;
|
||||
const QList<QUrl> m_urls;
|
||||
QMap<QString, QString> m_metaData;
|
||||
Qt::DropAction m_dropAction;
|
||||
QPoint m_relativePos;
|
||||
Qt::KeyboardModifiers m_keyboardModifiers;
|
||||
bool m_hasArkFormat;
|
||||
QString m_remoteArkDBusClient;
|
||||
QString m_remoteArkDBusPath;
|
||||
QUrl m_destUrl;
|
||||
KFileItem m_destItem; // null for remote URLs not found in the dirlister cache
|
||||
const JobFlags m_flags;
|
||||
const DropJobFlags m_dropjobFlags;
|
||||
QList<QAction *> m_appActions;
|
||||
QList<QAction *> m_pluginActions;
|
||||
bool m_triggered; // Tracks whether an action has been triggered in the popup menu.
|
||||
QSet<KIO::DropMenu *> m_menus;
|
||||
|
||||
Q_DECLARE_PUBLIC(DropJob)
|
||||
|
||||
void slotStart();
|
||||
void slotTriggered(QAction *);
|
||||
void slotAboutToHide();
|
||||
|
||||
static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
|
||||
{
|
||||
DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags));
|
||||
job->setUiDelegate(KIO::createDefaultJobUiDelegate());
|
||||
// Note: never KIO::getJobTracker()->registerJob here.
|
||||
// We don't want a progress dialog during the copy/move/link popup, it would in fact close
|
||||
// the popup
|
||||
return job;
|
||||
}
|
||||
};
|
||||
|
||||
DropMenu::DropMenu(QWidget *parent)
|
||||
: QMenu(parent)
|
||||
, m_extraActionsSeparator(nullptr)
|
||||
{
|
||||
m_cancelAction = new QAction(i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText), this);
|
||||
m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
|
||||
|
||||
m_lastSeparator = new QAction(this);
|
||||
m_lastSeparator->setSeparator(true);
|
||||
}
|
||||
|
||||
DropMenu::~DropMenu()
|
||||
{
|
||||
}
|
||||
|
||||
void DropMenu::addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions)
|
||||
{
|
||||
removeAction(m_lastSeparator);
|
||||
removeAction(m_cancelAction);
|
||||
|
||||
removeAction(m_extraActionsSeparator);
|
||||
for (QAction *action : std::as_const(m_appActions)) {
|
||||
removeAction(action);
|
||||
}
|
||||
for (QAction *action : std::as_const(m_pluginActions)) {
|
||||
removeAction(action);
|
||||
}
|
||||
|
||||
m_appActions = appActions;
|
||||
m_pluginActions = pluginActions;
|
||||
|
||||
if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) {
|
||||
QAction *firstExtraAction = m_appActions.value(0, m_pluginActions.value(0, nullptr));
|
||||
if (firstExtraAction && !firstExtraAction->isSeparator()) {
|
||||
if (!m_extraActionsSeparator) {
|
||||
m_extraActionsSeparator = new QAction(this);
|
||||
m_extraActionsSeparator->setSeparator(true);
|
||||
}
|
||||
addAction(m_extraActionsSeparator);
|
||||
}
|
||||
addActions(appActions);
|
||||
addActions(pluginActions);
|
||||
}
|
||||
|
||||
addAction(m_lastSeparator);
|
||||
addAction(m_cancelAction);
|
||||
}
|
||||
|
||||
DropJob::DropJob(DropJobPrivate &dd)
|
||||
: Job(dd)
|
||||
{
|
||||
Q_D(DropJob);
|
||||
|
||||
QTimer::singleShot(0, this, [d]() {
|
||||
d->slotStart();
|
||||
});
|
||||
}
|
||||
|
||||
DropJob::~DropJob()
|
||||
{
|
||||
}
|
||||
|
||||
void DropJobPrivate::slotStart()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
|
||||
#ifdef WITH_QTDBUS
|
||||
if (m_hasArkFormat) {
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(m_remoteArkDBusClient,
|
||||
m_remoteArkDBusPath,
|
||||
QStringLiteral("org.kde.ark.DndExtract"),
|
||||
QStringLiteral("extractSelectedFilesTo"));
|
||||
message.setArguments({m_destUrl.toDisplayString(QUrl::PreferLocalFile)});
|
||||
const auto pending = QDBusConnection::sessionBus().asyncCall(message);
|
||||
auto watcher = std::make_shared<QDBusPendingCallWatcher>(pending);
|
||||
QObject::connect(watcher.get(), &QDBusPendingCallWatcher::finished, q, [this, watcher] {
|
||||
Q_Q(DropJob);
|
||||
|
||||
if (watcher->isError()) {
|
||||
q->setError(KIO::ERR_UNKNOWN);
|
||||
}
|
||||
q->emitResult();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
} else if (m_mimeData) {
|
||||
// Dropping raw data
|
||||
KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(m_mimeData, m_destUrl, KIO::HideProgressInfo, false /*not clipboard*/);
|
||||
QObject::connect(job, &KIO::PasteJob::itemCreated, q, &KIO::DropJob::itemCreated);
|
||||
q->addSubjob(job);
|
||||
}
|
||||
}
|
||||
|
||||
void DropJobPrivate::fillPopupMenu(KIO::DropMenu *popup)
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
|
||||
// Check what the source can do
|
||||
// TODO: Determining the MIME type of the source URLs is difficult for remote URLs,
|
||||
// we would need to KIO::stat each URL in turn, asynchronously....
|
||||
KFileItemList fileItems;
|
||||
fileItems.reserve(m_urls.size());
|
||||
for (const QUrl &url : m_urls) {
|
||||
fileItems.append(KFileItem(url));
|
||||
}
|
||||
const bool allSourcesAreHttpUrls = std::ranges::all_of(m_urls, [](const auto &url) {
|
||||
return url.scheme().startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
|
||||
});
|
||||
const KFileItemListProperties itemProps(fileItems);
|
||||
|
||||
Q_EMIT q->popupMenuAboutToShow(itemProps);
|
||||
|
||||
const bool sReading = itemProps.supportsReading();
|
||||
// For http URLs, even though technically the protocol supports deleting,
|
||||
// this never makes sense for a drag operation.
|
||||
const bool sDeleting = allSourcesAreHttpUrls ? false : itemProps.supportsDeleting();
|
||||
const bool sMoving = itemProps.supportsMoving();
|
||||
|
||||
const int separatorLength = QCoreApplication::translate("QShortcut", "+").size();
|
||||
QString seq = QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText);
|
||||
seq.chop(separatorLength); // chop superfluous '+'
|
||||
QAction *popupMoveAction = new QAction(i18n("&Move Here") + QLatin1Char('\t') + seq, popup);
|
||||
popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))));
|
||||
popupMoveAction->setData(QVariant::fromValue(Qt::MoveAction));
|
||||
seq = QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
|
||||
seq.chop(separatorLength);
|
||||
|
||||
const QString copyActionName = allSourcesAreHttpUrls ? i18nc("@action:inmenu Download contents of URL here", "&Download Here") : i18n("&Copy Here");
|
||||
const QIcon copyActionIcon = QIcon::fromTheme(allSourcesAreHttpUrls ? QStringLiteral("download") : QStringLiteral("edit-copy"));
|
||||
QAction *popupCopyAction = new QAction(copyActionName + QLatin1Char('\t') + seq, popup);
|
||||
popupCopyAction->setIcon(copyActionIcon);
|
||||
popupCopyAction->setData(QVariant::fromValue(Qt::CopyAction));
|
||||
seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(QKeySequence::NativeText);
|
||||
seq.chop(separatorLength);
|
||||
QAction *popupLinkAction = new QAction(i18n("&Link Here") + QLatin1Char('\t') + seq, popup);
|
||||
popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
|
||||
popupLinkAction->setData(QVariant::fromValue(Qt::LinkAction));
|
||||
|
||||
if (sMoving || (sReading && sDeleting)) {
|
||||
const bool equalDestination = std::all_of(m_urls.cbegin(), m_urls.cend(), [this](const QUrl &src) {
|
||||
return m_destUrl.matches(src.adjusted(QUrl::RemoveFilename), QUrl::StripTrailingSlash);
|
||||
});
|
||||
|
||||
if (!equalDestination) {
|
||||
popup->addAction(popupMoveAction);
|
||||
}
|
||||
}
|
||||
|
||||
if (sReading) {
|
||||
popup->addAction(popupCopyAction);
|
||||
}
|
||||
|
||||
popup->addAction(popupLinkAction);
|
||||
|
||||
addPluginActions(popup, itemProps);
|
||||
}
|
||||
|
||||
void DropJobPrivate::addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps)
|
||||
{
|
||||
const QList<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio_dnd"));
|
||||
for (const KPluginMetaData &data : plugin_offers) {
|
||||
if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) {
|
||||
const auto actions = plugin->setup(itemProps, m_destUrl);
|
||||
for (auto action : actions) {
|
||||
action->setParent(popup);
|
||||
}
|
||||
m_pluginActions += actions;
|
||||
}
|
||||
}
|
||||
|
||||
popup->addExtraActions(m_appActions, m_pluginActions);
|
||||
}
|
||||
|
||||
void DropJob::setApplicationActions(const QList<QAction *> &actions)
|
||||
{
|
||||
Q_D(DropJob);
|
||||
|
||||
d->m_appActions = actions;
|
||||
|
||||
for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
|
||||
menu->addExtraActions(d->m_appActions, d->m_pluginActions);
|
||||
}
|
||||
}
|
||||
|
||||
void DropJob::showMenu(const QPoint &p, QAction *atAction)
|
||||
{
|
||||
Q_D(DropJob);
|
||||
|
||||
if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
|
||||
if (QWindow *transientParent = d->transientParent()) {
|
||||
if (menu->winId()) {
|
||||
menu->windowHandle()->setTransientParent(transientParent);
|
||||
}
|
||||
}
|
||||
menu->popup(p, atAction);
|
||||
}
|
||||
}
|
||||
|
||||
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::slotAboutToHide()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
// QMenu emits aboutToHide before triggered.
|
||||
// So we need to give the menu time in case it needs to emit triggered.
|
||||
// If it does, the cleanup will be done by slotTriggered.
|
||||
QTimer::singleShot(0, q, [=, this]() {
|
||||
if (!m_triggered) {
|
||||
q->setError(KIO::ERR_USER_CANCELED);
|
||||
q->emitResult();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DropJobPrivate::handleCopyToDirectory()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
|
||||
// Process m_dropAction as set by Qt at the time of the drop event
|
||||
if (!KProtocolManager::supportsWriting(m_destUrl)) {
|
||||
slotDropActionDetermined(KIO::ERR_CANNOT_WRITE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) {
|
||||
slotDropActionDetermined(KIO::ERR_WRITE_ACCESS_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
bool allItemsAreFromTrash = true;
|
||||
bool containsTrashRoot = false;
|
||||
for (const QUrl &url : m_urls) {
|
||||
const bool local = url.isLocalFile();
|
||||
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)) {
|
||||
slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool trashing = m_destUrl.scheme() == QLatin1String("trash");
|
||||
if (trashing) {
|
||||
if (allItemsAreFromTrash) {
|
||||
qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash";
|
||||
slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
|
||||
return;
|
||||
}
|
||||
m_dropAction = Qt::MoveAction;
|
||||
|
||||
auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(q);
|
||||
|
||||
// No UI Delegate set for this job, or a delegate that doesn't implement
|
||||
// AskUserActionInterface, then just proceed with the job without asking.
|
||||
// This is useful for non-interactive usage, (which doesn't actually apply
|
||||
// here as a DropJob is always interactive), but this is useful for unittests,
|
||||
// which are typically non-interactive.
|
||||
if (!askUserInterface) {
|
||||
slotDropActionDetermined(KJob::NoError);
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, q, [this](bool allowDelete) {
|
||||
if (allowDelete) {
|
||||
slotDropActionDetermined(KJob::NoError);
|
||||
} else {
|
||||
slotDropActionDetermined(KIO::ERR_USER_CANCELED);
|
||||
}
|
||||
});
|
||||
|
||||
askUserInterface->askUserDelete(m_urls, KIO::AskUserActionInterface::Trash, KIO::AskUserActionInterface::DefaultConfirmation, KJobWidgets::window(q));
|
||||
return;
|
||||
}
|
||||
|
||||
// If we can't determine the action below, we use ERR::UNKNOWN as we need to ask
|
||||
// the user via a popup menu.
|
||||
int err = KIO::ERR_UNKNOWN;
|
||||
const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash");
|
||||
if (implicitCopy) {
|
||||
m_dropAction = Qt::CopyAction;
|
||||
err = KJob::NoError; // Ok
|
||||
} else if (containsTrashRoot) {
|
||||
// Dropping a link to the trash: don't move the full contents, just make a link (#319660)
|
||||
m_dropAction = Qt::LinkAction;
|
||||
err = KJob::NoError; // Ok
|
||||
} else if (allItemsAreFromTrash) {
|
||||
// No point in asking copy/move/link when using dragging from the trash, just move the file out.
|
||||
m_dropAction = Qt::MoveAction;
|
||||
err = KJob::NoError; // Ok
|
||||
} else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
|
||||
// Qt determined m_dropAction from the modifiers already
|
||||
err = KJob::NoError; // Ok
|
||||
}
|
||||
slotDropActionDetermined(err);
|
||||
}
|
||||
|
||||
QWindow *DropJobPrivate::transientParent()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
|
||||
if (QWidget *widget = KJobWidgets::window(q)) {
|
||||
QWidget *window = widget->window();
|
||||
Q_ASSERT(window);
|
||||
return window->windowHandle();
|
||||
}
|
||||
|
||||
if (QWindow *window = KJobWindows::window(q)) {
|
||||
return window;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DropJobPrivate::slotDropActionDetermined(int error)
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
|
||||
if (error == KJob::NoError) {
|
||||
doCopyToDirectory();
|
||||
return;
|
||||
}
|
||||
|
||||
// There was an error, handle it
|
||||
if (error == KIO::ERR_UNKNOWN) {
|
||||
KIO::DropMenu *menu = new KIO::DropMenu();
|
||||
QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater);
|
||||
|
||||
// If the user clicks outside the menu, it will be destroyed without emitting the triggered signal.
|
||||
QObject::connect(menu, &QMenu::aboutToHide, q, [this]() {
|
||||
slotAboutToHide();
|
||||
});
|
||||
|
||||
fillPopupMenu(menu);
|
||||
QObject::connect(menu, &QMenu::triggered, q, [this](QAction *action) {
|
||||
m_triggered = true;
|
||||
slotTriggered(action);
|
||||
});
|
||||
|
||||
if (!(m_dropjobFlags & KIO::ShowMenuManually)) {
|
||||
if (QWindow *parent = transientParent()) {
|
||||
if (menu->winId()) {
|
||||
menu->windowHandle()->setTransientParent(parent);
|
||||
}
|
||||
}
|
||||
auto *window = KJobWidgets::window(q);
|
||||
menu->popup(window ? window->mapToGlobal(m_relativePos) : QCursor::pos());
|
||||
}
|
||||
m_menus.insert(menu);
|
||||
QObject::connect(menu, &QObject::destroyed, q, [this, menu]() {
|
||||
m_menus.remove(menu);
|
||||
});
|
||||
} else {
|
||||
q->setError(error);
|
||||
q->emitResult();
|
||||
}
|
||||
}
|
||||
|
||||
void DropJobPrivate::doCopyToDirectory()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
KIO::CopyJob *job = nullptr;
|
||||
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:
|
||||
qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction);
|
||||
q->setError(KIO::ERR_UNSUPPORTED_ACTION);
|
||||
q->emitResult();
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(job);
|
||||
job->setParentJob(q);
|
||||
job->setMetaData(m_metaData);
|
||||
QObject::connect(job, &KIO::CopyJob::copyingDone, q, [q](KIO::Job *, const QUrl &, const QUrl &to) {
|
||||
Q_EMIT q->itemCreated(to);
|
||||
});
|
||||
QObject::connect(job, &KIO::CopyJob::copyingLinkDone, q, [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) {
|
||||
Q_EMIT q->itemCreated(to);
|
||||
});
|
||||
q->addSubjob(job);
|
||||
|
||||
Q_EMIT q->copyJobStarted(job);
|
||||
}
|
||||
|
||||
void DropJobPrivate::handleDropToDesktopFile()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
const QString urlKey = QStringLiteral("URL");
|
||||
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
|
||||
KService::Ptr service(new KService(destFile));
|
||||
// Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job,
|
||||
// instead pass q as parent so that KIO::delegateExtension() can find a delegate
|
||||
KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
|
||||
job->setUrls(m_urls);
|
||||
QObject::connect(job, &KJob::result, q, [=]() {
|
||||
if (job->error()) {
|
||||
q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS);
|
||||
q->setErrorText(destFile);
|
||||
}
|
||||
q->emitResult();
|
||||
});
|
||||
job->start();
|
||||
} else if (desktopFile.hasLinkType() && desktopGroup.hasKey(urlKey)) {
|
||||
// Drop to link -> adjust destination directory
|
||||
m_destUrl = QUrl::fromUserInput(desktopGroup.readPathEntry(urlKey, QString()));
|
||||
handleCopyToDirectory();
|
||||
} else {
|
||||
if (desktopFile.hasDeviceType()) {
|
||||
qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@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()
|
||||
{
|
||||
Q_Q(DropJob);
|
||||
// Launch executable for each of the files
|
||||
QStringList args;
|
||||
args.reserve(m_urls.size());
|
||||
for (const QUrl &url : std::as_const(m_urls)) {
|
||||
args << url.toLocalFile(); // assume local files
|
||||
}
|
||||
QProcess::startDetached(m_destUrl.toLocalFile(), args);
|
||||
q->emitResult();
|
||||
}
|
||||
|
||||
void DropJob::slotResult(KJob *job)
|
||||
{
|
||||
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, KIO::DropJobDefaultFlags, flags);
|
||||
}
|
||||
|
||||
DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
|
||||
{
|
||||
return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags);
|
||||
}
|
||||
|
||||
#include "dropjob.moc"
|
||||
#include "moc_dropjob.cpp"
|
||||
Reference in New Issue
Block a user