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"