1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include "widgetsaskuseractionhandler.h"
9
10 #include <KConfig>
11 #include <KConfigGroup>
12 #include <KGuiItem>
13 #include <KIO/SlaveBase>
14 #include <KJob>
15 #include <KJobWidgets>
16 #include <KLocalizedString>
17 #include <KMessageDialog>
18 #include <KSharedConfig>
19 #include <KSslInfoDialog>
20 #include <KStandardGuiItem>
21 #include <kio_widgets_debug.h>
22
23 #include <QDialogButtonBox>
24 #include <QRegularExpression>
25 #include <QUrl>
26
27 class KIO::WidgetsAskUserActionHandlerPrivate
28 {
29 public:
WidgetsAskUserActionHandlerPrivate(WidgetsAskUserActionHandler * qq)30 WidgetsAskUserActionHandlerPrivate(WidgetsAskUserActionHandler *qq)
31 : q(qq)
32 {
33 }
34
35 // Creates a KSslInfoDialog or falls back to a generic Information dialog
36 void sslMessageBox(const QString &text, const KIO::MetaData &metaData, QWidget *parent);
37
38 WidgetsAskUserActionHandler *const q;
39 };
40
WidgetsAskUserActionHandler(QObject * parent)41 KIO::WidgetsAskUserActionHandler::WidgetsAskUserActionHandler(QObject *parent)
42 : KIO::AskUserActionInterface(parent)
43 , d(new WidgetsAskUserActionHandlerPrivate(this))
44 {
45 }
46
~WidgetsAskUserActionHandler()47 KIO::WidgetsAskUserActionHandler::~WidgetsAskUserActionHandler()
48 {
49 }
50
askUserRename(KJob * job,const QString & caption,const QUrl & src,const QUrl & dest,KIO::RenameDialog_Options options,KIO::filesize_t sizeSrc,KIO::filesize_t sizeDest,const QDateTime & ctimeSrc,const QDateTime & ctimeDest,const QDateTime & mtimeSrc,const QDateTime & mtimeDest)51 void KIO::WidgetsAskUserActionHandler::askUserRename(KJob *job,
52 const QString &caption,
53 const QUrl &src,
54 const QUrl &dest,
55 KIO::RenameDialog_Options options,
56 KIO::filesize_t sizeSrc,
57 KIO::filesize_t sizeDest,
58 const QDateTime &ctimeSrc,
59 const QDateTime &ctimeDest,
60 const QDateTime &mtimeSrc,
61 const QDateTime &mtimeDest)
62 {
63 auto *dlg = new KIO::RenameDialog(KJobWidgets::window(job), caption, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest);
64
65 dlg->setAttribute(Qt::WA_DeleteOnClose);
66 dlg->setWindowModality(Qt::WindowModal);
67
68 connect(job, &KJob::finished, dlg, &QDialog::reject);
69 connect(dlg, &QDialog::finished, this, [this, job, dlg](const int exitCode) {
70 KIO::RenameDialog_Result result = static_cast<RenameDialog_Result>(exitCode);
71 const QUrl newUrl = result == Result_AutoRename ? dlg->autoDestUrl() : dlg->newDestUrl();
72 Q_EMIT askUserRenameResult(result, newUrl, job);
73 });
74
75 dlg->show();
76 }
77
askUserSkip(KJob * job,KIO::SkipDialog_Options options,const QString & errorText)78 void KIO::WidgetsAskUserActionHandler::askUserSkip(KJob *job, KIO::SkipDialog_Options options, const QString &errorText)
79 {
80 auto *dlg = new KIO::SkipDialog(KJobWidgets::window(job), options, errorText);
81 dlg->setAttribute(Qt::WA_DeleteOnClose);
82 dlg->setWindowModality(Qt::WindowModal);
83
84 connect(job, &KJob::finished, dlg, &QDialog::reject);
85 connect(dlg, &QDialog::finished, this, [this, job](const int exitCode) {
86 Q_EMIT askUserSkipResult(static_cast<KIO::SkipDialog_Result>(exitCode), job);
87 });
88
89 dlg->show();
90 }
91
askUserDelete(const QList<QUrl> & urls,DeletionType deletionType,ConfirmationType confirmationType,QWidget * parent)92 void KIO::WidgetsAskUserActionHandler::askUserDelete(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType, QWidget *parent)
93 {
94 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals);
95
96 QString keyName;
97 bool ask = (confirmationType == ForceConfirmation);
98 if (!ask) {
99 // The default value for confirmations is true for delete and false
100 // for trash. If you change this, please also update:
101 // dolphin/src/settings/general/confirmationssettingspage.cpp
102 bool defaultValue = true;
103
104 switch (deletionType) {
105 case Delete:
106 keyName = QStringLiteral("ConfirmDelete");
107 break;
108 case Trash:
109 keyName = QStringLiteral("ConfirmTrash");
110 defaultValue = false;
111 break;
112 case EmptyTrash:
113 keyName = QStringLiteral("ConfirmEmptyTrash");
114 break;
115 }
116
117 ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue);
118 }
119
120 if (!ask) {
121 Q_EMIT askUserDeleteResult(true, urls, deletionType, parent);
122 return;
123 }
124
125 QStringList prettyList;
126 prettyList.reserve(urls.size());
127 for (const QUrl &url : urls) {
128 if (url.scheme() == QLatin1String("trash")) {
129 QString path = url.path();
130 // HACK (#98983): remove "0-foo". Note that it works better than
131 // displaying KFileItem::name(), for files under a subdir.
132 path.remove(QRegularExpression(QStringLiteral("^/[0-9]+-")));
133 prettyList.append(path);
134 } else {
135 prettyList.append(url.toDisplayString(QUrl::PreferLocalFile));
136 }
137 }
138
139 const int urlCount = prettyList.count();
140
141 KGuiItem acceptButton;
142 QString text;
143 QString caption = i18n("Delete Permanently");
144
145 switch (deletionType) {
146 case Delete: {
147 text = xi18ncp("@info",
148 "Do you really want to permanently delete this %1 item?<nl/><nl/>"
149 "<emphasis strong='true'>This action cannot be undone.</emphasis>",
150 "Do you really want to permanently delete these %1 items?<nl/><nl/>"
151 "<emphasis strong='true'>This action cannot be undone.</emphasis>",
152 urlCount);
153 acceptButton = KStandardGuiItem::del();
154 break;
155 }
156 case EmptyTrash: {
157 text = xi18nc("@info",
158 "Do you want to permanently delete all items from the Trash?<nl/><nl/>"
159 "<emphasis strong='true'>This action cannot be undone.</emphasis>");
160 acceptButton = KGuiItem(i18nc("@action:button", "Empty Trash"), QStringLiteral("user-trash"));
161 break;
162 }
163 case Trash: {
164 if (urlCount == 1) {
165 text = xi18nc("@info",
166 "Do you really want to move this item to the Trash?<nl/>"
167 "<filename>%1</filename>",
168 prettyList.at(0));
169 } else {
170 text =
171 xi18ncp("@info", "Do you really want to move this %1 item to the Trash?", "Do you really want to move these %1 items to the Trash?", urlCount);
172 }
173 caption = i18n("Move to Trash");
174 acceptButton = KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash"));
175 break;
176 }
177 default:
178 break;
179 }
180
181 KMessageDialog *dlg = new KMessageDialog(KMessageDialog::QuestionYesNo, text, parent);
182
183 dlg->setAttribute(Qt::WA_DeleteOnClose);
184 dlg->setCaption(caption);
185 dlg->setIcon(QIcon{});
186 dlg->setButtons(acceptButton, KStandardGuiItem::cancel());
187 if (urlCount > 1) {
188 dlg->setListWidgetItems(prettyList);
189 }
190 dlg->setDontAskAgainText(i18nc("@option:checkbox", "Do not ask again"));
191 // If we get here, !ask must be false
192 dlg->setDontAskAgainChecked(!ask);
193
194 connect(dlg, &QDialog::finished, this, [=](const int buttonCode) {
195 const bool isDelete = buttonCode == QDialogButtonBox::Yes;
196
197 Q_EMIT askUserDeleteResult(isDelete, urls, deletionType, parent);
198
199 if (isDelete) {
200 KConfigGroup cg = kioConfig->group("Confirmations");
201 cg.writeEntry(keyName, !dlg->isDontAskAgainChecked());
202 cg.sync();
203 }
204 });
205
206 dlg->setWindowModality(Qt::WindowModal);
207 dlg->show();
208 }
209
requestUserMessageBox(MessageDialogType type,const QString & text,const QString & caption,const QString & buttonYes,const QString & buttonNo,const QString & iconYes,const QString & iconNo,const QString & dontAskAgainName,const QString & details,const KIO::MetaData & metaData,QWidget * parent)210 void KIO::WidgetsAskUserActionHandler::requestUserMessageBox(MessageDialogType type,
211 const QString &text,
212 const QString &caption,
213 const QString &buttonYes,
214 const QString &buttonNo,
215 const QString &iconYes,
216 const QString &iconNo,
217 const QString &dontAskAgainName,
218 const QString &details,
219 const KIO::MetaData &metaData,
220 QWidget *parent)
221 {
222 KSharedConfigPtr reqMsgConfig = KSharedConfig::openConfig(QStringLiteral("kioslaverc"));
223 const bool ask = reqMsgConfig->group("Notification Messages").readEntry(dontAskAgainName, true);
224
225 if (!ask) {
226 Q_EMIT messageBoxResult(type == WarningContinueCancel ? KIO::SlaveBase::Continue : KIO::SlaveBase::Yes);
227 return;
228 }
229
230 auto acceptButton = KGuiItem(buttonYes, iconYes);
231 auto rejectButton = KGuiItem(buttonNo, iconNo);
232
233 // It's "Do not ask again" every where except with Information
234 QString dontAskAgainText = i18nc("@option:check", "Do not ask again");
235
236 KMessageDialog::Type dlgType;
237
238 switch (type) {
239 case QuestionYesNo:
240 dlgType = KMessageDialog::QuestionYesNo;
241 break;
242 case QuestionYesNoCancel:
243 dlgType = KMessageDialog::QuestionYesNoCancel;
244 break;
245 case WarningYesNo:
246 dlgType = KMessageDialog::WarningYesNo;
247 break;
248 case WarningYesNoCancel:
249 dlgType = KMessageDialog::WarningYesNoCancel;
250 break;
251 case WarningContinueCancel:
252 dlgType = KMessageDialog::WarningContinueCancel;
253 break;
254 case SSLMessageBox:
255 d->sslMessageBox(text, metaData, parent);
256 return;
257 case Information:
258 dlgType = KMessageDialog::Information;
259 dontAskAgainText = i18nc("@option:check", "Do not show this message again");
260 break;
261 case Sorry:
262 dlgType = KMessageDialog::Sorry;
263 dontAskAgainText = QString{}; // No dontAskAgain checkbox
264 break;
265 case Error:
266 dlgType = KMessageDialog::Error;
267 dontAskAgainText = QString{}; // No dontAskAgain checkbox
268 break;
269 default:
270 qCWarning(KIO_WIDGETS) << "Unknown message dialog type" << type;
271 return;
272 }
273
274 auto *dialog = new KMessageDialog(dlgType, text, parent);
275
276 dialog->setAttribute(Qt::WA_DeleteOnClose);
277 dialog->setCaption(caption);
278 dialog->setIcon(QIcon{});
279 // If a button has empty text, KMessageDialog will replace that button
280 // with a suitable KStandardGuiItem
281 dialog->setButtons(acceptButton, rejectButton);
282 dialog->setDetails(details);
283 dialog->setDontAskAgainText(dontAskAgainText);
284 dialog->setDontAskAgainChecked(!ask);
285 dialog->setOpenExternalLinks(true); // Allow opening external links in the text labels
286
287 connect(dialog, &QDialog::finished, this, [=](const int result) {
288 KIO::SlaveBase::ButtonCode btnCode;
289 switch (result) {
290 case QDialogButtonBox::Yes:
291 if (dlgType == KMessageDialog::WarningContinueCancel) {
292 btnCode = KIO::SlaveBase::Continue;
293 } else {
294 btnCode = KIO::SlaveBase::Yes;
295 }
296 break;
297 case QDialogButtonBox::No:
298 btnCode = KIO::SlaveBase::No;
299 break;
300 case QDialogButtonBox::Cancel:
301 btnCode = KIO::SlaveBase::Cancel;
302 break;
303 case QDialogButtonBox::Ok:
304 btnCode = KIO::SlaveBase::Ok;
305 break;
306 default:
307 qCWarning(KIO_WIDGETS) << "Unknown message dialog result" << result;
308 return;
309 }
310
311 Q_EMIT messageBoxResult(btnCode);
312
313 if (result != QDialogButtonBox::Cancel) {
314 KConfigGroup cg = reqMsgConfig->group("Notification Messages");
315 cg.writeEntry(dontAskAgainName, !dialog->isDontAskAgainChecked());
316 cg.sync();
317 }
318 });
319
320 dialog->show();
321 }
322
sslMessageBox(const QString & text,const KIO::MetaData & metaData,QWidget * parent)323 void KIO::WidgetsAskUserActionHandlerPrivate::sslMessageBox(const QString &text, const KIO::MetaData &metaData, QWidget *parent)
324 {
325 const QStringList sslList = metaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), Qt::SkipEmptyParts);
326
327 QList<QSslCertificate> certChain;
328 bool decodedOk = true;
329 for (const QString &str : sslList) {
330 certChain.append(QSslCertificate(str.toUtf8()));
331 if (certChain.last().isNull()) {
332 decodedOk = false;
333 break;
334 }
335 }
336
337 if (decodedOk) { // Use KSslInfoDialog
338 KSslInfoDialog *ksslDlg = new KSslInfoDialog(parent);
339 ksslDlg->setSslInfo(certChain,
340 metaData.value(QStringLiteral("ssl_peer_ip")),
341 text, // The URL
342 metaData.value(QStringLiteral("ssl_protocol_version")),
343 metaData.value(QStringLiteral("ssl_cipher")),
344 metaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(),
345 metaData.value(QStringLiteral("ssl_cipher_bits")).toInt(),
346 KSslInfoDialog::certificateErrorsFromString(metaData.value(QStringLiteral("ssl_cert_errors"))));
347
348 // KSslInfoDialog deletes itself by setting Qt::WA_DeleteOnClose
349
350 QObject::connect(ksslDlg, &QDialog::finished, q, [this]() {
351 // KSslInfoDialog only has one button, QDialogButtonBox::Close
352 Q_EMIT q->messageBoxResult(KIO::SlaveBase::Cancel);
353 });
354
355 ksslDlg->show();
356 return;
357 }
358
359 // Fallback to a generic message box
360 auto *dialog = new KMessageDialog(KMessageDialog::Information, i18n("The peer SSL certificate chain appears to be corrupt."), parent);
361
362 dialog->setAttribute(Qt::WA_DeleteOnClose);
363 dialog->setCaption(i18n("SSL"));
364 // KMessageDialog will set a proper icon
365 dialog->setIcon(QIcon{});
366 dialog->setButtons(KStandardGuiItem::ok());
367
368 QObject::connect(dialog, &QDialog::finished, q, [this](const int result) {
369 Q_EMIT q->messageBoxResult(result == QDialogButtonBox::Ok ? KIO::SlaveBase::Ok : KIO::SlaveBase::Cancel);
370 });
371
372 dialog->show();
373 }
374