File indexing completed on 2024-07-14 03:54:24

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"