diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp index 1177eb5..a9d4504 100644 --- a/src/Gui/ExportMenu.cpp +++ b/src/Gui/ExportMenu.cpp @@ -1,210 +1,210 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ExportMenu.h" #include "spectacle_gui_debug.h" #include "Config.h" #include #include #include #include #ifdef KIPI_FOUND #include #endif #include #include #include ExportMenu::ExportMenu(QWidget *parent) : QMenu(parent), #ifdef PURPOSE_FOUND mUpdatedImageAvailable(false), mPurposeMenu(new Purpose::Menu(this)), #endif mExportManager(ExportManager::instance()) { QTimer::singleShot(300, this, &ExportMenu::populateMenu); } void ExportMenu::populateMenu() { #ifdef PURPOSE_FOUND loadPurposeMenu(); #endif #ifdef KIPI_FOUND mKipiMenu = addMenu(i18n("More Online Services")); mKipiMenu->addAction(i18n("Please wait...")); mKipiMenuLoaded = false; connect(mKipiMenu, &QMenu::aboutToShow, this, &ExportMenu::loadKipiItems); #endif addSeparator(); getKServiceItems(); } void ExportMenu::imageUpdated() { #ifdef PURPOSE_FOUND // mark cached image as stale mUpdatedImageAvailable = true; mPurposeMenu->clear(); #endif } void ExportMenu::getKServiceItems() { // populate all locally installed applications and services // which can handle images first const KService::List services = KMimeTypeTrader::self()->query(QStringLiteral("image/png")); Q_FOREACH (auto service, services) { QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&")); QAction *action = new QAction(QIcon::fromTheme(service->icon()), name, this); - connect(action, &QAction::triggered, [=]() { + connect(action, &QAction::triggered, this, [=]() { const QUrl filename = mExportManager->getAutosaveFilename(); mExportManager->doSave(filename); QList whereIs({ filename }); KRun::runService(*service, whereIs, parentWidget(), true); }); addAction(action); } // now let the user manually chose an application to open the // image with addSeparator(); QAction *openWith = new QAction(this); openWith->setText(i18n("Other Application...")); openWith->setShortcuts(KStandardShortcut::open()); - connect(openWith, &QAction::triggered, [=]() { + connect(openWith, &QAction::triggered, this, [=]() { const QUrl filename = mExportManager->getAutosaveFilename(); mExportManager->doSave(filename); QList whereIs({ filename }); KRun::displayOpenWithDialog(whereIs, parentWidget(), true); }); addAction(openWith); } #ifdef KIPI_FOUND void ExportMenu::loadKipiItems() { if (!mKipiMenuLoaded) { QTimer::singleShot(500, this, &ExportMenu::getKipiItems); mKipiMenuLoaded = true; } } void ExportMenu::getKipiItems() { mKipiMenu->clear(); mKipiInterface = new KSGKipiInterface(this); KIPI::PluginLoader *loader = new KIPI::PluginLoader; loader->setInterface(mKipiInterface); loader->init(); KIPI::PluginLoader::PluginList pluginList = loader->pluginList(); Q_FOREACH (const auto &pluginInfo, pluginList) { if (!(pluginInfo->shouldLoad())) { continue; } KIPI::Plugin *plugin = pluginInfo->plugin(); if (!(plugin)) { qCWarning(SPECTACLE_GUI_LOG) << i18n("KIPI plugin from library %1 failed to load", pluginInfo->library()); continue; } plugin->setup(&mDummyWidget); QList actions = plugin->actions(); QSet exportActions; Q_FOREACH (auto action, actions) { KIPI::Category category = plugin->category(action); if (category == KIPI::ExportPlugin) { exportActions += action; } else if (category == KIPI::ImagesPlugin && pluginInfo->library().contains(QStringLiteral("kipiplugin_sendimages"))) { exportActions += action; } } Q_FOREACH (auto action, exportActions) { mKipiMenu->addAction(action); } } // If there are no export actions, then perhaps the kipi-plugins package is not installed. if (mKipiMenu->isEmpty()) { mKipiMenu->addAction(i18n("No KIPI plugins available"))->setEnabled(false); } } #endif #ifdef PURPOSE_FOUND void ExportMenu::loadPurposeMenu() { // attach the menu QAction *purposeMenu = addMenu(mPurposeMenu); purposeMenu->setText(i18n("Share")); purposeMenu->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); // set up the callback signal connect(mPurposeMenu, &Purpose::Menu::finished, this, [this](const QJsonObject &output, int error, const QString &message) { if (error) { emit imageShared(true, message); } else { emit imageShared(false, output[QStringLiteral("url")].toString()); } }); // update available options based on the latest picture connect(mPurposeMenu, &QMenu::aboutToShow, this, &ExportMenu::loadPurposeItems); } void ExportMenu::loadPurposeItems() { if (!mUpdatedImageAvailable) { return; } // updated image available, we lazily load it now QString dataUri = ExportManager::instance()->tempSave().toString(); mUpdatedImageAvailable = false; mPurposeMenu->model()->setInputData(QJsonObject { { QStringLiteral("mimeType"), QStringLiteral("image/png") }, { QStringLiteral("urls"), QJsonArray({ dataUri }) } }); mPurposeMenu->model()->setPluginType(QStringLiteral("Export")); mPurposeMenu->reload(); } #endif diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 8a36332..b693580 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,477 +1,477 @@ /* 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 "SettingsDialog/SettingsDialog.h" #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 static const int DEFAULT_WINDOW_HEIGHT = 420; static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; KSMainWindow::KSMainWindow(const Platform::GrabModes &theGrabModes, const Platform::ShutterModes &theShutterModes, QWidget *parent) : QDialog(parent), mKSWidget(new KSWidget(theGrabModes, this)), mDivider(new QFrame(this)), mDialogButtonBox(new QDialogButtonBox(this)), mConfigureButton(new QToolButton(this)), mToolsButton(new QPushButton(this)), mSendToButton(new QPushButton(this)), mClipboardButton(new QToolButton(this)), mSaveButton(new QToolButton(this)), mSaveMenu(new QMenu(this)), mSaveAsAction(new QAction(this)), mSaveAction(new QAction(this)), mMessageWidget(new KMessageWidget(this)), mToolsMenu(new QMenu(this)), mScreenRecorderToolsMenu(new QMenu(this)), mExportMenu(new ExportMenu(this)), mShutterModes(theShutterModes) { // before we do anything, we need to set a window property // that skips the close/hide window animation on kwin. this // fixes a ghost image of the spectacle window that appears // on subsequent screenshots taken with the take new screenshot // button // // credits for this goes to Thomas Lübking #ifdef XCB_FOUND if (KWindowSystem::isPlatformX11()) { // create a window if we haven't already. note that the QWidget constructor // should already have done this if (winId() == 0) { create(0, true, true); } // do the xcb shenanigans xcb_connection_t *xcbConn = QX11Info::connection(); const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_SKIP_CLOSE_ANIMATION"); xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(xcbConn, false, effectName.length(), effectName.constData()); QScopedPointer atom(xcb_intern_atom_reply(xcbConn, atomCookie, nullptr)); if (atom.isNull()) { goto done; } uint32_t value = 1; xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, winId(), atom->atom, XCB_ATOM_CARDINAL, 32, 1, &value); } done: #endif QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } // GUI init void KSMainWindow::init() { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); // window properties setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); QPoint location = guiConfig.readEntry("window-position", QPoint(50, 50)); move(location); // change window title on save connect(ExportManager::instance(), &ExportManager::imageSaved, this, &KSMainWindow::imageSaved); // the KSGWidget connect(mKSWidget, &KSWidget::newScreenshotRequest, this, &KSMainWindow::captureScreenshot); connect(mKSWidget, &KSWidget::dragInitiated, this, &KSMainWindow::dragAndDropRequest); // the Button Bar mDialogButtonBox->setStandardButtons(QDialogButtonBox::Help); mConfigureButton->setDefaultAction(KStandardAction::preferences(this, SLOT(showPreferencesDialog()), this)); mConfigureButton->setText(i18n("Configure...")); mConfigureButton->setToolTip(i18n("Change Spectacle's settings.")); mConfigureButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mConfigureButton, QDialogButtonBox::ResetRole); KGuiItem::assign(mToolsButton, KGuiItem(i18n("Tools"))); mToolsButton->setIcon(QIcon::fromTheme(QStringLiteral("tools"), QIcon::fromTheme(QStringLiteral("application-menu")))); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy to Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // the tools menu mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); mScreenRecorderToolsMenu->setIcon(QIcon::fromTheme(QStringLiteral("media-record"))); - connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, [this]() + 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); mHideMessageWidgetTimer = new QTimer(this); connect(mHideMessageWidgetTimer, &QTimer::timeout, mMessageWidget, &KMessageWidget::animatedHide); mHideMessageWidgetTimer->setInterval(10000); // done with the init } int KSMainWindow::windowWidth(const QPixmap &pixmap) const { // Calculates what the width of the window should be for the captured image to perfectly fit // the area reserved for the image, with the height already set. const float pixmapAspectRatio = (float)pixmap.width() / pixmap.height(); const int imageHeight = mKSWidget->height() - 2 * layout()->spacing(); const int imageWidth = pixmapAspectRatio * imageHeight; int alignedWindowWidth = qMin(mKSWidget->imagePaddingWidth() + imageWidth, MAXIMUM_WINDOW_WIDTH); alignedWindowWidth += layout()->contentsMargins().left() + layout()->contentsMargins().right(); alignedWindowWidth += 2; // margins is removing 1 - 1 pixel for some reason return alignedWindowWidth; } void KSMainWindow::setDefaultSaveAction() { switch (SpectacleConfig::instance()->lastUsedSaveMode()) { case SaveMode::SaveAs: default: mSaveButton->setDefaultAction(mSaveAsAction); mSaveButton->setText(i18n("Save As...")); break; case SaveMode::Save: mSaveButton->setDefaultAction(mSaveAction); break; } } // overrides void KSMainWindow::moveEvent(QMoveEvent *event) { Q_UNUSED(event) KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); guiConfig.writeEntry("window-position", pos()); guiConfig.sync(); } // slots void KSMainWindow::captureScreenshot(Spectacle::CaptureMode theCaptureMode, int theTimeout, bool theIncludePointer, bool theIncludeDecorations) { 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, [=] { unityUpdate({ {QStringLiteral("progress"), delayAnimation->currentValue().toDouble()} }); }); connect(timer, &QTimer::timeout, this, [=] { this->hide(); timer->deleteLater(); unityUpdate({ {QStringLiteral("progress-visible"), false} }); emit newScreenshotRequest(theCaptureMode, 0, theIncludePointer, theIncludeDecorations); }); unityUpdate({ {QStringLiteral("progress-visible"), true}, {QStringLiteral("progress"), 0 } }); timer->start(); delayAnimation->start(); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); show(); activateWindow(); 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}); } void KSMainWindow::quit(const QuitBehavior quitBehavior) { qApp->setQuitOnLastWindowClosed(false); hide(); if (quitBehavior == QuitBehavior::QuitImmediately) { // Allow some time for clipboard content to transfer // TODO: Find better solution QTimer::singleShot(250, qApp, &QApplication::quit); } // TODO for else case: // Currently it is expected that you emit forceNotify, and finally quit // via a callback through KNotification::action1Activated. However, that // is not working quite right, see Bug #389694 which needs fixing. } void KSMainWindow::showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration, const QList& actions) { for (QAction* action: mMessageWidget->actions()) { mMessageWidget->removeAction(action); } for (QAction* action : actions) { mMessageWidget->addAction(action); } mMessageWidget->setText(message); mMessageWidget->setMessageType(messageType); switch (messageType) { case KMessageWidget::Error: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); break; case KMessageWidget::Warning: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); break; case KMessageWidget::Positive: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); break; case KMessageWidget::Information: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); break; } mHideMessageWidgetTimer->stop(); mMessageWidget->animatedShow(); if (messageDuration == MessageDuration::AutoHide) { mHideMessageWidgetTimer->start(); } } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error) { showInlineMessage(i18n("There was a problem sharing the image: %1", message), KMessageWidget::Error); } else { if (message.isEmpty()) { showInlineMessage(i18n("Image shared"), KMessageWidget::Positive); } else { showInlineMessage(i18n("The shared image link (%1) has been copied to the clipboard.", message), KMessageWidget::Positive, MessageDuration::Persistent); QApplication::clipboard()->setText(message); } } } void KSMainWindow::sendToClipboard() { bool notify = false; ExportManager::instance()->doCopyToClipboard(notify); SpectacleConfig::instance()->quitAfterSaveOrCopyChecked() ? quit() : showInlineMessage(i18n("The screenshot has been copied to the clipboard."), KMessageWidget::Information); } void KSMainWindow::showPreferencesDialog() { SettingsDialog prefDialog(this); prefDialog.exec(); } void KSMainWindow::imageSaved(const QUrl &location) { setWindowTitle(location.fileName()); setWindowModified(false); QAction* openContaining = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), mMessageWidget); connect(openContaining, &QAction::triggered, [=] { KIO::highlightInFileManager({location});}); showInlineMessage(i18n("The screenshot was saved as %2", location.toLocalFile(), location.fileName()), KMessageWidget::Positive, MessageDuration::AutoHide, {openContaining}); } void KSMainWindow::save() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::Save); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); ExportManager::instance()->doSave(QUrl(), /* notify */ quitChecked); if (quitChecked) { quit(QuitBehavior::QuitExternally); } } void KSMainWindow::saveAs() { SpectacleConfig::instance()->setLastUsedSaveMode(SaveMode::SaveAs); setDefaultSaveAction(); const bool quitChecked = SpectacleConfig::instance()->quitAfterSaveOrCopyChecked(); if (ExportManager::instance()->doSaveAs(this, /* notify */ quitChecked) && quitChecked) { quit(QuitBehavior::QuitExternally); } } diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index 3e5104f..8dfb2e8 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,258 +1,258 @@ /* * 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 "SmartSpinBox.h" #include "SpectacleConfig.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)) mCaptureArea->insertItem(2, i18n("Current Screen"), Spectacle::CaptureMode::CurrentScreen); if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) mCaptureArea->insertItem(3, i18n("Active Window"), Spectacle::CaptureMode::ActiveWindow); 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); 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); 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); 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); 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); 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); mQuitAfterSaveOrCopy = new QCheckBox(i18n("Quit after Save or Copy"), this); mQuitAfterSaveOrCopy->setToolTip(i18n("Quit Spectacle after saving or copying the image")); connect(mQuitAfterSaveOrCopy, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setQuitAfterSaveOrCopyChecked); mContentOptionsForm = new QVBoxLayout; mContentOptionsForm->addWidget(mMousePointer); mContentOptionsForm->addWidget(mWindowDecorations); mContentOptionsForm->addWidget(mCaptureTransientOnly); mContentOptionsForm->addWidget(mQuitAfterSaveOrCopy); mContentOptionsForm->setContentsMargins(24, 0, 0, 0); // the take a new screenshot button mTakeScreenshotButton = new QPushButton(i18n("Take a New Screenshot"), this); mTakeScreenshotButton->setIcon(QIcon::fromTheme(QStringLiteral("spectacle"))); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setFocus(); connect(mTakeScreenshotButton, &QPushButton::clicked, this, &KSWidget::newScreenshotClicked); QShortcut *takeScreenshotShortcut = new QShortcut(QKeySequence(QKeySequence::New), mTakeScreenshotButton); - connect(takeScreenshotShortcut, &QShortcut::activated, [this]() { + connect(takeScreenshotShortcut, &QShortcut::activated, this, [this]() { mTakeScreenshotButton->animateClick(100); }); // 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()); } 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; } 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 lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); switch(lCaptureMode) { 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; } } diff --git a/src/Gui/SettingsDialog/SaveOptionsPage.cpp b/src/Gui/SettingsDialog/SaveOptionsPage.cpp index 830d51e..e42c78b 100644 --- a/src/Gui/SettingsDialog/SaveOptionsPage.cpp +++ b/src/Gui/SettingsDialog/SaveOptionsPage.cpp @@ -1,223 +1,223 @@ /* * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "SaveOptionsPage.h" #include "SpectacleCommon.h" #include "SpectacleConfig.h" #include "ExportManager.h" #include #include #include #include #include #include #include #include #include #include SaveOptionsPage::SaveOptionsPage(QWidget *parent) : SettingsPage(parent) { QFormLayout *mainLayout = new QFormLayout; setLayout(mainLayout); // Save location mUrlRequester = new KUrlRequester; mUrlRequester->setMode(KFile::Directory); connect(mUrlRequester, &KUrlRequester::textChanged, this, &SaveOptionsPage::markDirty); mainLayout->addRow(i18n("Save Location:"), mUrlRequester); // copy file location to clipboard after saving mCopyPathToClipboard = new QCheckBox(i18n("Copy file location to clipboard after saving"), this); connect(mCopyPathToClipboard, &QCheckBox::toggled, this, &SaveOptionsPage::markDirty); mainLayout->addRow(QString(), mCopyPathToClipboard); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // Compression quality slider and current value display QHBoxLayout *sliderHorizLayout = new QHBoxLayout(); QVBoxLayout *sliderVertLayout = new QVBoxLayout(); // Current value QLabel *qualityValue = new QLabel(); qualityValue->setNum(SpectacleConfig::instance()->compressionQuality()); qualityValue->setMinimumWidth(qualityValue->fontInfo().pointSize()*3); // Slider mQualitySlider = new QSlider(Qt::Horizontal); mQualitySlider->setRange(0, 100); mQualitySlider->setTickInterval(5); mQualitySlider->setSliderPosition(SpectacleConfig::instance()->compressionQuality()); mQualitySlider->setTickPosition(QSlider::TicksBelow); mQualitySlider->setTracking(true); - connect(mQualitySlider, &QSlider::valueChanged, [=](int value) { + connect(mQualitySlider, &QSlider::valueChanged, this, [=](int value) { qualityValue->setNum(value); markDirty(); }); sliderHorizLayout->addWidget(mQualitySlider); sliderHorizLayout->addWidget(qualityValue); sliderVertLayout->addLayout(sliderHorizLayout); QLabel *qualitySliderDescription = new QLabel(); qualitySliderDescription->setText(i18n("Choose the image quality when saving with lossy image formats like JPEG")); sliderVertLayout->addWidget(qualitySliderDescription); mainLayout->addRow(i18n("Compression Quality:"), sliderVertLayout); mainLayout->addItem(new QSpacerItem(0, 18, QSizePolicy::Fixed, QSizePolicy::Fixed)); // filename chooser text field QHBoxLayout *saveFieldLayout = new QHBoxLayout; mSaveNameFormat = new QLineEdit; connect(mSaveNameFormat, &QLineEdit::textEdited, this, &SaveOptionsPage::markDirty); - connect(mSaveNameFormat, &QLineEdit::textEdited, [&](const QString &newText) { + connect(mSaveNameFormat, &QLineEdit::textEdited, this, [&](const QString &newText) { QString fmt; Q_FOREACH(auto item, QImageWriter::supportedImageFormats()) { fmt = QString::fromLocal8Bit(item); if (newText.endsWith(QLatin1Char('.') + fmt, Qt::CaseInsensitive)) { QString txtCopy = newText; txtCopy.chop(fmt.length() + 1); mSaveNameFormat->setText(txtCopy); mSaveImageFormat->setCurrentIndex(mSaveImageFormat->findText(fmt.toUpper())); } } }); connect(mSaveNameFormat, &QLineEdit::textChanged,this, &SaveOptionsPage::updateFilenamePreview); mSaveNameFormat->setPlaceholderText(QStringLiteral("%d")); saveFieldLayout->addWidget(mSaveNameFormat); mSaveImageFormat = new QComboBox; mSaveImageFormat->addItems([&](){ QStringList items; Q_FOREACH(auto fmt, QImageWriter::supportedImageFormats()) { items.append(QString::fromLocal8Bit(fmt).toUpper()); } return items; }()); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::markDirty); connect(mSaveImageFormat, &QComboBox::currentTextChanged, this, &SaveOptionsPage::updateFilenamePreview); saveFieldLayout->addWidget(mSaveImageFormat); mainLayout->addRow(i18n("Filename:"), saveFieldLayout); mPreviewLabel = new QLabel(this); mainLayout->addRow(i18nc("Preview of the user configured filename", "Preview:"), mPreviewLabel); // now the save filename format layout QString helpText = i18n( "You can use the following placeholders in the filename, which will be replaced " "with actual text when the file is saved:
" ); for (auto option = ExportManager::filenamePlaceholders.cbegin(); option != ExportManager::filenamePlaceholders.cend(); ++option) { helpText += QStringLiteral("%1: %2
").arg(option.key(), option.value().toString()); } helpText += QStringLiteral("/: ") + i18n("To save to a sub-folder"); helpText += QStringLiteral("
"); QLabel *fmtHelpText = new QLabel(helpText, this); fmtHelpText->setWordWrap(true); fmtHelpText->setTextFormat(Qt::RichText); fmtHelpText->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - connect(fmtHelpText, &QLabel::linkActivated, [this](const QString& placeholder) { + 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/Main.cpp b/src/Main.cpp index 4197189..866eec8 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,163 +1,163 @@ /* * 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 "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); // 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, [&](const QUrl &savedAt) { + 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/SpectacleCore.cpp b/src/SpectacleCore.cpp index bdb4dfe..5afc422 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,405 +1,405 @@ /* * 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 #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) { 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()); } // 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); 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); const Platform::GrabMode lCaptureMode = toPlatformGrabMode(theCaptureMode); - QTimer::singleShot(lMsec, [ this, lCaptureMode, lShutterMode, lIncludePointer, lIncludeDecorations ]() { + 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)); break; } setUpShortcuts(); } void SpectacleCore::setUpShortcuts() { SpectacleConfig* config = SpectacleConfig::instance(); QAction* openAction = config->shortCutActions->action(QStringLiteral("_launch")); KGlobalAccel::self()->setGlobalShortcut(openAction, Qt::Key_Print); QAction* fullScreenAction = config->shortCutActions->action(QStringLiteral("FullScreenScreenShot")); KGlobalAccel::self()->setGlobalShortcut(fullScreenAction, Qt::SHIFT + Qt::Key_Print); QAction* activeWindowAction = config->shortCutActions->action(QStringLiteral("ActiveWindowScreenShot")); KGlobalAccel::self()->setGlobalShortcut(activeWindowAction, Qt::META + Qt::Key_Print); QAction* regionAction = config->shortCutActions->action(QStringLiteral("RectangularRegionScreenShot")); KGlobalAccel::self()->setGlobalShortcut(regionAction, Qt::META + Qt::SHIFT + Qt::Key_Print); QAction* currentScreenAction = config->shortCutActions->action(QStringLiteral("CurrentMonitorScreenShot")); KGlobalAccel::self()->setGlobalShortcut(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()) { 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, lShutterMode, lGrabMode, lIncludePointer, lIncludeDecorations]() { + 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, lGrabMode, theIncludePointer, theIncludeDecorations]() { + 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); connect(mQuickEditor.get(), &QuickEditor::grabDone, this, &SpectacleCore::screenshotUpdated); connect(mQuickEditor.get(), &QuickEditor::grabCancelled, this, &SpectacleCore::screenshotFailed); mQuickEditor->showFullScreen(); 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) { emit allDone(); } } break; case StartMode::Gui: mMainWindow->setScreenshotAndShow(thePixmap); } } 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->activateWindow(); mMainWindow->show(); } } 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, &SpectacleCore::allDone); } }); } connect(lNotify, &QObject::destroyed, this, &SpectacleCore::allDone); lNotify->sendEvent(); } void SpectacleCore::doCopyPath(const QUrl &savedAt) { if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { 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, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() { + QTimer::singleShot(0, this, [this, lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations]() { mPlatform->doGrab(lShutterMode, lGrabMode, theIncludePointer, theIncludeDecorations); }); } }