File indexing completed on 2024-04-28 15:40:12

0001 // SPDX-FileCopyrightText: 2003-2020 Jesper K. Pedersen <blackie@kde.org>
0002 // SPDX-FileCopyrightText: 2020-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 // SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
0004 //
0005 // SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007 #include "ExternalPopup.h"
0008 
0009 #include "Logging.h"
0010 #include "RunDialog.h"
0011 #include "Window.h"
0012 
0013 #include <DB/ImageInfo.h>
0014 #include <kpabase/FileNameList.h>
0015 #include <kpabase/SettingsData.h>
0016 
0017 #include <KFileItem>
0018 #include <KLocalizedString>
0019 #include <KMimeTypeTrader>
0020 #include <kio_version.h>
0021 #if KIO_VERSION >= QT_VERSION_CHECK(5, 70, 0)
0022 #include <KIO/ApplicationLauncherJob>
0023 #include <KIO/JobUiDelegate>
0024 #include <KIO/JobUiDelegateFactory>
0025 #endif
0026 #if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
0027 // KRun::displayOpenWithDialog() was both replaced and deprecated in 5.71
0028 #include <KRun>
0029 #endif
0030 #include <kservice_version.h>
0031 #if KSERVICE_VERSION >= QT_VERSION_CHECK(5, 68, 0)
0032 #include <KApplicationTrader>
0033 #endif
0034 #include <KService>
0035 #include <KShell>
0036 #include <QFile>
0037 #include <QIcon>
0038 #include <QLabel>
0039 #include <QMimeDatabase>
0040 #include <QPixmap>
0041 #include <QStringList>
0042 #include <QUrl>
0043 
0044 void MainWindow::ExternalPopup::populate(DB::ImageInfoPtr current, const DB::FileNameList &imageList)
0045 {
0046     m_list = imageList;
0047     m_currentInfo = current;
0048     clear();
0049     QAction *action;
0050 
0051     for (PopupAction which : { PopupAction::OpenCurrent, PopupAction::OpenAllSelected, PopupAction::CopyAndOpenAllSelected }) {
0052         if (which == PopupAction::OpenCurrent && !current)
0053             continue;
0054 
0055         const bool multiple = (m_list.count() > 1);
0056         const bool enabled = (which != PopupAction::OpenAllSelected && m_currentInfo) || (which == PopupAction::OpenAllSelected && multiple);
0057 
0058         // Submenu
0059         QString menuLabel;
0060         switch (which) {
0061         case PopupAction::OpenCurrent:
0062             menuLabel = i18n("Current Item");
0063             break;
0064         case PopupAction::OpenAllSelected:
0065             menuLabel = i18n("All Selected Items");
0066             break;
0067         case PopupAction::CopyAndOpenAllSelected:
0068             menuLabel = i18n("Copy and Open");
0069         }
0070         Q_ASSERT(!menuLabel.isNull());
0071 
0072         QMenu *submenu = addMenu(menuLabel);
0073         submenu->setEnabled(enabled);
0074 
0075         // Fetch set of offers
0076         KService::List offers;
0077         if (which == PopupAction::OpenCurrent)
0078             offers = appInfos(DB::FileNameList() << current->fileName());
0079         else
0080             offers = appInfos(imageList);
0081 
0082         for (KService::Ptr offer : qAsConst(offers)) {
0083             action = submenu->addAction(offer->name());
0084             action->setIcon(QIcon::fromTheme(offer->icon()));
0085             action->setEnabled(enabled);
0086             connect(action, &QAction::triggered, this, [this, offer, which] {
0087                 runService(offer, relevantUrls(which));
0088             });
0089         }
0090 
0091         // A personal command
0092         action = submenu->addAction(i18n("Open With..."));
0093         // XXX: action->setIcon( QIcon::fromTheme((*offerIt).second) );
0094         action->setEnabled(enabled);
0095         connect(action, &QAction::triggered, this, [this, which] {
0096             const QList<QUrl> urls = relevantUrls(which);
0097 
0098             auto *uiParent = MainWindow::Window::theMainWindow();
0099 #if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
0100             KRun::displayOpenWithDialog(urls, uiParent);
0101 #else
0102                 auto job = new KIO::ApplicationLauncherJob();
0103                 job->setUrls(urls);
0104 #if KIO_VERSION < QT_VERSION_CHECK(5, 98, 0)
0105                 job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent));
0106 #else
0107                 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent));
0108 #endif
0109                 job->start();
0110 #endif
0111         });
0112 
0113         // A personal command
0114         // XXX: see kdialog.h for simple usage
0115         action = submenu->addAction(i18n("Your Command Line"));
0116         // XXX: action->setIcon( QIcon::fromTheme((*offerIt).second) );
0117         action->setEnabled(enabled);
0118         connect(action, &QAction::triggered, this, [this] {
0119             static RunDialog *dialog = new RunDialog(MainWindow::Window::theMainWindow());
0120             dialog->setImageList(m_list);
0121             dialog->show();
0122         });
0123     }
0124 }
0125 
0126 QList<QUrl> MainWindow::ExternalPopup::relevantUrls(PopupAction which)
0127 {
0128     if (which == PopupAction::OpenCurrent) {
0129         return { QUrl(m_currentInfo->fileName().absolute()) };
0130     }
0131 
0132     if (which == PopupAction::OpenAllSelected) {
0133         QList<QUrl> lst;
0134         for (const DB::FileName &file : qAsConst(m_list)) {
0135             lst.append(QUrl(file.absolute()));
0136         }
0137         return lst;
0138     }
0139 
0140     if (which == PopupAction::CopyAndOpenAllSelected) {
0141         QList<QUrl> lst;
0142         // the way things are presented in the UI, a user is likely to expect this option to
0143         // copy and open all selected, not just one. Also, the computation of `enabled` in populate()
0144         // suggests the same (hence the enum name) ->
0145         // FIXME(jzarl) make this work on the file list!
0146         QString origFile = m_currentInfo->fileName().absolute();
0147         QString newFile = origFile;
0148 
0149         QString origRegexpString = Settings::SettingsData::instance()->copyFileComponent();
0150         QRegExp origRegexp = QRegExp(origRegexpString);
0151         QString copyFileReplacement = Settings::SettingsData::instance()->copyFileReplacementComponent();
0152 
0153         if (origRegexpString.length() > 0) {
0154             newFile.replace(origRegexp, copyFileReplacement);
0155             QFile::copy(origFile, newFile);
0156             lst.append(QUrl::fromLocalFile(newFile));
0157         } else {
0158             qCWarning(MainWindowLog, "No settings were appropriate for modifying the file name (you must fill in the regexp field; Opening the original instead");
0159             lst.append(QUrl::fromLocalFile(origFile));
0160         }
0161         return lst;
0162     }
0163     return {};
0164 }
0165 
0166 void MainWindow::ExternalPopup::runService(KService::Ptr service, QList<QUrl> urls)
0167 {
0168     auto *uiParent = MainWindow::Window::theMainWindow();
0169 #if KIO_VERSION < QT_VERSION_CHECK(5, 70, 0)
0170     KRun::runService(*service, urls, uiParent);
0171 #else
0172     auto job = new KIO::ApplicationLauncherJob(service);
0173     job->setUrls(urls);
0174 #if KIO_VERSION < QT_VERSION_CHECK(5, 98, 0)
0175     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent));
0176 #else
0177     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent));
0178 #endif
0179     job->start();
0180 #endif
0181 }
0182 
0183 MainWindow::ExternalPopup::ExternalPopup(QWidget *parent)
0184     : QMenu(parent)
0185 {
0186     setTitle(i18n("Invoke External Program"));
0187 }
0188 
0189 QString MainWindow::ExternalPopup::mimeType(const DB::FileName &file)
0190 {
0191     QMimeDatabase db;
0192     return db.mimeTypeForFile(file.absolute(), QMimeDatabase::MatchExtension).name();
0193 }
0194 
0195 Utilities::StringSet MainWindow::ExternalPopup::mimeTypes(const DB::FileNameList &files)
0196 {
0197     Utilities::StringSet res;
0198     Utilities::StringSet extensions;
0199     for (const DB::FileName &file : files) {
0200         const DB::FileName baseFileName = file;
0201         const int extStart = baseFileName.relative().lastIndexOf(QChar::fromLatin1('.'));
0202         const QString ext = baseFileName.relative().mid(extStart);
0203         if (!extensions.contains(ext)) {
0204             res.insert(mimeType(file));
0205             extensions.insert(ext);
0206         }
0207     }
0208     return res;
0209 }
0210 
0211 namespace
0212 {
0213 KService::List getServiceOffers(const QString &type)
0214 {
0215 #if KSERVICE_VERSION < QT_VERSION_CHECK(5, 68, 0)
0216     return KMimeTypeTrader::self()->query(type, QLatin1String("Application"));
0217 #else
0218     return KApplicationTrader::queryByMimeType(type);
0219 #endif
0220 }
0221 }
0222 
0223 KService::List MainWindow::ExternalPopup::appInfos(const DB::FileNameList &files)
0224 {
0225     const auto types = mimeTypes(files);
0226     if (types.isEmpty())
0227         return {};
0228 
0229     auto typesIt = types.cbegin();
0230 
0231     // get offers for first mimeType...
0232     const auto initialOffers = getServiceOffers(*typesIt);
0233     QSet<QString> commonOffers;
0234     for (const auto &offer : initialOffers) {
0235         commonOffers += offer->name();
0236     }
0237 
0238     // ... and eliminate any offer that does not support all files
0239     while (++typesIt != types.cend()) {
0240         const auto offers = getServiceOffers(*typesIt);
0241         QSet<QString> offerSet;
0242         for (const auto &offer : offers) {
0243             offerSet += offer->name();
0244         }
0245         commonOffers &= offerSet;
0246     }
0247 
0248     KService::List result;
0249     for (const auto &offer : initialOffers) {
0250         // KService::Ptrs will be different for different mime types, so we have to compare by name
0251         if (commonOffers.contains(offer->name()))
0252             result << offer;
0253     }
0254 
0255     return result;
0256 }
0257 
0258 bool operator<(const QPair<QString, QPixmap> &a, const QPair<QString, QPixmap> &b)
0259 {
0260     return a.first < b.first;
0261 }
0262 
0263 // vi:expandtab:tabstop=4 shiftwidth=4:
0264 
0265 #include "moc_ExternalPopup.cpp"