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 <KLocalizedString> |
28 | #include <KPluginFactory> |
29 | #include <KPluginMetaData> |
30 | #include <KProtocolManager> |
31 | #include <KService> |
32 | #include <KUrlMimeData> |
33 | |
34 | #include <QDBusConnection> |
35 | #include <QDBusPendingCall> |
36 | #include <QDropEvent> |
37 | #include <QFileInfo> |
38 | #include <QMenu> |
39 | #include <QMimeData> |
40 | #include <QProcess> |
41 | #include <QTimer> |
42 | |
43 | using namespace KIO; |
44 | |
45 | Q_DECLARE_METATYPE(Qt::DropAction) |
46 | |
47 | namespace KIO |
48 | { |
49 | class DropMenu; |
50 | } |
51 | |
52 | class KIO:: : public QMenu |
53 | { |
54 | Q_OBJECT |
55 | public: |
56 | explicit DropMenu(QWidget *parent = nullptr); |
57 | ~DropMenu() override; |
58 | |
59 | void (); |
60 | void addExtraActions(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions); |
61 | |
62 | private: |
63 | QList<QAction *> ; |
64 | QList<QAction *> ; |
65 | QAction *; |
66 | QAction *; |
67 | QAction *; |
68 | }; |
69 | |
70 | static const QString = // |
71 | QStringLiteral("application/x-kde-ark-dndextract-service" ); |
72 | static const QString = // |
73 | QStringLiteral("application/x-kde-ark-dndextract-path" ); |
74 | |
75 | class KIO::DropJobPrivate : public KIO::JobPrivate |
76 | { |
77 | public: |
78 | DropJobPrivate(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) |
79 | : JobPrivate() |
80 | , m_mimeData(dropEvent->mimeData()) // Extract everything from the dropevent, since it will be deleted before the job starts |
81 | , m_urls(KUrlMimeData::urlsFromMimeData(mimeData: m_mimeData, decodeOptions: KUrlMimeData::PreferLocalUrls, metaData: &m_metaData)) |
82 | , m_dropAction(dropEvent->dropAction()) |
83 | , m_relativePos(dropEvent->position().toPoint()) |
84 | , m_keyboardModifiers(dropEvent->modifiers()) |
85 | , m_hasArkFormat(m_mimeData->hasFormat(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashService) |
86 | && m_mimeData->hasFormat(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath)) |
87 | , m_destUrl(destUrl) |
88 | , m_destItem(KCoreDirLister::cachedItemForUrl(url: destUrl)) |
89 | , m_flags(flags) |
90 | , m_dropjobFlags(dropjobFlags) |
91 | , m_triggered(false) |
92 | { |
93 | // Check for the drop of a bookmark -> we want a Link action |
94 | if (m_mimeData->hasFormat(QStringLiteral("application/x-xbel" ))) { |
95 | m_keyboardModifiers |= Qt::KeyboardModifiers(Qt::ControlModifier | Qt::ShiftModifier); |
96 | m_dropAction = Qt::LinkAction; |
97 | } |
98 | if (m_destItem.isNull() && m_destUrl.isLocalFile()) { |
99 | m_destItem = KFileItem(m_destUrl); |
100 | } |
101 | |
102 | if (m_hasArkFormat) { |
103 | m_remoteArkDBusClient = QString::fromUtf8(ba: m_mimeData->data(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashService)); |
104 | m_remoteArkDBusPath = QString::fromUtf8(ba: m_mimeData->data(mimetype: s_applicationSlashXDashKDEDashArkDashDnDExtractDashPath)); |
105 | } |
106 | |
107 | if (!(m_flags & KIO::NoPrivilegeExecution)) { |
108 | m_privilegeExecutionEnabled = true; |
109 | switch (m_dropAction) { |
110 | case Qt::CopyAction: |
111 | m_operationType = Copy; |
112 | break; |
113 | case Qt::MoveAction: |
114 | m_operationType = Move; |
115 | break; |
116 | case Qt::LinkAction: |
117 | m_operationType = Symlink; |
118 | break; |
119 | default: |
120 | m_operationType = Other; |
121 | break; |
122 | } |
123 | } |
124 | } |
125 | |
126 | bool destIsDirectory() const |
127 | { |
128 | if (!m_destItem.isNull()) { |
129 | return m_destItem.isDir(); |
130 | } |
131 | // We support local dir, remote dir, local desktop file, local executable. |
132 | // So for remote URLs, we just assume they point to a directory, the user will get an error from KIO::copy if not. |
133 | return true; |
134 | } |
135 | void handleCopyToDirectory(); |
136 | void slotDropActionDetermined(int error); |
137 | void handleDropToDesktopFile(); |
138 | void handleDropToExecutable(); |
139 | void fillPopupMenu(KIO::DropMenu *); |
140 | void addPluginActions(KIO::DropMenu *, const KFileItemListProperties &itemProps); |
141 | void doCopyToDirectory(); |
142 | |
143 | QPointer<const QMimeData> m_mimeData; |
144 | const QList<QUrl> m_urls; |
145 | QMap<QString, QString> m_metaData; |
146 | Qt::DropAction m_dropAction; |
147 | QPoint m_relativePos; |
148 | Qt::KeyboardModifiers m_keyboardModifiers; |
149 | bool m_hasArkFormat; |
150 | QString m_remoteArkDBusClient; |
151 | QString m_remoteArkDBusPath; |
152 | QUrl m_destUrl; |
153 | KFileItem m_destItem; // null for remote URLs not found in the dirlister cache |
154 | const JobFlags m_flags; |
155 | const DropJobFlags m_dropjobFlags; |
156 | QList<QAction *> m_appActions; |
157 | QList<QAction *> m_pluginActions; |
158 | bool m_triggered; // Tracks whether an action has been triggered in the popup menu. |
159 | QSet<KIO::DropMenu *> ; |
160 | |
161 | Q_DECLARE_PUBLIC(DropJob) |
162 | |
163 | void slotStart(); |
164 | void slotTriggered(QAction *); |
165 | void slotAboutToHide(); |
166 | |
167 | static inline DropJob *newJob(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) |
168 | { |
169 | DropJob *job = new DropJob(*new DropJobPrivate(dropEvent, destUrl, dropjobFlags, flags)); |
170 | job->setUiDelegate(KIO::createDefaultJobUiDelegate()); |
171 | // Note: never KIO::getJobTracker()->registerJob here. |
172 | // We don't want a progress dialog during the copy/move/link popup, it would in fact close |
173 | // the popup |
174 | return job; |
175 | } |
176 | }; |
177 | |
178 | DropMenu::(QWidget *parent) |
179 | : QMenu(parent) |
180 | , m_extraActionsSeparator(nullptr) |
181 | { |
182 | m_cancelAction = new QAction(i18n("C&ancel" ) + QLatin1Char('\t') + QKeySequence(Qt::Key_Escape).toString(format: QKeySequence::NativeText), this); |
183 | m_cancelAction->setIcon(QIcon::fromTheme(QStringLiteral("process-stop" ))); |
184 | |
185 | m_lastSeparator = new QAction(this); |
186 | m_lastSeparator->setSeparator(true); |
187 | } |
188 | |
189 | DropMenu::() |
190 | { |
191 | } |
192 | |
193 | void DropMenu::(const QList<QAction *> &appActions, const QList<QAction *> &pluginActions) |
194 | { |
195 | removeAction(action: m_lastSeparator); |
196 | removeAction(action: m_cancelAction); |
197 | |
198 | removeAction(action: m_extraActionsSeparator); |
199 | for (QAction *action : std::as_const(t&: m_appActions)) { |
200 | removeAction(action); |
201 | } |
202 | for (QAction *action : std::as_const(t&: m_pluginActions)) { |
203 | removeAction(action); |
204 | } |
205 | |
206 | m_appActions = appActions; |
207 | m_pluginActions = pluginActions; |
208 | |
209 | if (!m_appActions.isEmpty() || !m_pluginActions.isEmpty()) { |
210 | QAction * = m_appActions.value(i: 0, defaultValue: m_pluginActions.value(i: 0, defaultValue: nullptr)); |
211 | if (firstExtraAction && !firstExtraAction->isSeparator()) { |
212 | if (!m_extraActionsSeparator) { |
213 | m_extraActionsSeparator = new QAction(this); |
214 | m_extraActionsSeparator->setSeparator(true); |
215 | } |
216 | addAction(action: m_extraActionsSeparator); |
217 | } |
218 | addActions(actions: appActions); |
219 | addActions(actions: pluginActions); |
220 | } |
221 | |
222 | addAction(action: m_lastSeparator); |
223 | addAction(action: m_cancelAction); |
224 | } |
225 | |
226 | DropJob::DropJob(DropJobPrivate &dd) |
227 | : Job(dd) |
228 | { |
229 | Q_D(DropJob); |
230 | |
231 | QTimer::singleShot(interval: 0, receiver: this, slot: [d]() { |
232 | d->slotStart(); |
233 | }); |
234 | } |
235 | |
236 | DropJob::~DropJob() |
237 | { |
238 | } |
239 | |
240 | void DropJobPrivate::slotStart() |
241 | { |
242 | Q_Q(DropJob); |
243 | |
244 | if (m_hasArkFormat) { |
245 | QDBusMessage message = QDBusMessage::createMethodCall(destination: m_remoteArkDBusClient, |
246 | path: m_remoteArkDBusPath, |
247 | QStringLiteral("org.kde.ark.DndExtract" ), |
248 | QStringLiteral("extractSelectedFilesTo" )); |
249 | message.setArguments({m_destUrl.toDisplayString(options: QUrl::PreferLocalFile)}); |
250 | const auto pending = QDBusConnection::sessionBus().asyncCall(message); |
251 | auto watcher = std::make_shared<QDBusPendingCallWatcher>(args: pending); |
252 | QObject::connect(sender: watcher.get(), signal: &QDBusPendingCallWatcher::finished, context: q, slot: [this, watcher] { |
253 | Q_Q(DropJob); |
254 | |
255 | if (watcher->isError()) { |
256 | q->setError(KIO::ERR_UNKNOWN); |
257 | } |
258 | q->emitResult(); |
259 | }); |
260 | |
261 | return; |
262 | } |
263 | |
264 | if (!m_urls.isEmpty()) { |
265 | if (destIsDirectory()) { |
266 | handleCopyToDirectory(); |
267 | } else { // local file |
268 | const QString destFile = m_destUrl.toLocalFile(); |
269 | if (KDesktopFile::isDesktopFile(path: destFile)) { |
270 | handleDropToDesktopFile(); |
271 | } else if (QFileInfo(destFile).isExecutable()) { |
272 | handleDropToExecutable(); |
273 | } else { |
274 | // should not happen, if KDirModel::flags is correct |
275 | q->setError(KIO::ERR_ACCESS_DENIED); |
276 | q->emitResult(); |
277 | } |
278 | } |
279 | } else if (m_mimeData) { |
280 | // Dropping raw data |
281 | KIO::PasteJob *job = KIO::PasteJobPrivate::newJob(mimeData: m_mimeData, destDir: m_destUrl, flags: KIO::HideProgressInfo, clipboard: false /*not clipboard*/); |
282 | QObject::connect(sender: job, signal: &KIO::PasteJob::itemCreated, context: q, slot: &KIO::DropJob::itemCreated); |
283 | q->addSubjob(job); |
284 | } |
285 | } |
286 | |
287 | void DropJobPrivate::(KIO::DropMenu *) |
288 | { |
289 | Q_Q(DropJob); |
290 | |
291 | // Check what the source can do |
292 | // TODO: Determining the MIME type of the source URLs is difficult for remote URLs, |
293 | // we would need to KIO::stat each URL in turn, asynchronously.... |
294 | KFileItemList fileItems; |
295 | fileItems.reserve(asize: m_urls.size()); |
296 | for (const QUrl &url : m_urls) { |
297 | fileItems.append(t: KFileItem(url)); |
298 | } |
299 | const KFileItemListProperties itemProps(fileItems); |
300 | |
301 | Q_EMIT q->popupMenuAboutToShow(itemProps); |
302 | |
303 | const bool sReading = itemProps.supportsReading(); |
304 | const bool sDeleting = itemProps.supportsDeleting(); |
305 | const bool sMoving = itemProps.supportsMoving(); |
306 | |
307 | const int separatorLength = QCoreApplication::translate(context: "QShortcut" , key: "+" ).size(); |
308 | QString seq = QKeySequence(Qt::ShiftModifier).toString(format: QKeySequence::NativeText); |
309 | seq.chop(n: separatorLength); // chop superfluous '+' |
310 | QAction * = new QAction(i18n("&Move Here" ) + QLatin1Char('\t') + seq, popup); |
311 | popupMoveAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-move" ), fallback: QIcon::fromTheme(QStringLiteral("go-jump" )))); |
312 | popupMoveAction->setData(QVariant::fromValue(value: Qt::MoveAction)); |
313 | seq = QKeySequence(Qt::ControlModifier).toString(format: QKeySequence::NativeText); |
314 | seq.chop(n: separatorLength); |
315 | QAction * = new QAction(i18n("&Copy Here" ) + QLatin1Char('\t') + seq, popup); |
316 | popupCopyAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy" ))); |
317 | popupCopyAction->setData(QVariant::fromValue(value: Qt::CopyAction)); |
318 | seq = QKeySequence(Qt::ControlModifier | Qt::ShiftModifier).toString(format: QKeySequence::NativeText); |
319 | seq.chop(n: separatorLength); |
320 | QAction * = new QAction(i18n("&Link Here" ) + QLatin1Char('\t') + seq, popup); |
321 | popupLinkAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-link" ))); |
322 | popupLinkAction->setData(QVariant::fromValue(value: Qt::LinkAction)); |
323 | |
324 | if (sMoving || (sReading && sDeleting)) { |
325 | const bool equalDestination = std::all_of(first: m_urls.cbegin(), last: m_urls.cend(), pred: [this](const QUrl &src) { |
326 | return m_destUrl.matches(url: src.adjusted(options: QUrl::RemoveFilename), options: QUrl::StripTrailingSlash); |
327 | }); |
328 | |
329 | if (!equalDestination) { |
330 | popup->addAction(action: popupMoveAction); |
331 | } |
332 | } |
333 | |
334 | if (sReading) { |
335 | popup->addAction(action: popupCopyAction); |
336 | } |
337 | |
338 | popup->addAction(action: popupLinkAction); |
339 | |
340 | addPluginActions(popup, itemProps); |
341 | } |
342 | |
343 | void DropJobPrivate::(KIO::DropMenu *, const KFileItemListProperties &itemProps) |
344 | { |
345 | const QList<KPluginMetaData> plugin_offers = KPluginMetaData::findPlugins(QStringLiteral("kf6/kio_dnd" )); |
346 | for (const KPluginMetaData &data : plugin_offers) { |
347 | if (auto plugin = KPluginFactory::instantiatePlugin<KIO::DndPopupMenuPlugin>(data).plugin) { |
348 | const auto actions = plugin->setup(popupMenuInfo: itemProps, destination: m_destUrl); |
349 | for (auto action : actions) { |
350 | action->setParent(popup); |
351 | } |
352 | m_pluginActions += actions; |
353 | } |
354 | } |
355 | |
356 | popup->addExtraActions(appActions: m_appActions, pluginActions: m_pluginActions); |
357 | } |
358 | |
359 | void DropJob::setApplicationActions(const QList<QAction *> &actions) |
360 | { |
361 | Q_D(DropJob); |
362 | |
363 | d->m_appActions = actions; |
364 | |
365 | for (KIO::DropMenu * : std::as_const(t&: d->m_menus)) { |
366 | menu->addExtraActions(appActions: d->m_appActions, pluginActions: d->m_pluginActions); |
367 | } |
368 | } |
369 | |
370 | void DropJob::(const QPoint &p, QAction *atAction) |
371 | { |
372 | Q_D(DropJob); |
373 | |
374 | if (!(d->m_dropjobFlags & KIO::ShowMenuManually)) { |
375 | return; |
376 | } |
377 | |
378 | for (KIO::DropMenu * : std::as_const(t&: d->m_menus)) { |
379 | menu->popup(pos: p, at: atAction); |
380 | } |
381 | } |
382 | |
383 | void DropJobPrivate::slotTriggered(QAction *action) |
384 | { |
385 | Q_Q(DropJob); |
386 | if (m_appActions.contains(t: action) || m_pluginActions.contains(t: action)) { |
387 | q->emitResult(); |
388 | return; |
389 | } |
390 | const QVariant data = action->data(); |
391 | if (!data.canConvert<Qt::DropAction>()) { |
392 | q->setError(KIO::ERR_USER_CANCELED); |
393 | q->emitResult(); |
394 | return; |
395 | } |
396 | m_dropAction = data.value<Qt::DropAction>(); |
397 | doCopyToDirectory(); |
398 | } |
399 | |
400 | void DropJobPrivate::slotAboutToHide() |
401 | { |
402 | Q_Q(DropJob); |
403 | // QMenu emits aboutToHide before triggered. |
404 | // So we need to give the menu time in case it needs to emit triggered. |
405 | // If it does, the cleanup will be done by slotTriggered. |
406 | QTimer::singleShot(interval: 0, receiver: q, slot: [=, this]() { |
407 | if (!m_triggered) { |
408 | q->setError(KIO::ERR_USER_CANCELED); |
409 | q->emitResult(); |
410 | } |
411 | }); |
412 | } |
413 | |
414 | void DropJobPrivate::handleCopyToDirectory() |
415 | { |
416 | Q_Q(DropJob); |
417 | |
418 | // Process m_dropAction as set by Qt at the time of the drop event |
419 | if (!KProtocolManager::supportsWriting(url: m_destUrl)) { |
420 | slotDropActionDetermined(error: KIO::ERR_CANNOT_WRITE); |
421 | return; |
422 | } |
423 | |
424 | if (!m_destItem.isNull() && !m_destItem.isWritable() && (m_flags & KIO::NoPrivilegeExecution)) { |
425 | slotDropActionDetermined(error: KIO::ERR_WRITE_ACCESS_DENIED); |
426 | return; |
427 | } |
428 | |
429 | bool allItemsAreFromTrash = true; |
430 | bool containsTrashRoot = false; |
431 | for (const QUrl &url : m_urls) { |
432 | const bool local = url.isLocalFile(); |
433 | if (!local /*optimization*/ && url.scheme() == QLatin1String("trash" )) { |
434 | if (url.path().isEmpty() || url.path() == QLatin1String("/" )) { |
435 | containsTrashRoot = true; |
436 | } |
437 | } else { |
438 | allItemsAreFromTrash = false; |
439 | } |
440 | if (url.matches(url: m_destUrl, options: QUrl::StripTrailingSlash)) { |
441 | slotDropActionDetermined(error: KIO::ERR_DROP_ON_ITSELF); |
442 | return; |
443 | } |
444 | } |
445 | |
446 | const bool trashing = m_destUrl.scheme() == QLatin1String("trash" ); |
447 | if (trashing) { |
448 | if (allItemsAreFromTrash) { |
449 | qCDebug(KIO_WIDGETS) << "Dropping items from trash to trash" ; |
450 | slotDropActionDetermined(error: KIO::ERR_DROP_ON_ITSELF); |
451 | return; |
452 | } |
453 | m_dropAction = Qt::MoveAction; |
454 | |
455 | auto *askUserInterface = KIO::delegateExtension<AskUserActionInterface *>(job: q); |
456 | |
457 | // No UI Delegate set for this job, or a delegate that doesn't implement |
458 | // AskUserActionInterface, then just proceed with the job without asking. |
459 | // This is useful for non-interactive usage, (which doesn't actually apply |
460 | // here as a DropJob is always interactive), but this is useful for unittests, |
461 | // which are typically non-interactive. |
462 | if (!askUserInterface) { |
463 | slotDropActionDetermined(error: KJob::NoError); |
464 | return; |
465 | } |
466 | |
467 | QObject::connect(sender: askUserInterface, signal: &KIO::AskUserActionInterface::askUserDeleteResult, context: q, slot: [this](bool allowDelete) { |
468 | if (allowDelete) { |
469 | slotDropActionDetermined(error: KJob::NoError); |
470 | } else { |
471 | slotDropActionDetermined(error: KIO::ERR_USER_CANCELED); |
472 | } |
473 | }); |
474 | |
475 | askUserInterface->askUserDelete(urls: m_urls, deletionType: KIO::AskUserActionInterface::Trash, confirmationType: KIO::AskUserActionInterface::DefaultConfirmation, parent: KJobWidgets::window(job: q)); |
476 | return; |
477 | } |
478 | |
479 | // If we can't determine the action below, we use ERR::UNKNOWN as we need to ask |
480 | // the user via a popup menu. |
481 | int err = KIO::ERR_UNKNOWN; |
482 | const bool implicitCopy = m_destUrl.scheme() == QLatin1String("stash" ); |
483 | if (implicitCopy) { |
484 | m_dropAction = Qt::CopyAction; |
485 | err = KJob::NoError; // Ok |
486 | } else if (containsTrashRoot) { |
487 | // Dropping a link to the trash: don't move the full contents, just make a link (#319660) |
488 | m_dropAction = Qt::LinkAction; |
489 | err = KJob::NoError; // Ok |
490 | } else if (allItemsAreFromTrash) { |
491 | // No point in asking copy/move/link when using dragging from the trash, just move the file out. |
492 | m_dropAction = Qt::MoveAction; |
493 | err = KJob::NoError; // Ok |
494 | } else if (m_keyboardModifiers & (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) { |
495 | // Qt determined m_dropAction from the modifiers already |
496 | err = KJob::NoError; // Ok |
497 | } |
498 | slotDropActionDetermined(error: err); |
499 | } |
500 | |
501 | void DropJobPrivate::slotDropActionDetermined(int error) |
502 | { |
503 | Q_Q(DropJob); |
504 | |
505 | if (error == KJob::NoError) { |
506 | doCopyToDirectory(); |
507 | return; |
508 | } |
509 | |
510 | // There was an error, handle it |
511 | if (error == KIO::ERR_UNKNOWN) { |
512 | auto *window = KJobWidgets::window(job: q); |
513 | KIO::DropMenu * = new KIO::DropMenu(window); |
514 | QObject::connect(sender: menu, signal: &QMenu::aboutToHide, context: menu, slot: &QObject::deleteLater); |
515 | |
516 | // If the user clicks outside the menu, it will be destroyed without emitting the triggered signal. |
517 | QObject::connect(sender: menu, signal: &QMenu::aboutToHide, context: q, slot: [this]() { |
518 | slotAboutToHide(); |
519 | }); |
520 | |
521 | fillPopupMenu(popup: menu); |
522 | QObject::connect(sender: menu, signal: &QMenu::triggered, context: q, slot: [this](QAction *action) { |
523 | m_triggered = true; |
524 | slotTriggered(action); |
525 | }); |
526 | |
527 | if (!(m_dropjobFlags & KIO::ShowMenuManually)) { |
528 | menu->popup(pos: window ? window->mapToGlobal(m_relativePos) : QCursor::pos()); |
529 | } |
530 | m_menus.insert(value: menu); |
531 | QObject::connect(sender: menu, signal: &QObject::destroyed, context: q, slot: [this, menu]() { |
532 | m_menus.remove(value: menu); |
533 | }); |
534 | } else { |
535 | q->setError(error); |
536 | q->emitResult(); |
537 | } |
538 | } |
539 | |
540 | void DropJobPrivate::doCopyToDirectory() |
541 | { |
542 | Q_Q(DropJob); |
543 | KIO::CopyJob *job = nullptr; |
544 | switch (m_dropAction) { |
545 | case Qt::MoveAction: |
546 | job = KIO::move(src: m_urls, dest: m_destUrl, flags: m_flags); |
547 | KIO::FileUndoManager::self()->recordJob(op: m_destUrl.scheme() == QLatin1String("trash" ) ? KIO::FileUndoManager::Trash : KIO::FileUndoManager::Move, |
548 | src: m_urls, |
549 | dst: m_destUrl, |
550 | job); |
551 | break; |
552 | case Qt::CopyAction: |
553 | job = KIO::copy(src: m_urls, dest: m_destUrl, flags: m_flags); |
554 | KIO::FileUndoManager::self()->recordCopyJob(copyJob: job); |
555 | break; |
556 | case Qt::LinkAction: |
557 | job = KIO::link(src: m_urls, destDir: m_destUrl, flags: m_flags); |
558 | KIO::FileUndoManager::self()->recordCopyJob(copyJob: job); |
559 | break; |
560 | default: |
561 | qCWarning(KIO_WIDGETS) << "Unknown drop action" << int(m_dropAction); |
562 | q->setError(KIO::ERR_UNSUPPORTED_ACTION); |
563 | q->emitResult(); |
564 | return; |
565 | } |
566 | Q_ASSERT(job); |
567 | job->setParentJob(q); |
568 | job->setMetaData(m_metaData); |
569 | QObject::connect(sender: job, signal: &KIO::CopyJob::copyingDone, context: q, slot: [q](KIO::Job *, const QUrl &, const QUrl &to) { |
570 | Q_EMIT q->itemCreated(url: to); |
571 | }); |
572 | QObject::connect(sender: job, signal: &KIO::CopyJob::copyingLinkDone, context: q, slot: [q](KIO::Job *, const QUrl &, const QString &, const QUrl &to) { |
573 | Q_EMIT q->itemCreated(url: to); |
574 | }); |
575 | q->addSubjob(job); |
576 | |
577 | Q_EMIT q->copyJobStarted(job); |
578 | } |
579 | |
580 | void DropJobPrivate::handleDropToDesktopFile() |
581 | { |
582 | Q_Q(DropJob); |
583 | const QString urlKey = QStringLiteral("URL" ); |
584 | const QString destFile = m_destUrl.toLocalFile(); |
585 | const KDesktopFile desktopFile(destFile); |
586 | const KConfigGroup desktopGroup = desktopFile.desktopGroup(); |
587 | if (desktopFile.hasApplicationType()) { |
588 | // Drop to application -> start app with urls as argument |
589 | KService::Ptr service(new KService(destFile)); |
590 | // Can't use setParentJob() because ApplicationLauncherJob isn't a KIO::Job, |
591 | // instead pass q as parent so that KIO::delegateExtension() can find a delegate |
592 | KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service, q); |
593 | job->setUrls(m_urls); |
594 | QObject::connect(sender: job, signal: &KJob::result, context: q, slot: [=]() { |
595 | if (job->error()) { |
596 | q->setError(KIO::ERR_CANNOT_LAUNCH_PROCESS); |
597 | q->setErrorText(destFile); |
598 | } |
599 | q->emitResult(); |
600 | }); |
601 | job->start(); |
602 | } else if (desktopFile.hasLinkType() && desktopGroup.hasKey(key: urlKey)) { |
603 | // Drop to link -> adjust destination directory |
604 | m_destUrl = QUrl::fromUserInput(userInput: desktopGroup.readPathEntry(pKey: urlKey, aDefault: QString())); |
605 | handleCopyToDirectory(); |
606 | } else { |
607 | if (desktopFile.hasDeviceType()) { |
608 | qCWarning(KIO_WIDGETS) << "Not re-implemented; please email kde-frameworks-devel@kde.org if you need this." ; |
609 | // take code from libkonq's old konq_operations.cpp |
610 | // for now, fallback |
611 | } |
612 | // Some other kind of .desktop file (service, servicetype...) |
613 | q->setError(KIO::ERR_UNSUPPORTED_ACTION); |
614 | q->emitResult(); |
615 | } |
616 | } |
617 | |
618 | void DropJobPrivate::handleDropToExecutable() |
619 | { |
620 | Q_Q(DropJob); |
621 | // Launch executable for each of the files |
622 | QStringList args; |
623 | args.reserve(asize: m_urls.size()); |
624 | for (const QUrl &url : std::as_const(t: m_urls)) { |
625 | args << url.toLocalFile(); // assume local files |
626 | } |
627 | QProcess::startDetached(program: m_destUrl.toLocalFile(), arguments: args); |
628 | q->emitResult(); |
629 | } |
630 | |
631 | void DropJob::slotResult(KJob *job) |
632 | { |
633 | if (job->error()) { |
634 | KIO::Job::slotResult(job); // will set the error and emit result(this) |
635 | return; |
636 | } |
637 | removeSubjob(job); |
638 | emitResult(); |
639 | } |
640 | |
641 | DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, JobFlags flags) |
642 | { |
643 | return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags: KIO::DropJobDefaultFlags, flags); |
644 | } |
645 | |
646 | DropJob *KIO::drop(const QDropEvent *dropEvent, const QUrl &destUrl, DropJobFlags dropjobFlags, JobFlags flags) |
647 | { |
648 | return DropJobPrivate::newJob(dropEvent, destUrl, dropjobFlags, flags); |
649 | } |
650 | |
651 | #include "dropjob.moc" |
652 | #include "moc_dropjob.cpp" |
653 | |