KIO

dropjob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "dropjob.h"
9
10#include "job_p.h"
11#include "jobuidelegate.h"
12#include "jobuidelegateextension.h"
13#include "kio_widgets_debug.h"
14#include "pastejob.h"
15#include "pastejob_p.h"
16
17#include <KConfigGroup>
18#include <KCoreDirLister>
19#include <KDesktopFile>
20#include <KFileItem>
21#include <KFileItemListProperties>
22#include <KIO/ApplicationLauncherJob>
23#include <KIO/CopyJob>
24#include <KIO/DndPopupMenuPlugin>
25#include <KIO/FileUndoManager>
26#include <KJobWidgets>
27#include <KJobWindows>
28#include <KLocalizedString>
29#include <KPluginFactory>
30#include <KPluginMetaData>
31#include <KProtocolManager>
32#include <KService>
33#include <KUrlMimeData>
34
35#ifdef WITH_QTDBUS
36#include <QDBusConnection>
37#include <QDBusPendingCall>
38#endif
39
40#include <QDropEvent>
41#include <QFileInfo>
42#include <QMenu>
43#include <QMimeData>
44#include <QProcess>
45#include <QTimer>
46#include <QWindow>
47
48using namespace KIO;
49
50Q_DECLARE_METATYPE(Qt::DropAction)
51
52namespace KIO
53{
54class DropMenu;
55}
56
57class KIO::DropMenu : public QMenu
58{
60public:
61 explicit DropMenu(QWidget *parent = nullptr);
62 ~DropMenu() override;
63
64 void addCancelAction();
65 void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions);
66
67private:
68 QList<QAction *> m_appActions;
69 QList<QAction *> m_pluginActions;
70 QAction *m_lastSeparator;
71 QAction *m_extraActionsSeparator;
72 QAction *m_cancelAction;
73};
74
75static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashService = //
76 QStringLiteral("application/x-kde-ark-dndextract-service");
77static const QString s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath = //
78 QStringLiteral("application/x-kde-ark-dndextract-path");
79
80class KIO::DropJobPrivate : public KIO::JobPrivate
81{
82public:
83 DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
84 : JobPrivate()
85 , m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts
86 , m_urls(KUrlMimeData::urlsFromMimeData(m_mimeData, KUrlMimeData::PreferLocalUrls, &m_metaData))
87 , m_dropAction(dropEvent->dropAction())
88 , m_relativePos(dropEvent->position().toPoint())
89 , m_keyboardModifiers(dropEvent->modifiers())
90 , m_hasArkFormat(m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)
91 && m_mimeData->hasFormat(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath))
92 , m_destUrl(destUrl)
93 , m_destItem(KCoreDirLister::cachedItemForUrl(destUrl))
94 , m_flags(flags)
95 , m_dropjobFlags(dropjobFlags)
96 , m_triggered(false)
97 {
98 // Check for the drop of a bookmark -> we want a Link action
99 if (m_mimeData->hasFormat(QStringLiteral("application/x-xbel"))) {
101 m_dropAction = Qt::LinkAction;
102 }
103 if (m_destItem.isNull() && m_destUrl.isLocalFile()) {
104 m_destItem = KFileItem(m_destUrl);
105 }
106
107 if (m_hasArkFormat) {
108 m_remoteArkDBusClient = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashService));
109 m_remoteArkDBusPath = QString::fromUtf8(m_mimeData->data(s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath));
110 }
111
112 if (!(m_flags & KIO::NoPrivilegeExecution)) {
113 m_privilegeExecutionEnabled = true;
114 switch (m_dropAction) {
115 case Qt::CopyAction:
116 m_operationType = Copy;
117 break;
118 case Qt::MoveAction:
119 m_operationType = Move;
120 break;
121 case Qt::LinkAction:
122 m_operationType = Symlink;
123 break;
124 default:
125 m_operationType = Other;
126 break;
127 }
128 }
129 }
130
131 bool destIsDirectory() const
132 {
133 if (!m_destItem.isNull()) {
134 return m_destItem.isDir();
135 }
136 // We support local dir, remote dir, local desktop file, local executable.
137 // So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not.
138 return true;
139 }
140 void handleCopyToDirectory();
141 void slotDropActionDetermined(int error);
142 void handleDropToDesktopFile();
143 void handleDropToExecutable();
144 void fillPopupMenu(KIO::DropMenu *popup);
145 void addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps);
146 void doCopyToDirectory();
147
148 QWindow *transientParent();
149
150 QPointer<const QMimeData> m_mimeData;
151 const QList<QUrl> m_urls;
152 QMap<QString, QString> m_metaData;
153 Qt::DropAction m_dropAction;
154 QPoint m_relativePos;
155 Qt::KeyboardModifiers m_keyboardModifiers;
156 bool m_hasArkFormat;
157 QString m_remoteArkDBusClient;
158 QString m_remoteArkDBusPath;
159 QUrl m_destUrl;
160 KFileItem m_destItem; // null for remote URLs not found in the dirlister cache
161 const JobFlags m_flags;
162 const DropJobFlags m_dropjobFlags;
163 QList<QAction *> m_appActions;
164 QList<QAction *> m_pluginActions;
165 bool m_triggered; // Tracks whether an action has been triggered in the popup menu.
166 QSet<KIO::DropMenu *> m_menus;
167
168 Q_DECLARE_PUBLIC(DropJob)
169
170 void slotStart();
171 void slotTriggered(QAction *);
172 void slotAboutToHide();
173
174 static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
175 {
176 DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags));
178 // Note: never KIO::getJobTracker()->registerJob here.
179 // We don't want a progress dialog during the copy/move/link popup, it would in fact close
180 // the popup
181 return job;
182 }
183};
184
185DropMenu::DropMenu(QWidget *parent)
186 : QMenu(parent)
187 , m_extraActionsSeparator(nullptr)
188{
189 m_cancelAction = new QAction(i18n("C&ancel") + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText), this);
190 m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
191
192 m_lastSeparator = new QAction(this);
193 m_lastSeparator->setSeparator(true);
194}
195
196DropMenu::~DropMenu()
197{
198}
199
200void DropMenu::addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions)
201{
202 removeAction(m_lastSeparator);
203 removeAction(m_cancelAction);
204
205 removeAction(m_extraActionsSeparator);
206 for (QAction *action : std::as_const(m_appActions)) {
207 removeAction(action);
208 }
209 for (QAction *action : std::as_const(m_pluginActions)) {
210 removeAction(action);
211 }
212
213 m_appActions = appActions;
214 m_pluginActions = pluginActions;
215
216 if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) {
217 QAction *firstExtraAction = m_appActions.value(0, m_pluginActions.value(0, nullptr));
218 if (firstExtraAction && !firstExtraAction->isSeparator()) {
219 if (!m_extraActionsSeparator) {
220 m_extraActionsSeparator = new QAction(this);
221 m_extraActionsSeparator->setSeparator(true);
222 }
223 addAction(m_extraActionsSeparator);
224 }
225 addActions(appActions);
226 addActions(pluginActions);
227 }
228
229 addAction(m_lastSeparator);
230 addAction(m_cancelAction);
231}
232
233DropJob::DropJob(DropJobPrivate &dd)
234 : Job(dd)
235{
236 Q_D(DropJob);
237
238 QTimer::singleShot(0, this, [d]() {
239 d->slotStart();
240 });
241}
242
243DropJob::~DropJob()
244{
245}
246
247void DropJobPrivate::slotStart()
248{
249 Q_Q(DropJob);
250
251#ifdef WITH_QTDBUS
252 if (m_hasArkFormat) {
253 QDBusMessage message = QDBusMessage::createMethodCall(m_remoteArkDBusClient,
254 m_remoteArkDBusPath,
255 QStringLiteral("org.kde.ark.DndExtract"),
256 QStringLiteral("extractSelectedFilesTo"));
257 message.setArguments({m_destUrl.toDisplayString(QUrl::PreferLocalFile)});
258 const auto pending = QDBusConnection::sessionBus().asyncCall(message);
259 auto watcher = std::make_shared<QDBusPendingCallWatcher>(pending);
260 QObject::connect(watcher.get(), &QDBusPendingCallWatcher::finished, q, [this, watcher] {
261 Q_Q(DropJob);
262
263 if (watcher->isError()) {
264 q->setError(KIO::ERR_UNKNOWN);
265 }
266 q->emitResult();
267 });
268
269 return;
270 }
271#endif
272
273 if (!m_urls.isEmpty()) {
274 if (destIsDirectory()) {
275 handleCopyToDirectory();
276 } else { // local file
277 const QString destFile = m_destUrl.toLocalFile();
278 if (KDesktopFile::isDesktopFile(destFile)) {
279 handleDropToDesktopFile();
280 } else if (QFileInfo(destFile).isExecutable()) {
281 handleDropToExecutable();
282 } else {
283 // should not happen, if KDirModel::flags is correct
284 q->setError(KIO::ERR_ACCESS_DENIED);
285 q->emitResult();
286 }
287 }
288 } else if (m_mimeData) {
289 // Dropping raw data
290 KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(m_mimeData, m_destUrl, KIO::HideProgressInfo, false /*not clipboard*/);
292 q->addSubjob(job);
293 }
294}
295
296void DropJobPrivate::fillPopupMenu(KIO::DropMenu *popup)
297{
298 Q_Q(DropJob);
299
300 // Check what the source can do
301 // TODO: Determining the MIME type of the source URLs is difficult for remote URLs,
302 // we would need to KIO::stat each URL in turn, asynchronously....
303 KFileItemList fileItems;
304 fileItems.reserve(m_urls.size());
305 for (const QUrl &url : m_urls) {
306 fileItems.append(KFileItem(url));
307 }
308 const bool allSourcesAreHttpUrls = std::ranges::all_of(m_urls, [](const auto &url) {
309 return url.scheme().startsWith(QStringLiteral("http"), Qt::CaseInsensitive);
310 });
311 const KFileItemListProperties itemProps(fileItems);
312
313 Q_EMIT q->popupMenuAboutToShow(itemProps);
314
315 const bool sReading = itemProps.supportsReading();
316 // For http URLs, even though technically the protocol supports deleting,
317 // this never makes sense for a drag operation.
318 const bool sDeleting = allSourcesAreHttpUrls ? false : itemProps.supportsDeleting();
319 const bool sMoving = itemProps.supportsMoving();
320
321 const int separatorLength = QCoreApplication::translate("QShortcut", "+").size();
322 QString seq = QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText);
323 seq.chop(separatorLength); // chop superfluous '+'
324 QAction *popupMoveAction = new QAction(i18n("&Move Here") + QLatin1Char('\t') + seq, popup);
325 popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move"), QIcon::fromTheme(QStringLiteral("go-jump"))));
326 popupMoveAction->setData(QVariant::fromValue(Qt::MoveAction));
327 seq = QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText);
328 seq.chop(separatorLength);
329
330 const QString copyActionName = allSourcesAreHttpUrls ? i18nc("@action:inmenu Download contents of URL here", "&Download Here") : i18n("&Copy Here");
331 const QIcon copyActionIcon = QIcon::fromTheme(allSourcesAreHttpUrls ? QStringLiteral("download") : QStringLiteral("edit-copy"));
332 QAction *popupCopyAction = new QAction(copyActionName + QLatin1Char('\t') + seq, popup);
333 popupCopyAction->setIcon(copyActionIcon);
334 popupCopyAction->setData(QVariant::fromValue(Qt::CopyAction));
335 seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(QKeySequence::NativeText);
336 seq.chop(separatorLength);
337 QAction *popupLinkAction = new QAction(i18n("&Link Here") + QLatin1Char('\t') + seq, popup);
338 popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
339 popupLinkAction->setData(QVariant::fromValue(Qt::LinkAction));
340
341 if (sMoving || (sReading && sDeleting)) {
342 const bool equalDestination = std::all_of(m_urls.cbegin(), m_urls.cend(), [this](const QUrl &src) {
343 return m_destUrl.matches(src.adjusted(QUrl::RemoveFilename), QUrl::StripTrailingSlash);
344 });
345
346 if (!equalDestination) {
347 popup->addAction(popupMoveAction);
348 }
349 }
350
351 if (sReading) {
352 popup->addAction(popupCopyAction);
353 }
354
355 popup->addAction(popupLinkAction);
356
357 addPluginActions(popup, itemProps);
358}
359
360void DropJobPrivate::addPluginActions(KIO::DropMenu *popup, const KFileItemListProperties &itemProps)
361{
362 const QList<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio_dnd"));
363 for (const KPluginMetaData &data : plugin_offers) {
364 if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) {
365 const auto actions = plugin->setup(itemProps, m_destUrl);
366 for (auto action : actions) {
367 action->setParent(popup);
368 }
369 m_pluginActions += actions;
370 }
371 }
372
373 popup->addExtraActions(m_appActions, m_pluginActions);
374}
375
377{
378 Q_D(DropJob);
379
380 d->m_appActions = actions;
381
382 for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
383 menu->addExtraActions(d->m_appActions, d->m_pluginActions);
384 }
385}
386
387void DropJob::showMenu(const QPoint &p, QAction *atAction)
388{
389 Q_D(DropJob);
390
391 if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) {
392 return;
393 }
394
395 for (KIO::DropMenu *menu : std::as_const(d->m_menus)) {
396 if (QWindow *transientParent = d->transientParent()) {
397 if (menu->winId()) {
398 menu->windowHandle()->setTransientParent(transientParent);
399 }
400 }
401 menu->popup(p, atAction);
402 }
403}
404
405void DropJobPrivate::slotTriggered(QAction *action)
406{
407 Q_Q(DropJob);
408 if (m_appActions.contains(action) || m_pluginActions.contains(action)) {
409 q->emitResult();
410 return;
411 }
412 const QVariant data = action->data();
413 if (!data.canConvert<Qt::DropAction>()) {
414 q->setError(KIO::ERR_USER_CANCELED);
415 q->emitResult();
416 return;
417 }
418 m_dropAction = data.value<Qt::DropAction>();
419 doCopyToDirectory();
420}
421
422void DropJobPrivate::slotAboutToHide()
423{
424 Q_Q(DropJob);
425 // QMenu emits aboutToHide before triggered.
426 // So we need to give the menu time in case it needs to emit triggered.
427 // If it does, the cleanup will be done by slotTriggered.
428 QTimer::singleShot(0, q, [=, this]() {
429 if (!m_triggered) {
430 q->setError(KIO::ERR_USER_CANCELED);
431 q->emitResult();
432 }
433 });
434}
435
436void DropJobPrivate::handleCopyToDirectory()
437{
438 Q_Q(DropJob);
439
440 // Process m_dropAction as set by Qt at the time of the drop event
441 if (!KProtocolManager::supportsWriting(m_destUrl)) {
442 slotDropActionDetermined(KIO::ERR_CANNOT_WRITE);
443 return;
444 }
445
446 if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) {
447 slotDropActionDetermined(KIO::ERR_WRITE_ACCESS_DENIED);
448 return;
449 }
450
451 bool allItemsAreFromTrash = true;
452 bool containsTrashRoot = false;
453 for (const QUrl &url : m_urls) {
454 const bool local = url.isLocalFile();
455#ifdef Q_OS_LINUX
456 // Check if the file is already in the xdg trash folder, BUG:497390
457 const QString xdgtrash = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/Trash");
458 if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) {
459 if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
460 containsTrashRoot = true;
461 }
462 } else if (local || url.scheme() == QLatin1String("file")) {
463 if (!url.toLocalFile().startsWith(xdgtrash)) {
464 allItemsAreFromTrash = false;
465 } else if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
466 containsTrashRoot = true;
467 }
468 } else {
469 allItemsAreFromTrash = false;
470 }
471#else
472 if (!local /*optimization*/ && url.scheme() == QLatin1String("trash")) {
473 if (url.path().isEmpty() || url.path() == QLatin1String("/")) {
474 containsTrashRoot = true;
475 }
476 } else {
477 allItemsAreFromTrash = false;
478 }
479#endif
480 if (url.matches(m_destUrl, QUrl::StripTrailingSlash)) {
481 slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
482 return;
483 }
484 }
485
486 const bool trashing = m_destUrl.scheme() == QLatin1String("trash");
487 if (trashing) {
488 if (allItemsAreFromTrash) {
489 qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash";
490 slotDropActionDetermined(KIO::ERR_DROP_ON_ITSELF);
491 return;
492 }
493 m_dropAction = Qt::MoveAction;
494
495 auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(q);
496
497 // No UI Delegate set for this job, or a delegate that doesn't implement
498 // AskUserActionInterface, then just proceed with the job without asking.
499 // This is useful for non-interactive usage, (which doesn't actually apply
500 // here as a DropJob is always interactive), but this is useful for unittests,
501 // which are typically non-interactive.
502 if (!askUserInterface) {
503 slotDropActionDetermined(KJob::NoError);
504 return;
505 }
506
507 QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, q, [this](bool allowDelete) {
508 if (allowDelete) {
509 slotDropActionDetermined(KJob::NoError);
510 } else {
511 slotDropActionDetermined(KIO::ERR_USER_CANCELED);
512 }
513 });
514
516 return;
517 }
518
519 // If we can't determine the action below, we use ERR::UNKNOWN as we need to ask
520 // the user via a popup menu.
521 int err = KIO::ERR_UNKNOWN;
522 const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash");
523 if (implicitCopy) {
524 m_dropAction = Qt::CopyAction;
525 err = KJob::NoError; // Ok
526 } else if (containsTrashRoot) {
527 // Dropping a link to the trash: don't move the full contents, just make a link (#319660)
528 m_dropAction = Qt::LinkAction;
529 err = KJob::NoError; // Ok
530 } else if (allItemsAreFromTrash) {
531 // No point in asking copy/move/link when using dragging from the trash, just move the file out.
532 m_dropAction = Qt::MoveAction;
533 err = KJob::NoError; // Ok
534 } else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
535 // Qt determined m_dropAction from the modifiers already
536 err = KJob::NoError; // Ok
537 }
538 slotDropActionDetermined(err);
539}
540
541QWindow *DropJobPrivate::transientParent()
542{
543 Q_Q(DropJob);
544
545 if (QWidget *widget = KJobWidgets::window(q)) {
546 QWidget *window = widget->window();
547 Q_ASSERT(window);
548 return window->windowHandle();
549 }
550
551 if (QWindow *window = KJobWindows::window(q)) {
552 return window;
553 }
554
555 return nullptr;
556}
557
558void DropJobPrivate::slotDropActionDetermined(int error)
559{
560 Q_Q(DropJob);
561
562 if (error == KJob::NoError) {
563 doCopyToDirectory();
564 return;
565 }
566
567 // There was an error, handle it
568 if (error == KIO::ERR_UNKNOWN) {
569 KIO::DropMenu *menu = new KIO::DropMenu();
571
572 // If the user clicks outside the menu, it will be destroyed without emitting the triggered signal.
573 QObject::connect(menu, &QMenu::aboutToHide, q, [this]() {
574 slotAboutToHide();
575 });
576
577 fillPopupMenu(menu);
578 QObject::connect(menu, &QMenu::triggered, q, [this](QAction *action) {
579 m_triggered = true;
580 slotTriggered(action);
581 });
582
583 if (!(m_dropjobFlags & KIO::ShowMenuManually)) {
584 if (QWindow *parent = transientParent()) {
585 if (menu->winId()) {
586 menu->windowHandle()->setTransientParent(parent);
587 }
588 }
589 auto *window = KJobWidgets::window(q);
590 menu->popup(window ? window->mapToGlobal(m_relativePos) : QCursor::pos());
591 }
592 m_menus.insert(menu);
593 QObject::connect(menu, &QObject::destroyed, q, [this, menu]() {
594 m_menus.remove(menu);
595 });
596 } else {
597 q->setError(error);
598 q->emitResult();
599 }
600}
601
602void DropJobPrivate::doCopyToDirectory()
603{
604 Q_Q(DropJob);
605 KIO::CopyJob *job = nullptr;
606 switch (m_dropAction) {
607 case Qt::MoveAction:
608 job = KIO::move(m_urls, m_destUrl, m_flags);
609 KIO::FileUndoManager::self()->recordJob(m_destUrl.scheme() == QLatin1String("trash") ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move,
610 m_urls,
611 m_destUrl,
612 job);
613 break;
614 case Qt::CopyAction:
615 job = KIO::copy(m_urls, m_destUrl, m_flags);
617 break;
618 case Qt::LinkAction:
619 job = KIO::link(m_urls, m_destUrl, m_flags);
621 break;
622 default:
623 qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction);
624 q->setError(KIO::ERR_UNSUPPORTED_ACTION);
625 q->emitResult();
626 return;
627 }
628 Q_ASSERT(job);
629 job->setParentJob(q);
630 job->setMetaData(m_metaData);
631 QObject::connect(job, &KIO::CopyJob::copyingDone, q, [q](KIO::Job *, const QUrl &, const QUrl &to) {
632 Q_EMIT q->itemCreated(to);
633 });
634 QObject::connect(job, &KIO::CopyJob::copyingLinkDone, q, [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) {
635 Q_EMIT q->itemCreated(to);
636 });
637 q->addSubjob(job);
638
639 Q_EMIT q->copyJobStarted(job);
640}
641
642void DropJobPrivate::handleDropToDesktopFile()
643{
644 Q_Q(DropJob);
645 const QString urlKey = QStringLiteral("URL");
646 const QString destFile = m_destUrl.toLocalFile();
647 const KDesktopFile desktopFile(destFile);
648 const KConfigGroup desktopGroup = desktopFile.desktopGroup();
649 if (desktopFile.hasApplicationType()) {
650 // Drop to application -> start app with urls as argument
651 KService::Ptr service(new KService(destFile));
652 // Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job,
653 // instead pass q as parent so that KIO::delegateExtension() can find a delegate
654 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q);
655 job->setUrls(m_urls);
656 QObject::connect(job, &KJob::result, q, [=]() {
657 if (job->error()) {
658 q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS);
659 q->setErrorText(destFile);
660 }
661 q->emitResult();
662 });
663 job->start();
664 } else if (desktopFile.hasLinkType() && desktopGroup.hasKey(urlKey)) {
665 // Drop to link -> adjust destination directory
666 m_destUrl = QUrl::fromUserInput(desktopGroup.readPathEntry(urlKey, QString()));
667 handleCopyToDirectory();
668 } else {
669 if (desktopFile.hasDeviceType()) {
670 qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@kde.org if you need this.";
671 // take code from libkonq's old konq_operations.cpp
672 // for now, fallback
673 }
674 // Some other kind of .desktop file (service, servicetype...)
675 q->setError(KIO::ERR_UNSUPPORTED_ACTION);
676 q->emitResult();
677 }
678}
679
680void DropJobPrivate::handleDropToExecutable()
681{
682 Q_Q(DropJob);
683 // Launch executable for each of the files
684 QStringList args;
685 args.reserve(m_urls.size());
686 for (const QUrl &url : std::as_const(m_urls)) {
687 args << url.toLocalFile(); // assume local files
688 }
689 QProcess::startDetached(m_destUrl.toLocalFile(), args);
690 q->emitResult();
691}
692
693void DropJob::slotResult(KJob *job)
694{
695 if (job->error()) {
696 KIO::Job::slotResult(job); // will set the error and emit result(this)
697 return;
698 }
699 removeSubjob(job);
700 emitResult();
701}
702
703DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags)
704{
705 return DropJobPrivate::newJob(dropEvent, destUrl, KIO::DropJobDefaultFlags, flags);
706}
707
708DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags)
709{
710 return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags);
711}
712
713#include "dropjob.moc"
714#include "moc_dropjob.cpp"
virtual void slotResult(KJob *job)
bool hasKey(const char *key) const
QString readPathEntry(const char *key, const QString &aDefault) const
static bool isDesktopFile(const QString &path)
void start() override
Starts the job.
void setUrls(const QList< QUrl > &urls)
Specifies the URLs to be passed to the application.
void askUserDeleteResult(bool allowDelete, const QList< QUrl > &urls, KIO::AskUserActionInterface::DeletionType deletionType, QWidget *parent)
Implementations of this interface must emit this signal when the dialog invoked by askUserDelete() fi...
@ Trash
Delete the files/directories directly, i.e. without moving them to Trash.
@ DefaultConfirmation
Do not ask if the user has previously set the "Do not ask again" checkbox (which is is shown in the m...
void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to)
The job is copying or moving a symbolic link, that points to target.
void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed)
The job emits this signal when copying or moving a file or directory successfully finished.
A KIO job that handles dropping into a file-manager-like view.
Definition dropjob.h:53
void setApplicationActions(const QList< QAction * > &actions)
Allows the application to set additional actions in the drop popup menu.
Definition dropjob.cpp:376
void itemCreated(const QUrl &url)
Signals that a file or directory was created.
void showMenu(const QPoint &p, QAction *atAction=nullptr)
Allows the application to show the menu manually.
Definition dropjob.cpp:387
void recordCopyJob(KIO::CopyJob *copyJob)
Record this CopyJob while it's happening and add a command for it so that the user can undo it.
static FileUndoManager * self()
void recordJob(CommandType op, const QList< QUrl > &src, const QUrl &dst, KIO::Job *job)
Record this job while it's happening and add a command for it so that the user can undo it.
The base class for all jobs.
Definition job_base.h:45
void setMetaData(const KIO::MetaData &metaData)
Set meta data to be sent to the worker, replacing existing meta data.
Definition job.cpp:215
void setParentJob(Job *parentJob)
Set the parent Job.
Definition job.cpp:192
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition job.cpp:80
void itemCreated(const QUrl &url)
Signals that a file or directory was created.
void emitResult()
int error() const
void result(KJob *job)
void setUiDelegate(KJobUiDelegate *delegate)
static QList< KPluginMetaData > findPlugins(const QString &directory, std::function< bool(const KPluginMetaData &)> filter={}, KPluginMetaDataOptions options={})
static bool supportsWriting(const QUrl &url)
Returns whether the protocol can store data to URLs.
QExplicitlySharedDataPointer< KService > Ptr
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
A namespace for KIO globals.
KIOCORE_EXPORT CopyJob * move(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Moves a file or directory src to the given destination dest.
Definition copyjob.cpp:2657
KIOCORE_EXPORT CopyJob * link(const QUrl &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
Create a link.
Definition copyjob.cpp:2691
@ ShowMenuManually
show the menu manually with DropJob::showMenu
Definition dropjob.h:30
QFlags< DropJobFlag > DropJobFlags
Stores a combination of DropJobFlag values.
Definition dropjob.h:35
T delegateExtension(KJob *job)
Returns the child of the job's uiDelegate() that implements the given extension, or nullptr if none w...
KIOCORE_EXPORT CopyJob * copy(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Copy a file or directory src into the destination dest, which can be a file (including the final file...
Definition copyjob.cpp:2635
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
KIOWIDGETS_EXPORT DropJob * drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags=DefaultFlags)
Drops the clipboard contents.
Definition dropjob.cpp:703
QFlags< JobFlag > JobFlags
Stores a combination of JobFlag values.
Definition job_base.h:281
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ NoPrivilegeExecution
When set, notifies the worker that application/job does not want privilege execution.
Definition job_base.h:276
@ ERR_DROP_ON_ITSELF
from KIO::DropJob,
Definition global.h:194
QWidget * window(QObject *job)
KGUIADDONS_EXPORT QWindow * window(QObject *job)
KCOREADDONS_EXPORT QList< QUrl > urlsFromMimeData(const QMimeData *mimeData, DecodeOptions decodeOptions=PreferKdeUrls, MetaDataMap *metaData=nullptr)
QVariant data() const const
void setIcon(const QIcon &icon)
bool isSeparator() const const
void setData(const QVariant &data)
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
QPoint pos()
QDBusPendingCall asyncCall(const QDBusMessage &message, int timeout) const const
QDBusConnection sessionBus()
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
void setArguments(const QList< QVariant > &arguments)
void finished(QDBusPendingCallWatcher *self)
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
bool contains(const AT &value) const const
bool isEmpty() const const
void reserve(qsizetype size)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void aboutToHide()
void popup(const QPoint &p, QAction *atAction)
void triggered(QAction *action)
Q_OBJECTQ_OBJECT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
QObject * parent() const const
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
QString writableLocation(StandardLocation type)
void chop(qsizetype n)
QString fromUtf8(QByteArrayView str)
qsizetype size() const const
CaseInsensitive
DropAction
Key_Escape
typedef KeyboardModifiers
PreferLocalFile
QUrl fromUserInput(const QString &userInput, const QString &workingDirectory, UserInputResolutionOptions options)
QString toLocalFile() const const
bool canConvert() const const
QVariant fromValue(T &&value)
T value() const const
QWidget(QWidget *parent, Qt::WindowFlags f)
void addActions(const QList< QAction * > &actions)
QPoint mapToGlobal(const QPoint &pos) const const
void removeAction(QAction *action)
WId winId() const const
QWidget * window() const const
QWindow * windowHandle() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:49:37 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.