KIO

jobuidelegate.cpp
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 *>(iface)) {
48 m_untrustedProgramHandler = obj;
49 } else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(iface)) {
50 m_openWithHandler = obj;
51 } else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(iface)) {
52 m_openOrExecuteFileHandler = obj;
53 } else if (auto obj = qobject_cast<AskUserActionInterface *>(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
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{
105public:
106 void registerWindow(QWidget *wid)
107 {
108 if (!wid) {
109 return;
110 }
111
112 QWidget *window = topLevelWindow(wid);
113 QObject *obj = static_cast<QObject *>(window);
114 if (!m_windowList.contains(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(obj, windowId);
120 connect(window, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow);
121#ifdef WITH_QTDBUS
122 QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6"))
123 .call(QDBus::NoBlock, QStringLiteral("registerWindowId"), 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(obj);
135 if (it == m_windowList.end()) {
136 return;
137 }
138 WId windowId = it.value();
139 disconnect(it.key(), &QObject::destroyed, this, &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(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), 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{
156
157 if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(d->m_openWithHandler)) {
158 obj->setWindow(window);
159 }
160 if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(d->m_untrustedProgramHandler)) {
161 obj->setWindow(window);
162 }
163 if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(d->m_openOrExecuteFileHandler)) {
164 obj->setWindow(window);
165 }
166 if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(d->m_askUserActionHandler)) {
167 obj->setWindow(window);
168 }
169
170 s_static()->registerWindow(window);
171}
172
174{
175 s_static()->slotUnregisterWindow(window);
176}
177
179{
180 QString keyName;
181 bool ask = (confirmationType == ForceConfirmation);
182 if (!ask) {
183 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), 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(keyName, defaultValue);
204 }
205 if (ask) {
206 QStringList prettyList;
207 prettyList.reserve(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(QRegularExpression(QStringLiteral("^/[0-9]*-")));
214 prettyList.append(path);
215 } else {
216 prettyList.append(url.toDisplayString(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) {
227 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 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
235 keyName,
236 options);
237 } else {
239 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 prettyList,
246 i18n("Delete Permanently"),
247 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")),
249 keyName,
250 options);
251 }
252 break;
253 case EmptyTrash:
255 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 KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))),
261 keyName,
262 options);
263 break;
264 case Trash:
265 default:
266 if (prettyList.count() == 1) {
268 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 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
273 keyName,
274 options);
275 } else {
277 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 prettyList,
280 i18n("Move to Trash"),
281 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")),
283 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(keyName, true)) {
292 notificationGroup.writeEntry(keyName, true);
293 notificationGroup.sync();
294
295 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
296 kioConfig->group(QStringLiteral("Confirmations")).writeEntry(keyName, 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
313{
314 if (qobject_cast<QGuiApplication *>(qApp)) {
315 KIO::ClipboardUpdater::update(src, dest);
316 }
317}
318
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(window);
327 }
328}
329
330class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory
331{
332public:
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;
349
350 static KIO::JobUiDelegate 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"
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
bool sync() override
virtual void setWindow(QWidget *window)
QWidget * window() const
DeletionType
The type of deletion: real deletion, moving the files to the trash or emptying the trash Used by askD...
ConfirmationType
ForceConfirmation: always ask the user for confirmation DefaultConfirmation: don't ask the user if he...
A factory for creating job ui delegates.
A UI delegate tuned to be used with KIO Jobs.
void updateUrlInClipboard(const QUrl &src, const QUrl &dest) override
Update URL in clipboard, if present.
static void unregisterWindow(QWidget *window)
Unregister the given window from kded.
JobUiDelegate(KJobUiDelegate::Flags flags=AutoHandlingDisabled, QWidget *window=nullptr, const QList< QObject * > &ifaces={})
Constructs a new KIO Job UI delegate.
bool askDeleteConfirmation(const QList< QUrl > &urls, DeletionType deletionType, ConfirmationType confirmationType) override
Ask for confirmation before deleting/trashing urls.
~JobUiDelegate() override
Destroys the KIO Job UI delegate.
ClipboardUpdater * createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) override
Creates a clipboard updater.
void setWindow(QWidget *window) override
Associate this job with a window given by window.
The base class for all jobs.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
QString xi18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
A namespace for KIO globals.
KIOCORE_EXPORT void setDefaultJobUiDelegateExtension(JobUiDelegateExtension *extension)
Internal.
KIOCORE_EXPORT void setDefaultJobUiDelegateFactory(JobUiDelegateFactory *factory)
Internal.
QWidget * window(QObject *job)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
ButtonCode warningContinueCancelList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
KGuiItem cancel()
QDBusMessage call(QDBus::CallMode mode, const QString &method, Args &&... args)
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
qsizetype count() const const
T & first()
void reserve(qsizetype size)
qsizetype size() const const
bool contains(const Key &key) const const
iterator end()
iterator erase(const_iterator first, const_iterator last)
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
Key key(const T &value, const Key &defaultKey) const const
T value(const Key &key, const T &defaultValue) const const
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
bool isEmpty() const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
PreferLocalFile
QWidget * parentWidget() const const
WId winId() const const
QWidget * window() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.