diff --git a/desktop/CMakeLists.txt b/desktop/CMakeLists.txt index 434052c..dbca09f 100644 --- a/desktop/CMakeLists.txt +++ b/desktop/CMakeLists.txt @@ -1,32 +1,36 @@ # install the .desktop and rc files in the correct place install( PROGRAMS org.kde.spectacle.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( DIRECTORY DESTINATION "${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel" ) install( CODE "execute_process(COMMAND \"${CMAKE_COMMAND}\" -E create_symlink \"${KDE_INSTALL_FULL_APPDIR}/org.kde.spectacle.desktop\" \"\$ENV{DESTDIR}${KDE_INSTALL_FULL_DATAROOTDIR}/kglobalaccel/org.kde.spectacle.desktop\")" ) install( FILES spectacle.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} ) install( FILES org.kde.spectacle.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) install( FILES spectacle_shortcuts.upd DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR} ) +install( + FILES spectacle_newConfig.upd + DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR} +) add_executable(spectacle-migrate-shortcuts MigrateShortcuts.cpp) target_link_libraries(spectacle-migrate-shortcuts Qt5::DBus KF5::GlobalAccel KF5::ConfigCore KF5::XmlGui KF5::I18n) install( TARGETS spectacle-migrate-shortcuts DESTINATION ${KDE_INSTALL_LIBDIR}/kconf_update_bin ) diff --git a/desktop/spectacle_newConfig.upd b/desktop/spectacle_newConfig.upd new file mode 100644 index 0000000..f843010 --- /dev/null +++ b/desktop/spectacle_newConfig.upd @@ -0,0 +1,23 @@ +Version=5 +Id=spectacle-new-config +File=spectaclerc +Group=GuiConfig,General +Key=showMagnifier +Key=useReleaseToCapture +Key=useLightMaskColour +Key=rememberLastRectangularRegion +Key=alwaysRememberRegion +Key=printKeyActionRunning +Group=GuiConfig,Save +Key=compressionQuality +Key=lastUsedSaveMode +Group=General,Save +Key=copySaveLocation +Key=lastSaveAsFile,lastSaveAsLocation +Key=lastSaveFile,lastSaveLocation +Key=save-filename-format,saveFilenameFormat +Key=default-save-location,defaultSaveLocation +Key=default-save-image-format,defaultSaveImageFormat +Group=GuiConfig +Key=captureModeIndex,captureMode + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ff1b30..6c6ee97 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,115 +1,116 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) set(CMAKE_AUTORCC 1) # target if(XCB_FOUND) set( SPECTACLE_SRCS_PLATFORM_XCB Platforms/PlatformXcb.cpp ) endif() set( SPECTACLE_SRCS_PLATFORM Platforms/PlatformLoader.cpp Platforms/Platform.cpp Platforms/PlatformNull.cpp Platforms/PlatformKWinWayland.cpp ${SPECTACLE_SRCS_PLATFORM_XCB} ) set( SPECTACLE_SRCS_DEFAULT Main.cpp ExportManager.cpp SpectacleCore.cpp - SpectacleConfig.cpp SpectacleDBusAdapter.cpp + ShortcutActions.cpp ${SPECTACLE_SRCS_PLATFORM} Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/ExportMenu.cpp Gui/ProgressButton.cpp Gui/SmartSpinBox.cpp - Gui/SettingsDialog/SettingsDialog.cpp - Gui/SettingsDialog/SettingsPage.cpp Gui/SettingsDialog/SaveOptionsPage.cpp + Gui/SettingsDialog/SettingsDialog.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp Gui/SettingsDialog/ShortcutsOptionsPage.cpp QuickEditor/QuickEditor.cpp ) +kconfig_add_kcfg_files(SPECTACLE_SRCS_DEFAULT Gui/SettingsDialog/settings.kcfgc) + ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_core_debug.h IDENTIFIER SPECTACLE_CORE_LOG CATEGORY_NAME org.kde.spectacle.core) ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_gui_debug.h IDENTIFIER SPECTACLE_GUI_LOG CATEGORY_NAME org.kde.spectacle.gui) if(KIPI_FOUND) set( SPECTACLE_SRCS_KIPI KipiInterface/KSGKipiInterface.cpp KipiInterface/KSGKipiInfoShared.cpp KipiInterface/KSGKipiImageCollectionShared.cpp KipiInterface/KSGKipiImageCollectionSelector.cpp ) endif() set( SPECTACLE_SRCS_ALL ${SPECTACLE_SRCS_DEFAULT} ${SPECTACLE_SRCS_KIPI} ) add_executable( spectacle ${SPECTACLE_SRCS_ALL} ) # link libraries target_link_libraries( spectacle Qt5::DBus Qt5::PrintSupport KF5::CoreAddons KF5::DBusAddons KF5::WidgetsAddons KF5::Notifications KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::NewStuff KF5::GlobalAccel KF5::XmlGui KF5::WaylandClient ) if(XCB_FOUND) target_link_libraries( spectacle XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras ) endif() if(KIPI_FOUND) target_link_libraries ( spectacle KF5::Kipi ) endif() if(PURPOSE_FOUND) target_link_libraries ( spectacle KF5::PurposeWidgets ) endif() install(TARGETS spectacle ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/ExportManager.cpp b/src/ExportManager.cpp index b96b9e2..a8f5363 100644 --- a/src/ExportManager.cpp +++ b/src/ExportManager.cpp @@ -1,593 +1,590 @@ /* This file is part of Spectacle, the KDE screenshot utility + * Copyright 2019 David Redondo * 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 "settings.h" + #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); - }); + connect(this, &ExportManager::imageSaved, &Settings::setLastSaveLocation); } 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(); + QString savePath = Settings::self()->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(), + Settings::self()->defaultSaveImageFormat(), &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()); + return formatFilename(Settings::self()->saveFilenameFormat()); } 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(QLatin1Char('/'), 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.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(); + result = QStringLiteral("Screenshot"); } return truncatedFilename(result); } QString ExportManager::autoIncrementFilename(const QString &baseName, const QString &extension, FileNameAlreadyUsedCheck isFileNameUsed) { QString result = truncatedFilename(baseName) + QLatin1String(".") + 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(QLatin1String("OVERFLOW-") + QString::number(QRandomGenerator::global()->bounded(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 Settings::self()->defaultSaveImageFormat(); } return type; } bool ExportManager::writeImage(QIODevice *device, const QByteArray &format) { QImageWriter imageWriter(device, format); - imageWriter.setQuality(SpectacleConfig::instance()->compressionQuality()); + imageWriter.setQuality(Settings::self()->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(QLatin1String("~/fakefile.") + filenameExtension, QMimeDatabase::MatchExtension).name(); + const QString filenameExtension = Settings::self()->defaultSaveImageFormat(); + 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.setDirectoryUrl(Settings::self()->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); - + Settings::setLastSaveAsLocation(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); + Settings::setLastSaveLocation(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); emit imageCopied(); 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 df1504a..26e1f72 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,554 +1,537 @@ /* This file is part of Spectacle, the KDE screenshot utility * Copyright 2019 David Redondo * 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 "settings.h" #include "SettingsDialog/SettingsDialog.h" +#include "SettingsDialog/GeneralOptionsPage.h" +#include "SettingsDialog/SaveOptionsPage.h" +#include "SettingsDialog/ShortcutsOptionsPage.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XCB_FOUND #include #include #endif #include #include #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 and on autosave connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::imageSaved); connect(ExportManager::instance(), &ExportManager::imageCopied, this, &KSMainWindow::imageCopied); connect(ExportManager::instance(), &ExportManager::imageSavedAndCopied, this, &KSMainWindow::imageSavedAndCopied); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help); mDialogButtonBox->button(QDialogButtonBox::Help)->setAutoDefault(false); 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")))); mToolsButton->setAutoDefault(false); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mSendToButton->setAutoDefault(false); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, &KSMainWindow::copy, 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"), + i18n("Open Default Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); mScreenRecorderToolsMenu->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, this, [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); // message: open containing folder mOpenContaining = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), mMessageWidget); - connect(mOpenContaining, &QAction::triggered, [=] { KIO::highlightInFileManager({SpectacleConfig::instance()->lastSaveFile()});}); + connect(mOpenContaining, &QAction::triggered, [=] { KIO::highlightInFileManager({Settings::lastSaveLocation()});}); mHideMessageWidgetTimer = new QTimer(this); - connect(mHideMessageWidgetTimer, &QTimer::timeout, - mMessageWidget, &KMessageWidget::animatedHide); +// 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: + switch (Settings::lastUsedSaveMode()) { + case Settings::SaveAs: mSaveButton->setDefaultAction(mSaveAsAction); mSaveButton->setText(i18n("Save As...")); break; - case SaveMode::Save: + case Settings::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) { if (theTimeout < 0) { // OnClick is checked (always the case on Wayland) hide(); emit newScreenshotRequest(theCaptureMode, theTimeout, theIncludePointer, theIncludeDecorations); return; } showMinimized(); mMessageWidget->hide(); QTimer* timer = new QTimer; timer->setSingleShot(true); timer->setInterval(theTimeout); auto unityUpdate = [](const QVariantMap &properties) { QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/org/kde/Spectacle"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update")); message.setArguments({QGuiApplication::desktopFileName(), properties}); QDBusConnection::sessionBus().send(message); }; auto delayAnimation = new QVariantAnimation(timer); delayAnimation->setStartValue(0.0); delayAnimation->setEndValue(1.0); delayAnimation->setDuration(timer->interval()); connect(delayAnimation, &QVariantAnimation::valueChanged, this, [=] { const double progress = delayAnimation->currentValue().toDouble(); const double timeoutInSeconds = theTimeout / 1000.0; mKSWidget->setProgress(progress); unityUpdate({ {QStringLiteral("progress"), progress} }); setWindowTitle(i18ncp("@title:window", "%1 second", "%1 seconds", qMin(int(timeoutInSeconds), qCeil((1 - progress) * timeoutInSeconds)))); }); connect(timer, &QTimer::timeout, this, [=] { this->hide(); timer->deleteLater(); mKSWidget->setProgress(0); unityUpdate({ {QStringLiteral("progress-visible"), false} }); emit newScreenshotRequest(theCaptureMode, 0, theIncludePointer, theIncludeDecorations); }); connect(mKSWidget, &KSWidget::screenshotCanceled, timer, [=] { timer->stop(); timer->deleteLater(); restoreWindowTitle(); unityUpdate({ {QStringLiteral("progress-visible"), false} }); }); unityUpdate({ {QStringLiteral("progress-visible"), true}, {QStringLiteral("progress"), 0 } }); timer->start(); delayAnimation->start(); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { if (!pixmap.isNull()) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); } else { restoreWindowTitle(); } mKSWidget->setButtonState(KSWidget::State::TakeNewScreenshot); show(); activateWindow(); /* NOTE windowWidth only produces the right result if it is called after the window is visible. * Because of this the call is not moved into the if above */ if(!pixmap.isNull()) { 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 = 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}); + new KRun(Settings::defaultSaveLocation(), this); } 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); } } void KSMainWindow::showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration, const QList& actions) { const auto messageWidgetActions = mMessageWidget->actions(); for (QAction* action: messageWidgetActions) { 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 == 1) { // error == 1 means the user cancelled the sharing return; } 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::copy() { - const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); + const bool quitChecked = Settings::quitAfterSaveCopyExport(); ExportManager::instance()->doCopyToClipboard(); if (quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::imageCopied() { showInlineMessage(i18n("The screenshot has been copied to the clipboard."), KMessageWidget::Information); } void KSMainWindow::showPreferencesDialog() { - SettingsDialog prefDialog(this); - prefDialog.exec(); + if (KConfigDialog::showDialog(QStringLiteral("settings"))) { + return; + } + (new SettingsDialog(this))->show(); } void KSMainWindow::imageSaved(const QUrl &location) { setWindowTitle(location.fileName()); setWindowModified(false); showInlineMessage(i18n("The screenshot was saved as %2", location.toString(), location.fileName()), KMessageWidget::Positive, MessageDuration::AutoHide, {mOpenContaining}); } void KSMainWindow::imageSavedAndCopied(const QUrl &location) { setWindowTitle(location.fileName()); setWindowModified(false); showInlineMessage(i18n("The screenshot was copied to the clipboard and saved as %2", location.toString(), location.fileName()), KMessageWidget::Positive, MessageDuration::AutoHide, {mOpenContaining}); } void KSMainWindow::save() { - SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::Save); + Settings::setLastUsedSaveMode(Settings::Save); setDefaultSaveAction(); - const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); + const bool quitChecked = Settings::quitAfterSaveCopyExport(); ExportManager::instance()->doSave(QUrl(), /* notify */ quitChecked); if (quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::saveAs() { - SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::SaveAs); + Settings::setLastUsedSaveMode(Settings::SaveAs); setDefaultSaveAction(); - const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); + const bool quitChecked = Settings::quitAfterSaveCopyExport(); if (ExportManager::instance()->doSaveAs(this, /* notify */ quitChecked) && quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::restoreWindowTitle() { if (isWindowModified()) { setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); } else { - setWindowTitle(SpectacleConfig::instance()->lastSaveFile().fileName()); + setWindowTitle(Settings::lastSaveLocation().fileName()); } } /* This event handler enables all Buttons to be activated with Enter. Normally only QPushButton can * be activated with Enter but we also use QToolButtons so we handle the event ourselves */ void KSMainWindow::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return) { QWidget *fw = focusWidget(); auto pb = qobject_cast(fw); if (pb) { pb->animateClick(); return; } auto tb = qobject_cast(fw); if (tb) { tb->animateClick(); return; } } QDialog::keyPressEvent(event); } diff --git a/src/Gui/KSMainWindow.h b/src/Gui/KSMainWindow.h index 4f3cdc0..3d0ff51 100644 --- a/src/Gui/KSMainWindow.h +++ b/src/Gui/KSMainWindow.h @@ -1,121 +1,121 @@ /* 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 */ #pragma once #include #include #include #include #include #include #include #include "SpectacleCommon.h" -#include "SpectacleConfig.h" + #include "KSWidget.h" #include "ExportMenu.h" #include "Platforms/Platform.h" #include class KSMainWindow: public QDialog { Q_OBJECT public: explicit KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent = nullptr); virtual ~KSMainWindow() = default; enum class MessageDuration { AutoHide, Persistent }; private: enum class QuitBehavior { QuitImmediately, QuitExternally }; void quit(const QuitBehavior quitBehavior = QuitBehavior::QuitImmediately); void showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration = MessageDuration::AutoHide, const QList& actions = {}); private Q_SLOTS: void captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void showPrintDialog(); void openScreenshotsFolder(); void showPreferencesDialog(); void showImageSharedFeedback(bool error, const QString &message); void imageCopied(); void init(); void setDefaultSaveAction(); void save(); void saveAs(); int windowWidth(const QPixmap &pixmap) const; void restoreWindowTitle(); public Q_SLOTS: void setScreenshotAndShow(const QPixmap &pixmap); void imageSaved(const QUrl &location); void imageSavedAndCopied(const QUrl &location); Q_SIGNALS: void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations); void dragAndDropRequest(); protected: void moveEvent(QMoveEvent *event) override; private: void keyPressEvent(QKeyEvent *event) override; void copy(); KSWidget *mKSWidget; QFrame *mDivider; QDialogButtonBox *mDialogButtonBox; QToolButton *mConfigureButton; QPushButton *mToolsButton; QPushButton *mSendToButton; QToolButton *mClipboardButton; QToolButton *mSaveButton; QMenu *mSaveMenu; QAction *mSaveAsAction; QAction *mSaveAction; QAction *mOpenContaining; KMessageWidget *mMessageWidget; QMenu *mToolsMenu; QMenu *mScreenRecorderToolsMenu; std::unique_ptr mScreenrecorderToolsMenuFactory; ExportMenu *mExportMenu; Platform::ShutterModes mShutterModes; QTimer *mHideMessageWidgetTimer; }; diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index 257fcfc..f0c4985 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,287 +1,287 @@ /* + * Copyright 2019 David Redondo * 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 "KSWidget.h" #include "spectacle_gui_debug.h" #include "KSImageWidget.h" +#include "settings.h" #include "SmartSpinBox.h" -#include "SpectacleConfig.h" #include "ProgressButton.h" #include #include #include #include #include #include #include #include #include #include KSWidget::KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent) : QWidget(parent) { - // get a handle to the configuration manager - SpectacleConfig *lConfigMgr = SpectacleConfig::instance(); - // we'll init the widget that holds the image first mImageWidget = new KSImageWidget(this); mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated); // the capture mode options first mCaptureModeLabel = new QLabel(i18n("Capture Mode"), this); mCaptureArea = new QComboBox(this); QString lFullScreenLabel = QApplication::screens().count() == 1 ? i18n("Full Screen") : i18n("Full Screen (All Monitors)"); - if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) - mCaptureArea->insertItem(1, lFullScreenLabel, Spectacle::CaptureMode::AllScreens); - if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) + if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) { + mCaptureArea->insertItem(0, lFullScreenLabel, Spectacle::CaptureMode::AllScreens); + mCaptureArea->insertItem(1, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion); + } + if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) { mCaptureArea->insertItem(2, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen); - if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) + } + if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) { mCaptureArea->insertItem(3, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow); - if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) + } + if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) { mCaptureArea->insertItem(4, i18n("Window Under Cursor"), Spectacle::CaptureMode::WindowUnderCursor); + } if (theGrabModes.testFlag(Platform::GrabMode::TransientWithParent)) { mTransientWithParentAvailable = true; } - mCaptureArea->insertItem(5, i18n("Rectangular Region"), Spectacle::CaptureMode::RectangularRegion); mCaptureArea->setMinimumWidth(240); + int index = mCaptureArea->findData(Settings::captureMode()); + mCaptureArea->setCurrentIndex(index >= 0 ? index : 0); connect(mCaptureArea, qOverload(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); mDelayMsec->setDecimals(1); mDelayMsec->setSingleStep(1.0); mDelayMsec->setMinimum(0.0); mDelayMsec->setMaximum(999.9); mDelayMsec->setSpecialValueText(i18n("No Delay")); mDelayMsec->setMinimumWidth(160); - connect(mDelayMsec, qOverload(&SmartSpinBox::valueChanged), lConfigMgr, &SpectacleConfig::setCaptureDelay); + connect(mDelayMsec, qOverload(&SmartSpinBox::valueChanged), &Settings::setCaptureDelay); mCaptureOnClick = new QCheckBox(i18n("On Click"), this); mCaptureOnClick->setToolTip(i18n("Wait for a mouse click before capturing the screenshot image")); connect(mCaptureOnClick, &QCheckBox::stateChanged, this, &KSWidget::onClickStateChanged); - connect(mCaptureOnClick, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setOnClickChecked); + connect(mCaptureOnClick, &QCheckBox::clicked, &Settings::setOnClickChecked); mDelayLayout = new QHBoxLayout; mDelayLayout->addWidget(mDelayMsec); mDelayLayout->addWidget(mCaptureOnClick); mCaptureModeForm = new QFormLayout; mCaptureModeForm->addRow(i18n("Area:"), mCaptureArea); mCaptureModeForm->addRow(i18n("Delay:"), mDelayLayout); mCaptureModeForm->setContentsMargins(24, 0, 0, 0); // options (mouse pointer, window decorations, quit after saving or copying) mContentOptionsLabel = new QLabel(this); mContentOptionsLabel->setText(i18n("Options")); mMousePointer = new QCheckBox(i18n("Include mouse pointer"), this); mMousePointer->setToolTip(i18n("Show the mouse cursor in the screenshot image")); - connect(mMousePointer, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setIncludePointerChecked); + connect(mMousePointer, &QCheckBox::clicked, &Settings::setIncludePointer); mWindowDecorations = new QCheckBox(i18n("Include window titlebar and borders"), this); mWindowDecorations->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border")); mWindowDecorations->setEnabled(false); - connect(mWindowDecorations, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setIncludeDecorationsChecked); + connect(mWindowDecorations, &QCheckBox::clicked, &Settings::setIncludeDecorations); mCaptureTransientOnly = new QCheckBox(i18n("Capture the current pop-up only"), this); mCaptureTransientOnly->setToolTip(i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n" "If disabled, the pop-up is captured along with the parent window")); mCaptureTransientOnly->setEnabled(false); - connect(mCaptureTransientOnly, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setCaptureTransientWindowOnlyChecked); + connect(mCaptureTransientOnly, &QCheckBox::clicked, &Settings::setTransientOnly); mQuitAfterSaveOrCopy = new QCheckBox(i18n("Quit after manual Save or Copy"), this); mQuitAfterSaveOrCopy->setToolTip(i18n("Quit Spectacle after manually saving or copying the image")); - connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setQuitAfterSaveOrCopyChecked); + connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, &Settings::setQuitAfterSaveCopyExport); mContentOptionsForm = new QVBoxLayout; mContentOptionsForm->addWidget(mMousePointer); mContentOptionsForm->addWidget(mWindowDecorations); mContentOptionsForm->addWidget(mCaptureTransientOnly); mContentOptionsForm->addWidget(mQuitAfterSaveOrCopy); mContentOptionsForm->setContentsMargins(24, 0, 0, 0); mTakeNewScreenshotAction = new QAction(QIcon::fromTheme(QStringLiteral("spectacle")), i18n("Take a New Screenshot"), this); mTakeNewScreenshotAction->setShortcut(QKeySequence::New); connect(mTakeNewScreenshotAction, &QAction::triggered, this, &KSWidget::newScreenshotClicked); mCancelAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); mCancelAction->setShortcut(QKeySequence::Cancel); connect(mCancelAction, &QAction::triggered, this, [this] { emit screenshotCanceled(); setButtonState(State::TakeNewScreenshot); }); // the take a new screenshot button mTakeScreenshotButton = new ProgressButton(this); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); setButtonState(State::TakeNewScreenshot); mTakeScreenshotButton->setFocus(); // finally, finish up the layouts mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); mRightLayout->addLayout(mCaptureModeForm); mRightLayout->addStretch(1); mRightLayout->addWidget(mContentOptionsLabel); mRightLayout->addLayout(mContentOptionsForm); mRightLayout->addStretch(10); mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter); mRightLayout->setContentsMargins(10, 0, 0, 10); mMainLayout = new QGridLayout(this); mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1); mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1); mMainLayout->setColumnMinimumWidth(0, 320); mMainLayout->setColumnMinimumWidth(1, 320); // and read in the saved checkbox states and capture mode indices - mMousePointer->setChecked (lConfigMgr->includePointerChecked()); - mWindowDecorations->setChecked (lConfigMgr->includeDecorationsChecked()); - mCaptureOnClick->setChecked (lConfigMgr->onClickChecked()); - mCaptureTransientOnly->setChecked (lConfigMgr->captureTransientWindowOnlyChecked()); - mQuitAfterSaveOrCopy->setChecked (lConfigMgr->quitAfterSaveOrCopyChecked()); - if (lConfigMgr->captureMode() >= 0) { - mCaptureArea->setCurrentIndex (lConfigMgr->captureMode()); - } - mDelayMsec->setValue (lConfigMgr->captureDelay()); + mMousePointer->setChecked(Settings::includePointer()); + mWindowDecorations->setChecked(Settings::includeDecorations()); + mCaptureOnClick->setChecked(Settings::onClickChecked()); + mCaptureTransientOnly->setChecked(Settings::transientOnly()); + mQuitAfterSaveOrCopy->setChecked(Settings::quitAfterSaveCopyExport()); + mDelayMsec->setValue(Settings::captureDelay()); } int KSWidget::imagePaddingWidth() const { int lRightLayoutLeft = 0; int lRightLayoutRight = 0; int lMainLayoutRight = 0; mRightLayout->getContentsMargins(&lRightLayoutLeft, nullptr, &lRightLayoutRight, nullptr); mMainLayout->getContentsMargins(nullptr, nullptr, &lMainLayoutRight, nullptr); int lPaddingWidth = (lRightLayoutLeft + lRightLayoutRight + lMainLayoutRight); lPaddingWidth += mRightLayout->contentsRect().width(); lPaddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow return lPaddingWidth; } // public slots void KSWidget::setScreenshotPixmap(const QPixmap &thePixmap) { mImageWidget->setScreenshot(thePixmap); } void KSWidget::lockOnClickEnabled() { mCaptureOnClick->setCheckState(Qt::Checked); mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(false); } void KSWidget::lockOnClickDisabled() { mCaptureOnClick->setCheckState(Qt::Unchecked); mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); auto lMode = static_cast(mCaptureArea->currentData().toInt()); if (mTransientWithParentAvailable && lMode == Spectacle::CaptureMode::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { lMode = Spectacle::CaptureMode::TransientWithParent; } setButtonState(State::Cancel); emit newScreenshotRequest(lMode, lDelay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } void KSWidget::onClickStateChanged(int theState) { if (theState == Qt::Checked) { mDelayMsec->setEnabled(false); } else if (theState == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } void KSWidget::captureModeChanged(int theIndex) { - SpectacleConfig::instance()->setCaptureMode(theIndex); - + Spectacle::CaptureMode captureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); + Settings::setCaptureMode(captureMode); - Spectacle::CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); - switch(lCaptureMode) { + switch(captureMode) { case Spectacle::CaptureMode::WindowUnderCursor: mWindowDecorations->setEnabled(true); if (mTransientWithParentAvailable) { mCaptureTransientOnly->setEnabled(true); } else { mCaptureTransientOnly->setEnabled(false); } break; case Spectacle::CaptureMode::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; case Spectacle::CaptureMode::AllScreens: case Spectacle::CaptureMode::CurrentScreen: case Spectacle::CaptureMode::RectangularRegion: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); break; case Spectacle::CaptureMode::TransientWithParent: case Spectacle::CaptureMode::InvalidChoice: default: qCWarning(SPECTACLE_GUI_LOG) << "Skipping invalid or unreachable enum value"; break; } } void KSWidget::setButtonState(State state) { switch (state) { case State::TakeNewScreenshot: mTakeScreenshotButton->removeAction(mCancelAction); mTakeScreenshotButton->setDefaultAction(mTakeNewScreenshotAction); mTakeScreenshotButton->setProgress(0); break; case State::Cancel: mTakeScreenshotButton->removeAction(mTakeNewScreenshotAction); mTakeScreenshotButton->setDefaultAction(mCancelAction); break; } } void KSWidget::setProgress(double progress) { mTakeScreenshotButton->setProgress(progress); } diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp index 4540367..aaa3284 100644 --- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp @@ -1,151 +1,132 @@ /* + * Copyright 2019 David Redondo * 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 "GeneralOptionsPage.h" -#include "SpectacleConfig.h" - #include #include #include #include #include +#include #include #include #include +#include GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) : - SettingsPage(parent) + QWidget{parent} { QFormLayout *mainLayout = new QFormLayout(this); setLayout(mainLayout); // When spectacle is running settings KTitleWidget* runningTitle = new KTitleWidget(this); runningTitle->setText(i18n("When Spectacle is Running")); runningTitle->setLevel(2); mainLayout->addRow(runningTitle); QRadioButton* takeNew = new QRadioButton(i18n("Take a new screenshot"), this); QRadioButton* startNewInstance = new QRadioButton(i18n("Open a new Spectacle window"), this); - mPrintKeyActionGroup = new QButtonGroup(this); - mPrintKeyActionGroup->setExclusive(true); - mPrintKeyActionGroup->addButton(takeNew, SpectacleConfig::PrintKeyActionRunning::TakeNewScreenshot); - mPrintKeyActionGroup->addButton(startNewInstance, SpectacleConfig::PrintKeyActionRunning::StartNewInstance); - connect( mPrintKeyActionGroup, qOverload(&QButtonGroup::buttonToggled), this, &GeneralOptionsPage::markDirty); + QButtonGroup* printKeyActionGroup = new QButtonGroup(this); + printKeyActionGroup->setExclusive(true); + printKeyActionGroup->addButton(takeNew,0);// SpectacleConfig::PrintKeyActionRunning::TakeNewScreenshot); + printKeyActionGroup->addButton(startNewInstance,1);// SpectacleConfig::PrintKeyActionRunning::StartNewInstance); mainLayout->addRow(i18n("Press screenshot key to:"), takeNew); mainLayout->addRow(QString(), startNewInstance); //On Wayland we can't programmatically raise and focus the window so we have to hide the option if (!(KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE"), "wayland") == 0)) { QRadioButton* focusWindow = new QRadioButton(i18n("Return focus to Spectacle"), this); - mPrintKeyActionGroup->addButton( focusWindow, SpectacleConfig::PrintKeyActionRunning::FocusWindow); + printKeyActionGroup->addButton( focusWindow,2);// SpectacleConfig::PrintKeyActionRunning::FocusWindow); mainLayout->addRow(QString(), focusWindow); } + //Workaround because KConfigWidgets doesn't support QButtonGroup (Bug 409037) + auto workaroundLabel = new QLineEdit(this); + workaroundLabel->setHidden(true); + workaroundLabel->setObjectName(QStringLiteral("kcfg_printKeyActionRunning")); + // Need to check default Button because we get no change event for that + takeNew->setChecked(true); + connect(workaroundLabel, &QLineEdit::textChanged, + printKeyActionGroup, [printKeyActionGroup, takeNew](const QString& text){ + auto button = printKeyActionGroup->button(text.toInt()); + // We are missing a button on Wayland + button ? button->setChecked(true) : takeNew->setChecked(true); + }); + connect(printKeyActionGroup, qOverload(&QButtonGroup::buttonToggled), + workaroundLabel, [workaroundLabel] (int value, bool checked) { + if (checked) { + workaroundLabel->setText(QString::number(value)); + } + }); + // /Workaround + mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // actions to take after taking a screenshot - mCopyImageToClipboard = new QCheckBox(i18n("Copy image to clipboard"), this); - connect(mCopyImageToClipboard, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); - mainLayout->addRow(i18n("After taking a screenshot:"), mCopyImageToClipboard); + auto copyImageToClipboard = new QCheckBox(i18n("Copy image to clipboard"), this); + copyImageToClipboard->setObjectName(QStringLiteral("kcfg_copyImageToClipboard")); + mainLayout->addRow(i18n("After taking a screenshot:"), copyImageToClipboard); - mAutoSaveImage = new QCheckBox(i18n("Autosave the image to the default location"), this); - connect(mAutoSaveImage, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); - mainLayout->addRow(QString(), mAutoSaveImage); + auto autoSaveImage = new QCheckBox(i18n("Autosave the image to the default location"), this); + autoSaveImage->setObjectName(QStringLiteral("kcfg_autoSaveImage")); + mainLayout->addRow(QString(), autoSaveImage); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // Rectangular Region settings KTitleWidget *titleWidget = new KTitleWidget(this); titleWidget->setText(i18n("Rectangular Region")); titleWidget->setLevel(2); mainLayout->addRow(titleWidget); // use light background - mUseLightBackground = new QCheckBox(i18n("Use light background"), this); - connect(mUseLightBackground, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); - mainLayout->addRow(i18n("General:"), mUseLightBackground); + QCheckBox* kcfg_useLightMaskColour = new QCheckBox(i18n("Use light background"), this); + kcfg_useLightMaskColour->setObjectName(QStringLiteral("kcfg_useLightMaskColour")); + mainLayout->addRow(i18n("General:"), kcfg_useLightMaskColour); // show magnifier - mShowMagnifier = new QCheckBox(i18n("Show magnifier"), this); - connect(mShowMagnifier, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); - mainLayout->addRow(QString(), mShowMagnifier); + auto showMagnifier = new QCheckBox(i18n("Show magnifier"), this); + showMagnifier->setObjectName(QStringLiteral("kcfg_showMagnifier")); + mainLayout->addRow(QString(), showMagnifier); // release mouse-button to capture - mReleaseToCapture = new QCheckBox(i18n("Accept on click-and-release"), this); - connect(mReleaseToCapture, &QCheckBox::toggled, this, &GeneralOptionsPage::markDirty); - mainLayout->addRow(QString(), mReleaseToCapture); + auto releaseToCapture = new QCheckBox(i18n("Accept on click-and-release"), this); + releaseToCapture->setObjectName(QStringLiteral("kcfg_useReleaseToCapture")); + mainLayout->addRow(QString(), releaseToCapture); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // remember Rectangular Region box QButtonGroup* rememberGroup = new QButtonGroup(this); rememberGroup->setExclusive(true); QRadioButton* neverButton = new QRadioButton(i18n("Never"), this); - mRememberAlways = new QRadioButton(i18n("Always"), this); - mRememberUntilClosed = new QRadioButton(i18n("Until Spectacle is closed"), this); + auto rememberAlways = new QRadioButton(i18n("Always"), this); + rememberAlways->setObjectName(QStringLiteral("kcfg_alwaysRememberRegion")); + auto rememberUntilClosed = new QRadioButton(i18n("Until Spectacle is closed"), this); + rememberUntilClosed->setObjectName(QStringLiteral("kcfg_rememberLastRectangularRegion")); rememberGroup->addButton(neverButton); - rememberGroup->addButton(mRememberAlways); - rememberGroup->addButton(mRememberUntilClosed); - neverButton->setChecked(true); - connect(rememberGroup, qOverload(&QButtonGroup::buttonToggled), this, &GeneralOptionsPage::markDirty); + rememberGroup->addButton(rememberAlways); + rememberGroup->addButton(rememberUntilClosed); mainLayout->addRow(i18n("Remember selected area:"), neverButton); - mainLayout->addRow(QString(), mRememberAlways); - mainLayout->addRow(QString(), mRememberUntilClosed ); - - // read in the data - resetChanges(); -} -void GeneralOptionsPage::markDirty() -{ - mChangesMade = true; -} - -void GeneralOptionsPage::saveChanges() -{ - SpectacleConfig *cfgManager = SpectacleConfig::instance(); - - cfgManager->setUseLightRegionMaskColour(mUseLightBackground->checkState() == Qt::Checked); - cfgManager->setRememberLastRectangularRegion(mRememberUntilClosed->isChecked() || mRememberAlways->isChecked()); - cfgManager->setAlwaysRememberRegion (mRememberAlways->isChecked()); - cfgManager->setShowMagnifierChecked(mShowMagnifier->checkState() == Qt::Checked); - cfgManager->setUseReleaseToCaptureChecked(mReleaseToCapture->checkState() == Qt::Checked); - cfgManager->setPrintKeyActionRunning(static_cast(mPrintKeyActionGroup->checkedId())); - cfgManager->setCopyImageToClipboard(mCopyImageToClipboard->checkState() == Qt::Checked); - cfgManager->setAutoSaveImage(mAutoSaveImage->checkState() == Qt::Checked); - - mChangesMade = false; -} - -void GeneralOptionsPage::resetChanges() -{ - SpectacleConfig *cfgManager = SpectacleConfig::instance(); - - mUseLightBackground->setChecked(cfgManager->useLightRegionMaskColour()); - mRememberUntilClosed->setChecked(cfgManager->rememberLastRectangularRegion()); - mRememberAlways->setChecked(cfgManager->alwaysRememberRegion()); - mShowMagnifier->setChecked(cfgManager->showMagnifierChecked()); - mReleaseToCapture->setChecked(cfgManager->useReleaseToCapture()); - mPrintKeyActionGroup->button(cfgManager->printKeyActionRunning())->setChecked(true); - mCopyImageToClipboard->setChecked(cfgManager->copyImageToClipboard()); - mAutoSaveImage->setChecked(cfgManager->autoSaveImage()); - - mChangesMade = false; + mainLayout->addRow(QString(), rememberAlways); + mainLayout->addRow(QString(), rememberUntilClosed); } diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h index f6b9049..5be6bde 100644 --- a/src/Gui/SettingsDialog/GeneralOptionsPage.h +++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h @@ -1,58 +1,34 @@ /* * 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 GENERALOPTIONSPAGE_H #define GENERALOPTIONSPAGE_H -#include "SettingsPage.h" +#include -class QButtonGroup; -class QCheckBox; -class QRadioButton; - -class GeneralOptionsPage : public SettingsPage +class GeneralOptionsPage : public QWidget { Q_OBJECT public: explicit GeneralOptionsPage(QWidget *parent = nullptr); - - public Q_SLOTS: - - void saveChanges() override; - void resetChanges() override; - - private Q_SLOTS: - - void markDirty(); - - private: - - QButtonGroup *mPrintKeyActionGroup; - QRadioButton *mRememberAlways; - QRadioButton *mRememberUntilClosed; - QCheckBox *mCopyImageToClipboard; - QCheckBox *mAutoSaveImage; - QCheckBox *mUseLightBackground; - QCheckBox *mShowMagnifier; - QCheckBox *mReleaseToCapture; }; #endif // GENERALOPTIONSPAGE_H diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp index b9d1e96..5ad47ed 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -1,224 +1,169 @@ /* + * Copyright 2019 David Redondo * 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) +SaveOptionsPage::SaveOptionsPage(QWidget *parent) : QWidget(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); + auto urlRequester = new KUrlRequester(this); + urlRequester->setObjectName(QStringLiteral("kcfg_defaultSaveLocation")); + urlRequester->setMode(KFile::Directory); + mainLayout->addRow(i18n("Save Location:"), urlRequester); // 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); + auto copyPathToClipboard = new QCheckBox(i18n("Copy file location to clipboard after saving"), this); + copyPathToClipboard->setObjectName(QStringLiteral("kcfg_copySaveLocation")); + mainLayout->addRow(QString(), copyPathToClipboard); 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(); + QHBoxLayout *sliderHorizLayout = new QHBoxLayout(this); + QVBoxLayout *sliderVertLayout = new QVBoxLayout(this); // Current value - QSpinBox *mQualitySpinner = new QSpinBox(); - mQualitySpinner->setSuffix(QString::fromUtf8("%")); - mQualitySpinner->setRange(0, 100); - mQualitySpinner->setValue(SpectacleConfig::instance()->compressionQuality()); - connect(mQualitySpinner, QOverload::of(&QSpinBox::valueChanged), this, [=] (int value) {mQualitySlider->setValue(value);}); + auto qualitySpinner = new QSpinBox(this); + qualitySpinner->setSuffix(QString::fromUtf8("%")); + qualitySpinner->setRange(0, 100); + qualitySpinner->setObjectName(QStringLiteral("kcfg_compressionQuality")); // Slider - mQualitySlider = new QSlider(Qt::Horizontal); - mQualitySlider->setRange(0, 100); - mQualitySlider->setSliderPosition(SpectacleConfig::instance()->compressionQuality()); - mQualitySlider->setTracking(true); - connect(mQualitySlider, &QSlider::valueChanged, this, [=](int value) { - mQualitySpinner->setValue(value); - markDirty(); + auto qualitySlider = new QSlider(Qt::Horizontal, this); + qualitySlider->setRange(0, 100); + qualitySlider->setSliderPosition(qualitySpinner->value()); + qualitySlider->setTracking(true); + connect(qualitySlider, &QSlider::valueChanged, this, [=](int value) { + qualitySpinner->setValue(value); }); - - sliderHorizLayout->addWidget(mQualitySlider); - sliderHorizLayout->addWidget(mQualitySpinner); + connect(qualitySpinner, QOverload::of(&QSpinBox::valueChanged), this, [=] (int value) {qualitySlider->setValue(value);}); + sliderHorizLayout->addWidget(qualitySlider); + sliderHorizLayout->addWidget(qualitySpinner); sliderVertLayout->addLayout(sliderHorizLayout); - QLabel *qualitySliderDescription = new QLabel(); + QLabel *qualitySliderDescription = new QLabel(this); 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); + QHBoxLayout *saveFieldLayout = new QHBoxLayout(this); + mSaveNameFormat = new QLineEdit(this); + mSaveNameFormat->setObjectName(QStringLiteral("kcfg_saveFilenameFormat")); + connect(mSaveNameFormat, &QLineEdit::textEdited, this, [&](const QString &newText) { QString fmt; const auto imageFormats = QImageWriter::supportedImageFormats(); for (const auto &item : imageFormats) { 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 = new QComboBox(this); + mSaveImageFormat->setObjectName(QStringLiteral("kcfg_defaultSaveImageFormat")); + mSaveImageFormat->setProperty("kcfg_property", QByteArray("currentText")); mSaveImageFormat->addItems([&](){ QStringList items; const auto formats = QImageWriter::supportedImageFormats(); for (const auto &fmt : formats) { 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 += QLatin1String("/: ") + 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, [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()); - 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(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/Gui/SettingsDialog/SaveOptionsPage.h b/src/Gui/SettingsDialog/SaveOptionsPage.h index 213a7e8..7d40f2b 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.h +++ b/src/Gui/SettingsDialog/SaveOptionsPage.h @@ -1,61 +1,46 @@ /* * 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 SAVEOPTIONSPAGE_H #define SAVEOPTIONSPAGE_H -#include "SettingsPage.h" +#include -class QLineEdit; class QComboBox; -class KUrlRequester; -class QCheckBox; -class QSlider; class QLabel; +class QLineEdit; -class SaveOptionsPage : public SettingsPage +class SaveOptionsPage : public QWidget { Q_OBJECT public: explicit SaveOptionsPage(QWidget *parent = nullptr); - - public Q_SLOTS: - - void saveChanges() override; - void resetChanges() override; - - private Q_SLOTS: - - void markDirty(); - + private: - + + QLineEdit* mSaveNameFormat; + QLabel* mPreviewLabel; + QComboBox* mSaveImageFormat; + void updateFilenamePreview(); - - QLineEdit *mSaveNameFormat; - KUrlRequester *mUrlRequester; - QComboBox *mSaveImageFormat; - QCheckBox *mCopyPathToClipboard; - QSlider *mQualitySlider; - QLabel *mPreviewLabel; }; #endif // SAVEOPTIONSPAGE_H diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp index daf0678..8a8e08f 100644 --- a/src/Gui/SettingsDialog/SettingsDialog.cpp +++ b/src/Gui/SettingsDialog/SettingsDialog.cpp @@ -1,95 +1,81 @@ /* + * Copyright 2019 David Redondo * 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 "SettingsDialog.h" #include "GeneralOptionsPage.h" #include "SaveOptionsPage.h" +#include "settings.h" #include "ShortcutsOptionsPage.h" #include #include #include #include SettingsDialog::SettingsDialog(QWidget *parent) : - KPageDialog(parent) + KConfigDialog(parent, QStringLiteral("settings"), Settings::self()) { - // set up window options and geometry - setWindowTitle(i18nc("@title:window", "Configure")); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - resize(600, 550); - setFaceType(List); - - // init all pages - QMetaObject::invokeMethod(this, "initPages", Qt::QueuedConnection); + addPage(new GeneralOptionsPage(this), Settings::self(), + i18n("General"), QStringLiteral("spectacle")); + addPage(new SaveOptionsPage(this), Settings::self(), + i18n("Save"), QStringLiteral("document-save")); + mShortcutsPage = new ShortcutsOptionsPage(this); + addPage(mShortcutsPage, i18n("Shortcuts"), QStringLiteral("preferences-desktop-keyboard")); + connect(mShortcutsPage, &ShortcutsOptionsPage::shortCutsChanged, this, [this] { + updateButtons(); + }); + resize(600, 590); + connect(this, &KConfigDialog::currentPageChanged, this, &SettingsDialog::updateButtons); } -void SettingsDialog::initPages() +bool SettingsDialog::hasChanged() { - KPageWidgetItem *generalOptions = new KPageWidgetItem(new GeneralOptionsPage(this), i18n("General")); - generalOptions->setHeader(i18n("General")); - generalOptions->setIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); - addPage(generalOptions); - mPages.insert(generalOptions); - - KPageWidgetItem *saveOptions = new KPageWidgetItem(new SaveOptionsPage(this), i18n("Save")); - saveOptions->setHeader(i18n("Save")); - saveOptions->setIcon(QIcon::fromTheme(QStringLiteral("document-save"))); - addPage(saveOptions); - mPages.insert(saveOptions); - - KPageWidgetItem *shortcutOptions = new KPageWidgetItem(new ShortcutsOptionsPage(this), i18n("Shortcuts")); - shortcutOptions->setHeader(i18n("Shortcuts")); - shortcutOptions->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-keyboard"))); - addPage(shortcutOptions); - mPages.insert(shortcutOptions); + return mShortcutsPage->isModified() || KConfigDialog::hasChanged(); +} - connect(this, &SettingsDialog::currentPageChanged, this, &SettingsDialog::onPageChanged); +bool SettingsDialog::isDefault() +{ + return currentPage()->name() != i18n("Shortcuts") && KConfigDialog::isDefault(); } -void SettingsDialog::accept() +void SettingsDialog::updateSettings() { - for (auto page : qAsConst(mPages)) { - SettingsPage *pageWidget = dynamic_cast(page->widget()); - if (pageWidget) { - pageWidget->saveChanges(); - } - } + KConfigDialog::updateSettings(); + mShortcutsPage->saveChanges(); +} - done(QDialog::Accepted); +void SettingsDialog::updateWidgets() +{ + KConfigDialog::updateWidgets(); + mShortcutsPage->resetChanges(); } -void SettingsDialog::onPageChanged(KPageWidgetItem *current, KPageWidgetItem *before) +void SettingsDialog::updateWidgetsDefault() { - Q_UNUSED(current); + KConfigDialog::updateWidgetsDefault(); + mShortcutsPage->defaults(); +} + + + - SettingsPage *pageWidget = dynamic_cast(before->widget()); - if (pageWidget && (pageWidget->changesMade())) { - QMessageBox::StandardButton response = QMessageBox::question(this, i18n("Apply Unsaved Changes"), - i18n("You have made changes to the settings in this tab. Do you want to apply those changes?")); - if (response == QMessageBox::Yes) { - pageWidget->saveChanges(); - } else { - pageWidget->resetChanges(); - } - } -} diff --git a/src/Gui/SettingsDialog/SettingsDialog.h b/src/Gui/SettingsDialog/SettingsDialog.h index 7fcddb7..5fb3fe6 100644 --- a/src/Gui/SettingsDialog/SettingsDialog.h +++ b/src/Gui/SettingsDialog/SettingsDialog.h @@ -1,50 +1,46 @@ /* * 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 SETTINGSDIALOG_H #define SETTINGSDIALOG_H -#include -#include +#include -class KPageWidgetItem; +class ShortcutsOptionsPage; -class SettingsDialog : public KPageDialog +class SettingsDialog : public KConfigDialog { Q_OBJECT public: explicit SettingsDialog(QWidget *parent = nullptr); - public Q_SLOTS: - - void accept() override; - - private Q_SLOTS: - - void initPages(); - void onPageChanged(KPageWidgetItem *current, KPageWidgetItem *before); - private: - QSet mPages; + bool hasChanged() override; + bool isDefault() override; + void updateSettings() override; + void updateWidgets() override; + void updateWidgetsDefault() override; + + ShortcutsOptionsPage* mShortcutsPage; }; #endif // SETTINGSDIALOG_H diff --git a/src/Gui/SettingsDialog/SettingsPage.cpp b/src/Gui/SettingsDialog/SettingsPage.cpp deleted file mode 100644 index f6323f3..0000000 --- a/src/Gui/SettingsDialog/SettingsPage.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 "SettingsPage.h" - -SettingsPage::SettingsPage(QWidget *parent) : - QWidget(parent), - mChangesMade(false) -{} - -SettingsPage::~SettingsPage() -{} - -bool SettingsPage::changesMade() -{ - return mChangesMade; -} diff --git a/src/Gui/SettingsDialog/SettingsPage.h b/src/Gui/SettingsDialog/SettingsPage.h deleted file mode 100644 index 61ac9f8..0000000 --- a/src/Gui/SettingsDialog/SettingsPage.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 SETTINGSPAGE_H -#define SETTINGSPAGE_H - -#include - -class SettingsPage : public QWidget -{ - Q_OBJECT - - public: - - explicit SettingsPage(QWidget *parent = nullptr); - virtual ~SettingsPage(); - - public Q_SLOTS: - - virtual void saveChanges() = 0; - virtual void resetChanges() = 0; - bool changesMade(); - - protected: - - bool mChangesMade; -}; - -#endif // SETTINGSPAGE_H diff --git a/src/Gui/SettingsDialog/ShortcutsOptionsPage.cpp b/src/Gui/SettingsDialog/ShortcutsOptionsPage.cpp index 2d13fb2..05f846d 100644 --- a/src/Gui/SettingsDialog/ShortcutsOptionsPage.cpp +++ b/src/Gui/SettingsDialog/ShortcutsOptionsPage.cpp @@ -1,41 +1,62 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 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 "ShortcutsOptionsPage.h" -#include "SpectacleConfig.h" +#include "ShortcutActions.h" #include #include -ShortcutsOptionsPage::ShortcutsOptionsPage(QWidget* parent) : SettingsPage(parent) +ShortcutsOptionsPage::ShortcutsOptionsPage(QWidget* parent) : QWidget(parent) { QVBoxLayout *mainLayout = new QVBoxLayout(this); setLayout(mainLayout); - - mEditor = new KShortcutsEditor(SpectacleConfig::instance()->shortCutActions, this, KShortcutsEditor::ActionType::GlobalAction); + mEditor = new KShortcutsEditor(ShortcutActions::self()->shortcutActions(), this, KShortcutsEditor::ActionType::GlobalAction); mainLayout->addWidget(mEditor); - connect(mEditor, &KShortcutsEditor::keyChange, this, &ShortcutsOptionsPage::markDirty); + connect(mEditor, &KShortcutsEditor::keyChange, this, &ShortcutsOptionsPage::shortCutsChanged); } ShortcutsOptionsPage::~ShortcutsOptionsPage() { mEditor->undoChanges(); } void ShortcutsOptionsPage::resetChanges() { mEditor->undoChanges(); - mChangesMade = false; } void ShortcutsOptionsPage::saveChanges() { mEditor->commit(); - mChangesMade = false; } -void ShortcutsOptionsPage::markDirty() +bool ShortcutsOptionsPage::isModified() +{ + return mEditor->isModified(); +} + +void ShortcutsOptionsPage::defaults() { - mChangesMade = true; + mEditor->allDefault(); } + diff --git a/src/Gui/SettingsDialog/ShortcutsOptionsPage.h b/src/Gui/SettingsDialog/ShortcutsOptionsPage.h index dbf80c3..090a8c7 100644 --- a/src/Gui/SettingsDialog/ShortcutsOptionsPage.h +++ b/src/Gui/SettingsDialog/ShortcutsOptionsPage.h @@ -1,31 +1,52 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 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 SHORTCUTSOPTIONSPAGE_H #define SHORTCUTSOPTIONSPAGE_H -#include "SettingsPage.h" +#include class KShortcutsEditor; -class ShortcutsOptionsPage : public SettingsPage +class ShortcutsOptionsPage : public QWidget { Q_OBJECT public: - explicit ShortcutsOptionsPage ( QWidget* parent ); + explicit ShortcutsOptionsPage (QWidget* parent); ~ShortcutsOptionsPage(); - public Q_SLOTS: + bool isModified(); + void defaults(); - void saveChanges() override; - void resetChanges() override; + Q_SIGNALS: + void shortCutsChanged(); + + public Q_SLOTS: - private Q_SLOTS: + void saveChanges(); + void resetChanges(); - void markDirty(); private: KShortcutsEditor* mEditor; }; #endif // SHORTCUTSOPTIONSPAGE_H diff --git a/src/Gui/SettingsDialog/settings.kcfgc b/src/Gui/SettingsDialog/settings.kcfgc new file mode 100644 index 0000000..2f5df23 --- /dev/null +++ b/src/Gui/SettingsDialog/settings.kcfgc @@ -0,0 +1,6 @@ +File=spectacle.kcfg +ClassName=Settings +Singleton=true +Mutators=true +UseEnumTypes=true +GlobalEnums=true diff --git a/src/Gui/SettingsDialog/spectacle.kcfg b/src/Gui/SettingsDialog/spectacle.kcfg new file mode 100644 index 0000000..cb5fce2 --- /dev/null +++ b/src/Gui/SettingsDialog/spectacle.kcfg @@ -0,0 +1,124 @@ + + + +QStandardPaths +SpectacleCommon.h + + + + + + + + + + TakeNewScreenshot + + + + false + + + + false + + + + false + + + + false + + + + false + + + + true + + + + false + + + + + + 0,0,0,0 + + + + false + + + + true + + + + true + + + + false + + + + false + + + + 0 + + + + Spectacle::CaptureMode::AllScreens + + + + + + + QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)+QLatin1Char('/')) + + + + + true + + + + 90 + + + + PNG + + + + Screenshot_%Y%M%D_%H%m%S + + + + defaultSaveLocation() + + + + defaultSaveLocation() + + + + + + + + SaveAs + + + diff --git a/src/Main.cpp b/src/Main.cpp index 4d0c907..960e422 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,163 +1,166 @@ /* + * Copyright 2019 David Redondo * 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 "Config.h" +#include "settings.h" #include "SpectacleCommon.h" #include "SpectacleCore.h" #include "SpectacleDBusAdapter.h" #include #include #include #include #include #include int main(int argc, char **argv) { // set up the application QApplication lApp(argc, argv); lApp.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); lApp.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KLocalizedString::setApplicationDomain("spectacle"); KAboutData aboutData(QStringLiteral("spectacle"), i18n("Spectacle"), QStringLiteral(SPECTACLE_VERSION), i18n("KDE Screenshot Utility"), KAboutLicense::GPL_V2, i18n("(C) 2015 Boudhayan Gupta")); aboutData.addAuthor(QStringLiteral("Boudhayan Gupta"), QString(), QStringLiteral("bgupta@kde.org")); aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); lApp.setWindowIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); // set up the command line options parser QCommandLineParser lCmdLineParser; aboutData.setupCommandLine(&lCmdLineParser); lCmdLineParser.addOptions({ {{QStringLiteral("f"), QStringLiteral("fullscreen")}, i18n("Capture the entire desktop (default)")}, {{QStringLiteral("m"), QStringLiteral("current")}, i18n("Capture the current monitor")}, {{QStringLiteral("a"), QStringLiteral("activewindow")}, i18n("Capture the active window")}, {{QStringLiteral("u"), QStringLiteral("windowundercursor")}, i18n("Capture the window currently under the cursor, including parents of pop-up menus")}, {{QStringLiteral("t"), QStringLiteral("transientonly")}, i18n("Capture the window currently under the cursor, excluding parents of pop-up menus")}, {{QStringLiteral("r"), QStringLiteral("region")}, i18n("Capture a rectangular region of the screen")}, {{QStringLiteral("g"), QStringLiteral("gui")}, i18n("Start in GUI mode (default)")}, {{QStringLiteral("b"), QStringLiteral("background")}, i18n("Take a screenshot and exit without showing the GUI")}, {{QStringLiteral("s"), QStringLiteral("dbus")}, i18n("Start in DBus-Activation mode")}, {{QStringLiteral("n"), QStringLiteral("nonotify")}, i18n("In background mode, do not pop up a notification when the screenshot is taken")}, {{QStringLiteral("o"), QStringLiteral("output")}, i18n("In background mode, save image to specified file"), QStringLiteral("fileName")}, {{QStringLiteral("d"), QStringLiteral("delay")}, i18n("In background mode, delay before taking the shot (in milliseconds)"), QStringLiteral("delayMsec")}, {{QStringLiteral("c"), QStringLiteral("clipboard")}, i18n("In background mode, copy screenshot to clipboard")}, {{QStringLiteral("w"), QStringLiteral("onclick")}, i18n("Wait for a click before taking screenshot. Invalidates delay")} }); lCmdLineParser.process(lApp); aboutData.processCommandLine(&lCmdLineParser); // extract the capture mode Spectacle::CaptureMode lCaptureMode = Spectacle::CaptureMode::AllScreens; if (lCmdLineParser.isSet(QStringLiteral("current"))) { lCaptureMode = Spectacle::CaptureMode::CurrentScreen; } else if (lCmdLineParser.isSet(QStringLiteral("activewindow"))) { lCaptureMode = Spectacle::CaptureMode::ActiveWindow; } else if (lCmdLineParser.isSet(QStringLiteral("region"))) { lCaptureMode = Spectacle::CaptureMode::RectangularRegion; } else if (lCmdLineParser.isSet(QStringLiteral("windowundercursor"))) { lCaptureMode = Spectacle::CaptureMode::TransientWithParent; } else if (lCmdLineParser.isSet(QStringLiteral("transientonly"))) { lCaptureMode = Spectacle::CaptureMode::WindowUnderCursor; } // are we running in background or dbus mode? SpectacleCore::StartMode lStartMode = SpectacleCore::StartMode::Gui; bool lNotify = true; bool lCopyToClipboard = false; qint64 lDelayMsec = 0; QString lFileName = QString(); if (lCmdLineParser.isSet(QStringLiteral("background"))) { lStartMode = SpectacleCore::StartMode::Background; } else if (lCmdLineParser.isSet(QStringLiteral("dbus"))) { lStartMode = SpectacleCore::StartMode::DBus; } switch(lStartMode) { case SpectacleCore::StartMode::Background: if (lCmdLineParser.isSet(QStringLiteral("nonotify"))) { lNotify = false; } if (lCmdLineParser.isSet(QStringLiteral("output"))) { lFileName = lCmdLineParser.value(QStringLiteral("output")); } if (lCmdLineParser.isSet(QStringLiteral("delay"))) { bool lParseOk = false; qint64 lDelayValue = lCmdLineParser.value(QStringLiteral("delay")).toLongLong(&lParseOk); if (lParseOk) { lDelayMsec = lDelayValue; } } if (lCmdLineParser.isSet(QStringLiteral("onclick"))) { lDelayMsec = -1; } if (lCmdLineParser.isSet(QStringLiteral("clipboard"))) { lCopyToClipboard = true; } lApp.setQuitOnLastWindowClosed(false); break; case SpectacleCore::StartMode::DBus: lApp.setQuitOnLastWindowClosed(false); break; case SpectacleCore::StartMode::Gui: break; } // release the kraken SpectacleCore lCore(lStartMode, lCaptureMode, lFileName, lDelayMsec, lNotify, lCopyToClipboard); QObject::connect(&lCore, &SpectacleCore::allDone, qApp, &QApplication::quit); - + QObject::connect(qApp, &QApplication::aboutToQuit, Settings::self(), &Settings::save); + // create the dbus connections new KDBusService(KDBusService::Multiple, &lCore); SpectacleDBusAdapter *lDBusAdapter = new SpectacleDBusAdapter(&lCore); QObject::connect(&lCore, &SpectacleCore::grabFailed, lDBusAdapter, &SpectacleDBusAdapter::ScreenshotFailed); QObject::connect(ExportManager::instance(), &ExportManager::imageSaved, &lCore, [&](const QUrl &savedAt) { emit lDBusAdapter->ScreenshotTaken(savedAt.toLocalFile()); }); QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), &lCore); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Spectacle")); // fire it up return lApp.exec(); } diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp index 41e5858..ff364b6 100644 --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -1,865 +1,865 @@ /* * Copyright (C) 2018 Ambareesh "Amby" Balaji * * 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 #include #include #include #include #include #include "QuickEditor.h" -#include "SpectacleConfig.h" +#include "settings.h" const int QuickEditor::handleRadiusMouse = 9; const int QuickEditor::handleRadiusTouch = 12; const qreal QuickEditor::increaseDragAreaFactor = 2.0; const int QuickEditor::minSpacingBetweenHandles = 20; const int QuickEditor::borderDragAreaSize = 10; const int QuickEditor::selectionSizeThreshold = 100; const int QuickEditor::selectionBoxPaddingX = 5; const int QuickEditor::selectionBoxPaddingY = 4; const int QuickEditor::selectionBoxMarginY = 5; bool QuickEditor::bottomHelpTextPrepared = false; const int QuickEditor::bottomHelpBoxPaddingX = 12; const int QuickEditor::bottomHelpBoxPaddingY = 8; const int QuickEditor::bottomHelpBoxPairSpacing = 6; const int QuickEditor::bottomHelpBoxMarginBottom = 5; const int QuickEditor::midHelpTextFontSize = 12; const int QuickEditor::magnifierLargeStep = 15; const int QuickEditor::magZoom = 5; const int QuickEditor::magPixels = 16; const int QuickEditor::magOffset = 32; QuickEditor::QuickEditor(const QPixmap &thePixmap, KWayland::Client::PlasmaShell *plasmashell, QWidget *parent) : QWidget(parent), mMaskColor(QColor::fromRgbF(0, 0, 0, 0.15)), mStrokeColor(palette().highlight().color()), mCrossColor(QColor::fromRgbF(mStrokeColor.redF(), mStrokeColor.greenF(), mStrokeColor.blueF(), 0.7)), mLabelBackgroundColor(QColor::fromRgbF( palette().light().color().redF(), palette().light().color().greenF(), palette().light().color().blueF(), 0.85 )), mLabelForegroundColor(palette().windowText().color()), mMidHelpText(i18n("Click and drag to draw a selection rectangle,\nor press Esc to quit")), mMidHelpTextFont(font()), mBottomHelpTextFont(font()), mBottomHelpGridLeftWidth(0), mMouseDragState(MouseState::None), mPixmap(thePixmap), mMagnifierAllowed(false), - mShowMagnifier(SpectacleConfig::instance()->showMagnifierChecked()), + mShowMagnifier(Settings::showMagnifier()), mToggleMagnifier(false), - mReleaseToCapture(SpectacleConfig::instance()->useReleaseToCapture()), - mRememberRegion(SpectacleConfig::instance()->alwaysRememberRegion() || SpectacleConfig::instance()->rememberLastRectangularRegion()), + mReleaseToCapture(Settings::useReleaseToCapture()), + mRememberRegion(Settings::alwaysRememberRegion() || Settings::rememberLastRectangularRegion()), mDisableArrowKeys(false), mPrimaryScreenGeo(QGuiApplication::primaryScreen()->geometry()), mbottomHelpLength(bottomHelpMaxLength), mHandleRadius(handleRadiusMouse) { - SpectacleConfig *config = SpectacleConfig::instance(); - if (config->useLightRegionMaskColour()) { + if (Settings::useLightMaskColour()) { mMaskColor = QColor(255, 255, 255, 100); } setMouseTracking(true); setAttribute(Qt::WA_StaticContents); setWindowFlags(Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint | Qt::Popup | Qt::WindowStaysOnTopHint); dprI = 1.0 / devicePixelRatioF(); setGeometry(0, 0, static_cast(mPixmap.width() * dprI), static_cast(mPixmap.height() * dprI)); // TODO This is a hack until a better interface is available if (plasmashell) { using namespace KWayland::Client; winId(); auto surface = Surface::fromWindow(windowHandle()); if (!surface) { return; } PlasmaShellSurface *plasmashellSurface = plasmashell->createSurface(surface, this); plasmashellSurface->setRole(PlasmaShellSurface::Role::Panel); plasmashellSurface->setPanelTakesFocus(true); plasmashellSurface->setPosition(geometry().topLeft()); } - if (config->rememberLastRectangularRegion()) { - QRect cropRegion = config->cropRegion(); + if (Settings::rememberLastRectangularRegion() || Settings::alwaysRememberRegion()) { + auto savedRect = Settings::cropRegion(); + QRect cropRegion = QRect(savedRect[0], savedRect[1], savedRect[2], savedRect[3]); if (!cropRegion.isEmpty()) { mSelection = QRectF( cropRegion.x() * dprI, cropRegion.y() * dprI, cropRegion.width() * dprI, cropRegion.height() * dprI ).intersected(geometry()); } setMouseCursor(QCursor::pos()); } else { setCursor(Qt::CrossCursor); } setBottomHelpText(); mMidHelpTextFont.setPointSize(midHelpTextFontSize); if (!bottomHelpTextPrepared) { bottomHelpTextPrepared = true; const auto prepare = [this](QStaticText& item) { item.prepare(QTransform(), mBottomHelpTextFont); item.setPerformanceHint(QStaticText::AggressiveCaching); }; for (auto& pair : mBottomHelpText) { prepare(pair.first); for (auto &item : pair.second) { prepare(item); } } } layoutBottomHelpText(); update(); } void QuickEditor::acceptSelection() { if (!mSelection.isEmpty()) { const qreal dpr = devicePixelRatioF(); QRect scaledCropRegion = QRect( qRound(mSelection.x() * dpr), qRound(mSelection.y() * dpr), qRound(mSelection.width() * dpr), qRound(mSelection.height() * dpr) ); - SpectacleConfig::instance()->setCropRegion(scaledCropRegion); + Settings::setCropRegion({scaledCropRegion.x(), scaledCropRegion.y(), scaledCropRegion.width(), scaledCropRegion.height()}); emit grabDone(mPixmap.copy(scaledCropRegion)); } } void QuickEditor::keyPressEvent(QKeyEvent* event) { const auto modifiers = event->modifiers(); const bool shiftPressed = modifiers & Qt::ShiftModifier; if (shiftPressed) { mToggleMagnifier = true; } switch(event->key()) { case Qt::Key_Escape: emit grabCancelled(); break; case Qt::Key_Return: case Qt::Key_Enter: acceptSelection(); break; case Qt::Key_Up: { if(mDisableArrowKeys) { update(); break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); const int newPos = boundsUp(qRound(mSelection.top() * devicePixelRatioF() - step), false); if (modifiers & Qt::AltModifier) { mSelection.setBottom(dprI * newPos + mSelection.height()); mSelection = mSelection.normalized(); } else { mSelection.moveTop(dprI * newPos); } update(); break; } case Qt::Key_Right: { if(mDisableArrowKeys) { update(); break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); const int newPos = boundsRight(qRound(mSelection.left() * devicePixelRatioF() + step), false); if (modifiers & Qt::AltModifier) { mSelection.setRight(dprI * newPos + mSelection.width()); } else { mSelection.moveLeft(dprI * newPos); } update(); break; } case Qt::Key_Down: { if(mDisableArrowKeys) { update(); break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); const int newPos = boundsDown(qRound(mSelection.top() * devicePixelRatioF() + step), false); if (modifiers & Qt::AltModifier) { mSelection.setBottom(dprI * newPos + mSelection.height()); } else { mSelection.moveTop(dprI * newPos); } update(); break; } case Qt::Key_Left: { if(mDisableArrowKeys) { update(); break; } const qreal step = (shiftPressed ? 1 : magnifierLargeStep); const int newPos = boundsLeft(qRound(mSelection.left() * devicePixelRatioF() - step), false); if (modifiers & Qt::AltModifier) { mSelection.setRight(dprI * newPos + mSelection.width()); mSelection = mSelection.normalized(); } else { mSelection.moveLeft(dprI * newPos); } update(); break; } default: break; } event->accept(); } void QuickEditor::keyReleaseEvent(QKeyEvent* event) { if (mToggleMagnifier && !(event->modifiers() & Qt::ShiftModifier)) { mToggleMagnifier = false; update(); } event->accept(); } int QuickEditor::boundsLeft(int newTopLeftX, const bool mouse) { if (newTopLeftX < 0) { if (mouse) { // tweak startPos to prevent rectangle from getting stuck mStartPos.setX(mStartPos.x() + newTopLeftX * dprI); } newTopLeftX = 0; } return newTopLeftX; } int QuickEditor::boundsRight(int newTopLeftX, const bool mouse) { // the max X coordinate of the top left point const int realMaxX = qRound((width() - mSelection.width()) * devicePixelRatioF()); const int xOffset = newTopLeftX - realMaxX; if (xOffset > 0) { if (mouse) { mStartPos.setX(mStartPos.x() + xOffset * dprI); } newTopLeftX = realMaxX; } return newTopLeftX; } int QuickEditor::boundsUp(int newTopLeftY, const bool mouse) { if (newTopLeftY < 0) { if (mouse) { mStartPos.setY(mStartPos.y() + newTopLeftY * dprI); } newTopLeftY = 0; } return newTopLeftY; } int QuickEditor::boundsDown(int newTopLeftY, const bool mouse) { // the max Y coordinate of the top left point const int realMaxY = qRound((height() - mSelection.height()) * devicePixelRatioF()); const int yOffset = newTopLeftY - realMaxY; if (yOffset > 0) { if (mouse) { mStartPos.setY(mStartPos.y() + yOffset * dprI); } newTopLeftY = realMaxY; } return newTopLeftY; } void QuickEditor::mousePressEvent(QMouseEvent* event) { if(event->source() == Qt::MouseEventNotSynthesized) { mHandleRadius = handleRadiusMouse; } else { mHandleRadius = handleRadiusTouch; } if (event->button() & Qt::LeftButton) { /* NOTE Workaround for Bug 407843 * If we show the selection Widget when a right click menu is open we lose focus on X. * When the user clicks we get the mouse back. We can only grab the keyboard if we already * have mouse focus. So just grab it undconditionally here. */ grabKeyboard(); const QPointF& pos = event->pos(); mMousePos = pos; mMagnifierAllowed = true; mMouseDragState = mouseLocation(pos); mDisableArrowKeys = true; switch(mMouseDragState) { case MouseState::Outside: mStartPos = pos; break; case MouseState::Inside: mStartPos = pos; mMagnifierAllowed = false; mInitialTopLeft = mSelection.topLeft(); setCursor(Qt::ClosedHandCursor); break; case MouseState::Top: case MouseState::Left: case MouseState::TopLeft: mStartPos = mSelection.bottomRight(); break; case MouseState::Bottom: case MouseState::Right: case MouseState::BottomRight: mStartPos = mSelection.topLeft(); break; case MouseState::TopRight: mStartPos = mSelection.bottomLeft(); break; case MouseState::BottomLeft: mStartPos = mSelection.topRight(); break; default: break; } } if (mMagnifierAllowed) { update(); } event->accept(); } void QuickEditor::mouseMoveEvent(QMouseEvent* event) { const QPointF& pos = event->pos(); mMousePos = pos; mMagnifierAllowed = true; switch (mMouseDragState) { case MouseState::None: { setMouseCursor(pos); mMagnifierAllowed = false; break; } case MouseState::TopLeft: case MouseState::TopRight: case MouseState::BottomRight: case MouseState::BottomLeft: { const bool afterX = pos.x() >= mStartPos.x(); const bool afterY = pos.y() >= mStartPos.y(); mSelection.setRect( afterX ? mStartPos.x() : pos.x(), afterY ? mStartPos.y() : pos.y(), qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) ); update(); break; } case MouseState::Outside: { mSelection.setRect( qMin(pos.x(), mStartPos.x()), qMin(pos.y(), mStartPos.y()), qAbs(pos.x() - mStartPos.x()) + dprI, qAbs(pos.y() - mStartPos.y()) + dprI ); update(); break; } case MouseState::Top: case MouseState::Bottom: { const bool afterY = pos.y() >= mStartPos.y(); mSelection.setRect( mSelection.x(), afterY ? mStartPos.y() : pos.y(), mSelection.width(), qAbs(pos.y() - mStartPos.y()) + (afterY ? dprI : 0) ); update(); break; } case MouseState::Right: case MouseState::Left: { const bool afterX = pos.x() >= mStartPos.x(); mSelection.setRect( afterX ? mStartPos.x() : pos.x(), mSelection.y(), qAbs(pos.x() - mStartPos.x()) + (afterX ? dprI : 0), mSelection.height() ); update(); break; } case MouseState::Inside: { mMagnifierAllowed = false; // We use some math here to figure out if the diff with which we // move the rectangle with moves it out of bounds, // in which case we adjust the diff to not let that happen const qreal dpr = devicePixelRatioF(); // new top left point of the rectangle QPoint newTopLeft = ((pos - mStartPos + mInitialTopLeft) * dpr).toPoint(); int newTopLeftX = boundsLeft(newTopLeft.x()); if (newTopLeftX != 0) { newTopLeftX = boundsRight(newTopLeftX); } int newTopLeftY = boundsUp(newTopLeft.y()); if (newTopLeftY != 0) { newTopLeftY = boundsDown(newTopLeftY); } const auto newTopLeftF = QPointF(newTopLeftX * dprI, newTopLeftY * dprI); mSelection.moveTo(newTopLeftF); update(); break; } default: break; } event->accept(); } void QuickEditor::mouseReleaseEvent(QMouseEvent* event) { const auto button = event->button(); if (button == Qt::LeftButton) { mDisableArrowKeys = false; if(mMouseDragState == MouseState::Inside) { setCursor(Qt::OpenHandCursor); } else if(mMouseDragState == MouseState::Outside && mReleaseToCapture) { event->accept(); mMouseDragState = MouseState::None; return acceptSelection(); } } else if (button == Qt::RightButton) { mSelection.setWidth(0); mSelection.setHeight(0); } event->accept(); mMouseDragState = MouseState::None; update(); } void QuickEditor::mouseDoubleClickEvent(QMouseEvent* event) { event->accept(); if (event->button() == Qt::LeftButton && mSelection.contains(event->pos())) { acceptSelection(); } } void QuickEditor::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing); QBrush brush(mPixmap); brush.setTransform(QTransform().scale(dprI, dprI)); painter.setBackground(brush); painter.eraseRect(geometry()); if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { painter.fillRect(mSelection, mStrokeColor); const QRectF innerRect = mSelection.adjusted(1, 1, -1, -1); if (innerRect.width() > 0 && innerRect.height() > 0) { painter.eraseRect(mSelection.adjusted(1, 1, -1, -1)); } QRectF top(0, 0, width(), mSelection.top()); QRectF right(mSelection.right(), mSelection.top(), width() - mSelection.right(), mSelection.height()); QRectF bottom(0, mSelection.bottom(), width(), height() - mSelection.bottom()); QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); for (const auto& rect : { top, right, bottom, left }) { painter.fillRect(rect, mMaskColor); } bool dragHandlesVisible = false; if (mMouseDragState == MouseState::None) { dragHandlesVisible = true; drawDragHandles(painter); } else if (mMagnifierAllowed && (mShowMagnifier ^ mToggleMagnifier)) { drawMagnifier(painter); } drawSelectionSizeTooltip(painter, dragHandlesVisible); drawBottomHelpText(painter); } else { drawMidHelpText(painter); } } void QuickEditor::layoutBottomHelpText() { int maxRightWidth = 0; int contentWidth = 0; int contentHeight = 0; mBottomHelpGridLeftWidth = 0; for (int i = 0; i < mbottomHelpLength; i++) { const auto& item = mBottomHelpText[i]; const auto& left = item.first; const auto& right = item.second; const auto leftSize = left.size().toSize(); mBottomHelpGridLeftWidth = qMax(mBottomHelpGridLeftWidth, leftSize.width()); for (const auto& item : right) { const auto rightItemSize = item.size().toSize(); maxRightWidth = qMax(maxRightWidth, rightItemSize.width()); contentHeight += rightItemSize.height(); } contentWidth = qMax(contentWidth, mBottomHelpGridLeftWidth + maxRightWidth + bottomHelpBoxPairSpacing); contentHeight += (i != bottomHelpMaxLength ? bottomHelpBoxMarginBottom : 0); } mBottomHelpContentPos.setX((mPrimaryScreenGeo.width() - contentWidth) / 2 + mPrimaryScreenGeo.x()); mBottomHelpContentPos.setY(height() - contentHeight - 8); mBottomHelpGridLeftWidth += mBottomHelpContentPos.x(); mBottomHelpBorderBox.setRect( mBottomHelpContentPos.x() - bottomHelpBoxPaddingX, mBottomHelpContentPos.y() - bottomHelpBoxPaddingY, contentWidth + bottomHelpBoxPaddingX * 2, contentHeight + bottomHelpBoxPaddingY * 2 - 1 ); } void QuickEditor::setBottomHelpText() { if (mReleaseToCapture && mSelection.size().isEmpty()) { //Release to capture enabled and NO saved region available mbottomHelpLength = 3; mBottomHelpText[0] = { QStaticText(i18n("Take Screenshot:")), { QStaticText(i18nc("Mouse action", "Release left-click")), QStaticText(i18nc("Keyboard action", "Enter")) } }; mBottomHelpText[1] = { QStaticText(i18n("Create new selection rectangle:")), { QStaticText(i18nc("Mouse action", "Drag outside selection rectangle")), QStaticText(i18nc("Keyboard action", "+ Shift: Magnifier"))} }; mBottomHelpText[2] = { QStaticText(i18n("Cancel:")), { QStaticText(i18nc("Keyboard action", "Escape")) } }; } else { //Default text, Release to capture option disabled mBottomHelpText[0] = { QStaticText(i18n("Take Screenshot:")), { QStaticText(i18nc("Mouse action", "Double-click")), QStaticText(i18nc("Keyboard action", "Enter")) } }; mBottomHelpText[1] = { QStaticText(i18n("Create new selection rectangle:")), { QStaticText(i18nc("Mouse action", "Drag outside selection rectangle")), QStaticText(i18nc("Keyboard action", "+ Shift: Magnifier"))} }; mBottomHelpText[2] = { QStaticText(i18n("Move selection rectangle:")), { QStaticText(i18nc("Mouse action", "Drag inside selection rectangle")), QStaticText(i18nc("Keyboard action", "Arrow keys")), QStaticText(i18nc("Keyboard action", "+ Shift: Move in 1 pixel steps"))} }; mBottomHelpText[3] = { QStaticText(i18n("Resize selection rectangle:")), { QStaticText(i18nc("Mouse action", "Drag handles")), QStaticText(i18nc("Keyboard action", "Arrow keys + Alt")), QStaticText(i18nc("Keyboard action", "+ Shift: Resize in 1 pixel steps"))} }; mBottomHelpText[4] = { QStaticText(i18n("Reset selection:")), { QStaticText(i18nc("Mouse action", "Right-click")) } }; mBottomHelpText[5] = { QStaticText(i18n("Cancel:")), { QStaticText(i18nc("Keyboard action", "Escape")) } }; } } void QuickEditor::drawBottomHelpText(QPainter &painter) { if (mSelection.intersects(mBottomHelpBorderBox)) { return; } painter.setBrush(mLabelBackgroundColor); painter.setPen(mLabelForegroundColor); painter.setFont(mBottomHelpTextFont); painter.setRenderHint(QPainter::Antialiasing, false); painter.drawRect(mBottomHelpBorderBox); painter.setRenderHint(QPainter::Antialiasing, true); int topOffset = mBottomHelpContentPos.y(); for (int i = 0; i < mbottomHelpLength; i++) { const auto& item = mBottomHelpText[i]; const auto& left = item.first; const auto& right = item.second; const auto leftSize = left.size().toSize(); painter.drawStaticText(mBottomHelpGridLeftWidth - leftSize.width(), topOffset, left); for (const auto& item : right) { const auto rightItemSize = item.size().toSize(); painter.drawStaticText(mBottomHelpGridLeftWidth + bottomHelpBoxPairSpacing, topOffset, item); topOffset += rightItemSize.height(); } if (i != bottomHelpMaxLength) { topOffset += bottomHelpBoxMarginBottom; } } } void QuickEditor::drawDragHandles(QPainter &painter) { // Rectangular region const qreal left = mSelection.x(); const qreal centerX = left + mSelection.width() / 2.0; const qreal right = left + mSelection.width(); const qreal top = mSelection.y(); const qreal centerY = top + mSelection.height() / 2.0; const qreal bottom = top + mSelection.height(); // rectangle too small: make handles free-floating qreal offset = 0; // rectangle too close to screen edges: move handles on that edge inside the rectangle, so they're still visible qreal offsetTop = 0; qreal offsetRight = 0; qreal offsetBottom = 0; qreal offsetLeft = 0; const qreal minDragHandleSpace = 4 * mHandleRadius + 2 * minSpacingBetweenHandles; const qreal minEdgeLength = qMin(mSelection.width(), mSelection.height()); if (minEdgeLength < minDragHandleSpace) { offset = (minDragHandleSpace - minEdgeLength) / 2.0; } else { QRect virtualScreenGeo = QGuiApplication::primaryScreen()->virtualGeometry(); const int penWidth = painter.pen().width(); offsetTop = top - virtualScreenGeo.top() - mHandleRadius; offsetTop = (offsetTop >= 0) ? 0 : offsetTop; offsetRight = virtualScreenGeo.right() - right - mHandleRadius + penWidth; offsetRight = (offsetRight >= 0) ? 0 : offsetRight; offsetBottom = virtualScreenGeo.bottom() - bottom - mHandleRadius + penWidth; offsetBottom = (offsetBottom >= 0) ? 0 : offsetBottom; offsetLeft = left - virtualScreenGeo.left() - mHandleRadius; offsetLeft = (offsetLeft >= 0) ? 0 : offsetLeft; } //top-left handle this->mHandlePositions[0] = QPointF {left - offset - offsetLeft, top - offset - offsetTop}; //top-right handle this->mHandlePositions[1] = QPointF {right + offset + offsetRight, top - offset - offsetTop}; // bottom-right handle this->mHandlePositions[2] = QPointF {right + offset + offsetRight, bottom + offset + offsetBottom}; // bottom-left this->mHandlePositions[3] = QPointF {left - offset - offsetLeft, bottom + offset + offsetBottom}; // top-center handle this->mHandlePositions[4] = QPointF {centerX, top - offset - offsetTop}; // right-center handle this->mHandlePositions[5] = QPointF {right + offset + offsetRight, centerY}; // bottom-center handle this->mHandlePositions[6] = QPointF {centerX, bottom + offset + offsetBottom}; // left-center handle this->mHandlePositions[7] = QPointF {left - offset - offsetLeft, centerY}; // start path QPainterPath path; // add handles to the path for (const QPointF &handlePosition : this->mHandlePositions) { path.addEllipse(handlePosition, mHandleRadius, mHandleRadius); } // draw the path painter.fillPath(path, mStrokeColor); } void QuickEditor::drawMagnifier(QPainter &painter) { const int pixels = 2 * magPixels + 1; int magX = static_cast(mMousePos.x() * devicePixelRatioF() - magPixels); int offsetX = 0; if (magX < 0) { offsetX = magX; magX = 0; } else { const int maxX = mPixmap.width() - pixels; if (magX > maxX) { offsetX = magX - maxX; magX = maxX; } } int magY = static_cast(mMousePos.y() * devicePixelRatioF() - magPixels); int offsetY = 0; if (magY < 0) { offsetY = magY; magY = 0; } else { const int maxY = mPixmap.height() - pixels; if (magY > maxY) { offsetY = magY - maxY; magY = maxY; } } QRectF magniRect(magX, magY, pixels, pixels); qreal drawPosX = mMousePos.x() + magOffset + pixels * magZoom / 2; if (drawPosX > width() - pixels * magZoom / 2) { drawPosX = mMousePos.x() - magOffset - pixels * magZoom / 2; } qreal drawPosY = mMousePos.y() + magOffset + pixels * magZoom / 2; if (drawPosY > height() - pixels * magZoom / 2) { drawPosY = mMousePos.y() - magOffset - pixels * magZoom / 2; } QPointF drawPos(drawPosX, drawPosY); QRectF crossHairTop(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() - magZoom * (magPixels + 0.5), magZoom, magZoom * (magPixels + offsetY)); QRectF crossHairRight(drawPos.x() + magZoom * (0.5 + offsetX), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels - offsetX), magZoom); QRectF crossHairBottom(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() + magZoom * (0.5 + offsetY), magZoom, magZoom * (magPixels - offsetY)); QRectF crossHairLeft(drawPos.x() - magZoom * (magPixels + 0.5), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels + offsetX), magZoom); QRectF crossHairBorder(drawPos.x() - magZoom * (magPixels + 0.5) - 1, drawPos.y() - magZoom * (magPixels + 0.5) - 1, pixels * magZoom + 2, pixels * magZoom + 2); const auto frag = QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom); painter.fillRect(crossHairBorder, mLabelForegroundColor); painter.drawPixmapFragments(&frag, 1, mPixmap, QPainter::OpaqueHint); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); for (auto& rect : { crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) { painter.fillRect(rect, mCrossColor); } } void QuickEditor::drawMidHelpText(QPainter &painter) { painter.fillRect(geometry(), mMaskColor); painter.setFont(mMidHelpTextFont); QRect textSize = painter.boundingRect(QRect(), Qt::AlignCenter, mMidHelpText); QPoint pos((mPrimaryScreenGeo.width() - textSize.width()) / 2 + mPrimaryScreenGeo.x(), (height() - textSize.height()) / 2); painter.setBrush(mLabelBackgroundColor); QPen pen(mLabelForegroundColor); pen.setWidth(2); painter.setPen(pen); painter.drawRoundedRect(QRect(pos.x() - 20, pos.y() - 20, textSize.width() + 40, textSize.height() + 40), 4, 4); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawText(QRect(pos, textSize.size()), Qt::AlignCenter, mMidHelpText); } void QuickEditor::drawSelectionSizeTooltip(QPainter &painter, bool dragHandlesVisible) { // Set the selection size and finds the most appropriate position: // - vertically centered inside the selection if the box is not covering the a large part of selection // - on top of the selection if the selection x position fits the box height plus some margin // - at the bottom otherwise const qreal dpr = devicePixelRatioF(); QString selectionSizeText = ki18n("%1×%2").subs(qRound(mSelection.width() * dpr)).subs(qRound(mSelection.height() * dpr)).toString(); const QRect selectionSizeTextRect = painter.boundingRect(QRect(), 0, selectionSizeText); const int selectionBoxWidth = selectionSizeTextRect.width() + selectionBoxPaddingX * 2; const int selectionBoxHeight = selectionSizeTextRect.height() + selectionBoxPaddingY * 2; const int selectionBoxX = qBound( 0, static_cast(mSelection.x()) + (static_cast(mSelection.width()) - selectionSizeTextRect.width()) / 2 - selectionBoxPaddingX, width() - selectionBoxWidth ); int selectionBoxY; if ((mSelection.width() >= selectionSizeThreshold) && (mSelection.height() >= selectionSizeThreshold)) { // show inside the box selectionBoxY = static_cast(mSelection.y() + (mSelection.height() - selectionSizeTextRect.height()) / 2); } else { // show on top by default, above the drag Handles if they're visible if (dragHandlesVisible) { selectionBoxY = static_cast(mHandlePositions[4].y() - mHandleRadius - selectionBoxHeight - selectionBoxMarginY); if (selectionBoxY < 0) { selectionBoxY = static_cast(mHandlePositions[6].y() + mHandleRadius + selectionBoxMarginY); } } else { selectionBoxY = static_cast(mSelection.y() - selectionBoxHeight - selectionBoxMarginY); if (selectionBoxY < 0) { selectionBoxY = static_cast(mSelection.y() + mSelection.height() + selectionBoxMarginY); } } } // Now do the actual box, border, and text drawing painter.setBrush(mLabelBackgroundColor); painter.setPen(mLabelForegroundColor); const QRect selectionBoxRect( selectionBoxX, selectionBoxY, selectionBoxWidth, selectionBoxHeight ); painter.setRenderHint(QPainter::Antialiasing, false); painter.drawRect(selectionBoxRect); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawText(selectionBoxRect, Qt::AlignCenter, selectionSizeText); } void QuickEditor::setMouseCursor(const QPointF& pos) { MouseState mouseState = mouseLocation(pos); if (mouseState == MouseState::Outside) { setCursor(Qt::CrossCursor); } else if (MouseState::TopLeftOrBottomRight & mouseState) { setCursor(Qt::SizeFDiagCursor); } else if (MouseState::TopRightOrBottomLeft & mouseState) { setCursor(Qt::SizeBDiagCursor); } else if (MouseState::TopOrBottom & mouseState) { setCursor(Qt::SizeVerCursor); } else if (MouseState::RightOrLeft & mouseState) { setCursor(Qt::SizeHorCursor); } else { setCursor(Qt::OpenHandCursor); } } QuickEditor::MouseState QuickEditor::mouseLocation(const QPointF& pos) { auto isPointInsideCircle = [](const QPointF & circleCenter, qreal radius, const QPointF & point) { return (qPow(point.x() - circleCenter.x(), 2) + qPow(point.y() - circleCenter.y(), 2) <= qPow(radius, 2)) ? true : false; }; if (isPointInsideCircle(mHandlePositions[0], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::TopLeft; } else if (isPointInsideCircle(mHandlePositions[1], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::TopRight; } else if (isPointInsideCircle(mHandlePositions[2], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::BottomRight; } else if (isPointInsideCircle(mHandlePositions[3], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::BottomLeft; } else if (isPointInsideCircle(mHandlePositions[4], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::Top; } else if (isPointInsideCircle(mHandlePositions[5], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::Right; } else if (isPointInsideCircle(mHandlePositions[6], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::Bottom; } else if (isPointInsideCircle(mHandlePositions[7], mHandleRadius * increaseDragAreaFactor, pos)) { return MouseState::Left; } auto inRange = [](qreal low, qreal high, qreal value) { return value >= low && value <= high; }; auto withinThreshold = [](qreal offset, qreal threshold) { return qFabs(offset) <= threshold; }; //Rectangle can be resized when border is dragged, if it's big enough if(mSelection.width() >= 100 && mSelection.height() >= 100) { if(inRange(mSelection.x(), mSelection.x() + mSelection.width(), pos.x())) { if(withinThreshold(pos.y() - mSelection.y(), borderDragAreaSize)) { return MouseState::Top; } else if(withinThreshold(pos.y() - mSelection.y() - mSelection.height(), borderDragAreaSize)) { return MouseState::Bottom; } } if(inRange(mSelection.y(), mSelection.y() + mSelection.height(), pos.y())) { if(withinThreshold(pos.x() - mSelection.x(), borderDragAreaSize)) { return MouseState::Left; } else if(withinThreshold(pos.x() - mSelection.x() - mSelection.width(), borderDragAreaSize)) { return MouseState::Right; } } } if (mSelection.contains(pos)) { return MouseState::Inside; } else { return MouseState::Outside; } } diff --git a/src/ShortcutActions.cpp b/src/ShortcutActions.cpp new file mode 100644 index 0000000..2949890 --- /dev/null +++ b/src/ShortcutActions.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 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 "ShortcutActions.h" + +#include + +#include + +ShortcutActions* ShortcutActions::self() +{ + static ShortcutActions self; + return &self; +} + +ShortcutActions::ShortcutActions() : mActions{nullptr, QString()} +{ + //everything here is named to match the jumplist actions in our .desktop file + mActions.setComponentName(componentName()); + //qdbus org.kde.kglobalaccel /component/org_kde_spectacle_desktop org.kde.kglobalaccel.Component.shortcutNames + // ActiveWindowScreenShot + // CurrentMonitorScreenShot + // RectangularRegionScreenShot + // FullScreenScreenShot + // _launch + { + QAction *action = new QAction(i18n("Launch Spectacle")); + action->setObjectName(QStringLiteral("_launch")); + mActions.addAction(action->objectName(), action); + } + { + QAction *action = new QAction(i18n("Capture Entire Desktop")); + action->setObjectName(QStringLiteral("FullScreenScreenShot")); + mActions.addAction(action->objectName(), action); + } + { + QAction *action = new QAction(i18n("Capture Current Monitor")); + action->setObjectName(QStringLiteral("CurrentMonitorScreenShot")); + mActions.addAction(action->objectName(), action); + } + { + QAction *action = new QAction(i18n("Capture Active Window")); + action->setObjectName(QStringLiteral("ActiveWindowScreenShot")); + mActions.addAction(action->objectName(), action); + } + { + QAction *action = new QAction(i18n("Capture Rectangular Region")); + action->setObjectName(QStringLiteral("RectangularRegionScreenShot")); + mActions.addAction(action->objectName(), action); + } +} + +KActionCollection* ShortcutActions::shortcutActions() +{ + return &mActions; +} + +QString ShortcutActions::componentName() const +{ + return QGuiApplication::desktopFileName().append(QStringLiteral(".desktop")); +} + +QAction* ShortcutActions::openAction() const +{ + return mActions.action(0); +} + +QAction* ShortcutActions::fullScreenAction() const +{ + return mActions.action(1); +} + +QAction* ShortcutActions::currentScreenAction() const +{ + return mActions.action(2); +} + +QAction* ShortcutActions::activeWindowAction() const +{ + return mActions.action(3); +} + +QAction* ShortcutActions::regionAction() const +{ + return mActions.action(4); +} diff --git a/src/ShortcutActions.h b/src/ShortcutActions.h new file mode 100644 index 0000000..e43246e --- /dev/null +++ b/src/ShortcutActions.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU 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 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 SHORTCUT_ACTIONS_H +#define SHORTCUT_ACTIONS_H + +#include + +class ShortcutActions { +public: + static ShortcutActions* self(); + + KActionCollection* shortcutActions(); + + QString componentName() const; + + QAction* openAction() const; + QAction* fullScreenAction() const; + QAction* currentScreenAction() const; + QAction* activeWindowAction() const; + QAction* regionAction() const; + +private: + ShortcutActions(); + KActionCollection mActions; +}; + +#endif diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp deleted file mode 100644 index 0f85e39..0000000 --- a/src/SpectacleConfig.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/* - * 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 -#include - - - -SpectacleConfig::SpectacleConfig(QObject *parent) : - QObject(parent) -{ - mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); - mGeneralConfig = KConfigGroup(mConfig, "General"); - mGuiConfig = KConfigGroup(mConfig, "GuiConfig"); - - shortCutActions = new KActionCollection(this); - - //everything here is named to match the jumplist actions in our .desktop file - shortCutActions->setComponentName(QStringLiteral("org.kde.spectacle.desktop")); - //qdbus org.kde.kglobalaccel /component/org_kde_spectacle_desktop org.kde.kglobalaccel.Component.shortcutNames - // ActiveWindowScreenShot - // CurrentMonitorScreenShot - // RectangularRegionScreenShot - // FullScreenScreenShot - // _launch - { - 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); - } -} - -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"), 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"), 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 - -QUrl SpectacleConfig::defaultSaveLocation() const -{ - 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 QUrl &location) -{ - mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location.toString()); - mGeneralConfig.sync(); -} - -// copy file to clipboard after the screenshot has been made - -bool SpectacleConfig::copyImageToClipboard() const -{ - return mGeneralConfig.readEntry(QStringLiteral("copyImageToClipboard"), false); -} - -void SpectacleConfig::setCopyImageToClipboard (bool enabled) -{ - mGeneralConfig.writeEntry(QStringLiteral("copyImageToClipboard"), enabled); - mGeneralConfig.sync(); -} - -bool SpectacleConfig::autoSaveImage() const -{ - return mGeneralConfig.readEntry(QStringLiteral("autoSaveImage"), false); -} -void SpectacleConfig::setAutoSaveImage(bool enabled) -{ - mGeneralConfig.writeEntry(QStringLiteral("autoSaveImage"), enabled); - 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 deleted file mode 100644 index 4ff5f99..0000000 --- a/src/SpectacleConfig.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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 -#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 - }; - - KActionCollection* shortCutActions; - - 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); - - QUrl defaultSaveLocation() const; - void setDefaultSaveLocation(const QUrl &location); - - bool copyImageToClipboard() const; - void setCopyImageToClipboard(bool enabled); - - bool autoSaveImage() const; - void setAutoSaveImage(bool enabled); - - 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 diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index 91fbecb..18da788 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,449 +1,441 @@ /* + * Copyright 2019 David Redondo * 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 "SpectacleCore.h" #include "spectacle_core_debug.h" #include "Config.h" +#include "settings.h" +#include "ShortcutActions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SpectacleCore::SpectacleCore(StartMode theStartMode, Spectacle::CaptureMode theCaptureMode, QString &theSaveFileName, qint64 theDelayMsec, bool theNotifyOnGrab, bool theCopyToClipboard, QObject *parent) : QObject(parent), mStartMode(theStartMode), mNotify(theNotifyOnGrab), mPlatform(loadPlatform()), mMainWindow(nullptr), mIsGuiInited(false), mCopyToClipboard(theCopyToClipboard), mWaylandPlasmashell(nullptr) { - auto lConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); - KConfigGroup lGuiConfig(lConfig, "GuiConfig"); - if (!(theSaveFileName.isEmpty() || theSaveFileName.isNull())) { if (QDir::isRelativePath(theSaveFileName)) { theSaveFileName = QDir::current().absoluteFilePath(theSaveFileName); } setFilename(theSaveFileName); } // essential connections connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage); connect(mPlatform.get(), &Platform::newScreenshotTaken, this, &SpectacleCore::screenshotUpdated); connect(mPlatform.get(), &Platform::newScreenshotFailed, this, &SpectacleCore::screenshotFailed); auto lImmediateAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate); auto lOnClickAvailable = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::OnClick); if ((!lOnClickAvailable) && (theDelayMsec < 0)) { theDelayMsec = 0; } // reset last region if it should not be remembered across restarts - auto lSpectacleConfig = SpectacleConfig::instance(); - if(!lSpectacleConfig->alwaysRememberRegion()) { - lSpectacleConfig->setCropRegion(QRect()); + if(!Settings::alwaysRememberRegion()) { + Settings::setCropRegion({0, 0, 0, 0}); } // set up the export manager auto lExportManager = ExportManager::instance(); lExportManager->setCaptureMode(theCaptureMode); connect(lExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath); connect(lExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); connect(mPlatform.get(), &Platform::windowTitleChanged, lExportManager, &ExportManager::setWindowTitle); // Needed so the QuickEditor can go fullscreen on wayland if (KWindowSystem::isPlatformWayland()) { using namespace KWayland::Client; ConnectionThread *connection = ConnectionThread::fromApplication(this); if (!connection) { return; } Registry *registry = new Registry(this); registry->create(connection); connect(registry, &Registry::plasmaShellAnnounced, this, [this, registry] (quint32 name, quint32 version) { mWaylandPlasmashell = registry->createPlasmaShell(name, version, this); } ); registry->setup(); connection->roundtrip(); } switch (theStartMode) { case StartMode::DBus: break; case StartMode::Background: { auto lMsec = (KWindowSystem::compositingActive() ? 200 : 50) + theDelayMsec; auto lShutterMode = lImmediateAvailable ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; - auto lIncludePointer = lGuiConfig.readEntry("includePointer", true); - auto lIncludeDecorations = lGuiConfig.readEntry("includeDecorations", true); + auto lIncludePointer = Settings::includePointer(); + auto lIncludeDecorations = Settings::includeDecorations(); const Platform::GrabMode lCaptureMode = toPlatformGrabMode(theCaptureMode); QTimer::singleShot(lMsec, this, [ this, lCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() { mPlatform->doGrab(lShutterMode, lCaptureMode, lIncludePointer, lIncludeDecorations); }); } break; case StartMode::Gui: - initGui(lGuiConfig.readEntry("includePointer", true), lGuiConfig.readEntry("includeDecorations", true)); + initGui(Settings::includePointer(), Settings::includeDecorations()); break; } setUpShortcuts(); } void SpectacleCore::setUpShortcuts() { - SpectacleConfig* config = SpectacleConfig::instance(); - - QAction* openAction = config->shortCutActions->action(QStringLiteral("_launch")); - KGlobalAccel::self()->setGlobalShortcut(openAction, Qt::Key_Print); + KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->openAction(), Qt::Key_Print); - QAction* fullScreenAction = config->shortCutActions->action(QStringLiteral("FullScreenScreenShot")); - KGlobalAccel::self()->setGlobalShortcut(fullScreenAction, Qt::SHIFT + Qt::Key_Print); + KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->fullScreenAction(), Qt::SHIFT + Qt::Key_Print); - QAction* activeWindowAction = config->shortCutActions->action(QStringLiteral("ActiveWindowScreenShot")); - KGlobalAccel::self()->setGlobalShortcut(activeWindowAction, Qt::META + Qt::Key_Print); + KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->activeWindowAction(), Qt::META + Qt::Key_Print); - QAction* regionAction = config->shortCutActions->action(QStringLiteral("RectangularRegionScreenShot")); - KGlobalAccel::self()->setGlobalShortcut(regionAction, Qt::META + Qt::SHIFT + Qt::Key_Print); + KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->regionAction(), Qt::META + Qt::SHIFT + Qt::Key_Print); - QAction* currentScreenAction = config->shortCutActions->action(QStringLiteral("CurrentMonitorScreenShot")); - KGlobalAccel::self()->setGlobalShortcut(currentScreenAction, QList()); + KGlobalAccel::self()->setGlobalShortcut(ShortcutActions::self()->currentScreenAction(), QList()); } QString SpectacleCore::filename() const { return mFileNameString; } void SpectacleCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); auto lConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup lGuiConfig(lConfig, "GuiConfig"); auto lIncludePointer = lGuiConfig.readEntry("includePointer", true); auto lIncludeDecorations = lGuiConfig.readEntry("includeDecorations", true); if (!(mStartMode == StartMode::Gui)) { mStartMode = StartMode::Gui; initGui(lIncludePointer, lIncludeDecorations); } else { - using Actions = SpectacleConfig::PrintKeyActionRunning; - switch (SpectacleConfig::instance()->printKeyActionRunning()) { + using Actions = Settings::EnumPrintKeyActionRunning; + switch (Settings::printKeyActionRunning()) { case Actions::TakeNewScreenshot: { auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode()); QTimer::singleShot(KWindowSystem::compositingActive() ? 200 : 50, this, [this, lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations]() { mPlatform->doGrab(lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations); }); break; } case Actions::FocusWindow: if (mMainWindow->isMinimized()) { mMainWindow->setWindowState(mMainWindow->windowState() & ~Qt::WindowMinimized); } mMainWindow->activateWindow(); break; case Actions::StartNewInstance: QProcess newInstance; newInstance.setProgram(QStringLiteral("spectacle")); newInstance.startDetached(); break; } } } void SpectacleCore::takeNewScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { ExportManager::instance()->setCaptureMode(theCaptureMode); auto lGrabMode = toPlatformGrabMode(theCaptureMode); if (theTimeout < 0) { mPlatform->doGrab(Platform::ShutterMode::OnClick, lGrabMode, theIncludePointer, theIncludeDecorations); return; } // when compositing is enabled, we need to give it enough time for the window // to disappear and all the effects are complete before we take the shot. there's // no way of knowing how long the disappearing effects take, but as per default // settings (and unless the user has set an extremely slow effect), 200 // milliseconds is a good amount of wait time. auto lMsec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(theTimeout + lMsec, this, [this, lGrabMode, theIncludePointer, theIncludeDecorations]() { mPlatform->doGrab(Platform::ShutterMode::Immediate, lGrabMode, theIncludePointer, theIncludeDecorations); }); } void SpectacleCore::showErrorMessage(const QString &theErrString) { qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << theErrString; if (mStartMode == StartMode::Gui) { KMessageBox::error(nullptr, theErrString); } } void SpectacleCore::screenshotUpdated(const QPixmap &thePixmap) { auto lExportManager = ExportManager::instance(); // if we were running in rectangular crop mode, now would be // the time to further process the image if (lExportManager->captureMode() == Spectacle::CaptureMode::RectangularRegion) { if(!mQuickEditor) { mQuickEditor = std::make_unique(thePixmap, mWaylandPlasmashell); connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated); connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotFailed); mQuickEditor->show(); return; } else { mQuickEditor->hide(); mQuickEditor.reset(nullptr); } } lExportManager->setPixmap(thePixmap); lExportManager->updatePixmapTimestamp(); switch (mStartMode) { case StartMode::Background: case StartMode::DBus: { if (mNotify) { connect(lExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } if (mCopyToClipboard) { lExportManager->doCopyToClipboard(mNotify); } else { QUrl lSavePath = (mStartMode == StartMode::Background && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); lExportManager->doSave(lSavePath); } // if we notify, we emit allDone only if the user either dismissed the notification or pressed // the "Open" button, otherwise the app closes before it can react to it. if (!mNotify && mCopyToClipboard) { // Allow some time for clipboard content to transfer if '--nonotify' is used, see Bug #411263 // TODO: Find better solution QTimer::singleShot(250, this, &SpectacleCore::allDone); } else if (!mNotify) { emit allDone(); } } break; case StartMode::Gui: mMainWindow->setScreenshotAndShow(thePixmap); - bool autoSaveImage = SpectacleConfig::instance()->autoSaveImage(); - bool copyImageToClipboard = SpectacleConfig::instance()->copyImageToClipboard(); + bool autoSaveImage = Settings::autoSaveImage(); + bool copyImageToClipboard = Settings::copyImageToClipboard(); if (autoSaveImage && copyImageToClipboard) { lExportManager->doSaveAndCopy(); } else if (autoSaveImage) { lExportManager->doSave(); } else if (copyImageToClipboard) { lExportManager->doCopyToClipboard(false); } } } void SpectacleCore::screenshotFailed() { if (ExportManager::instance()->captureMode() == Spectacle::CaptureMode::RectangularRegion && mQuickEditor) { mQuickEditor->hide(); mQuickEditor.reset(nullptr); } switch (mStartMode) { case StartMode::Background: showErrorMessage(i18n("Screenshot capture canceled or failed")); emit allDone(); return; case StartMode::DBus: emit grabFailed(); emit allDone(); return; case StartMode::Gui: mMainWindow->setScreenshotAndShow(QPixmap()); } } void SpectacleCore::doNotify(const QUrl &theSavedAt) { KNotification *lNotify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(ExportManager::instance()->captureMode()) { case Spectacle::CaptureMode::AllScreens: lNotify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; case Spectacle::CaptureMode::CurrentScreen: lNotify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; case Spectacle::CaptureMode::ActiveWindow: lNotify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; case Spectacle::CaptureMode::WindowUnderCursor: case Spectacle::CaptureMode::TransientWithParent: lNotify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; case Spectacle::CaptureMode::RectangularRegion: lNotify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; case Spectacle::CaptureMode::InvalidChoice: break; } // a speaking message is prettier than a URL, special case for copy to clipboard and the default pictures location const QString &lSavePath = theSavedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); if (mCopyToClipboard) { lNotify->setText(i18n("A screenshot was saved to your clipboard.")); } else if (lSavePath == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { lNotify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", theSavedAt.fileName())); } else { lNotify->setText(i18n("A screenshot was saved as '%1' to '%2'.", theSavedAt.fileName(), lSavePath)); } if (!mCopyToClipboard) { lNotify->setUrls({theSavedAt}); lNotify->setDefaultAction(i18nc("Open the screenshot we just saved", "Open")); connect(lNotify, QOverload::of(&KNotification::activated), this, [this, theSavedAt](uint index) { if (index == 0) { new KRun(theSavedAt, nullptr); QTimer::singleShot(250, this, [this] { - if (mStartMode != StartMode::Gui || SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) { + if (mStartMode != StartMode::Gui || Settings::quitAfterSaveCopyExport()) { emit allDone(); } }); } }); } connect(lNotify, &QObject::destroyed, this, [this] { - if (mStartMode != StartMode::Gui || SpectacleConfig::instance()->quitAfterSaveOrCopyChecked()) { + if (mStartMode != StartMode::Gui || Settings::quitAfterSaveCopyExport()) { emit allDone(); } }); lNotify->sendEvent(); } void SpectacleCore::doCopyPath(const QUrl &savedAt) { - if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { + if (Settings::copySaveLocation()) { qApp->clipboard()->setText(savedAt.toLocalFile()); } } void SpectacleCore::doStartDragAndDrop() { auto lExportManager = ExportManager::instance(); QUrl lTempFile = lExportManager->tempSave(); if (!lTempFile.isValid()) { return; } auto lMimeData = new QMimeData; lMimeData->setUrls(QList { lTempFile }); lMimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(lTempFile.fileName())); auto lDragHandler = new QDrag(this); lDragHandler->setMimeData(lMimeData); lDragHandler->setPixmap(lExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); lDragHandler->exec(Qt::CopyAction); } // Private Platform::GrabMode SpectacleCore::toPlatformGrabMode(Spectacle::CaptureMode theCaptureMode) { switch(theCaptureMode) { case Spectacle::CaptureMode::InvalidChoice: return Platform::GrabMode::InvalidChoice; case Spectacle::CaptureMode::AllScreens: case Spectacle::CaptureMode::RectangularRegion: return Platform::GrabMode::AllScreens; case Spectacle::CaptureMode::TransientWithParent: return Platform::GrabMode::TransientWithParent; case Spectacle::CaptureMode::CurrentScreen: return Platform::GrabMode::CurrentScreen; case Spectacle::CaptureMode::ActiveWindow: return Platform::GrabMode::ActiveWindow; case Spectacle::CaptureMode::WindowUnderCursor: return Platform::GrabMode::WindowUnderCursor; } return Platform::GrabMode::InvalidChoice; } void SpectacleCore::initGui(bool theIncludePointer, bool theIncludeDecorations) { if (!mIsGuiInited) { mMainWindow = std::make_unique(mPlatform->supportedGrabModes(), mPlatform->supportedShutterModes()); connect(mMainWindow.get(), &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow.get(), &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); mIsGuiInited = true; auto lShutterMode = mPlatform->supportedShutterModes().testFlag(Platform::ShutterMode::Immediate) ? Platform::ShutterMode::Immediate : Platform::ShutterMode::OnClick; auto lGrabMode = toPlatformGrabMode(ExportManager::instance()->captureMode()); QTimer::singleShot(0, this, [this, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() { mPlatform->doGrab(lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations); }); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d85daae..fbd1d8b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,17 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/src) -ecm_add_test(FilenameTest.cpp - ../src/ExportManager.cpp ../src/SpectacleConfig.cpp ../src/Platforms/Platform.cpp +SET(FILENAME_TEST_SRCS + FilenameTest.cpp + ../src/ExportManager.cpp + ../src/Platforms/Platform.cpp +) + + +kconfig_add_kcfg_files(FILENAME_TEST_SRCS ${PROJECT_SOURCE_DIR}/src/Gui/SettingsDialog/settings.kcfgc) + +ecm_add_test( + ${FILENAME_TEST_SRCS} TEST_NAME "filename_test" LINK_LIBRARIES Qt5::Test Qt5::PrintSupport KF5::I18n KF5::ConfigCore KF5::GlobalAccel KF5::KIOCore KF5::WindowSystem KF5::XmlGui )