1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
6 SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org>
7 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "jobuidelegate.h"
13#include "kio_widgets_debug.h"
14#include "kiogui_export.h"
15#include "widgetsaskuseractionhandler.h"
16#include "widgetsopenorexecutefilehandler.h"
17#include "widgetsopenwithhandler.h"
18#include "widgetsuntrustedprogramhandler.h"
19#include <kio/jobuidelegatefactory.h>
20
21#include <KConfigGroup>
22#include <KJob>
23#include <KJobWidgets>
24#include <KLocalizedString>
25#include <KMessageBox>
26#include <KSharedConfig>
27#include <clipboardupdater_p.h>
28#include <ksslinfodialog.h>
29
30#ifndef KIO_ANDROID_STUB
31#include <QDBusInterface>
32#endif
33#include <QGuiApplication>
34#include <QIcon>
35#include <QPointer>
36#include <QRegularExpression>
37#include <QUrl>
38#include <QWidget>
39
40class KIO::JobUiDelegatePrivate
41{
42public:
43 JobUiDelegatePrivate(KIO::JobUiDelegate *qq, const QList<QObject *> &ifaces)
44 {
45 for (auto iface : ifaces) {
46 iface->setParent(qq);
47 if (auto obj = qobject_cast<UntrustedProgramHandlerInterface *>(object: iface)) {
48 m_untrustedProgramHandler = obj;
49 } else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(object: iface)) {
50 m_openWithHandler = obj;
51 } else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(object: iface)) {
52 m_openOrExecuteFileHandler = obj;
53 } else if (auto obj = qobject_cast<AskUserActionInterface *>(object: iface)) {
54 m_askUserActionHandler = obj;
55 }
56 }
57
58 if (!m_untrustedProgramHandler) {
59 m_untrustedProgramHandler = new WidgetsUntrustedProgramHandler(qq);
60 }
61 if (!m_openWithHandler) {
62 m_openWithHandler = new WidgetsOpenWithHandler(qq);
63 }
64 if (!m_openOrExecuteFileHandler) {
65 m_openOrExecuteFileHandler = new WidgetsOpenOrExecuteFileHandler(qq);
66 }
67 if (!m_askUserActionHandler) {
68 m_askUserActionHandler = new WidgetsAskUserActionHandler(qq);
69 }
70 }
71
72 UntrustedProgramHandlerInterface *m_untrustedProgramHandler = nullptr;
73 OpenWithHandlerInterface *m_openWithHandler = nullptr;
74 OpenOrExecuteFileInterface *m_openOrExecuteFileHandler = nullptr;
75 AskUserActionInterface *m_askUserActionHandler = nullptr;
76};
77
78KIO::JobUiDelegate::~JobUiDelegate() = default;
79
80/*
81 Returns the top most window associated with widget.
82
83 Unlike QWidget::window(), this function does its best to find and return the
84 main application window associated with the given widget.
85
86 If widget itself is a dialog or its parent is a dialog, and that dialog has a
87 parent widget then this function will iterate through all those widgets to
88 find the top most window, which most of the time is the main window of the
89 application. By contrast, QWidget::window() would simply return the first
90 file dialog it encountered since it is the "next ancestor widget that has (or
91 could have) a window-system frame".
92*/
93static QWidget *topLevelWindow(QWidget *widget)
94{
95 QWidget *w = widget;
96 while (w && w->parentWidget()) {
97 w = w->parentWidget();
98 }
99 return (w ? w->window() : nullptr);
100}
101
102class JobUiDelegateStatic : public QObject
103{
104 Q_OBJECT
105public:
106 void registerWindow(QWidget *wid)
107 {
108 if (!wid) {
109 return;
110 }
111
112 QWidget *window = topLevelWindow(widget: wid);
113 QObject *obj = static_cast<QObject *>(window);
114 if (!m_windowList.contains(key: obj)) {
115 // We must store the window Id because by the time
116 // the destroyed signal is emitted we can no longer
117 // access QWidget::winId() (already destructed)
118 WId windowId = window->winId();
119 m_windowList.insert(key: obj, value: windowId);
120 connect(sender: window, signal: &QObject::destroyed, context: this, slot: &JobUiDelegateStatic::slotUnregisterWindow);
121#ifndef KIO_ANDROID_STUB
122 QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6"))
123 .call(mode: QDBus::NoBlock, QStringLiteral("registerWindowId"), args: qlonglong(windowId));
124#endif
125 }
126 }
127public Q_SLOTS:
128 void slotUnregisterWindow(QObject *obj)
129 {
130 if (!obj) {
131 return;
132 }
133
134 QMap<QObject *, WId>::Iterator it = m_windowList.find(key: obj);
135 if (it == m_windowList.end()) {
136 return;
137 }
138 WId windowId = it.value();
139 disconnect(sender: it.key(), signal: &QObject::destroyed, receiver: this, slot: &JobUiDelegateStatic::slotUnregisterWindow);
140 m_windowList.erase(it);
141#ifndef KIO_ANDROID_STUB
142 QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6"))
143 .call(mode: QDBus::NoBlock, QStringLiteral("unregisterWindowId"), args: qlonglong(windowId));
144#endif
145 }
146
147private:
148 QMap<QObject *, WId> m_windowList;
149};
150
151Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static)
152
153void KIO::JobUiDelegate::setWindow(QWidget *window)
154{
155 KDialogJobUiDelegate::setWindow(window);
156
157 if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(object: d->m_openWithHandler)) {
158 obj->setWindow(window);
159 }
160 if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(object: d->m_untrustedProgramHandler)) {
161 obj->setWindow(window);
162 }
163 if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(object: d->m_openOrExecuteFileHandler)) {
164 obj->setWindow(window);
165 }
166 if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(object: d->m_askUserActionHandler)) {
167 obj->setWindow(window);
168 }
169
170 s_static()->registerWindow(wid: window);
171}
172
173void KIO::JobUiDelegate::unregisterWindow(QWidget *window)
174{
175 s_static()->slotUnregisterWindow(obj: window);
176}
177
178bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType)
179{
180 QString keyName;
181 bool ask = (confirmationType == ForceConfirmation);
182 if (!ask) {
183 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), mode: KConfig::NoGlobals);
184
185 // The default value for confirmations is true for delete and false
186 // for trash. If you change this, please also update:
187 // dolphin/src/settings/general/confirmationssettingspage.cpp
188 bool defaultValue = true;
189
190 switch (deletionType) {
191 case Delete:
192 keyName = QStringLiteral("ConfirmDelete");
193 break;
194 case Trash:
195 keyName = QStringLiteral("ConfirmTrash");
196 defaultValue = false;
197 break;
198 case EmptyTrash:
199 keyName = QStringLiteral("ConfirmEmptyTrash");
200 break;
201 }
202
203 ask = kioConfig->group(QStringLiteral("Confirmations")).readEntry(key: keyName, aDefault: defaultValue);
204 }
205 if (ask) {
206 QStringList prettyList;
207 prettyList.reserve(asize: urls.size());
208 for (const QUrl &url : urls) {
209 if (url.scheme() == QLatin1String("trash")) {
210 QString path = url.path();
211 // HACK (#98983): remove "0-foo". Note that it works better than
212 // displaying KFileItem::name(), for files under a subdir.
213 path.remove(re: QRegularExpression(QStringLiteral("^/[0-9]*-")));
214 prettyList.append(t: path);
215 } else {
216 prettyList.append(t: url.toDisplayString(options: QUrl::PreferLocalFile));
217 }
218 }
219
220 int result;
221 QWidget *widget = window();
222 const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal);
223 switch (deletionType) {
224 case Delete:
225 if (prettyList.count() == 1) {
226 result = KMessageBox::warningContinueCancel(
227 parent: widget,
228 xi18nc("@info",
229 "Do you really want to permanently delete this item?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action "
230 "cannot be undone.</emphasis>",
231 prettyList.first()),
232 i18n("Delete Permanently"),
233 buttonContinue: KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
234 buttonCancel: KStandardGuiItem::cancel(),
235 dontAskAgainName: keyName,
236 options);
237 } else {
238 result = KMessageBox::warningContinueCancelList(
239 parent: widget,
240 xi18ncp(
241 "@info",
242 "Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
243 "Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
244 prettyList.count()),
245 strlist: prettyList,
246 i18n("Delete Permanently"),
247 buttonContinue: KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
248 buttonCancel: KStandardGuiItem::cancel(),
249 dontAskAgainName: keyName,
250 options);
251 }
252 break;
253 case EmptyTrash:
254 result = KMessageBox::warningContinueCancel(
255 parent: widget,
256 xi18nc("@info",
257 "Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"),
258 i18n("Delete Permanently"),
259 buttonContinue: KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))),
260 buttonCancel: KStandardGuiItem::cancel(),
261 dontAskAgainName: keyName,
262 options);
263 break;
264 case Trash:
265 default:
266 if (prettyList.count() == 1) {
267 result = KMessageBox::warningContinueCancel(
268 parent: widget,
269 xi18nc("@info", "Do you really want to move this item to the Trash?<nl/><filename>%1</filename>", prettyList.first()),
270 i18n("Move to Trash"),
271 buttonContinue: KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
272 buttonCancel: KStandardGuiItem::cancel(),
273 dontAskAgainName: keyName,
274 options);
275 } else {
276 result = KMessageBox::warningContinueCancelList(
277 parent: widget,
278 i18np("Do you really want to move this item to the Trash?", "Do you really want to move these %1 items to the Trash?", prettyList.count()),
279 strlist: prettyList,
280 i18n("Move to Trash"),
281 buttonContinue: KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
282 buttonCancel: KStandardGuiItem::cancel(),
283 dontAskAgainName: keyName,
284 options);
285 }
286 }
287 if (!keyName.isEmpty()) {
288 // Check kmessagebox setting... erase & copy to konquerorrc.
289 KSharedConfig::Ptr config = KSharedConfig::openConfig();
290 KConfigGroup notificationGroup(config, QStringLiteral("Notification Messages"));
291 if (!notificationGroup.readEntry(key: keyName, aDefault: true)) {
292 notificationGroup.writeEntry(key: keyName, value: true);
293 notificationGroup.sync();
294
295 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), mode: KConfig::NoGlobals);
296 kioConfig->group(QStringLiteral("Confirmations")).writeEntry(key: keyName, value: false);
297 }
298 }
299 return (result == KMessageBox::Continue);
300 }
301 return true;
302}
303
304KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
305{
306 if (qobject_cast<QGuiApplication *>(qApp)) {
307 return new KIO::ClipboardUpdater(job, mode);
308 }
309 return nullptr;
310}
311
312void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest)
313{
314 if (qobject_cast<QGuiApplication *>(qApp)) {
315 KIO::ClipboardUpdater::update(srcUrl: src, destUrl: dest);
316 }
317}
318
319KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window, const QList<QObject *> &ifaces)
320 : KDialogJobUiDelegate(flags, window)
321 , d(new JobUiDelegatePrivate(this, ifaces))
322{
323 // TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces)
324 if (window) {
325 s_static()->registerWindow(wid: window);
326 setWindow(window);
327 }
328}
329
330class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory
331{
332public:
333 using KIO::JobUiDelegateFactory::JobUiDelegateFactory;
334
335 KJobUiDelegate *createDelegate() const override
336 {
337 return new KIO::JobUiDelegate;
338 }
339
340 KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override
341 {
342 return new KIO::JobUiDelegate(flags, window);
343 }
344
345 static void registerJobUiDelegate()
346 {
347 static KIOWidgetJobUiDelegateFactory factory;
348 KIO::setDefaultJobUiDelegateFactory(&factory);
349
350 static KIO::JobUiDelegate delegate;
351 KIO::setDefaultJobUiDelegateExtension(&delegate);
352 }
353};
354
355// Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs
356static void registerJobUiDelegate()
357{
358 // Inside the factory class so it is a friend of the delegate and can construct it.
359 KIOWidgetJobUiDelegateFactory::registerJobUiDelegate();
360}
361
362Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate)
363
364#include "jobuidelegate.moc"
365#include "moc_jobuidelegate.cpp"
366

source code of kio/src/widgets/jobuidelegate.cpp