diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index 3911d70..d9fa5cf 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,580 +1,576 @@ /* 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 #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(); - if (savePath.isEmpty() || savePath.isNull()) { - savePath = QDir::homePath(); - } + QString savePath = SpectacleConfig::instance()->defaultSaveLocation().toString(); savePath = QDir::cleanPath(savePath); QDir savePathDir(savePath); if (!(savePathDir.exists())) { savePathDir.mkpath(QStringLiteral(".")); - SpectacleConfig::instance()->setDefaultSaveLocation(savePath); } 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 direcory 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); } // 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 Q_FOREACH (auto mimeType, QImageWriter::supportedMimeTypes()) { 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().first(); if (saveUrl.isValid()) { if (save(saveUrl)) { emit imageSaved(saveUrl); config->setLastSaveAsFile(saveUrl); if (notify) { emit forceNotify(saveUrl); } return true; } } } return false; } // 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/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 4ad35f5..1bcb77f 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,445 +1,445 @@ /* 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 "KSMainWindow.h" #include "Config.h" #include "SettingsDialog/SettingsDialog.h" #include #include #include #include #include #include #include #include #ifdef XCB_FOUND #include #include #endif #include #include #include #include #include #include #include #include static const int DEFAULT_WINDOW_HEIGHT = 420; static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; KSMainWindow::KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent) : QDialog(parent), mKSWidget(new KSWidget(theGrabModes, this)), mDivider(new QFrame(this)), mDialogButtonBox(new QDialogButtonBox(this)), mConfigureButton(new QToolButton(this)), mToolsButton(new QPushButton(this)), mSendToButton(new QPushButton(this)), mClipboardButton(new QToolButton(this)), mSaveButton(new QToolButton(this)), mSaveMenu(new QMenu(this)), mSaveAsAction(new QAction(this)), mSaveAction(new QAction(this)), mMessageWidget(new KMessageWidget(this)), mToolsMenu(new QMenu(this)), mScreenRecorderToolsMenu(new QMenu(this)), mExportMenu(new ExportMenu(this)), mShutterModes(theShutterModes) { // before we do anything, we need to set a window property // that skips the close/hide window animation on kwin. this // fixes a ghost image of the spectacle window that appears // on subsequent screenshots taken with the take new screenshot // button // // credits for this goes to Thomas Lübking #ifdef XCB_FOUND if (KWindowSystem::isPlatformX11()) { // create a window if we haven't already. note that the QWidget constructor // should already have done this if (winId() == 0) { create(0, true, true); } // do the xcb shenanigans xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); if (atom.isNull()) { goto done; } uint32_t value = 1; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } done: #endif QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } // GUI init void KSMainWindow::init() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); // window properties setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50)); move(location); // change window title on save connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::imageSaved); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help); mConfigureButton->setDefaultAction(KStandardAction::preferences(this, SLOT(showPreferencesDialog()), this)); mConfigureButton->setText(i18n("Configure...")); mConfigureButton->setToolTip(i18n("Change Spectacle's settings.")); mConfigureButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mConfigureButton, QDialogButtonBox::ResetRole); KGuiItem::assign(mToolsButton, KGuiItem(i18n("Tools"))); mToolsButton->setIcon(QIcon::fromTheme(QStringLiteral("tools"), QIcon::fromTheme(QStringLiteral("application-menu")))); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy to Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // the tools menu mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, [this]() { KMoreToolsMenuFactory *moreToolsMenuFactory = new KMoreToolsMenuFactory(QStringLiteral("spectacle/screenrecorder-tools")); moreToolsMenuFactory->setParentWidget(this); mScreenrecorderToolsMenuFactory.reset(moreToolsMenuFactory); mScreenRecorderToolsMenu->clear(); mScreenrecorderToolsMenuFactory->fillMenuFromGroupingNames(mScreenRecorderToolsMenu, { QStringLiteral("screenrecorder") }); } ); // the save menu mSaveAsAction = KStandardAction::saveAs(this, &KSMainWindow::saveAs, this); mSaveAction = KStandardAction::save(this, &KSMainWindow::save, this); mSaveMenu->addAction(mSaveAsAction); mSaveMenu->addAction(mSaveAction); setDefaultSaveAction(); // message widget connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) { QDesktopServices::openUrl(QUrl(str)); } ); // layouts mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mKSWidget); layout->addWidget(mMessageWidget); layout->addWidget(mDivider); layout->addWidget(mDialogButtonBox); mMessageWidget->hide(); // populate our send-to actions mSendToButton->setMenu(mExportMenu); connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); // lock down the onClick mode depending on available shutter modes if (!mShutterModes.testFlag(Platform::ShutterMode::OnClick)) { mKSWidget->lockOnClickDisabled(); } else if (!mShutterModes.testFlag(Platform::ShutterMode::Immediate)) { mKSWidget->lockOnClickEnabled(); } resize(QSize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT).expandedTo(minimumSize())); // Allow Ctrl+Q to quit the app QAction *actionQuit = KStandardAction::quit(qApp, &QApplication::quit, this); actionQuit->setShortcut(QKeySequence::Quit); addAction(actionQuit); mHideMessageWidgetTimer = new QTimer(this); connect(mHideMessageWidgetTimer, &QTimer::timeout, mMessageWidget, &KMessageWidget::animatedHide); mHideMessageWidgetTimer->setInterval(10000); // done with the init } int KSMainWindow::windowWidth(const QPixmap &pixmap) const { // Calculates what the width of the window should be for the captured image to perfectly fit // the area reserved for the image, with the height already set. const float pixmapAspectRatio = (float)pixmap.width() / pixmap.height(); const int imageHeight = mKSWidget->height() - 2 * layout()->spacing(); const int imageWidth = pixmapAspectRatio * imageHeight; int alignedWindowWidth = qMin(mKSWidget->imagePaddingWidth() + imageWidth, MAXIMUM_WINDOW_WIDTH); alignedWindowWidth += layout()->contentsMargins().left() + layout()->contentsMargins().right(); alignedWindowWidth += 2; // margins is removing 1 - 1 pixel for some reason return alignedWindowWidth; } void KSMainWindow::setDefaultSaveAction() { switch (SpectacleConfig::instance()->lastUsedSaveMode()) { case SaveMode::SaveAs: default: mSaveButton->setDefaultAction(mSaveAsAction); mSaveButton->setText(i18n("Save As...")); break; case SaveMode::Save: mSaveButton->setDefaultAction(mSaveAction); break; } } // overrides void KSMainWindow::moveEvent(QMoveEvent *event) { Q_UNUSED(event) KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); guiConfig.writeEntry("window-position", pos()); guiConfig.sync(); } // slots void KSMainWindow::captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { hide(); mMessageWidget->hide(); emit newScreenshotRequest(theCaptureMode, theTimeout, theIncludePointer, theIncludeDecorations); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); show(); resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT)); } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::openScreenshotsFolder() { // Highlight last screenshot in file manager if user saved at least once ever // (since last save and saveas file names are stored in spectaclerc), otherwise, // if in save mode, open default save location from configure > save > location // if in save as mode, open last save as files location // failsafe for either option is default save location from configure > save > location SpectacleConfig *cfgManager = SpectacleConfig::instance(); ExportManager *exportManager = ExportManager::instance(); QUrl location; switch(cfgManager->lastUsedSaveMode()) { case SaveMode::Save: location = cfgManager->lastSaveFile(); if (!exportManager->isFileExists(location)) { - location = QUrl(cfgManager->defaultSaveLocation()); + location = cfgManager->defaultSaveLocation(); } break; case SaveMode::SaveAs: location = cfgManager->lastSaveAsFile(); // already has a "/" at the end if (!exportManager->isFileExists(location)) { location = cfgManager->lastSaveAsLocation(); } break; } KIO::highlightInFileManager({location}); } void KSMainWindow::quit(const QuitBehavior quitBehavior) { qApp->setQuitOnLastWindowClosed(false); hide(); if (quitBehavior == QuitBehavior::QuitImmediately) { // Allow some time for clipboard content to transfer // TODO: Find better solution QTimer::singleShot(250, qApp, &QApplication::quit); } // TODO for else case: // Currently it is expected that you emit forceNotify, and finally quit // via a callback through KNotification::action1Activated. However, that // is not working quite right, see Bug #389694 which needs fixing. } void KSMainWindow::showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration, const QList& actions) { for (QAction* action: mMessageWidget->actions()) { mMessageWidget->removeAction(action); } for (QAction* action : actions) { mMessageWidget->addAction(action); } mMessageWidget->setText(message); mMessageWidget->setMessageType(messageType); switch (messageType) { case KMessageWidget::Error: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); break; case KMessageWidget::Warning: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); break; case KMessageWidget::Positive: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); break; case KMessageWidget::Information: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); break; } mHideMessageWidgetTimer->stop(); mMessageWidget->animatedShow(); if (messageDuration == MessageDuration::AutoHide) { mHideMessageWidgetTimer->start(); } } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error) { showInlineMessage(i18n("There was a problem sharing the image: %1", message), KMessageWidget::Error); } else { if (message.isEmpty()) { showInlineMessage(i18n("Image shared"), KMessageWidget::Positive); } else { showInlineMessage(i18n("The shared image link (%1) has been copied to the clipboard.", message), KMessageWidget::Positive, MessageDuration::Persistent); QApplication::clipboard()->setText(message); } } } void KSMainWindow::sendToClipboard() { bool notify = false; ExportManager::instance()->doCopyToClipboard(notify); SpectacleConfig::instance()->quitAfterSaveOrCopyChecked() ? quit() : showInlineMessage(i18n("The screenshot has been copied to the clipboard."), KMessageWidget::Information); } void KSMainWindow::showPreferencesDialog() { SettingsDialog prefDialog(this); prefDialog.exec(); } void KSMainWindow::imageSaved(const QUrl &location) { setWindowTitle(location.fileName()); setWindowModified(false); QAction* openContaining = new QAction(i18n("Open Containing Folder"), mMessageWidget); connect(openContaining, &QAction::triggered, [=] { KIO::highlightInFileManager({location});}); showInlineMessage(i18n("The screenshot was saved as %2", location.toLocalFile(), location.fileName()), KMessageWidget::Positive, MessageDuration::AutoHide, {openContaining}); } void KSMainWindow::save() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::Save); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); ExportManager::instance()->doSave(QUrl(), /* notify */ quitChecked); if (quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::saveAs() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::SaveAs); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); if (ExportManager::instance()->doSaveAs(this, /* notify */ quitChecked) && quitChecked) { quit(QuitBehavior::QuitExternally); } } diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp index f014f5c..830d51e 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -1,223 +1,223 @@ /* * 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 "SaveOptionsPage.h" #include "SpectacleCommon.h" #include "SpectacleConfig.h" #include "ExportManager.h" #include #include #include #include #include #include #include #include #include #include SaveOptionsPage::SaveOptionsPage(QWidget *parent) : SettingsPage(parent) { QFormLayout *mainLayout = new QFormLayout; setLayout(mainLayout); // Save location mUrlRequester = new KUrlRequester; mUrlRequester->setMode(KFile::Directory); connect(mUrlRequester, &KUrlRequester::textChanged, this, &SaveOptionsPage::markDirty); mainLayout->addRow(i18n("Save Location:"), mUrlRequester); // copy file location to clipboard after saving mCopyPathToClipboard = new QCheckBox(i18n("Copy file location to clipboard after saving"), this); connect(mCopyPathToClipboard, &QCheckBox::toggled, this, &SaveOptionsPage::markDirty); mainLayout->addRow(QString(), mCopyPathToClipboard); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // Compression quality slider and current value display QHBoxLayout *sliderHorizLayout = new QHBoxLayout(); QVBoxLayout *sliderVertLayout = new QVBoxLayout(); // Current value QLabel *qualityValue = new QLabel(); qualityValue->setNum(SpectacleConfig::instance()->compressionQuality()); qualityValue->setMinimumWidth(qualityValue->fontInfo().pointSize()*3); // Slider mQualitySlider = new QSlider(Qt::Horizontal); mQualitySlider->setRange(0, 100); mQualitySlider->setTickInterval(5); mQualitySlider->setSliderPosition(SpectacleConfig::instance()->compressionQuality()); mQualitySlider->setTickPosition(QSlider::TicksBelow); mQualitySlider->setTracking(true); connect(mQualitySlider, &QSlider::valueChanged, [=](int value) { qualityValue->setNum(value); markDirty(); }); sliderHorizLayout->addWidget(mQualitySlider); sliderHorizLayout->addWidget(qualityValue); sliderVertLayout->addLayout(sliderHorizLayout); QLabel *qualitySliderDescription = new QLabel(); qualitySliderDescription->setText(i18n("Choose the image quality when saving with lossy image formats like JPEG")); sliderVertLayout->addWidget(qualitySliderDescription); mainLayout->addRow(i18n("Compression Quality:"), sliderVertLayout); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // filename chooser text field QHBoxLayout *saveFieldLayout = new QHBoxLayout; mSaveNameFormat = new QLineEdit; connect(mSaveNameFormat, &QLineEdit::textEdited, this, &SaveOptionsPage::markDirty); connect(mSaveNameFormat, &QLineEdit::textEdited, [&](const QString &newText) { QString fmt; Q_FOREACH(auto item, QImageWriter::supportedImageFormats()) { fmt = QString::fromLocal8Bit(item); if (newText.endsWith(QLatin1Char('.') + fmt, Qt::CaseInsensitive)) { QString txtCopy = newText; txtCopy.chop(fmt.length() + 1); mSaveNameFormat->setText(txtCopy); mSaveImageFormat->setCurrentIndex(mSaveImageFormat->findText(fmt.toUpper())); } } }); connect(mSaveNameFormat, &QLineEdit::textChanged,this, &SaveOptionsPage::updateFilenamePreview); mSaveNameFormat->setPlaceholderText(QStringLiteral("%d")); saveFieldLayout->addWidget(mSaveNameFormat); mSaveImageFormat = new QComboBox; mSaveImageFormat->addItems([&](){ QStringList items; Q_FOREACH(auto fmt, QImageWriter::supportedImageFormats()) { items.append(QString::fromLocal8Bit(fmt).toUpper()); } return items; }()); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::markDirty); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::updateFilenamePreview); saveFieldLayout->addWidget(mSaveImageFormat); mainLayout->addRow(i18n("Filename:"), saveFieldLayout); mPreviewLabel = new QLabel(this); mainLayout->addRow(i18nc("Preview of the user configured filename", "Preview:"), mPreviewLabel); // now the save filename format layout QString helpText = i18n( "You can use the following placeholders in the filename, which will be replaced " "with actual text when the file is saved:
" ); for (auto option = ExportManager::filenamePlaceholders.cbegin(); option != ExportManager::filenamePlaceholders.cend(); ++option) { helpText += QStringLiteral("%1: %2
").arg(option.key(), option.value().toString()); } helpText += QStringLiteral("/: ") + i18n("To save to a sub-folder"); helpText += QStringLiteral("
"); QLabel *fmtHelpText = new QLabel(helpText, this); fmtHelpText->setWordWrap(true); fmtHelpText->setTextFormat(Qt::RichText); fmtHelpText->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); connect(fmtHelpText, &QLabel::linkActivated, [this](const QString& placeholder) { mSaveNameFormat->insert(placeholder); }); mainLayout->addWidget(fmtHelpText); // read in the data resetChanges(); } void SaveOptionsPage::markDirty() { mChangesMade = true; } void SaveOptionsPage::saveChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // save the data - cfgManager->setDefaultSaveLocation(mUrlRequester->url().toDisplayString(QUrl::PreferLocalFile)); + cfgManager->setDefaultSaveLocation(mUrlRequester->url()); cfgManager->setAutoSaveFilenameFormat(mSaveNameFormat->text()); cfgManager->setSaveImageFormat(mSaveImageFormat->currentText().toLower()); cfgManager->setCopySaveLocationToClipboard(mCopyPathToClipboard->checkState() == Qt::Checked); cfgManager->setCompressionQuality(mQualitySlider->value()); // done mChangesMade = false; } void SaveOptionsPage::resetChanges() { // bring up the configuration reader SpectacleConfig *cfgManager = SpectacleConfig::instance(); // read in the data mSaveNameFormat->setText(cfgManager->autoSaveFilenameFormat()); - mUrlRequester->setUrl(QUrl::fromUserInput(cfgManager->defaultSaveLocation())); + mUrlRequester->setUrl(cfgManager->defaultSaveLocation()); mCopyPathToClipboard->setChecked(cfgManager->copySaveLocationToClipboard()); mQualitySlider->setSliderPosition(cfgManager->compressionQuality()); // read in the save image format and calculate its index { int index = mSaveImageFormat->findText(cfgManager->saveImageFormat().toUpper()); if (index >= 0) { mSaveImageFormat->setCurrentIndex(index); } } // done mChangesMade = false; } void SaveOptionsPage::updateFilenamePreview() { auto lExportManager = ExportManager::instance(); lExportManager->setWindowTitle(QStringLiteral("Spectacle")); Spectacle::CaptureMode lOldMode = lExportManager->captureMode(); // If the grabMode is not one of those below we need to change it to have the placeholder // replaced by the window title bool lSwitchGrabMode = !(lOldMode == Spectacle::CaptureMode::ActiveWindow || lOldMode == Spectacle::CaptureMode::TransientWithParent || lOldMode == Spectacle::CaptureMode::WindowUnderCursor); if (lSwitchGrabMode) { lExportManager->setCaptureMode(Spectacle::CaptureMode::ActiveWindow); } const QString lFileName = lExportManager->formatFilename(mSaveNameFormat->text()); mPreviewLabel->setText(xi18nc("@info", "%1.%2", lFileName, mSaveImageFormat->currentText().toLower())); if (lSwitchGrabMode) { lExportManager->setCaptureMode(lOldMode); } } diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp index f8f276f..c3da1af 100644 --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -1,363 +1,365 @@ /* * 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 "SpectacleConfig.h" #include SpectacleConfig::SpectacleConfig(QObject *parent) : QObject(parent) { mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); mGeneralConfig = KConfigGroup(mConfig, "General"); mGuiConfig = KConfigGroup(mConfig, "GuiConfig"); } SpectacleConfig::~SpectacleConfig() {} SpectacleConfig* SpectacleConfig::instance() { static SpectacleConfig instance; return &instance; } QString SpectacleConfig::defaultFilename() const { return QStringLiteral("Screenshot"); } QString SpectacleConfig::defaultTimestampTemplate() const { // includes separator at the front return QStringLiteral("_%Y%M%D_%H%m%S"); } // lastSaveAsLocation QUrl SpectacleConfig::lastSaveAsFile() const { - return mGeneralConfig.readEntry(QStringLiteral("lastSaveAsFile"), - QUrl(this->defaultSaveLocation())); + return mGeneralConfig.readEntry(QStringLiteral("lastSaveAsFile"), this->defaultSaveLocation()); } void SpectacleConfig::setLastSaveAsFile(const QUrl &location) { mGeneralConfig.writeEntry(QStringLiteral("lastSaveAsFile"), location); mGeneralConfig.sync(); } QUrl SpectacleConfig::lastSaveAsLocation() const { return this->lastSaveAsFile().adjusted(QUrl::RemoveFilename); } // lastSaveLocation QUrl SpectacleConfig::lastSaveFile() const { - return mGeneralConfig.readEntry(QStringLiteral("lastSaveFile"), - QUrl(this->defaultSaveLocation())); + return mGeneralConfig.readEntry(QStringLiteral("lastSaveFile"), this->defaultSaveLocation()); } void SpectacleConfig::setLastSaveFile(const QUrl &location) { mGeneralConfig.writeEntry(QStringLiteral("lastSaveFile"), location); mGeneralConfig.sync(); } QUrl SpectacleConfig::lastSaveLocation() const { return this->lastSaveFile().adjusted(QUrl::RemoveFilename); } // cropRegion QRect SpectacleConfig::cropRegion() const { return mGuiConfig.readEntry(QStringLiteral("cropRegion"), QRect()); } void SpectacleConfig::setCropRegion(const QRect ®ion) { mGuiConfig.writeEntry(QStringLiteral("cropRegion"), region); mGuiConfig.sync(); } // onclick bool SpectacleConfig::onClickChecked() const { return mGuiConfig.readEntry(QStringLiteral("onClickChecked"), false); } void SpectacleConfig::setOnClickChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("onClickChecked"), enabled); mGuiConfig.sync(); } // include pointer bool SpectacleConfig::includePointerChecked() const { return mGuiConfig.readEntry(QStringLiteral("includePointer"), true); } void SpectacleConfig::setIncludePointerChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includePointer"), enabled); mGuiConfig.sync(); } // include decorations bool SpectacleConfig::includeDecorationsChecked() const { return mGuiConfig.readEntry(QStringLiteral("includeDecorations"), true); } void SpectacleConfig::setIncludeDecorationsChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("includeDecorations"), enabled); mGuiConfig.sync(); } // capture transient window only bool SpectacleConfig::captureTransientWindowOnlyChecked() const { return mGuiConfig.readEntry(QStringLiteral("transientOnly"), false); } void SpectacleConfig::setCaptureTransientWindowOnlyChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("transientOnly"), enabled); mGuiConfig.sync(); } // quit after saving, copying, or exporting the image bool SpectacleConfig::quitAfterSaveOrCopyChecked() const { return mGuiConfig.readEntry(QStringLiteral("quitAfterSaveCopyExport"), false); } void SpectacleConfig::setQuitAfterSaveOrCopyChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("quitAfterSaveCopyExport"), enabled); mGuiConfig.sync(); } // show magnifier bool SpectacleConfig::showMagnifierChecked() const { return mGuiConfig.readEntry(QStringLiteral("showMagnifier"), false); } void SpectacleConfig::setShowMagnifierChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("showMagnifier"), enabled); mGuiConfig.sync(); } // release mouse-button to capture bool SpectacleConfig::useReleaseToCapture() const { return mGuiConfig.readEntry(QStringLiteral("useReleaseToCapture"), false); } void SpectacleConfig::setUseReleaseToCaptureChecked(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("useReleaseToCapture"), enabled); mGuiConfig.sync(); } // capture delay qreal SpectacleConfig::captureDelay() const { return mGuiConfig.readEntry(QStringLiteral("captureDelay"), 0.0); } void SpectacleConfig::setCaptureDelay(qreal delay) { mGuiConfig.writeEntry(QStringLiteral("captureDelay"), delay); mGuiConfig.sync(); } // capture mode int SpectacleConfig::captureMode() const { return std::max(0, mGuiConfig.readEntry(QStringLiteral("captureModeIndex"), 0)); } void SpectacleConfig::setCaptureMode(int index) { mGuiConfig.writeEntry(QStringLiteral("captureModeIndex"), index); mGuiConfig.sync(); } // remember last rectangular region bool SpectacleConfig::rememberLastRectangularRegion() const { return mGuiConfig.readEntry(QStringLiteral("rememberLastRectangularRegion"), true); } void SpectacleConfig::setRememberLastRectangularRegion(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("rememberLastRectangularRegion"), enabled); mGuiConfig.sync(); } bool SpectacleConfig::alwaysRememberRegion() const { // Default Value is for compatibility reasons as the old behavior was always to remember across restarts bool useOldBehavior = mGuiConfig.readEntry(QStringLiteral("rememberLastRectangularRegion"), false); return mGuiConfig.readEntry(QStringLiteral("alwaysRememberRegion"), useOldBehavior); } void SpectacleConfig::setAlwaysRememberRegion (bool enabled) { mGuiConfig.writeEntry(QStringLiteral("alwaysRememberRegion"), enabled); mGuiConfig.sync(); } // use light region mask colour bool SpectacleConfig::useLightRegionMaskColour() const { return mGuiConfig.readEntry(QStringLiteral("useLightMaskColour"), false); } void SpectacleConfig::setUseLightRegionMaskColour(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("useLightMaskColour"), enabled); mGuiConfig.sync(); } // compression quality setting int SpectacleConfig::compressionQuality() const { return mGuiConfig.readEntry(QStringLiteral("compressionQuality"), 90); } void SpectacleConfig::setCompressionQuality(int value) { mGuiConfig.writeEntry(QStringLiteral("compressionQuality"), value); mGuiConfig.sync(); } // last used save mode SaveMode SpectacleConfig::lastUsedSaveMode() const { switch (mGuiConfig.readEntry(QStringLiteral("lastUsedSaveMode"), 0)) { default: case 0: return SaveMode::SaveAs; case 1: return SaveMode::Save; } } void SpectacleConfig::setLastUsedSaveMode(SaveMode mode) { mGuiConfig.writeEntry(QStringLiteral("lastUsedSaveMode"), static_cast(mode)); mGuiConfig.sync(); } // autosave filename format QString SpectacleConfig::autoSaveFilenameFormat() const { const QString sff = mGeneralConfig.readEntry(QStringLiteral("save-filename-format"), QString(defaultFilename() + defaultTimestampTemplate())); return sff.isEmpty() ? QStringLiteral("%d") : sff; } void SpectacleConfig::setAutoSaveFilenameFormat(const QString &format) { mGeneralConfig.writeEntry(QStringLiteral("save-filename-format"), format); mGeneralConfig.sync(); } // autosave location -QString SpectacleConfig::defaultSaveLocation() const +QUrl SpectacleConfig::defaultSaveLocation() const { - return mGeneralConfig.readPathEntry(QStringLiteral("default-save-location"), - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); + QString path = mGeneralConfig.readPathEntry(QStringLiteral("default-save-location"), + (QStandardPaths::writableLocation(QStandardPaths::PicturesLocation))); + if (! path.endsWith(QLatin1Char('/'))) { + path += QLatin1Char('/'); + } + return QUrl::fromUserInput(path); } -void SpectacleConfig::setDefaultSaveLocation(const QString &location) +void SpectacleConfig::setDefaultSaveLocation(const QUrl &location) { - mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location); + mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location.toString()); mGeneralConfig.sync(); } // copy file location to clipboard after saving bool SpectacleConfig::copySaveLocationToClipboard() const { return mGeneralConfig.readEntry(QStringLiteral("copySaveLocation"), false); } void SpectacleConfig::setCopySaveLocationToClipboard(bool enabled) { mGeneralConfig.writeEntry(QStringLiteral("copySaveLocation"), enabled); mGeneralConfig.sync(); } // autosave image format QString SpectacleConfig::saveImageFormat() const { return mGeneralConfig.readEntry(QStringLiteral("default-save-image-format"), QStringLiteral("png")); } void SpectacleConfig::setSaveImageFormat(const QString &saveFmt) { mGeneralConfig.writeEntry(QStringLiteral("default-save-image-format"), saveFmt); mGeneralConfig.sync(); } SpectacleConfig::PrintKeyActionRunning SpectacleConfig::printKeyActionRunning() const { mConfig->reparseConfiguration(); int newScreenshotAction = static_cast(SpectacleConfig::PrintKeyActionRunning::TakeNewScreenshot); int readValue = mGuiConfig.readEntry(QStringLiteral("printKeyActionRunning"), newScreenshotAction); if ((KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE"), "wayland") == 0 ) && readValue == SpectacleConfig::PrintKeyActionRunning::FocusWindow) { return SpectacleConfig::PrintKeyActionRunning::TakeNewScreenshot; } return static_cast(readValue); } void SpectacleConfig::setPrintKeyActionRunning (SpectacleConfig::PrintKeyActionRunning action) { mGuiConfig.writeEntry(QStringLiteral("printKeyActionRunning"), static_cast(action)); mGuiConfig.sync(); } diff --git a/src/SpectacleConfig.h b/src/SpectacleConfig.h index 8578d22..b18b25d 100644 --- a/src/SpectacleConfig.h +++ b/src/SpectacleConfig.h @@ -1,142 +1,142 @@ /* * 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. */ #ifndef SPECTACLECONFIG_H #define SPECTACLECONFIG_H #include #include #include #include #include enum class SaveMode { SaveAs, Save }; class SpectacleConfig : public QObject { Q_OBJECT // singleton-ize the class public: static SpectacleConfig* instance(); QString defaultFilename() const; QString defaultTimestampTemplate() const; QUrl lastSaveAsLocation() const; QUrl lastSaveLocation() const; enum PrintKeyActionRunning : int { TakeNewScreenshot = 0, StartNewInstance, FocusWindow }; private: explicit SpectacleConfig(QObject *parent = nullptr); virtual ~SpectacleConfig(); SpectacleConfig(SpectacleConfig const&) = delete; void operator= (SpectacleConfig const&) = delete; // everything else public Q_SLOTS: QUrl lastSaveAsFile() const; void setLastSaveAsFile(const QUrl &location); QUrl lastSaveFile() const; void setLastSaveFile(const QUrl &location); QRect cropRegion() const; void setCropRegion(const QRect ®ion); bool onClickChecked() const; void setOnClickChecked(bool enabled); bool includePointerChecked() const; void setIncludePointerChecked(bool enabled); bool includeDecorationsChecked() const; void setIncludeDecorationsChecked(bool enabled); bool captureTransientWindowOnlyChecked() const; void setCaptureTransientWindowOnlyChecked(bool enabled); bool quitAfterSaveOrCopyChecked() const; void setQuitAfterSaveOrCopyChecked(bool enabled); bool showMagnifierChecked() const; void setShowMagnifierChecked(bool enabled); bool useReleaseToCapture() const; void setUseReleaseToCaptureChecked(bool enabled); qreal captureDelay() const; void setCaptureDelay(qreal delay); int captureMode() const; void setCaptureMode(int index); bool rememberLastRectangularRegion() const; void setRememberLastRectangularRegion(bool enabled); bool alwaysRememberRegion() const; void setAlwaysRememberRegion(bool enabled); bool useLightRegionMaskColour() const; void setUseLightRegionMaskColour(bool enabled); int compressionQuality() const; void setCompressionQuality(int value); SaveMode lastUsedSaveMode() const; void setLastUsedSaveMode(SaveMode mode); QString autoSaveFilenameFormat() const; void setAutoSaveFilenameFormat(const QString &format); - QString defaultSaveLocation() const; - void setDefaultSaveLocation(const QString &location); + QUrl defaultSaveLocation() const; + void setDefaultSaveLocation(const QUrl &location); bool copySaveLocationToClipboard() const; void setCopySaveLocationToClipboard(bool enabled); QString saveImageFormat() const; void setSaveImageFormat(const QString &saveFmt); PrintKeyActionRunning printKeyActionRunning() const; void setPrintKeyActionRunning (PrintKeyActionRunning action); private: KSharedConfigPtr mConfig; KConfigGroup mGeneralConfig; KConfigGroup mGuiConfig; }; #endif // SPECTACLECONFIG_H