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#ifdef WITH_QTDBUS
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#ifdef WITH_QTDBUS
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#ifdef WITH_QTDBUS
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
178#if KIOWIDGETS_BUILD_DEPRECATED_SINCE(6, 15)
179bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType)
180{
181 QString keyName;
182 bool ask = (confirmationType == ForceConfirmation);
183 if (!ask) {
184 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), mode: KConfig::NoGlobals);
185
186 // The default value for confirmations is true for delete and false
187 // for trash. If you change this, please also update:
188 // dolphin/src/settings/general/confirmationssettingspage.cpp
189 bool defaultValue = true;
190
191 switch (deletionType) {
192 case Delete:
193 keyName = QStringLiteral("ConfirmDelete");
194 break;
195 case Trash:
196 keyName = QStringLiteral("ConfirmTrash");
197 defaultValue = false;
198 break;
199 case EmptyTrash:
200 keyName = QStringLiteral("ConfirmEmptyTrash");
201 break;
202 }
203
204 ask = kioConfig->group(QStringLiteral("Confirmations")).readEntry(key: keyName, aDefault: defaultValue);
205 }
206 if (ask) {
207 QStringList prettyList;
208 prettyList.reserve(asize: urls.size());
209 for (const QUrl &url : urls) {
210 if (url.scheme() == QLatin1String("trash")) {
211 QString path = url.path();
212 // HACK (#98983): remove "0-foo". Note that it works better than
213 // displaying KFileItem::name(), for files under a subdir.
214 path.remove(re: QRegularExpression(QStringLiteral("^/[0-9]*-")));
215 prettyList.append(t: path);
216 } else {
217 prettyList.append(t: url.toDisplayString(options: QUrl::PreferLocalFile));
218 }
219 }
220
221 int result;
222 QWidget *widget = window();
223 const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal);
224 switch (deletionType) {
225 case Delete:
226 if (prettyList.count() == 1) {
227 result = KMessageBox::warningContinueCancel(
228 parent: widget,
229 xi18nc("@info",
230 "Do you really want to permanently delete this item?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action "
231 "cannot be undone.</emphasis>",
232 prettyList.first()),
233 i18n("Delete Permanently"),
234 buttonContinue: KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
235 buttonCancel: KStandardGuiItem::cancel(),
236 dontAskAgainName: keyName,
237 options);
238 } else {
239 result = KMessageBox::warningContinueCancelList(
240 parent: widget,
241 xi18ncp(
242 "@info",
243 "Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
244 "Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>",
245 prettyList.count()),
246 strlist: prettyList,
247 i18n("Delete Permanently"),
248 buttonContinue: KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
249 buttonCancel: KStandardGuiItem::cancel(),
250 dontAskAgainName: keyName,
251 options);
252 }
253 break;
254 case EmptyTrash:
255 result = KMessageBox::warningContinueCancel(
256 parent: widget,
257 xi18nc("@info",
258 "Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"),
259 i18n("Delete Permanently"),
260 buttonContinue: KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("edit-delete"))),
261 buttonCancel: KStandardGuiItem::cancel(),
262 dontAskAgainName: keyName,
263 options);
264 break;
265 case Trash:
266 default:
267 if (prettyList.count() == 1) {
268 result = KMessageBox::warningContinueCancel(
269 parent: widget,
270 xi18nc("@info", "Do you really want to move this item to the Trash?<nl/><filename>%1</filename>", prettyList.first()),
271 i18n("Move to Trash"),
272 buttonContinue: KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
273 buttonCancel: KStandardGuiItem::cancel(),
274 dontAskAgainName: keyName,
275 options);
276 } else {
277 result = KMessageBox::warningContinueCancelList(
278 parent: widget,
279 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()),
280 strlist: prettyList,
281 i18n("Move to Trash"),
282 buttonContinue: KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
283 buttonCancel: KStandardGuiItem::cancel(),
284 dontAskAgainName: keyName,
285 options);
286 }
287 }
288 if (!keyName.isEmpty()) {
289 // Check kmessagebox setting... erase & copy to konquerorrc.
290 KSharedConfig::Ptr config = KSharedConfig::openConfig();
291 KConfigGroup notificationGroup(config, QStringLiteral("Notification Messages"));
292 if (!notificationGroup.readEntry(key: keyName, aDefault: true)) {
293 notificationGroup.writeEntry(key: keyName, value: true);
294 notificationGroup.sync();
295
296 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), mode: KConfig::NoGlobals);
297 kioConfig->group(QStringLiteral("Confirmations")).writeEntry(key: keyName, value: false);
298 }
299 }
300 return (result == KMessageBox::Continue);
301 }
302 return true;
303}
304#endif
305
306KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
307{
308 if (qobject_cast<QGuiApplication *>(qApp)) {
309 return new KIO::ClipboardUpdater(job, mode);
310 }
311 return nullptr;
312}
313
314void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest)
315{
316 if (qobject_cast<QGuiApplication *>(qApp)) {
317 KIO::ClipboardUpdater::update(srcUrl: src, destUrl: dest);
318 }
319}
320
321KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window, const QList<QObject *> &ifaces)
322 : KDialogJobUiDelegate(flags, window)
323 , d(new JobUiDelegatePrivate(this, ifaces))
324{
325 // TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces)
326 if (window) {
327 s_static()->registerWindow(wid: window);
328 setWindow(window);
329 }
330}
331
332class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory
333{
334public:
335 using KIO::JobUiDelegateFactory::JobUiDelegateFactory;
336
337 KJobUiDelegate *createDelegate() const override
338 {
339 return new KIO::JobUiDelegate;
340 }
341
342 KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override
343 {
344 return new KIO::JobUiDelegate(flags, window);
345 }
346
347 static void registerJobUiDelegate()
348 {
349 static KIOWidgetJobUiDelegateFactory factory;
350 KIO::setDefaultJobUiDelegateFactory(&factory);
351
352 static KIO::JobUiDelegate delegate;
353 KIO::setDefaultJobUiDelegateExtension(&delegate);
354 }
355};
356
357// Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs
358static void registerJobUiDelegate()
359{
360 // Inside the factory class so it is a friend of the delegate and can construct it.
361 KIOWidgetJobUiDelegateFactory::registerJobUiDelegate();
362}
363
364Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate)
365
366#include "jobuidelegate.moc"
367#include "moc_jobuidelegate.cpp"
368

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