diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index feeefaf..80e50dd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,113 +1,114 @@ # common - configure file and version definitions configure_file(Config.h.in ${CMAKE_CURRENT_BINARY_DIR}/Config.h) set(CMAKE_AUTORCC 1) # target if(XCB_FOUND) set( SPECTACLE_SRCS_PLATFORM_XCB Platforms/PlatformXcb.cpp ) endif() set( SPECTACLE_SRCS_PLATFORM Platforms/PlatformLoader.cpp Platforms/Platform.cpp Platforms/PlatformNull.cpp Platforms/PlatformKWinWayland.cpp ${SPECTACLE_SRCS_PLATFORM_XCB} ) set( SPECTACLE_SRCS_DEFAULT Main.cpp ExportManager.cpp SpectacleCore.cpp SpectacleConfig.cpp SpectacleDBusAdapter.cpp ${SPECTACLE_SRCS_PLATFORM} Gui/KSMainWindow.cpp Gui/KSWidget.cpp Gui/KSImageWidget.cpp Gui/ExportMenu.cpp + Gui/ProgressButton.cpp Gui/SmartSpinBox.cpp Gui/SettingsDialog/SettingsDialog.cpp Gui/SettingsDialog/SettingsPage.cpp Gui/SettingsDialog/SaveOptionsPage.cpp Gui/SettingsDialog/GeneralOptionsPage.cpp Gui/SettingsDialog/ShortcutsOptionsPage.cpp QuickEditor/QuickEditor.cpp ) ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_core_debug.h IDENTIFIER SPECTACLE_CORE_LOG CATEGORY_NAME org.kde.spectacle.core) ecm_qt_declare_logging_category(SPECTACLE_SRCS_DEFAULT HEADER spectacle_gui_debug.h IDENTIFIER SPECTACLE_GUI_LOG CATEGORY_NAME org.kde.spectacle.gui) if(KIPI_FOUND) set( SPECTACLE_SRCS_KIPI KipiInterface/KSGKipiInterface.cpp KipiInterface/KSGKipiInfoShared.cpp KipiInterface/KSGKipiImageCollectionShared.cpp KipiInterface/KSGKipiImageCollectionSelector.cpp ) endif() set( SPECTACLE_SRCS_ALL ${SPECTACLE_SRCS_DEFAULT} ${SPECTACLE_SRCS_KIPI} ) add_executable( spectacle ${SPECTACLE_SRCS_ALL} ) # link libraries target_link_libraries( spectacle Qt5::DBus Qt5::PrintSupport KF5::CoreAddons KF5::DBusAddons KF5::WidgetsAddons KF5::Notifications KF5::ConfigCore KF5::I18n KF5::KIOWidgets KF5::WindowSystem KF5::NewStuff KF5::GlobalAccel KF5::XmlGui ) if(XCB_FOUND) target_link_libraries( spectacle XCB::XFIXES XCB::IMAGE XCB::CURSOR XCB::UTIL Qt5::X11Extras ) endif() if(KIPI_FOUND) target_link_libraries ( spectacle KF5::Kipi ) endif() if(PURPOSE_FOUND) target_link_libraries ( spectacle KF5::PurposeWidgets ) endif() install(TARGETS spectacle ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/Gui/KSMainWindow.cpp b/src/Gui/KSMainWindow.cpp index 901af86..d0443dd 100644 --- a/src/Gui/KSMainWindow.cpp +++ b/src/Gui/KSMainWindow.cpp @@ -1,515 +1,517 @@ /* 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 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, [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) { if (theTimeout < 0) { // OnClick is checked (always the case on Wayland) hide(); emit newScreenshotRequest(theCaptureMode, theTimeout, theIncludePointer, theIncludeDecorations); return; } showMinimized(); mMessageWidget->hide(); QTimer* timer = new QTimer; timer->setSingleShot(true); timer->setInterval(theTimeout); auto unityUpdate = [](const QVariantMap &properties) { QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/org/kde/Spectacle"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update")); message.setArguments({QGuiApplication::desktopFileName(), properties}); QDBusConnection::sessionBus().send(message); }; auto delayAnimation = new QVariantAnimation(timer); delayAnimation->setStartValue(0.0); delayAnimation->setEndValue(1.0); delayAnimation->setDuration(timer->interval()); connect(delayAnimation, &QVariantAnimation::valueChanged, this, [=] { const double progress = delayAnimation->currentValue().toDouble(); const double timeoutInSeconds = theTimeout / 1000.0; + mKSWidget->setProgress(progress); unityUpdate({ {QStringLiteral("progress"), progress} }); setWindowTitle(i18ncp("@title:window", "%1 second", "%1 seconds", qMin(int(timeoutInSeconds), qCeil((1 - progress) * timeoutInSeconds)))); }); connect(timer, &QTimer::timeout, this, [=] { this->hide(); timer->deleteLater(); + mKSWidget->setProgress(0); unityUpdate({ {QStringLiteral("progress-visible"), false} }); emit newScreenshotRequest(theCaptureMode, 0, theIncludePointer, theIncludeDecorations); }); connect(mKSWidget, &KSWidget::screenshotCanceled, timer, [=] { timer->stop(); timer->deleteLater(); restoreWindowTitle(); unityUpdate({ {QStringLiteral("progress-visible"), false} }); }); unityUpdate({ {QStringLiteral("progress-visible"), true}, {QStringLiteral("progress"), 0 } }); timer->start(); delayAnimation->start(); } void KSMainWindow::setScreenshotAndShow(const QPixmap &pixmap) { if (!pixmap.isNull()) { mKSWidget->setScreenshotPixmap(pixmap); mExportMenu->imageUpdated(); setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); setWindowModified(true); } else { restoreWindowTitle(); } mKSWidget->setButtonState(KSWidget::State::TakeNewScreenshot); show(); activateWindow(); /* NOTE windowWidth only produces the right result if it is called after the window is visible. * Because of this the call is not moved into the if above */ if(!pixmap.isNull()) { resize(QSize(windowWidth(pixmap), DEFAULT_WINDOW_HEIGHT)); } } void KSMainWindow::showPrintDialog() { QPrinter *printer = new QPrinter(QPrinter::HighResolution); QPrintDialog printDialog(printer, this); if (printDialog.exec() == QDialog::Accepted) { ExportManager::instance()->doPrint(printer); return; } delete printer; } void KSMainWindow::openScreenshotsFolder() { // Highlight last screenshot in file manager if user saved at least once ever // (since last save and saveas file names are stored in spectaclerc), otherwise, // if in save mode, open default save location from configure > save > location // if in save as mode, open last save as files location // failsafe for either option is default save location from configure > save > location SpectacleConfig *cfgManager = SpectacleConfig::instance(); ExportManager *exportManager = ExportManager::instance(); QUrl location; switch(cfgManager->lastUsedSaveMode()) { case SaveMode::Save: location = cfgManager->lastSaveFile(); if (!exportManager->isFileExists(location)) { location = cfgManager->defaultSaveLocation(); } break; case SaveMode::SaveAs: location = cfgManager->lastSaveAsFile(); // already has a "/" at the end if (!exportManager->isFileExists(location)) { location = cfgManager->lastSaveAsLocation(); } break; } KIO::highlightInFileManager({location}); } 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) { const auto messageWidgetActions = mMessageWidget->actions(); for (QAction* action: messageWidgetActions) { mMessageWidget->removeAction(action); } for (QAction* action : actions) { mMessageWidget->addAction(action); } mMessageWidget->setText(message); mMessageWidget->setMessageType(messageType); switch (messageType) { case KMessageWidget::Error: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); break; case KMessageWidget::Warning: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); break; case KMessageWidget::Positive: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); break; case KMessageWidget::Information: mMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); break; } mHideMessageWidgetTimer->stop(); mMessageWidget->animatedShow(); if (messageDuration == MessageDuration::AutoHide) { mHideMessageWidgetTimer->start(); } } void KSMainWindow::showImageSharedFeedback(bool error, const QString &message) { if (error == 1) { // error == 1 means the user cancelled the sharing return; } if (error) { showInlineMessage(i18n("There was a problem sharing the image: %1", message), KMessageWidget::Error); } else { if (message.isEmpty()) { showInlineMessage(i18n("Image shared"), KMessageWidget::Positive); } else { showInlineMessage(i18n("The shared image link (%1) has been copied to the clipboard.", message), KMessageWidget::Positive, MessageDuration::Persistent); QApplication::clipboard()->setText(message); } } } void KSMainWindow::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.toString(), 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); } } void KSMainWindow::restoreWindowTitle() { if (isWindowModified()) { setWindowTitle(i18nc("@title:window Unsaved Screenshot", "Unsaved[*]")); } else { setWindowTitle(SpectacleConfig::instance()->lastSaveFile().fileName()); } } diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp index f53b694..c33cf0b 100644 --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -1,279 +1,287 @@ /* * 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 "ProgressButton.h" #include #include #include #include #include #include #include #include #include #include KSWidget::KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent) : QWidget(parent) { // get a handle to the configuration manager SpectacleConfig *lConfigMgr = SpectacleConfig::instance(); // we'll init the widget that holds the image first mImageWidget = new KSImageWidget(this); mImageWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); connect(mImageWidget, &KSImageWidget::dragInitiated, this, &KSWidget::dragInitiated); // the capture mode options first mCaptureModeLabel = new QLabel(i18n("Capture Mode"), this); mCaptureArea = new QComboBox(this); QString lFullScreenLabel = QApplication::screens().count() == 1 ? i18n("Full Screen") : i18n("Full Screen (All Monitors)"); if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) mCaptureArea->insertItem(1, lFullScreenLabel, Spectacle::CaptureMode::AllScreens); if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) 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); mTakeNewScreenshotAction = new QAction(QIcon::fromTheme(QStringLiteral("spectacle")), i18n("Take a New Screenshot"), this); mTakeNewScreenshotAction->setShortcut(QKeySequence::New); connect(mTakeNewScreenshotAction, &QAction::triggered, this, &KSWidget::newScreenshotClicked); mCancelAction = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); mCancelAction->setShortcut(QKeySequence::Cancel); connect(mCancelAction, &QAction::triggered, this, [this] { emit screenshotCanceled(); setButtonState(State::TakeNewScreenshot); }); // the take a new screenshot button - mTakeScreenshotButton = new QToolButton(this); + mTakeScreenshotButton = new ProgressButton(this); mTakeScreenshotButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mTakeScreenshotButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); setButtonState(State::TakeNewScreenshot); mTakeScreenshotButton->setFocus(); // finally, finish up the layouts mRightLayout = new QVBoxLayout; mRightLayout->addStretch(1); mRightLayout->addWidget(mCaptureModeLabel); mRightLayout->addLayout(mCaptureModeForm); mRightLayout->addStretch(1); mRightLayout->addWidget(mContentOptionsLabel); mRightLayout->addLayout(mContentOptionsForm); mRightLayout->addStretch(10); mRightLayout->addWidget(mTakeScreenshotButton, 1, Qt::AlignHCenter); mRightLayout->setContentsMargins(10, 0, 0, 10); mMainLayout = new QGridLayout(this); mMainLayout->addWidget(mImageWidget, 0, 0, 1, 1); mMainLayout->addLayout(mRightLayout, 0, 1, 1, 1); mMainLayout->setColumnMinimumWidth(0, 320); mMainLayout->setColumnMinimumWidth(1, 320); // and read in the saved checkbox states and capture mode indices mMousePointer->setChecked (lConfigMgr->includePointerChecked()); mWindowDecorations->setChecked (lConfigMgr->includeDecorationsChecked()); mCaptureOnClick->setChecked (lConfigMgr->onClickChecked()); mCaptureTransientOnly->setChecked (lConfigMgr->captureTransientWindowOnlyChecked()); mQuitAfterSaveOrCopy->setChecked (lConfigMgr->quitAfterSaveOrCopyChecked()); if (lConfigMgr->captureMode() >= 0) { mCaptureArea->setCurrentIndex (lConfigMgr->captureMode()); } mDelayMsec->setValue (lConfigMgr->captureDelay()); } int KSWidget::imagePaddingWidth() const { int lRightLayoutLeft = 0; int lRightLayoutRight = 0; int lMainLayoutRight = 0; mRightLayout->getContentsMargins(&lRightLayoutLeft, nullptr, &lRightLayoutRight, nullptr); mMainLayout->getContentsMargins(nullptr, nullptr, &lMainLayoutRight, nullptr); int lPaddingWidth = (lRightLayoutLeft + lRightLayoutRight + lMainLayoutRight); lPaddingWidth += mRightLayout->contentsRect().width(); lPaddingWidth += 2 * SpectacleImage::SHADOW_RADIUS; // image drop shadow return lPaddingWidth; } // public slots void KSWidget::setScreenshotPixmap(const QPixmap &thePixmap) { mImageWidget->setScreenshot(thePixmap); } void KSWidget::lockOnClickEnabled() { mCaptureOnClick->setCheckState(Qt::Checked); mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(false); } void KSWidget::lockOnClickDisabled() { mCaptureOnClick->setCheckState(Qt::Unchecked); mCaptureOnClick->setEnabled(false); mDelayMsec->setEnabled(true); } // private slots void KSWidget::newScreenshotClicked() { int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); auto lMode = static_cast(mCaptureArea->currentData().toInt()); if (mTransientWithParentAvailable && lMode == Spectacle::CaptureMode::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { lMode = Spectacle::CaptureMode::TransientWithParent; } setButtonState(State::Cancel); emit newScreenshotRequest(lMode, lDelay, mMousePointer->isChecked(), mWindowDecorations->isChecked()); } void KSWidget::onClickStateChanged(int theState) { if (theState == Qt::Checked) { mDelayMsec->setEnabled(false); } else if (theState == Qt::Unchecked) { mDelayMsec->setEnabled(true); } } void KSWidget::captureModeChanged(int theIndex) { SpectacleConfig::instance()->setCaptureMode(theIndex); Spectacle::CaptureMode 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; } } void KSWidget::setButtonState(State state) { switch (state) { case State::TakeNewScreenshot: mTakeScreenshotButton->removeAction(mCancelAction); mTakeScreenshotButton->setDefaultAction(mTakeNewScreenshotAction); + mTakeScreenshotButton->setProgress(0); break; case State::Cancel: mTakeScreenshotButton->removeAction(mTakeNewScreenshotAction); mTakeScreenshotButton->setDefaultAction(mCancelAction); break; } } + +void KSWidget::setProgress(double progress) +{ + mTakeScreenshotButton->setProgress(progress); +} + diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h index d91d94c..5561543 100644 --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -1,99 +1,101 @@ /* This file is part of Spectacle, the KDE screenshot utility * Copyright (C) 2015 Boudhayan Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * SPDX-License-Identifier: LGPL-2.0-or-later */ #pragma once #include #include #include "SpectacleCommon.h" #include "Platforms/Platform.h" class QAction; class QGridLayout; class QHBoxLayout; class QVBoxLayout; class QFormLayout; class QComboBox; class QCheckBox; class QLabel; -class QToolButton; class KSImageWidget; +class ProgressButton; class SmartSpinBox; class KSWidget : public QWidget { Q_OBJECT public: explicit KSWidget(const Platform::GrabModes &theGrabModes, QWidget *parent = nullptr); virtual ~KSWidget() = default; + enum class State { TakeNewScreenshot, Cancel }; int imagePaddingWidth() const; Q_SIGNALS: void dragInitiated(); void newScreenshotRequest(Spectacle::CaptureMode theCaptureMode, int theCaptureDelat, bool theIncludePointer, bool theIncludeDecorations); void screenshotCanceled(); public Q_SLOTS: void setScreenshotPixmap(const QPixmap &thePixmap); void lockOnClickDisabled(); void lockOnClickEnabled(); void setButtonState(State state); + void setProgress(double progress); private Q_SLOTS: void newScreenshotClicked(); void onClickStateChanged(int theState); void captureModeChanged(int theIndex); private: QGridLayout *mMainLayout { nullptr }; QHBoxLayout *mDelayLayout { nullptr }; QVBoxLayout *mRightLayout { nullptr }; QFormLayout *mCaptureModeForm { nullptr }; QVBoxLayout *mContentOptionsForm { nullptr }; KSImageWidget *mImageWidget { nullptr }; - QToolButton *mTakeScreenshotButton; + ProgressButton*mTakeScreenshotButton; QComboBox *mCaptureArea { nullptr }; SmartSpinBox *mDelayMsec { nullptr }; QCheckBox *mCaptureOnClick { nullptr }; QCheckBox *mMousePointer { nullptr }; QCheckBox *mWindowDecorations { nullptr }; QCheckBox *mCaptureTransientOnly { nullptr }; QCheckBox *mQuitAfterSaveOrCopy { nullptr }; QLabel *mCaptureModeLabel { nullptr }; QLabel *mContentOptionsLabel { nullptr }; bool mTransientWithParentAvailable { false }; QAction *mTakeNewScreenshotAction; QAction *mCancelAction; }; diff --git a/src/Gui/ProgressButton.cpp b/src/Gui/ProgressButton.cpp new file mode 100644 index 0000000..838eafa --- /dev/null +++ b/src/Gui/ProgressButton.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ProgressButton.h" + +#include +#include + +#include + +ProgressButton::ProgressButton(QWidget* parent) + : QToolButton{parent} + , mProgress(0) +{ +} + +void ProgressButton::setProgress(double progress) +{ + mProgress = progress; + repaint(); +} + +void ProgressButton::paintEvent(QPaintEvent* event) +{ + //Draw Button without text and icon, note the missing text and icon in options + QStylePainter painter(this); + QStyleOption toolbuttonOptions; + toolbuttonOptions.initFrom(this); + if (isDown()) { + toolbuttonOptions.state.setFlag(QStyle::State_Sunken); + } else { + toolbuttonOptions.state.setFlag(QStyle::State_Raised); + } + painter.drawPrimitive(QStyle::PE_PanelButtonTool, toolbuttonOptions); + auto pal = palette(); + if (!qFuzzyIsNull(mProgress)) { + //Draw overlay + KColorScheme::adjustForeground(pal, KColorScheme::PositiveText, QPalette::Button, KColorScheme::Button); + QStyleOption overlayOption; + overlayOption.rect = layoutDirection() == Qt::LeftToRight + ? QRect(0, 0, width() * mProgress, height()) + : QRect(width() * (1-mProgress), 0, width(), height()); + overlayOption.palette = pal; + overlayOption.state.setFlag(QStyle::State_Sunken, isDown()); + painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); + painter.setOpacity(0.5); + painter.drawPrimitive(QStyle::PE_PanelButtonTool, overlayOption); + } + //Finally draw text and icon and outline + QStyleOptionToolButton labelOptions; + labelOptions.initFrom(this); + labelOptions.text = text(); + labelOptions.icon = icon(); + labelOptions.toolButtonStyle = Qt::ToolButtonTextBesideIcon; + labelOptions.iconSize = iconSize(); + labelOptions.state.setFlag(QStyle::State_Sunken, isDown()); + painter.setOpacity(1); + painter.drawControl(QStyle::CE_ToolButtonLabel, labelOptions); +} diff --git a/src/Gui/ProgressButton.h b/src/Gui/ProgressButton.h new file mode 100644 index 0000000..ea6b6f5 --- /dev/null +++ b/src/Gui/ProgressButton.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 David Redondo + * + * This library 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.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PROGRESSBUTTON_H +#define PROGRESSBUTTON_H + +#include + +/** + * @todo write docs + */ +class ProgressButton : public QToolButton +{ +public: + + ProgressButton(QWidget* parent); + + void setProgress(double progress); + +protected: + void paintEvent(QPaintEvent* event) override; + + double mProgress; +}; + +#endif // PROGRESSBUTTON_H