diff --git a/desktop/MigrateShortcuts.cpp b/desktop/MigrateShortcuts.cpp index f9606cf..6b007df 100644 --- a/desktop/MigrateShortcuts.cpp +++ b/desktop/MigrateShortcuts.cpp @@ -1,114 +1,114 @@ #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { QCoreApplication app(argc, argv); QDBusInterface khotkeys(QStringLiteral("org.kde.kded5"), QStringLiteral("/modules/khotkeys"), QStringLiteral("org.kde.khotkeys")); KConfig khotkeysrc(QStringLiteral("khotkeysrc"), KConfig::SimpleConfig); int dataCount = KConfigGroup(&khotkeysrc, "Data").readEntry("DataCount", 0); bool found_spectacle = false; int spectacleIndex; for (int i = 1; i <= dataCount; ++i) { - if( KConfigGroup(&khotkeysrc, QStringLiteral("Data_%1").arg(i)).readEntry("ImportId", QString()) == QStringLiteral("spectacle")) { + if( KConfigGroup(&khotkeysrc, QStringLiteral("Data_%1").arg(i)).readEntry("ImportId", QString()) == QLatin1String("spectacle")) { found_spectacle = true; spectacleIndex = i; break; } } QList launchKey; QList fullScreenKey; QList regionKey; QList activeWindowKey; QStringList ids; if (found_spectacle) { for (int i = 1; i <= 4; ++i) { QString groupName = QStringLiteral("Data_%1_%2").arg(spectacleIndex).arg(i); QString method = KConfigGroup(&khotkeysrc, groupName + QStringLiteral("Actions0")).readEntry("Call"); QString id = KConfigGroup(&khotkeysrc, groupName + QStringLiteral("Triggers0")).readEntry("Uuid"); QList shortcut = KGlobalAccel::self()->globalShortcut(QStringLiteral("khotkeys"), id); ids.append(id); /* Name and Comment field are translated but we can find out which action is which by looking at the called * D-Bus Method */ - if (method == QStringLiteral("StartAgent")) { + if (method == QLatin1String("StartAgent")) { launchKey = shortcut; - } else if (method == QStringLiteral("FullScreen")) { + } else if (method == QLatin1String("FullScreen")) { fullScreenKey = shortcut; - } else if (method == QStringLiteral("ActiveWindow")) { + } else if (method == QLatin1String("ActiveWindow")) { activeWindowKey = shortcut; - } else if (method == QStringLiteral("RectangularRegion")) { + } else if (method == QLatin1String("RectangularRegion")) { regionKey = shortcut; } // Delete the groups from khotkeysrc khotkeysrc.deleteGroup(groupName); khotkeysrc.deleteGroup(groupName + QStringLiteral("Actions")); khotkeysrc.deleteGroup(groupName + QStringLiteral("Actions0")); khotkeysrc.deleteGroup(groupName + QStringLiteral("Conditions")); khotkeysrc.deleteGroup(groupName + QStringLiteral("Triggers")); khotkeysrc.deleteGroup(groupName + QStringLiteral("Triggers0")); } khotkeysrc.deleteGroup(QStringLiteral("Data_%1").arg(spectacleIndex)); khotkeysrc.deleteGroup(QStringLiteral("Data_%1Conditions").arg(spectacleIndex)); khotkeysrc.sync(); } QDBusInterface kglobalaccel(QStringLiteral("org.kde.kglobalaccel"), QStringLiteral("/kglobalaccel"), QStringLiteral("org.kde.KGlobalAccel")); // Unregister the khotkeysActions from globalAccel, removeAll didn't Work, so using D-Bus for(const QString &action : ids) { kglobalaccel.call(QStringLiteral("unregister"), QStringLiteral("khotkeys"), action); } //Setup the default Shortcuts KActionCollection shortCutActions(static_cast(nullptr)); shortCutActions.setComponentName(QStringLiteral("org.kde.spectacle.desktop")); shortCutActions.setComponentDisplayName(QStringLiteral("Spectacle")); { QAction *action = new QAction(i18n("Launch Spectacle")); action->setObjectName(QStringLiteral("_launch")); shortCutActions.addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Entire Desktop")); action->setObjectName(QStringLiteral("FullScreenScreenShot")); shortCutActions.addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Current Monitor")); action->setObjectName(QStringLiteral("CurrentMonitorScreenShot")); shortCutActions.addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Active Window")); action->setObjectName(QStringLiteral("ActiveWindowScreenShot")); shortCutActions.addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Rectangular Region")); action->setObjectName(QStringLiteral("RectangularRegionScreenShot")); shortCutActions.addAction(action->objectName(), action); } QAction* openAction = shortCutActions.action(QStringLiteral("_launch")); KGlobalAccel::self()->setDefaultShortcut(openAction, {Qt::Key_Print}); QAction* fullScreenAction = shortCutActions.action(QStringLiteral("FullScreenScreenShot")); KGlobalAccel::self()->setDefaultShortcut(fullScreenAction, {Qt::SHIFT + Qt::Key_Print}); QAction* currentScreenAction = shortCutActions.action(QStringLiteral("CurrentMonitorScreenShot")); QAction* activeWindowAction = shortCutActions.action(QStringLiteral("ActiveWindowScreenShot")); KGlobalAccel::self()->setDefaultShortcut(activeWindowAction, {Qt::META + Qt::Key_Print}); QAction* regionAction = shortCutActions.action(QStringLiteral("RectangularRegionScreenShot")); KGlobalAccel::self()->setDefaultShortcut(regionAction, {Qt::META + Qt::SHIFT + Qt::Key_Print}); // Finally reinstate the old shortcuts if (found_spectacle) { KGlobalAccel::self()->setShortcut(openAction, launchKey, KGlobalAccel::NoAutoloading); KGlobalAccel::self()->setShortcut(fullScreenAction, fullScreenKey, KGlobalAccel::NoAutoloading); KGlobalAccel::self()->setShortcut(activeWindowAction, activeWindowKey, KGlobalAccel::NoAutoloading); KGlobalAccel::self()->setShortcut(regionAction, regionKey, KGlobalAccel::NoAutoloading); } } diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index 8184eea..feb5b91 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,592 +1,592 @@ /* This file is part of Spectacle, the KDE screenshot utility * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * SPDX-License-Identifier: LGPL-2.0-or-later */ #include "ExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SpectacleConfig.h" ExportManager::ExportManager(QObject *parent) : QObject(parent), mSavePixmap(QPixmap()), mTempFile(QUrl()), mTempDir(nullptr) { connect(this, &ExportManager::imageSaved, [](const QUrl &savedAt) { SpectacleConfig::instance()->setLastSaveFile(savedAt); }); } ExportManager::~ExportManager() { delete mTempDir; } ExportManager* ExportManager::instance() { static ExportManager instance; return &instance; } // screenshot pixmap setter and getter QPixmap ExportManager::pixmap() const { return mSavePixmap; } void ExportManager::setWindowTitle(const QString &windowTitle) { mWindowTitle = windowTitle; } QString ExportManager::windowTitle() const { return mWindowTitle; } Spectacle::CaptureMode ExportManager::captureMode() const { return mCaptureMode; } void ExportManager::setCaptureMode(const Spectacle::CaptureMode &theCaptureMode) { mCaptureMode = theCaptureMode; } void ExportManager::setPixmap(const QPixmap &pixmap) { mSavePixmap = pixmap; // reset our saved tempfile if (mTempFile.isValid()) { mUsedTempFileNames.append(mTempFile); QFile file(mTempFile.toLocalFile()); file.remove(); mTempFile = QUrl(); } } void ExportManager::updatePixmapTimestamp() { mPixmapTimestamp = QDateTime::currentDateTime(); } void ExportManager::setTimestamp(const QDateTime ×tamp) { mPixmapTimestamp = timestamp; } // native file save helpers QString ExportManager::defaultSaveLocation() const { QString savePath = SpectacleConfig::instance()->defaultSaveLocation().toLocalFile(); savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath(QStringLiteral(".")); } return savePath; } QUrl ExportManager::getAutosaveFilename() { const QString baseDir = defaultSaveLocation(); const QDir baseDirPath(baseDir); const QString filename = makeAutosaveFilename(); const QString fullpath = autoIncrementFilename(baseDirPath.filePath(filename), SpectacleConfig::instance()->saveImageFormat(), &ExportManager::isFileExists); const QUrl fileNameUrl = QUrl::fromUserInput(fullpath); if (fileNameUrl.isValid()) { return fileNameUrl; } else { return QUrl(); } } QString ExportManager::truncatedFilename(QString const &filename) { QString result = filename; constexpr auto maxFilenameLength = 255; constexpr auto maxExtensionLength = 5; // For example, ".jpeg" constexpr auto maxCounterLength = 20; // std::numeric_limits::max() == 18446744073709551615 constexpr auto maxLength = maxFilenameLength - maxCounterLength - maxExtensionLength; result.truncate(maxLength); return result; } QString ExportManager::makeAutosaveFilename() { return formatFilename(SpectacleConfig::instance()->autoSaveFilenameFormat()); } QString ExportManager::formatFilename(const QString &nameTemplate) { const QDateTime timestamp = mPixmapTimestamp; QString baseName = nameTemplate; const QString baseDir = defaultSaveLocation(); QString title; if (mCaptureMode == Spectacle::CaptureMode::ActiveWindow || mCaptureMode == Spectacle::CaptureMode::TransientWithParent || mCaptureMode == Spectacle::CaptureMode::WindowUnderCursor) { title = mWindowTitle.replace(QLatin1String("/"), QLatin1String("_")); // POSIX doesn't allow "/" in filenames } else { // Remove '%T' with separators around it const auto wordSymbol = QStringLiteral(R"(\p{L}\p{M}\p{N})"); const auto separator = QStringLiteral("([^%1]+)").arg(wordSymbol); const auto re = QRegularExpression(QStringLiteral("(.*?)(%1%T|%T%1)(.*?)").arg(separator)); baseName.replace(re, QStringLiteral(R"(\1\5)")); } QString result = baseName.replace(QLatin1String("%Y"), timestamp.toString(QStringLiteral("yyyy"))) .replace(QLatin1String("%y"), timestamp.toString(QStringLiteral("yy"))) .replace(QLatin1String("%M"), timestamp.toString(QStringLiteral("MM"))) .replace(QLatin1String("%D"), timestamp.toString(QStringLiteral("dd"))) .replace(QLatin1String("%H"), timestamp.toString(QStringLiteral("hh"))) .replace(QLatin1String("%m"), timestamp.toString(QStringLiteral("mm"))) .replace(QLatin1String("%S"), timestamp.toString(QStringLiteral("ss"))) .replace(QLatin1String("%T"), title); // check if basename includes %[N]d token for sequential file numbering QRegularExpression paddingRE; paddingRE.setPattern(QStringLiteral("%(\\d*)d")); QRegularExpressionMatch paddingMatch; while (result.indexOf(paddingRE, 0, &paddingMatch) > -1) { int highestFileNumber = 0; // determine padding value int paddedLength = 1; if (!paddingMatch.captured(1).isEmpty()) { paddedLength = paddingMatch.captured(1).toInt(); } // search save directory for files QDir dir(baseDir); const QStringList fileNames = dir.entryList(QDir::Files, QDir::Name); // if there are files in the directory... if (fileNames.length() > 0) { QString resultCopy = result; QRegularExpression fileNumberRE; const QString replacement = QStringLiteral("(\\d{").append(QString::number(paddedLength)).append(QLatin1String(",})")); const QString fullNameMatch = QStringLiteral("^").append(resultCopy.replace(paddingMatch.captured(),replacement)).append(QStringLiteral("\\..*$")); fileNumberRE.setPattern(fullNameMatch); // ... check the file names for string matching token with padding specified in result const QStringList filteredFiles = fileNames.filter(fileNumberRE); // if there are files in the directory that look like the file name with sequential numbering if (filteredFiles.length() > 0) { // loop through filtered file names looking for highest number for (const QString &filteredFile: filteredFiles) { int currentFileNumber = fileNumberRE.match(filteredFile).captured(1).toInt(); if (currentFileNumber > highestFileNumber) { highestFileNumber = currentFileNumber; } } } } // replace placeholder with next number padded const QString nextFileNumberPadded = QString::number(highestFileNumber + 1).rightJustified(paddedLength, QLatin1Char('0')); - result = result.replace(paddingMatch.captured(), nextFileNumberPadded); + result.replace(paddingMatch.captured(), nextFileNumberPadded); } // Remove leading and trailing '/' while (result.startsWith(QLatin1Char('/'))) { result.remove(0, 1); } while (result.endsWith(QLatin1Char('/'))) { result.chop(1); } if (result.isEmpty()) { result = SpectacleConfig::instance()->defaultFilename(); } return truncatedFilename(result); } QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension, FileNameAlreadyUsedCheck isFileNameUsed) { QString result = truncatedFilename(baseName) + QLatin1Literal(".") + extension; if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) { return result; } QString fileNameFmt = truncatedFilename(baseName) + QStringLiteral("-%1."); for (quint64 i = 1; i < std::numeric_limits::max(); i++) { result = fileNameFmt.arg(i) + extension; if (!((this->*isFileNameUsed)(QUrl::fromUserInput(result)))) { return result; } } // unlikely this will ever happen, but just in case we've run // out of numbers result = fileNameFmt.arg(QStringLiteral("OVERFLOW-") + QString::number(qrand() % 10000)); return truncatedFilename(result) + extension; } QString ExportManager::makeSaveMimetype(const QUrl &url) { QMimeDatabase mimedb; QString type = mimedb.mimeTypeForUrl(url).preferredSuffix(); if (type.isEmpty()) { return SpectacleConfig::instance()->saveImageFormat(); } return type; } bool ExportManager::writeImage(QIODevice *device, const QByteArray &format) { QImageWriter imageWriter(device, format); imageWriter.setQuality(SpectacleConfig::instance()->compressionQuality()); /** Set compression 50 if the format is png. Otherwise if no compression value is specified * it will fallback to using quality (QTBUG-43618) and produce huge files. * See also qpnghandler.cpp#n1075. The other formats that do compression seem to have it * enabled by default and only disabled if compression is set to 0, also any value except 0 * has the same effect for them. */ if (format == "png") { imageWriter.setCompression(50); } if (!(imageWriter.canWrite())) { emit errorMessage(i18n("QImageWriter cannot write image: %1", imageWriter.errorString())); return false; } return imageWriter.write(mSavePixmap.toImage()); } bool ExportManager::localSave(const QUrl &url, const QString &mimetype) { // Create save directory if it doesn't exist const QUrl dirPath(url.adjusted(QUrl::RemoveFilename)); const QDir dir(dirPath.path()); if (!dir.mkpath(QStringLiteral("."))) { emit errorMessage(xi18nc("@info", "Cannot save screenshot because creating " "the directory failed:%1", dirPath.path())); return false; } QFile outputFile(url.toLocalFile()); outputFile.open(QFile::WriteOnly); if(!writeImage(&outputFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing file.")); return false; } return true; } bool ExportManager::remoteSave(const QUrl &url, const QString &mimetype) { // Check if remote save directory exists const QUrl dirPath(url.adjusted(QUrl::RemoveFilename)); KIO::ListJob *listJob = KIO::listDir(dirPath); listJob->exec(); if (listJob->error() != KJob::NoError) { // Create remote save directory KIO::MkpathJob *mkpathJob = KIO::mkpath(dirPath, QUrl(defaultSaveLocation())); mkpathJob->exec(); if (mkpathJob->error() != KJob::NoError) { emit errorMessage(xi18nc("@info", "Cannot save screenshot because creating the " "remote directory failed:%1", dirPath.path())); return false; } } QTemporaryFile tmpFile; if (tmpFile.open()) { if(!writeImage(&tmpFile, mimetype.toLatin1())) { emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return false; } KIO::FileCopyJob *uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmpFile.fileName()), url); uploadJob->exec(); if (uploadJob->error() != KJob::NoError) { emit errorMessage(i18n("Unable to save image. Could not upload file to remote location.")); return false; } return true; } return false; } QUrl ExportManager::tempSave() { // if we already have a temp file saved, use that if (mTempFile.isValid()) { if (QFile(mTempFile.toLocalFile()).exists()) { return mTempFile; } } if (!mTempDir) { mTempDir = new QTemporaryDir(QDir::tempPath() + QDir::separator() + QStringLiteral("Spectacle.XXXXXX")); } if (mTempDir && mTempDir->isValid()) { // create the temporary file itself with normal file name and also unique one for this session // supports the use-case of creating multiple screenshots in a row // and exporting them to the same destination e.g. via clipboard, // where the temp file name is used as filename suggestion const QString baseFileName = mTempDir->path() + QDir::separator() + makeAutosaveFilename(); QString mimetype = makeSaveMimetype(QUrl(baseFileName)); const QString fileName = autoIncrementFilename(baseFileName, mimetype, &ExportManager::isTempFileAlreadyUsed); QFile tmpFile(fileName); if (tmpFile.open(QFile::WriteOnly)) { if(writeImage(&tmpFile, mimetype.toLatin1())) { mTempFile = QUrl::fromLocalFile(tmpFile.fileName()); // try to make sure 3rd-party which gets the url of the temporary file e.g. on export // properly treats this as readonly, also hide from other users tmpFile.setPermissions(QFile::ReadUser); return mTempFile; } } } emit errorMessage(i18n("Cannot save screenshot. Error while writing temporary local file.")); return QUrl(); } bool ExportManager::save(const QUrl &url) { if (!(url.isValid())) { emit errorMessage(i18n("Cannot save screenshot. The save filename is invalid.")); return false; } QString mimetype = makeSaveMimetype(url); if (url.isLocalFile()) { return localSave(url, mimetype); } return remoteSave(url, mimetype); } bool ExportManager::isFileExists(const QUrl &url) const { if (!(url.isValid())) { return false; } KIO::StatJob * existsJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0); existsJob->exec(); return (existsJob->error() == KJob::NoError); } bool ExportManager::isTempFileAlreadyUsed(const QUrl &url) const { return mUsedTempFileNames.contains(url); } // save slots void ExportManager::doSave(const QUrl &url, bool notify) { if (mSavePixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath = url.isValid() ? url : getAutosaveFilename(); if (save(savePath)) { QDir dir(savePath.path()); dir.cdUp(); SpectacleConfig::instance()->setLastSaveFile(savePath); emit imageSaved(savePath); if (notify) { emit forceNotify(savePath); } } } bool ExportManager::doSaveAs(QWidget *parentWindow, bool notify) { QStringList supportedFilters; SpectacleConfig *config = SpectacleConfig::instance(); // construct the supported mimetype list const auto mimeTypes = QImageWriter::supportedMimeTypes(); for (const auto &mimeType : mimeTypes) { supportedFilters.append(QString::fromUtf8(mimeType).trimmed()); } // construct the file name const QString filenameExtension = SpectacleConfig::instance()->saveImageFormat(); const QString mimetype = QMimeDatabase().mimeTypeForFile(QStringLiteral("~/fakefile.") + filenameExtension, QMimeDatabase::MatchExtension).name(); QFileDialog dialog(parentWindow); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setFileMode(QFileDialog::AnyFile); dialog.setDirectoryUrl(config->lastSaveAsLocation()); dialog.selectFile(makeAutosaveFilename() + QStringLiteral(".") + filenameExtension); dialog.setDefaultSuffix(QStringLiteral(".") + filenameExtension); dialog.setMimeTypeFilters(supportedFilters); dialog.selectMimeTypeFilter(mimetype); // launch the dialog if (dialog.exec() == QFileDialog::Accepted) { const QUrl saveUrl = dialog.selectedUrls().constFirst(); if (saveUrl.isValid()) { if (save(saveUrl)) { emit imageSaved(saveUrl); config->setLastSaveAsFile(saveUrl); if (notify) { emit forceNotify(saveUrl); } return true; } } } return false; } void ExportManager::doSaveAndCopy(const QUrl &url) { if (mSavePixmap.isNull()) { emit errorMessage(i18n("Cannot save an empty screenshot image.")); return; } QUrl savePath = url.isValid() ? url : getAutosaveFilename(); if (save(savePath)) { QDir dir(savePath.path()); dir.cdUp(); SpectacleConfig::instance()->setLastSaveFile(savePath); doCopyToClipboard(false); emit imageSavedAndCopied(savePath); } } // misc helpers void ExportManager::doCopyToClipboard(bool notify) { auto data = new QMimeData(); data->setImageData(mSavePixmap.toImage()); data->setData(QStringLiteral("x-kde-force-image-copy"), QByteArray()); QApplication::clipboard()->setMimeData(data, QClipboard::Clipboard); if (notify) { emit forceNotify(QUrl()); } } void ExportManager::doPrint(QPrinter *printer) { QPainter painter; if (!(painter.begin(printer))) { emit errorMessage(i18n("Printing failed. The printer failed to initialize.")); delete printer; return; } QRect devRect(0, 0, printer->width(), printer->height()); QPixmap pixmap = mSavePixmap.scaled(devRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect srcRect = pixmap.rect(); srcRect.moveCenter(devRect.center()); painter.drawPixmap(srcRect.topLeft(), pixmap); painter.end(); delete printer; return; } const QMap ExportManager::filenamePlaceholders { {QStringLiteral("%Y"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Year (4 digit)")}, {QStringLiteral("%y"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Year (2 digit)")}, {QStringLiteral("%M"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Month")}, {QStringLiteral("%D"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Day")}, {QStringLiteral("%H"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Hour")}, {QStringLiteral("%m"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Minute")}, {QStringLiteral("%S"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Second")}, {QStringLiteral("%T"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Window Title")}, {QStringLiteral("%d"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Sequential numbering")}, {QStringLiteral("%Nd"), ki18nc( "A placeholder in the user configurable filename will replaced by the specified value", "Sequential numbering, padded out to N digits")}, }; diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp index 7f513c0..2a91dc8 100644 --- a/src/Gui/ExportMenu.cpp +++ b/src/Gui/ExportMenu.cpp @@ -1,209 +1,209 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ExportMenu.h" #include "spectacle_gui_debug.h" #include "Config.h" #include #include #include #include #ifdef KIPI_FOUND #include #endif #include #include ExportMenu::ExportMenu(QWidget *parent) : QMenu(parent), #ifdef PURPOSE_FOUND mUpdatedImageAvailable(false), mPurposeMenu(new Purpose::Menu(this)), #endif mExportManager(ExportManager::instance()) { QTimer::singleShot(300, this, &ExportMenu::populateMenu); } void ExportMenu::populateMenu() { #ifdef PURPOSE_FOUND loadPurposeMenu(); #endif #ifdef KIPI_FOUND mKipiMenu = addMenu(i18n("More Online Services")); mKipiMenu->addAction(i18n("Please wait...")); mKipiMenuLoaded = false; connect(mKipiMenu, &QMenu::aboutToShow, this, &ExportMenu::loadKipiItems); #endif addSeparator(); getKServiceItems(); } void ExportMenu::imageUpdated() { #ifdef PURPOSE_FOUND // mark cached image as stale mUpdatedImageAvailable = true; mPurposeMenu->clear(); #endif } void ExportMenu::getKServiceItems() { // populate all locally installed applications and services // which can handle images first const KService::List services = KMimeTypeTrader::self()->query(QStringLiteral("image/png")); for (auto service : services) { QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&")); QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, this); connect(action, &QAction::triggered, this, [=]() { const QUrl filename = mExportManager->getAutosaveFilename(); mExportManager->doSave(filename); QList whereIs({ filename }); KRun::runService(*service, whereIs, parentWidget(), true); }); addAction(action); } // now let the user manually chose an application to open the // image with addSeparator(); QAction *openWith = new QAction(this); openWith->setText(i18n("Other Application...")); openWith->setShortcuts(KStandardShortcut::open()); connect(openWith, &QAction::triggered, this, [=]() { const QUrl filename = mExportManager->getAutosaveFilename(); mExportManager->doSave(filename); QList whereIs({ filename }); KRun::displayOpenWithDialog(whereIs, parentWidget(), true); }); addAction(openWith); } #ifdef KIPI_FOUND void ExportMenu::loadKipiItems() { if (!mKipiMenuLoaded) { QTimer::singleShot(500, this, &ExportMenu::getKipiItems); mKipiMenuLoaded = true; } } void ExportMenu::getKipiItems() { mKipiMenu->clear(); mKipiInterface = new KSGKipiInterface(this); KIPI::PluginLoader *loader = new KIPI::PluginLoader; loader->setInterface(mKipiInterface); loader->init(); KIPI::PluginLoader::PluginList pluginList = loader->pluginList(); for (const auto &pluginInfo : qAsConst(pluginList)) { if (!(pluginInfo->shouldLoad())) { continue; } KIPI::Plugin *plugin = pluginInfo->plugin(); if (!(plugin)) { qCWarning(SPECTACLE_GUI_LOG) << i18n("KIPI plugin from library %1 failed to load", pluginInfo->library()); continue; } plugin->setup(&mDummyWidget); const QList actions = plugin->actions(); QSet exportActions; for (auto action : actions) { KIPI::Category category = plugin->category(action); if (category == KIPI::ExportPlugin) { exportActions += action; - } else if (category == KIPI::ImagesPlugin && pluginInfo->library().contains(QStringLiteral("kipiplugin_sendimages"))) { + } else if (category == KIPI::ImagesPlugin && pluginInfo->library().contains(QLatin1String("kipiplugin_sendimages"))) { exportActions += action; } } for (auto action : qAsConst(exportActions)) { mKipiMenu->addAction(action); } } // If there are no export actions, then perhaps the kipi-plugins package is not installed. if (mKipiMenu->isEmpty()) { mKipiMenu->addAction(i18n("No KIPI plugins available"))->setEnabled(false); } } #endif #ifdef PURPOSE_FOUND void ExportMenu::loadPurposeMenu() { // attach the menu QAction *purposeMenu = addMenu(mPurposeMenu); purposeMenu->setText(i18n("Share")); purposeMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); // set up the callback signal connect(mPurposeMenu, &Purpose::Menu::finished, this, [this](const QJsonObject &output, int error, const QString &message) { if (error) { emit imageShared(true, message); } else { emit imageShared(false, output[QStringLiteral("url")].toString()); } }); // update available options based on the latest picture connect(mPurposeMenu, &QMenu::aboutToShow, this, &ExportMenu::loadPurposeItems); } void ExportMenu::loadPurposeItems() { if (!mUpdatedImageAvailable) { return; } // updated image available, we lazily load it now QString dataUri = ExportManager::instance()->tempSave().toString(); mUpdatedImageAvailable = false; mPurposeMenu->model()->setInputData(QJsonObject { { QStringLiteral("mimeType"), QStringLiteral("image/png") }, { QStringLiteral("urls"), QJsonArray({ dataUri }) } }); mPurposeMenu->model()->setPluginType(QStringLiteral("Export")); mPurposeMenu->reload(); } #endif