File indexing completed on 2025-02-02 04:26:11

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Boudhayan Gupta <bgupta@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "ExportMenu.h"
0008 #include "CaptureWindow.h"
0009 #include "SpectacleCore.h"
0010 #include "WidgetWindowUtils.h"
0011 #include "spectacle_gui_debug.h"
0012 #include "settings.h"
0013 
0014 #include <KApplicationTrader>
0015 #include <KIO/ApplicationLauncherJob>
0016 #include <kio_version.h>
0017 #include <KIO/JobUiDelegateFactory>
0018 #include <KIO/OpenFileManagerWindowJob>
0019 #include <KIO/OpenUrlJob>
0020 #include <KLocalizedString>
0021 #include <KNotificationJobUiDelegate>
0022 #include <KStandardAction>
0023 #include <KStandardShortcut>
0024 
0025 #include <QJsonArray>
0026 #include <QMimeDatabase>
0027 #include <QPrintDialog>
0028 #include <QPrinter>
0029 #include <QTimer>
0030 #include <QWindow>
0031 #include <chrono>
0032 
0033 using namespace std::chrono_literals;
0034 using namespace Qt::StringLiterals;
0035 
0036 class ExportMenuSingleton
0037 {
0038 public:
0039     ExportMenu self;
0040 };
0041 
0042 Q_GLOBAL_STATIC(ExportMenuSingleton, privateExportMenuSelf)
0043 
0044 ExportMenu::ExportMenu(QWidget *parent)
0045     : SpectacleMenu(parent)
0046 #ifdef PURPOSE_FOUND
0047     , mUpdatedImageAvailable(true)
0048     , mPurposeMenu(new Purpose::Menu)
0049 #endif
0050 {
0051     addAction(QIcon::fromTheme(u"document-open-folder"_s),
0052               i18n("Open Default Screenshots Folder"),
0053               this, &ExportMenu::openScreenshotsFolder);
0054     addAction(KStandardAction::print(this, &ExportMenu::openPrintDialog, this));
0055 
0056 #ifdef PURPOSE_FOUND
0057     loadPurposeMenu();
0058     connect(ExportManager::instance(), &ExportManager::imageChanged, this, &ExportMenu::onImageChanged);
0059 #endif
0060 
0061     addSeparator();
0062     getKServiceItems();
0063 }
0064 
0065 ExportMenu *ExportMenu::instance()
0066 {
0067     return &privateExportMenuSelf->self;
0068 }
0069 
0070 void ExportMenu::onImageChanged()
0071 {
0072 #ifdef PURPOSE_FOUND
0073     // mark cached image as stale
0074     mUpdatedImageAvailable = true;
0075     mPurposeMenu->clear();
0076 #endif
0077 }
0078 
0079 void ExportMenu::getKServiceItems()
0080 {
0081     // populate all locally installed applications and services
0082     // which can handle images first
0083 
0084     const KService::List services = KApplicationTrader::queryByMimeType(u"image/png"_s);
0085 
0086     for (auto service : services) {
0087         const QString name = service->name().replace('&'_L1, "&&"_L1);
0088         QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, this);
0089 
0090         connect(action, &QAction::triggered, this, [this, service]() {
0091             auto captureWindow = qobject_cast<CaptureWindow *>(getWidgetTransientParent(this));
0092             if(captureWindow && !captureWindow->accept()) {
0093                 return;
0094             }
0095             QUrl filename;
0096             if(ExportManager::instance()->isImageSavedNotInTemp()) {
0097                 filename = Settings::self()->lastImageSaveLocation();
0098             } else {
0099                 filename = ExportManager::instance()->getAutosaveFilename();
0100                 SpectacleCore::instance()->syncExportImage();
0101                 ExportManager::instance()->exportImage(ExportManager::Save, filename);
0102             }
0103 
0104             auto *job = new KIO::ApplicationLauncherJob(service);
0105             auto *delegate = new KNotificationJobUiDelegate;
0106             delegate->setAutoErrorHandlingEnabled(true);
0107             job->setUiDelegate(delegate);
0108 
0109             job->setUrls({filename});
0110             job->start();
0111         });
0112         addAction(action);
0113     }
0114 
0115     // now let the user manually chose an application to open the
0116     // image with
0117 
0118     addSeparator();
0119 
0120     QAction *openWith = new QAction(i18n("Other Application..."), this);
0121     openWith->setShortcuts(KStandardShortcut::open());
0122 
0123     connect(openWith, &QAction::triggered, this, [this]() {
0124         auto captureWindow = qobject_cast<CaptureWindow *>(getWidgetTransientParent(this));
0125         if(captureWindow && !captureWindow->accept()) {
0126             return;
0127         }
0128         QUrl filename;
0129         if(ExportManager::instance()->isImageSavedNotInTemp()) {
0130             filename = Settings::self()->lastImageSaveLocation();
0131         } else {
0132             filename = ExportManager::instance()->getAutosaveFilename();
0133             SpectacleCore::instance()->syncExportImage();
0134             ExportManager::instance()->exportImage(ExportManager::Save, filename);
0135         }
0136 
0137         auto job = new KIO::ApplicationLauncherJob;
0138         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window()));
0139         job->setUrls({filename});
0140         job->start();
0141     });
0142     addAction(openWith);
0143 }
0144 
0145 #ifdef PURPOSE_FOUND
0146 void ExportMenu::loadPurposeMenu()
0147 {
0148     // attach the menu
0149     auto purposeMenu = mPurposeMenu.get();
0150     QAction *purposeMenuAction = addMenu(purposeMenu);
0151     purposeMenuAction->setObjectName("purposeMenuAction");
0152     purposeMenuAction->setText(i18n("Share"));
0153     purposeMenuAction->setIcon(QIcon::fromTheme(u"document-share"_s));
0154 
0155     // set up the callback signal
0156     connect(purposeMenu, &Purpose::Menu::finished, this, [this](const QJsonObject &output, int error, const QString &message) {
0157         if (error) {
0158             Q_EMIT imageShared(error, message);
0159         } else {
0160             Q_EMIT imageShared(error, output[u"url"_s].toString());
0161         }
0162     });
0163 
0164     // update available options based on the latest picture
0165     connect(purposeMenu, &QMenu::aboutToShow, this, [this]() {
0166         loadPurposeItems();
0167         setWidgetTransientParentToWidget(mPurposeMenu.get(), this);
0168     });
0169 }
0170 
0171 void ExportMenu::loadPurposeItems()
0172 {
0173     if (!mUpdatedImageAvailable) {
0174         return;
0175     }
0176 
0177     // updated image available, we lazily load it now
0178     const QString dataUri = ExportManager::instance()->tempSave().toString();
0179     mUpdatedImageAvailable = false;
0180 
0181     auto mimeType = QMimeDatabase().mimeTypeForFile(dataUri).name();
0182     QJsonObject inputData = {
0183         {u"mimeType"_s, mimeType},
0184         {u"urls"_s, QJsonArray({dataUri})}
0185     };
0186     mPurposeMenu->model()->setInputData(inputData);
0187     mPurposeMenu->model()->setPluginType(u"Export"_s);
0188     mPurposeMenu->reload();
0189 }
0190 #endif
0191 
0192 void ExportMenu::openScreenshotsFolder()
0193 {
0194     auto job = new KIO::OpenUrlJob(Settings::imageSaveLocation());
0195     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0196     job->start();
0197 }
0198 
0199 void ExportMenu::openPrintDialog()
0200 {
0201     if (auto captureWindow = qobject_cast<CaptureWindow *>(getWidgetTransientParent(this))) {
0202         captureWindow->accept();
0203     }
0204     auto printer = new QPrinter(QPrinter::HighResolution);
0205     auto dialog = new QPrintDialog(printer);
0206     dialog->setAttribute(Qt::WA_DeleteOnClose);
0207 
0208     // properly set the transientparent chain
0209     setWidgetTransientParent(dialog, getWidgetTransientParent(this));
0210 
0211     connect(dialog, &QDialog::finished, dialog, [printer](int result){
0212         if (result == QDialog::Accepted) {
0213             ExportManager::instance()->doPrint(printer);
0214         }
0215         delete printer;
0216     });
0217 
0218     dialog->setVisible(true);
0219 }
0220 
0221 #include "moc_ExportMenu.cpp"