diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 747406e..fa13c16 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,432 +1,432 @@ /* * 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 "KSMainWindow.h" #include "Config.h" #include "SettingsDialog/SettingsDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef XCB_FOUND #include #include #endif static const int DEFAULT_WINDOW_HEIGHT = 420; static const int DEFAULT_WINDOW_WIDTH = 840; static const int MAXIMUM_WINDOW_WIDTH = 1000; -KSMainWindow::KSMainWindow(bool onClickAvailable, QWidget *parent) : +KSMainWindow::KSMainWindow(const QVector& supportedModes, bool onClickAvailable, QWidget *parent) : QDialog(parent), - mKSWidget(new KSWidget(this)), + mKSWidget(new KSWidget(supportedModes, 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)), mOnClickAvailable(onClickAvailable) { // 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 - done: QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } KSMainWindow::~KSMainWindow() {} // 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::setScreenshotWindowTitle); // 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("application-menu"))); mDialogButtonBox->addButton(mToolsButton, QDialogButtonBox::ActionRole); mToolsButton->setMenu(mToolsMenu); KGuiItem::assign(mSendToButton, KGuiItem(i18n("Export"))); mSendToButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share"))); mDialogButtonBox->addButton(mSendToButton, QDialogButtonBox::ActionRole); mClipboardButton->setDefaultAction(KStandardAction::copy(this, SLOT(sendToClipboard()), this)); mClipboardButton->setText(i18n("Copy to Clipboard")); mClipboardButton->setToolTip(i18n("Copy the current screenshot image to the clipboard.")); mClipboardButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mDialogButtonBox->addButton(mClipboardButton, QDialogButtonBox::ActionRole); mSaveButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); mSaveButton->setMenu(mSaveMenu); mSaveButton->setPopupMode(QToolButton::MenuButtonPopup); mDialogButtonBox->addButton(mSaveButton, QDialogButtonBox::ActionRole); // the help menu KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); mDialogButtonBox->button(QDialogButtonBox::Help)->setMenu(helpMenu->menu()); // the tools menu mToolsMenu->addAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Screenshots Folder"), this, &KSMainWindow::openScreenshotsFolder); mToolsMenu->addAction(KStandardAction::print(this, &KSMainWindow::showPrintDialog, this)); mScreenRecorderToolsMenu = mToolsMenu->addMenu(i18n("Record Screen")); connect(mScreenRecorderToolsMenu, &QMenu::aboutToShow, [this]() { KMoreToolsMenuFactory *moreToolsMenuFactory = new KMoreToolsMenuFactory(QStringLiteral("spectacle/screenrecorder-tools")); moreToolsMenuFactory->setParentWidget(this); mScreenrecorderToolsMenuFactory.reset(moreToolsMenuFactory); mScreenRecorderToolsMenu->clear(); mScreenrecorderToolsMenuFactory->fillMenuFromGroupingNames(mScreenRecorderToolsMenu, { QStringLiteral("screenrecorder") }); } ); // the save menu mSaveAsAction = KStandardAction::saveAs(this, &KSMainWindow::saveAs, this); mSaveAction = KStandardAction::save(this, &KSMainWindow::save, this); mSaveMenu->addAction(mSaveAsAction); mSaveMenu->addAction(mSaveAction); setDefaultSaveAction(); // message widget connect(mMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &str) { QDesktopServices::openUrl(QUrl(str)); } ); // layouts mDivider->setFrameShape(QFrame::HLine); mDivider->setLineWidth(2); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(mKSWidget); layout->addWidget(mMessageWidget); layout->addWidget(mDivider); layout->addWidget(mDialogButtonBox); mMessageWidget->hide(); // populate our send-to actions mSendToButton->setMenu(mExportMenu); connect(mExportMenu, &ExportMenu::imageShared, this, &KSMainWindow::showImageSharedFeedback); // disable onClick mode if not available on the platform if (!mOnClickAvailable) { mKSWidget->disableOnClick(); } 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); // 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(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations) { hide(); mMessageWidget->hide(); emit newScreenshotRequest(mode, timeout, includePointer, includeDecorations); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); show(); resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT)); } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::openScreenshotsFolder() { // Highlight last screenshot in file manager if user saved at least once ever // (since last save and saveas file names are stored in spectaclerc), otherwise, // if in save mode, open default save location from configure > save > location // if in save as mode, open last save as files location // failsafe for either option is default save location from configure > save > location SpectacleConfig *cfgManager = SpectacleConfig::instance(); ExportManager *exportManager = ExportManager::instance(); QUrl location; switch(cfgManager->lastUsedSaveMode()) { case SaveMode::Save: location = cfgManager->lastSaveFile(); if (!exportManager->isFileExists(location)) { location = QUrl(cfgManager->defaultSaveLocation()); } 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) { 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; default: ; } mMessageWidget->animatedShow(); if (messageDuration == MessageDuration::AutoHide) { QTimer::singleShot(10000, mMessageWidget, &KMessageWidget::animatedHide); } } 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() { ExportManager::instance()->doCopyToClipboard(); 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::setScreenshotWindowTitle(QUrl location) { setWindowTitle(location.fileName()); setWindowModified(false); } 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/KSMainWindow.h b/src/Gui/KSMainWindow.h index 3285cd9..a0ba79a 100644 --- a/src/Gui/KSMainWindow.h +++ b/src/Gui/KSMainWindow.h @@ -1,112 +1,112 @@ /* * 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 KSMAINWINDOW_H #define KSMAINWINDOW_H #include #include #include #include #include #include #include "PlatformBackends/ImageGrabber.h" #include "ExportMenu.h" #include "KSWidget.h" #include "SpectacleConfig.h" class KMoreToolsMenuFactory; class KSMainWindow : public QDialog { Q_OBJECT public: - explicit KSMainWindow(bool onClickAvailable, QWidget *parent = nullptr); + explicit KSMainWindow(const QVector& supportedModes, bool onClickAvailable, QWidget *parent = nullptr); ~KSMainWindow() Q_DECL_OVERRIDE; private: enum class QuitBehavior { QuitImmediately, QuitExternally }; void quit(const QuitBehavior quitBehavior = QuitBehavior::QuitImmediately); enum class MessageDuration { AutoHide, Persistent }; void showInlineMessage(const QString& message, const KMessageWidget::MessageType messageType, const MessageDuration messageDuration = MessageDuration::AutoHide); private Q_SLOTS: void captureScreenshot(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations); void showPrintDialog(); void openScreenshotsFolder(); void showPreferencesDialog(); void showImageSharedFeedback(bool error, const QString &message); void sendToClipboard(); void init(); void setDefaultSaveAction(); void save(); void saveAs(); int windowWidth(const QPixmap &pixmap) const; public Q_SLOTS: void setScreenshotAndShow(const QPixmap &pixmap); void setScreenshotWindowTitle(QUrl location); Q_SIGNALS: void newScreenshotRequest(ImageGrabber::GrabMode mode, int timeout, bool includePointer, bool includeDecorations); void dragAndDropRequest(); protected: void moveEvent(QMoveEvent *event) Q_DECL_OVERRIDE; private: KSWidget *mKSWidget; QFrame *mDivider; QDialogButtonBox *mDialogButtonBox; QToolButton *mConfigureButton; QPushButton *mToolsButton; QPushButton *mSendToButton; QToolButton *mClipboardButton; QToolButton *mSaveButton; QMenu *mSaveMenu; QAction *mSaveAsAction; QAction *mSaveAction; KMessageWidget *mMessageWidget; QMenu *mToolsMenu; QMenu *mScreenRecorderToolsMenu; QScopedPointer mScreenrecorderToolsMenuFactory; ExportMenu *mExportMenu; bool mOnClickAvailable; }; #endif // KSMAINWINDOW_H diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index c919a60..1a3ee2d 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,242 +1,248 @@ /* * 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 -KSWidget::KSWidget(QWidget *parent) : +KSWidget::KSWidget(const QVector& supportedModes, QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager SpectacleConfig *configManager = 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); - mCaptureArea->insertItem(1, i18n("Full Screen (All Monitors)"), ImageGrabber::FullScreen); - mCaptureArea->insertItem(2, i18n("Current Screen"), ImageGrabber::CurrentScreen); - mCaptureArea->insertItem(3, i18n("Active Window"), ImageGrabber::ActiveWindow); - mCaptureArea->insertItem(4, i18n("Window Under Cursor"), ImageGrabber::WindowUnderCursor); - mCaptureArea->insertItem(5, i18n("Rectangular Region"), ImageGrabber::RectangularRegion); + if (supportedModes.contains(ImageGrabber::FullScreen)) + mCaptureArea->insertItem(1, i18n("Full Screen (All Monitors)"), ImageGrabber::FullScreen); + if (supportedModes.contains(ImageGrabber::CurrentScreen)) + mCaptureArea->insertItem(2, i18n("Current Screen"), ImageGrabber::CurrentScreen); + if (supportedModes.contains(ImageGrabber::ActiveWindow)) + mCaptureArea->insertItem(3, i18n("Active Window"), ImageGrabber::ActiveWindow); + if (supportedModes.contains(ImageGrabber::WindowUnderCursor)) + mCaptureArea->insertItem(4, i18n("Window Under Cursor"), ImageGrabber::WindowUnderCursor); + if (supportedModes.contains(ImageGrabber::RectangularRegion)) + mCaptureArea->insertItem(5, i18n("Rectangular Region"), ImageGrabber::RectangularRegion); mCaptureArea->setMinimumWidth(240); connect(mCaptureArea, static_cast(&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, static_cast(&SmartSpinBox::valueChanged), configManager, &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, configManager, &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, configManager, &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, configManager, &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, configManager, &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, configManager, &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]() { 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 (configManager->includePointerChecked()); mWindowDecorations->setChecked (configManager->includeDecorationsChecked()); mCaptureOnClick->setChecked (configManager->onClickChecked()); mCaptureTransientOnly->setChecked (configManager->captureTransientWindowOnlyChecked()); mQuitAfterSaveOrCopy->setChecked (configManager->quitAfterSaveOrCopyChecked()); - mCaptureArea->setCurrentIndex (configManager->captureMode()); + if (configManager->captureMode()>=0) + mCaptureArea->setCurrentIndex (configManager->captureMode()); mDelayMsec->setValue (configManager->captureDelay()); // done } int KSWidget::imagePaddingWidth() const { int rightLayoutLeft = 0; int rightLayoutRight = 0; int mainLayoutRight = 0; mRightLayout->getContentsMargins(&rightLayoutLeft, nullptr, &rightLayoutRight, nullptr); mMainLayout->getContentsMargins(nullptr, nullptr, &mainLayoutRight, nullptr); int paddingWidth = (rightLayoutLeft + rightLayoutRight + mainLayoutRight); paddingWidth += mRightLayout->contentsRect().width(); paddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow return paddingWidth; } // public slots void KSWidget::setScreenshotPixmap(const QPixmap &pixmap) { mImageWidget->setScreenshot(pixmap); } void KSWidget::disableOnClick() { mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { int delay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->currentData().toInt()); if (mode == ImageGrabber::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { mode = ImageGrabber::TransientWithParent; } emit newScreenshotRequest(mode, delay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } void KSWidget::onClickStateChanged(int state) { if (state == Qt::Checked) { mDelayMsec->setEnabled(false); } else if (state == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } void KSWidget::captureModeChanged(int index) { SpectacleConfig::instance()->setCaptureMode(index); ImageGrabber::GrabMode mode = static_cast(mCaptureArea->itemData(index).toInt()); switch (mode) { case ImageGrabber::WindowUnderCursor: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(true); break; case ImageGrabber::ActiveWindow: mWindowDecorations->setEnabled(true); mCaptureTransientOnly->setEnabled(false); break; case ImageGrabber::FullScreen: case ImageGrabber::CurrentScreen: case ImageGrabber::RectangularRegion: mWindowDecorations->setEnabled(false); mCaptureTransientOnly->setEnabled(false); break; case ImageGrabber::TransientWithParent: case ImageGrabber::InvalidChoice: default: qCWarning(SPECTACLE_GUI_LOG) << "Skipping invalid or unreachable enum value"; break; } } diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h index 91f1509..a898da2 100644 --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -1,86 +1,86 @@ /* * 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 KSWIDGET_H #define KSWIDGET_H #include #include #include "PlatformBackends/ImageGrabber.h" class QGridLayout; class QHBoxLayout; class QVBoxLayout; class QFormLayout; class QComboBox; class QCheckBox; class QLabel; class QPushButton; class KSImageWidget; class SmartSpinBox; class KSWidget : public QWidget { Q_OBJECT public: - explicit KSWidget(QWidget *parent = nullptr); + explicit KSWidget(const QVector& supportedModes, QWidget *parent = nullptr); int imagePaddingWidth() const; Q_SIGNALS: void dragInitiated(); void newScreenshotRequest(ImageGrabber::GrabMode mode, int captureDelay, bool capturePointer, bool captureDecorations); public Q_SLOTS: void setScreenshotPixmap(const QPixmap &pixmap); void disableOnClick(); private Q_SLOTS: void newScreenshotClicked(); void onClickStateChanged(int state); void captureModeChanged(int index); private: QGridLayout *mMainLayout; QHBoxLayout *mDelayLayout; QVBoxLayout *mRightLayout; QFormLayout *mCaptureModeForm; QVBoxLayout *mContentOptionsForm; KSImageWidget *mImageWidget; QPushButton *mTakeScreenshotButton; QComboBox *mCaptureArea; SmartSpinBox *mDelayMsec; QCheckBox *mCaptureOnClick; QCheckBox *mMousePointer; QCheckBox *mWindowDecorations; QCheckBox *mCaptureTransientOnly; QCheckBox *mQuitAfterSaveOrCopy; QLabel *mCaptureModeLabel; QLabel *mContentOptionsLabel; }; #endif // KSWIDGET_H diff --git a/src/PlatformBackends/DummyImageGrabber.h b/src/PlatformBackends/DummyImageGrabber.h index 55ab652..f699320 100644 --- a/src/PlatformBackends/DummyImageGrabber.h +++ b/src/PlatformBackends/DummyImageGrabber.h @@ -1,50 +1,51 @@ /* * 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 DUMMYIMAGEGRABBER_H #define DUMMYIMAGEGRABBER_H #include #include #include "ImageGrabber.h" class DummyImageGrabber : public ImageGrabber { Q_OBJECT public: explicit DummyImageGrabber(QObject *parent = nullptr); ~DummyImageGrabber() Q_DECL_OVERRIDE; + QVector supportedModes() const override { return {FullScreen, CurrentScreen, ActiveWindow, WindowUnderCursor, TransientWithParent, RectangularRegion}; } bool onClickGrabSupported() const Q_DECL_OVERRIDE; protected: QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) Q_DECL_OVERRIDE; void grabFullScreen() Q_DECL_OVERRIDE; void grabCurrentScreen() Q_DECL_OVERRIDE; void grabActiveWindow() Q_DECL_OVERRIDE; void grabRectangularRegion() Q_DECL_OVERRIDE; void grabWindowUnderCursor() Q_DECL_OVERRIDE; void grabTransientWithParent() Q_DECL_OVERRIDE; }; #endif // DUMMYIMAGEGRABBER_H diff --git a/src/PlatformBackends/ImageGrabber.h b/src/PlatformBackends/ImageGrabber.h index bacf0cb..ec91277 100644 --- a/src/PlatformBackends/ImageGrabber.h +++ b/src/PlatformBackends/ImageGrabber.h @@ -1,99 +1,100 @@ /* * 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 IMAGEGRABBER_H #define IMAGEGRABBER_H #include #include #include #include #include #include #include "QuickEditor/QuickEditor.h" class ImageGrabber : public QObject { Q_OBJECT Q_PROPERTY(QPixmap pixmap READ pixmap NOTIFY pixmapChanged) Q_PROPERTY(bool capturePointer READ capturePointer WRITE setCapturePointer NOTIFY capturePointerChanged) Q_PROPERTY(bool captureDecorations READ captureDecorations WRITE setCaptureDecorations NOTIFY captureDecorationsChanged) Q_PROPERTY(GrabMode grabMode READ grabMode WRITE setGrabMode NOTIFY grabModeChanged) public: enum GrabMode { InvalidChoice = -1, FullScreen = 0, CurrentScreen = 1, ActiveWindow = 2, WindowUnderCursor = 3, TransientWithParent = 4, RectangularRegion = 5 }; Q_ENUM(GrabMode) explicit ImageGrabber(QObject *parent = nullptr); ~ImageGrabber(); QPixmap pixmap() const; bool capturePointer() const; bool captureDecorations() const; GrabMode grabMode() const; + virtual QVector supportedModes() const = 0; virtual bool onClickGrabSupported() const; void setCapturePointer(const bool newCapturePointer); void setCaptureDecorations(const bool newCaptureDecorations); void setGrabMode(const GrabMode newGrabMode); Q_SIGNALS: void pixmapChanged(const QPixmap &pixmap); void windowTitleChanged(const QString &windowTitle); void imageGrabFailed(); void capturePointerChanged(bool capturePointer); void captureDecorationsChanged(bool captureDecorations); void grabModeChanged(GrabMode grabMode); public Q_SLOTS: virtual void doImageGrab(); virtual void doOnClickGrab(); protected: virtual void grabFullScreen() = 0; virtual void grabCurrentScreen() = 0; virtual void grabActiveWindow() = 0; virtual void grabRectangularRegion() = 0; virtual void grabWindowUnderCursor() = 0; virtual void grabTransientWithParent() = 0; virtual QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) = 0; bool mCapturePointer; bool mCaptureDecorations; GrabMode mGrabMode; QPixmap mPixmap; }; #endif // IMAGEGRABBER_H diff --git a/src/PlatformBackends/KWinWaylandImageGrabber.h b/src/PlatformBackends/KWinWaylandImageGrabber.h index a182f3d..4fe3d1f 100644 --- a/src/PlatformBackends/KWinWaylandImageGrabber.h +++ b/src/PlatformBackends/KWinWaylandImageGrabber.h @@ -1,59 +1,60 @@ /* * Copyright (C) 2016 Martin Graesslin * * 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 KWINWAYLANDIMAGEGRABBER_H #define KWINWAYLANDIMAGEGRABBER_H #include "ImageGrabber.h" class KWinWaylandImageGrabber : public ImageGrabber { Q_OBJECT public: explicit KWinWaylandImageGrabber(QObject * parent = nullptr); ~KWinWaylandImageGrabber() Q_DECL_OVERRIDE; + QVector supportedModes() const override { return {FullScreen, CurrentScreen, /*ActiveWindow, */WindowUnderCursor, TransientWithParent/*, RectangularRegion*/}; } bool onClickGrabSupported() const Q_DECL_OVERRIDE; protected: void grabFullScreen() Q_DECL_OVERRIDE; void grabCurrentScreen() Q_DECL_OVERRIDE; void grabActiveWindow() Q_DECL_OVERRIDE; void grabRectangularRegion() Q_DECL_OVERRIDE; void grabWindowUnderCursor() Q_DECL_OVERRIDE; void grabTransientWithParent() Q_DECL_OVERRIDE; QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) Q_DECL_OVERRIDE; private: void startReadImage(int readPipe); enum class Mode { Window, CurrentScreen, FullScreen }; template void callDBus(Mode mode, int writeFd, T argument); template void grab(Mode mode, T argument); }; #endif diff --git a/src/PlatformBackends/X11ImageGrabber.h b/src/PlatformBackends/X11ImageGrabber.h index b4b036a..98bfb3e 100644 --- a/src/PlatformBackends/X11ImageGrabber.h +++ b/src/PlatformBackends/X11ImageGrabber.h @@ -1,104 +1,105 @@ /* * 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 X11IMAGEGRABBER_H #define X11IMAGEGRABBER_H #include #include #include #include "ImageGrabber.h" class X11ImageGrabber; class OnClickEventFilter : public QAbstractNativeEventFilter { public: explicit OnClickEventFilter(X11ImageGrabber *grabber); bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) Q_DECL_OVERRIDE; private: X11ImageGrabber *mImageGrabber; }; class X11ImageGrabber : public ImageGrabber { Q_OBJECT public: explicit X11ImageGrabber(QObject * parent = nullptr); ~X11ImageGrabber() Q_DECL_OVERRIDE; + QVector supportedModes() const override { return {FullScreen, CurrentScreen, ActiveWindow, WindowUnderCursor, TransientWithParent, RectangularRegion}; } bool onClickGrabSupported() const Q_DECL_OVERRIDE; protected: void grabFullScreen() Q_DECL_OVERRIDE; void grabCurrentScreen() Q_DECL_OVERRIDE; void grabActiveWindow() Q_DECL_OVERRIDE; void grabRectangularRegion() Q_DECL_OVERRIDE; void grabWindowUnderCursor() Q_DECL_OVERRIDE; void grabTransientWithParent() Q_DECL_OVERRIDE; QPixmap blendCursorImage(const QPixmap &pixmap, int x, int y, int width, int height) Q_DECL_OVERRIDE; private Q_SLOTS: void KWinDBusScreenshotHelper(quint64 window); void rectangleSelectionConfirmed(const QPixmap &pixmap, const QRect ®ion); void rectangleSelectionCancelled(); public Q_SLOTS: void doOnClickGrab() Q_DECL_OVERRIDE; private: bool isKWinAvailable(); xcb_window_t getRealWindowUnderCursor(); void grabApplicationWindowHelper(xcb_window_t window); QRect getDrawableGeometry(xcb_drawable_t drawable); QPixmap postProcessPixmap(QPixmap pixmap, QRect rect, bool blendPointer); QPixmap getPixmapFromDrawable(xcb_drawable_t drawableId, const QRect &rect); QPixmap getToplevelPixmap(QRect rect, bool blendPointer); QPixmap getWindowPixmap(xcb_window_t window, bool blendPointer); QPixmap convertFromNative(xcb_image_t *xcbImage); xcb_window_t getTransientWindowParent(xcb_window_t winId, QRect &outRect); QPoint getNativeCursorPosition(); OnClickEventFilter *mNativeEventFilter; void updateWindowTitle(xcb_window_t window); }; template using CScopedPointer = QScopedPointer; struct ScopedPointerXcbImageDeleter { static inline void cleanup(xcb_image_t *xcbImage) { if (xcbImage) { xcb_image_destroy(xcbImage); } } }; #endif // X11IMAGEGRABBER_H diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp index 8c7a265..2eb4eb3 100644 --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -1,304 +1,304 @@ /* * 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" SpectacleConfig::SpectacleConfig(QObject *parent) : QObject(parent) { mConfig = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); mGeneralConfig = KConfigGroup(mConfig, "General"); mGuiConfig = KConfigGroup(mConfig, "GuiConfig"); } SpectacleConfig::~SpectacleConfig() {} SpectacleConfig* SpectacleConfig::instance() { static SpectacleConfig instance; return &instance; } QString SpectacleConfig::defaultFilename() const { return QStringLiteral("Screenshot"); } QString SpectacleConfig::defaultTimestampTemplate() const { // includes separator at the front return QStringLiteral("_%Y%M%D_%H%m%S"); } // lastSaveAsLocation QUrl SpectacleConfig::lastSaveAsFile() const { return mGeneralConfig.readEntry(QStringLiteral("lastSaveAsFile"), QUrl(this->defaultSaveLocation())); } void SpectacleConfig::setLastSaveAsFile(const QUrl &location) { mGeneralConfig.writeEntry(QStringLiteral("lastSaveAsFile"), location); mGeneralConfig.sync(); } QUrl SpectacleConfig::lastSaveAsLocation() const { return this->lastSaveAsFile().adjusted(QUrl::RemoveFilename); } // lastSaveLocation QUrl SpectacleConfig::lastSaveFile() const { return mGeneralConfig.readEntry(QStringLiteral("lastSaveFile"), QUrl(this->defaultSaveLocation())); } 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(); } // 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 mGuiConfig.readEntry(QStringLiteral("captureModeIndex"), 0); + 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"), false); } void SpectacleConfig::setRememberLastRectangularRegion(bool enabled) { mGuiConfig.writeEntry(QStringLiteral("rememberLastRectangularRegion"), 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(); } // last used save mode SaveMode SpectacleConfig::lastUsedSaveMode() const { switch (mGuiConfig.readEntry(QStringLiteral("lastUsedSaveMode"), 0)) { default: case 0: return SaveMode::SaveAs; case 1: return SaveMode::Save; } } void SpectacleConfig::setLastUsedSaveMode(SaveMode mode) { mGuiConfig.writeEntry(QStringLiteral("lastUsedSaveMode"), static_cast(mode)); mGuiConfig.sync(); } // autosave filename format QString SpectacleConfig::autoSaveFilenameFormat() const { const QString sff = mGeneralConfig.readEntry(QStringLiteral("save-filename-format"), QString(defaultFilename() + defaultTimestampTemplate())); return sff.isEmpty() ? QStringLiteral("%d") : sff; } void SpectacleConfig::setAutoSaveFilenameFormat(const QString &format) { mGeneralConfig.writeEntry(QStringLiteral("save-filename-format"), format); mGeneralConfig.sync(); } // autosave location QString SpectacleConfig::defaultSaveLocation() const { return mGeneralConfig.readPathEntry(QStringLiteral("default-save-location"), QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } void SpectacleConfig::setDefaultSaveLocation(const QString &location) { mGeneralConfig.writePathEntry(QStringLiteral("default-save-location"), location); 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(); } diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp index d885efb..3894eba 100644 --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -1,315 +1,315 @@ /* * 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 "PlatformBackends/DummyImageGrabber.h" #ifdef XCB_FOUND #include "PlatformBackends/X11ImageGrabber.h" #endif #include "PlatformBackends/KWinWaylandImageGrabber.h" #include #include #include #include #include #include #include #include #include #include #include SpectacleCore::SpectacleCore(StartMode startMode, ImageGrabber::GrabMode grabMode, QString &saveFileName, qint64 delayMsec, bool notifyOnGrab, QObject *parent) : QObject(parent), mExportManager(ExportManager::instance()), mStartMode(startMode), mNotify(notifyOnGrab), mImageGrabber(nullptr), mMainWindow(nullptr), isGuiInited(false) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("spectaclerc")); KConfigGroup guiConfig(config, "GuiConfig"); if (!(saveFileName.isEmpty() || saveFileName.isNull())) { if (QDir::isRelativePath(saveFileName)) { saveFileName = QDir::current().absoluteFilePath(saveFileName); } setFilename(saveFileName); } // We might be using the XCB platform (with Xwayland) in a wayland session, // but the X11 grabber won't work in that case. So force the Wayland grabber // in Wayland sessions. if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE"), "wayland") == 0) { mImageGrabber = new KWinWaylandImageGrabber; } #ifdef XCB_FOUND else if (KWindowSystem::isPlatformX11()) { mImageGrabber = new X11ImageGrabber; } #endif else { mImageGrabber = new DummyImageGrabber; } setGrabMode(grabMode); mImageGrabber->setCapturePointer(guiConfig.readEntry("includePointer", true)); mImageGrabber->setCaptureDecorations(guiConfig.readEntry("includeDecorations", true)); if ((!(mImageGrabber->onClickGrabSupported())) && (delayMsec < 0)) { delayMsec = 0; } connect(mExportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); connect(this, &SpectacleCore::errorMessage, this, &SpectacleCore::showErrorMessage); connect(mImageGrabber, &ImageGrabber::pixmapChanged, this, &SpectacleCore::screenshotUpdated); connect(mImageGrabber, &ImageGrabber::windowTitleChanged, mExportManager, &ExportManager::setWindowTitle); connect(mImageGrabber, &ImageGrabber::imageGrabFailed, this, &SpectacleCore::screenshotFailed); connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doCopyPath); connect(mExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); switch (startMode) { case DBusMode: default: break; case BackgroundMode: { int msec = (KWindowSystem::compositingActive() ? 200 : 50) + delayMsec; QTimer::singleShot(msec, mImageGrabber, &ImageGrabber::doImageGrab); } break; case GuiMode: initGui(); break; } } SpectacleCore::~SpectacleCore() { if (mMainWindow) { delete mMainWindow; } delete mImageGrabber; } // Q_PROPERTY stuff QString SpectacleCore::filename() const { return mFileNameString; } void SpectacleCore::setFilename(const QString &filename) { mFileNameString = filename; mFileNameUrl = QUrl::fromUserInput(filename); } ImageGrabber::GrabMode SpectacleCore::grabMode() const { return mImageGrabber->grabMode(); } void SpectacleCore::setGrabMode(const ImageGrabber::GrabMode &grabMode) { mImageGrabber->setGrabMode(grabMode); mExportManager->setGrabMode(grabMode); } // Slots void SpectacleCore::dbusStartAgent() { qApp->setQuitOnLastWindowClosed(true); if (!(mStartMode == GuiMode)) { mStartMode = GuiMode; return initGui(); } } void SpectacleCore::takeNewScreenshot(const ImageGrabber::GrabMode &mode, const int &timeout, const bool &includePointer, const bool &includeDecorations) { setGrabMode(mode); mImageGrabber->setCapturePointer(includePointer); mImageGrabber->setCaptureDecorations(includeDecorations); if (timeout < 0) { mImageGrabber->doOnClickGrab(); 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. const int msec = KWindowSystem::compositingActive() ? 200 : 50; QTimer::singleShot(timeout + msec, mImageGrabber, &ImageGrabber::doImageGrab); } void SpectacleCore::showErrorMessage(const QString &errString) { qCDebug(SPECTACLE_CORE_LOG) << "ERROR: " << errString; if (mStartMode == GuiMode) { KMessageBox::error(nullptr, errString); } } void SpectacleCore::screenshotUpdated(const QPixmap &pixmap) { mExportManager->setPixmap(pixmap); mExportManager->updatePixmapTimestamp(); switch (mStartMode) { case BackgroundMode: case DBusMode: default: { if (mNotify) { connect(mExportManager, &ExportManager::imageSaved, this, &SpectacleCore::doNotify); } QUrl savePath = (mStartMode == BackgroundMode && mFileNameUrl.isValid() && mFileNameUrl.isLocalFile()) ? mFileNameUrl : QUrl(); mExportManager->doSave(savePath); // 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 GuiMode: mMainWindow->setScreenshotAndShow(pixmap); } } void SpectacleCore::screenshotFailed() { switch (mStartMode) { case BackgroundMode: showErrorMessage(i18n("Screenshot capture canceled or failed")); emit allDone(); return; case DBusMode: default: emit grabFailed(); emit allDone(); return; case GuiMode: mMainWindow->show(); } } void SpectacleCore::doNotify(const QUrl &savedAt) { KNotification *notify = new KNotification(QStringLiteral("newScreenshotSaved")); switch(mImageGrabber->grabMode()) { case ImageGrabber::GrabMode::FullScreen: notify->setTitle(i18nc("The entire screen area was captured, heading", "Full Screen Captured")); break; case ImageGrabber::GrabMode::CurrentScreen: notify->setTitle(i18nc("The current screen was captured, heading", "Current Screen Captured")); break; case ImageGrabber::GrabMode::ActiveWindow: notify->setTitle(i18nc("The active window was captured, heading", "Active Window Captured")); break; case ImageGrabber::GrabMode::WindowUnderCursor: case ImageGrabber::GrabMode::TransientWithParent: notify->setTitle(i18nc("The window under the mouse was captured, heading", "Window Under Cursor Captured")); break; case ImageGrabber::GrabMode::RectangularRegion: notify->setTitle(i18nc("A rectangular region was captured, heading", "Rectangular Region Captured")); break; case ImageGrabber::GrabMode::InvalidChoice: default: break; } const QString &path = savedAt.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); // a speaking message is prettier than a URL, special case for the default pictures location if (path == QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)) { notify->setText(i18nc("Placeholder is filename", "A screenshot was saved as '%1' to your Pictures folder.", savedAt.fileName())); } else { notify->setText(i18n("A screenshot was saved as '%1' to '%2'.", savedAt.fileName(), path)); } notify->setActions({i18nc("Open the screenshot we just saved", "Open")}); notify->setUrls({savedAt}); connect(notify, &KNotification::action1Activated, this, [this, savedAt] { new KRun(savedAt, nullptr); QTimer::singleShot(250, this, &SpectacleCore::allDone); }); connect(notify, &QObject::destroyed, this, &SpectacleCore::allDone); notify->sendEvent(); } void SpectacleCore::doCopyPath(const QUrl &savedAt) { if (SpectacleConfig::instance()->copySaveLocationToClipboard()) { qApp->clipboard()->setText(savedAt.toLocalFile()); } } void SpectacleCore::doStartDragAndDrop() { QUrl tempFile = mExportManager->tempSave(); if (!tempFile.isValid()) { return; } QMimeData *mimeData = new QMimeData; mimeData->setUrls(QList { tempFile }); mimeData->setData(QStringLiteral("application/x-kde-suggestedfilename"), QFile::encodeName(tempFile.fileName())); QDrag *dragHandler = new QDrag(this); dragHandler->setMimeData(mimeData); dragHandler->setPixmap(mExportManager->pixmap().scaled(256, 256, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); dragHandler->exec(Qt::CopyAction); } // Private void SpectacleCore::initGui() { if (!isGuiInited) { - mMainWindow = new KSMainWindow(mImageGrabber->onClickGrabSupported()); + mMainWindow = new KSMainWindow(mImageGrabber->supportedModes(), mImageGrabber->onClickGrabSupported()); connect(mMainWindow, &KSMainWindow::newScreenshotRequest, this, &SpectacleCore::takeNewScreenshot); connect(mMainWindow, &KSMainWindow::dragAndDropRequest, this, &SpectacleCore::doStartDragAndDrop); isGuiInited = true; QMetaObject::invokeMethod(mImageGrabber, "doImageGrab", Qt::QueuedConnection); } }