File indexing completed on 2024-12-08 03:40:43
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org> 0006 SPDX-FileCopyrightText: 2013 Dawit Alemayehu <adawit@kde.org> 0007 SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0008 0009 SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include "jobuidelegate.h" 0013 #include "kio_widgets_debug.h" 0014 #include "kiogui_export.h" 0015 #include "widgetsaskuseractionhandler.h" 0016 #include "widgetsopenorexecutefilehandler.h" 0017 #include "widgetsopenwithhandler.h" 0018 #include "widgetsuntrustedprogramhandler.h" 0019 #include <kio/jobuidelegatefactory.h> 0020 0021 #include <KConfigGroup> 0022 #include <KJob> 0023 #include <KJobWidgets> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <KSharedConfig> 0027 #include <clipboardupdater_p.h> 0028 #include <ksslinfodialog.h> 0029 0030 #ifndef KIO_ANDROID_STUB 0031 #include <QDBusInterface> 0032 #endif 0033 #include <QGuiApplication> 0034 #include <QIcon> 0035 #include <QPointer> 0036 #include <QRegularExpression> 0037 #include <QUrl> 0038 #include <QWidget> 0039 0040 class KIO::JobUiDelegatePrivate 0041 { 0042 public: 0043 JobUiDelegatePrivate(KIO::JobUiDelegate *qq, const QList<QObject *> &ifaces) 0044 { 0045 for (auto iface : ifaces) { 0046 iface->setParent(qq); 0047 if (auto obj = qobject_cast<UntrustedProgramHandlerInterface *>(iface)) { 0048 m_untrustedProgramHandler = obj; 0049 } else if (auto obj = qobject_cast<OpenWithHandlerInterface *>(iface)) { 0050 m_openWithHandler = obj; 0051 } else if (auto obj = qobject_cast<OpenOrExecuteFileInterface *>(iface)) { 0052 m_openOrExecuteFileHandler = obj; 0053 } else if (auto obj = qobject_cast<AskUserActionInterface *>(iface)) { 0054 m_askUserActionHandler = obj; 0055 } 0056 } 0057 0058 if (!m_untrustedProgramHandler) { 0059 m_untrustedProgramHandler = new WidgetsUntrustedProgramHandler(qq); 0060 } 0061 if (!m_openWithHandler) { 0062 m_openWithHandler = new WidgetsOpenWithHandler(qq); 0063 } 0064 if (!m_openOrExecuteFileHandler) { 0065 m_openOrExecuteFileHandler = new WidgetsOpenOrExecuteFileHandler(qq); 0066 } 0067 if (!m_askUserActionHandler) { 0068 m_askUserActionHandler = new WidgetsAskUserActionHandler(qq); 0069 } 0070 } 0071 0072 UntrustedProgramHandlerInterface *m_untrustedProgramHandler = nullptr; 0073 OpenWithHandlerInterface *m_openWithHandler = nullptr; 0074 OpenOrExecuteFileInterface *m_openOrExecuteFileHandler = nullptr; 0075 AskUserActionInterface *m_askUserActionHandler = nullptr; 0076 }; 0077 0078 KIO::JobUiDelegate::~JobUiDelegate() = default; 0079 0080 /* 0081 Returns the top most window associated with widget. 0082 0083 Unlike QWidget::window(), this function does its best to find and return the 0084 main application window associated with the given widget. 0085 0086 If widget itself is a dialog or its parent is a dialog, and that dialog has a 0087 parent widget then this function will iterate through all those widgets to 0088 find the top most window, which most of the time is the main window of the 0089 application. By contrast, QWidget::window() would simply return the first 0090 file dialog it encountered since it is the "next ancestor widget that has (or 0091 could have) a window-system frame". 0092 */ 0093 static QWidget *topLevelWindow(QWidget *widget) 0094 { 0095 QWidget *w = widget; 0096 while (w && w->parentWidget()) { 0097 w = w->parentWidget(); 0098 } 0099 return (w ? w->window() : nullptr); 0100 } 0101 0102 class JobUiDelegateStatic : public QObject 0103 { 0104 Q_OBJECT 0105 public: 0106 void registerWindow(QWidget *wid) 0107 { 0108 if (!wid) { 0109 return; 0110 } 0111 0112 QWidget *window = topLevelWindow(wid); 0113 QObject *obj = static_cast<QObject *>(window); 0114 if (!m_windowList.contains(obj)) { 0115 // We must store the window Id because by the time 0116 // the destroyed signal is emitted we can no longer 0117 // access QWidget::winId() (already destructed) 0118 WId windowId = window->winId(); 0119 m_windowList.insert(obj, windowId); 0120 connect(window, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); 0121 #ifndef KIO_ANDROID_STUB 0122 QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6")) 0123 .call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId)); 0124 #endif 0125 } 0126 } 0127 public Q_SLOTS: 0128 void slotUnregisterWindow(QObject *obj) 0129 { 0130 if (!obj) { 0131 return; 0132 } 0133 0134 QMap<QObject *, WId>::Iterator it = m_windowList.find(obj); 0135 if (it == m_windowList.end()) { 0136 return; 0137 } 0138 WId windowId = it.value(); 0139 disconnect(it.key(), &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); 0140 m_windowList.erase(it); 0141 #ifndef KIO_ANDROID_STUB 0142 QDBusInterface(QStringLiteral("org.kde.kded6"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded6")) 0143 .call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId)); 0144 #endif 0145 } 0146 0147 private: 0148 QMap<QObject *, WId> m_windowList; 0149 }; 0150 0151 Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static) 0152 0153 void KIO::JobUiDelegate::setWindow(QWidget *window) 0154 { 0155 KDialogJobUiDelegate::setWindow(window); 0156 0157 if (auto obj = qobject_cast<WidgetsUntrustedProgramHandler *>(d->m_openWithHandler)) { 0158 obj->setWindow(window); 0159 } 0160 if (auto obj = qobject_cast<WidgetsOpenWithHandler *>(d->m_untrustedProgramHandler)) { 0161 obj->setWindow(window); 0162 } 0163 if (auto obj = qobject_cast<WidgetsOpenOrExecuteFileHandler *>(d->m_openOrExecuteFileHandler)) { 0164 obj->setWindow(window); 0165 } 0166 if (auto obj = qobject_cast<WidgetsAskUserActionHandler *>(d->m_askUserActionHandler)) { 0167 obj->setWindow(window); 0168 } 0169 0170 s_static()->registerWindow(window); 0171 } 0172 0173 void KIO::JobUiDelegate::unregisterWindow(QWidget *window) 0174 { 0175 s_static()->slotUnregisterWindow(window); 0176 } 0177 0178 bool KIO::JobUiDelegate::askDeleteConfirmation(const QList<QUrl> &urls, DeletionType deletionType, ConfirmationType confirmationType) 0179 { 0180 QString keyName; 0181 bool ask = (confirmationType == ForceConfirmation); 0182 if (!ask) { 0183 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); 0184 0185 // The default value for confirmations is true for delete and false 0186 // for trash. If you change this, please also update: 0187 // dolphin/src/settings/general/confirmationssettingspage.cpp 0188 bool defaultValue = true; 0189 0190 switch (deletionType) { 0191 case Delete: 0192 keyName = QStringLiteral("ConfirmDelete"); 0193 break; 0194 case Trash: 0195 keyName = QStringLiteral("ConfirmTrash"); 0196 defaultValue = false; 0197 break; 0198 case EmptyTrash: 0199 keyName = QStringLiteral("ConfirmEmptyTrash"); 0200 break; 0201 } 0202 0203 ask = kioConfig->group(QStringLiteral("Confirmations")).readEntry(keyName, defaultValue); 0204 } 0205 if (ask) { 0206 QStringList prettyList; 0207 prettyList.reserve(urls.size()); 0208 for (const QUrl &url : urls) { 0209 if (url.scheme() == QLatin1String("trash")) { 0210 QString path = url.path(); 0211 // HACK (#98983): remove "0-foo". Note that it works better than 0212 // displaying KFileItem::name(), for files under a subdir. 0213 path.remove(QRegularExpression(QStringLiteral("^/[0-9]*-"))); 0214 prettyList.append(path); 0215 } else { 0216 prettyList.append(url.toDisplayString(QUrl::PreferLocalFile)); 0217 } 0218 } 0219 0220 int result; 0221 QWidget *widget = window(); 0222 const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); 0223 switch (deletionType) { 0224 case Delete: 0225 if (prettyList.count() == 1) { 0226 result = KMessageBox::warningContinueCancel( 0227 widget, 0228 xi18nc("@info", 0229 "Do you really want to permanently delete this item?<nl/><filename>%1</filename><nl/><nl/><emphasis strong='true'>This action " 0230 "cannot be undone.</emphasis>", 0231 prettyList.first()), 0232 i18n("Delete Permanently"), 0233 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")), 0234 KStandardGuiItem::cancel(), 0235 keyName, 0236 options); 0237 } else { 0238 result = KMessageBox::warningContinueCancelList( 0239 widget, 0240 xi18ncp( 0241 "@info", 0242 "Do you really want to permanently delete this item?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>", 0243 "Do you really want to permanently delete these %1 items?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>", 0244 prettyList.count()), 0245 prettyList, 0246 i18n("Delete Permanently"), 0247 KGuiItem(i18nc("@action:button", "Delete Permanently"), QStringLiteral("edit-delete")), 0248 KStandardGuiItem::cancel(), 0249 keyName, 0250 options); 0251 } 0252 break; 0253 case EmptyTrash: 0254 result = KMessageBox::warningContinueCancel( 0255 widget, 0256 xi18nc("@info", 0257 "Do you want to permanently delete all items from the Trash?<nl/><nl/><emphasis strong='true'>This action cannot be undone.</emphasis>"), 0258 i18n("Delete Permanently"), 0259 KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))), 0260 KStandardGuiItem::cancel(), 0261 keyName, 0262 options); 0263 break; 0264 case Trash: 0265 default: 0266 if (prettyList.count() == 1) { 0267 result = KMessageBox::warningContinueCancel( 0268 widget, 0269 xi18nc("@info", "Do you really want to move this item to the Trash?<nl/><filename>%1</filename>", prettyList.first()), 0270 i18n("Move to Trash"), 0271 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), 0272 KStandardGuiItem::cancel(), 0273 keyName, 0274 options); 0275 } else { 0276 result = KMessageBox::warningContinueCancelList( 0277 widget, 0278 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()), 0279 prettyList, 0280 i18n("Move to Trash"), 0281 KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), 0282 KStandardGuiItem::cancel(), 0283 keyName, 0284 options); 0285 } 0286 } 0287 if (!keyName.isEmpty()) { 0288 // Check kmessagebox setting... erase & copy to konquerorrc. 0289 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0290 KConfigGroup notificationGroup(config, QStringLiteral("Notification Messages")); 0291 if (!notificationGroup.readEntry(keyName, true)) { 0292 notificationGroup.writeEntry(keyName, true); 0293 notificationGroup.sync(); 0294 0295 KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); 0296 kioConfig->group(QStringLiteral("Confirmations")).writeEntry(keyName, false); 0297 } 0298 } 0299 return (result == KMessageBox::Continue); 0300 } 0301 return true; 0302 } 0303 0304 KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) 0305 { 0306 if (qobject_cast<QGuiApplication *>(qApp)) { 0307 return new KIO::ClipboardUpdater(job, mode); 0308 } 0309 return nullptr; 0310 } 0311 0312 void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest) 0313 { 0314 if (qobject_cast<QGuiApplication *>(qApp)) { 0315 KIO::ClipboardUpdater::update(src, dest); 0316 } 0317 } 0318 0319 KIO::JobUiDelegate::JobUiDelegate(KJobUiDelegate::Flags flags, QWidget *window, const QList<QObject *> &ifaces) 0320 : KDialogJobUiDelegate(flags, window) 0321 , d(new JobUiDelegatePrivate(this, ifaces)) 0322 { 0323 // TODO KF6: change the API to accept QWindows rather than QWidgets (this also carries through to the Interfaces) 0324 if (window) { 0325 s_static()->registerWindow(window); 0326 setWindow(window); 0327 } 0328 } 0329 0330 class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory 0331 { 0332 public: 0333 using KIO::JobUiDelegateFactory::JobUiDelegateFactory; 0334 0335 KJobUiDelegate *createDelegate() const override 0336 { 0337 return new KIO::JobUiDelegate; 0338 } 0339 0340 KJobUiDelegate *createDelegate(KJobUiDelegate::Flags flags, QWidget *window) const override 0341 { 0342 return new KIO::JobUiDelegate(flags, window); 0343 } 0344 0345 static void registerJobUiDelegate() 0346 { 0347 static KIOWidgetJobUiDelegateFactory factory; 0348 KIO::setDefaultJobUiDelegateFactory(&factory); 0349 0350 static KIO::JobUiDelegate delegate; 0351 KIO::setDefaultJobUiDelegateExtension(&delegate); 0352 } 0353 }; 0354 0355 // Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs 0356 static void registerJobUiDelegate() 0357 { 0358 // Inside the factory class so it is a friend of the delegate and can construct it. 0359 KIOWidgetJobUiDelegateFactory::registerJobUiDelegate(); 0360 } 0361 0362 Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate) 0363 0364 #include "jobuidelegate.moc" 0365 #include "moc_jobuidelegate.cpp"