diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,8 @@ Gui/ExportMenu.cpp Gui/ProgressButton.cpp Gui/SmartSpinBox.cpp + Gui/CaptureAreaComboBox/CaptureModeModel.cpp + Gui/CaptureAreaComboBox/CaptureModeDelegate.cpp Gui/SettingsDialog/SettingsDialog.cpp Gui/SettingsDialog/SettingsPage.cpp Gui/SettingsDialog/SaveOptionsPage.cpp diff --git a/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.h b/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.h new file mode 100644 --- /dev/null +++ b/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.h @@ -0,0 +1,18 @@ +#ifndef COMBO_BOX_SHORTCUT_DELEGATE_H +#define COMBO_BOX_SHORTCUT_DELEGATE_H + +#include + +class QPainter; + +class CaptureModeDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + using QStyledItemDelegate::QStyledItemDelegate; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +#endif //COMBO_BOX_SHORTCUT_DELEGATE_H diff --git a/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.cpp b/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.cpp new file mode 100644 --- /dev/null +++ b/src/Gui/CaptureAreaComboBox/CaptureModeDelegate.cpp @@ -0,0 +1,28 @@ +#include "CaptureModeDelegate.h" +#include "CaptureModeModel.h" + +#include +#include + +void CaptureModeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + + QStyledItemDelegate::paint(painter, opt, index); + + const QWidget* widget = opt.widget; + QStyle *style = widget ? widget->style() : QApplication::style(); + + QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt); + if (opt.state & QStyle::State_Selected) { + painter->setPen(option.palette.color(QPalette::HighlightedText)); + } else { + painter->setPen(option.palette.color(QPalette::Text)); + } + + int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &opt, widget) + 1; + textRect.adjust(textMargin, 0, -textMargin, 0); + + QString rightText = index.data(ShortcutRole).toString(); + painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, rightText); +} \ No newline at end of file diff --git a/src/Gui/CaptureAreaComboBox/CaptureModeModel.h b/src/Gui/CaptureAreaComboBox/CaptureModeModel.h new file mode 100644 --- /dev/null +++ b/src/Gui/CaptureAreaComboBox/CaptureModeModel.h @@ -0,0 +1,42 @@ +#ifndef CAPTURE_MODE_MODEL_H +#define CAPTURE_MODE_MODEL_H + +#include "Platforms/Platform.h" + +#include +#include +#include +#include + +#include + +class QAction; + +enum ComboBoxRoles { ShortcutRole = Qt::UserRole + 1, ActionRole }; + + +class CaptureModeModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit CaptureModeModel(const Platform::GrabModes &theGrabModes, QObject* parent = nullptr); + + QHash roleNames() const override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public Q_SLOT: + void changeShortcut(QAction *action, const QKeySequence &seq); + +private: + struct ModelEntry { + QString captureModeLabel; + QKeySequence captureModeShortcut; + QAction* action; + }; + + std::vector modelData; +}; +#endif //COMBO_BOX_MODEL_H diff --git a/src/Gui/CaptureAreaComboBox/CaptureModeModel.cpp b/src/Gui/CaptureAreaComboBox/CaptureModeModel.cpp new file mode 100644 --- /dev/null +++ b/src/Gui/CaptureAreaComboBox/CaptureModeModel.cpp @@ -0,0 +1,69 @@ +#include "CaptureModeModel.h" +#include "SpectacleConfig.h" + +#include + +#include +#include + +#include + +CaptureModeModel::CaptureModeModel(const Platform::GrabModes &theGrabModes, QObject* parent) : + QAbstractListModel(parent) +{ + SpectacleConfig *config = SpectacleConfig::instance(); + KGlobalAccel *accelerator = KGlobalAccel::self(); + + std::vector availableActions; + if (theGrabModes.testFlag(Platform::GrabMode::AllScreens)) { + availableActions.push_back(config->shortCutActions->action(QStringLiteral("FullScreenScreenShot"))); + } + if (theGrabModes.testFlag(Platform::GrabMode::CurrentScreen)) { + availableActions.push_back(config->shortCutActions->action(QStringLiteral("CurrentMonitorScreenShot"))); + } + if (theGrabModes.testFlag(Platform::GrabMode::ActiveWindow)) { + availableActions.push_back(config->shortCutActions->action(QStringLiteral("ActiveWindowScreenShot"))); + } + if (theGrabModes.testFlag(Platform::GrabMode::WindowUnderCursor)) { + availableActions.push_back(config->shortCutActions->action(QStringLiteral("WindowUnderCursorScreenShot"))); + } + availableActions.push_back(config->shortCutActions->action(QStringLiteral("RectangularRegionScreenShot"))); + + for(QAction* action: availableActions) { + QString label = action ? action->text().remove(QStringLiteral("Capture ")) : QString(); + QKeySequence shortcut = accelerator->hasShortcut(action) ? accelerator->shortcut(action)[0] : QKeySequence(); + modelData.push_back( {label, shortcut, action} ); + } + + connect(accelerator, &KGlobalAccel::globalShortcutChanged, this, &CaptureModeModel::changeShortcut); +} + +int CaptureModeModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return modelData.size(); +} + +QHash CaptureModeModel::roleNames() const { + QHash roleNames = QAbstractListModel::roleNames(); + roleNames.insert(ShortcutRole, "shortcut"); + roleNames.insert(ActionRole, "action"); + return roleNames; +} + +QVariant CaptureModeModel::data(const QModelIndex &index, int role) const { + switch (role) { + case Qt::DisplayRole: return modelData[index.row()].captureModeLabel; + case ShortcutRole: return modelData[index.row()].captureModeShortcut; + case ActionRole: return modelData[index.row()].action->data(); + } + return QVariant {}; +} + +void CaptureModeModel::changeShortcut(QAction *action, const QKeySequence &seq) { + auto modelEntryIterator = std::find_if(modelData.begin(), modelData.end(), [action](const ModelEntry& entry) { + return (entry.action == action); + }); + if(modelEntryIterator != modelData.end()) { + (*modelEntryIterator).captureModeShortcut = seq; + } +} diff --git a/src/Gui/KSWidget.h b/src/Gui/KSWidget.h --- a/src/Gui/KSWidget.h +++ b/src/Gui/KSWidget.h @@ -78,7 +78,7 @@ void captureModeChanged(int theIndex); private: - + QGridLayout *mMainLayout { nullptr }; QHBoxLayout *mDelayLayout { nullptr }; QVBoxLayout *mRightLayout { nullptr }; diff --git a/src/Gui/KSWidget.cpp b/src/Gui/KSWidget.cpp --- a/src/Gui/KSWidget.cpp +++ b/src/Gui/KSWidget.cpp @@ -24,14 +24,18 @@ #include "SmartSpinBox.h" #include "SpectacleConfig.h" #include "ProgressButton.h" +#include "CaptureAreaComboBox/CaptureModeModel.h" +#include "CaptureAreaComboBox/CaptureModeDelegate.h" +#include #include #include #include #include #include #include #include +#include #include #include @@ -50,24 +54,27 @@ // 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); + mCaptureArea->setModel(new CaptureModeModel {theGrabModes}); + //Custom Delegate for comboBox with support for displaying shortcuts + mCaptureArea->setItemDelegate(new CaptureModeDelegate {}); + + //Calculate the mim width needed to display action + shortcut + spacing in the delegates + QFontMetrics fontMetrics {QGuiApplication::font()}; + int maxWidthLeftText = 0; + int maxWidthRightText = 0; + for (int i = 0; i < mCaptureArea->count(); ++i) { + int leftWidth = fontMetrics.horizontalAdvance(mCaptureArea->itemData(i, Qt::DisplayRole).toString()); + maxWidthLeftText = qMax(maxWidthLeftText, leftWidth); + int rightWidth = fontMetrics.horizontalAdvance(mCaptureArea->itemData(i, ShortcutRole).toString()); + maxWidthRightText = qMax(maxWidthRightText, rightWidth); + } + //spacing between longest left text and longest right text in the popup of the QComboBox + int spacingComboBox = 30; + mCaptureArea->view()->setMinimumWidth(maxWidthLeftText + maxWidthRightText + spacingComboBox); + connect(mCaptureArea, qOverload(&QComboBox::currentIndexChanged), this, &KSWidget::captureModeChanged); mDelayMsec = new SmartSpinBox(this); @@ -106,6 +113,10 @@ mWindowDecorations->setEnabled(false); connect(mWindowDecorations, &QCheckBox::clicked, lConfigMgr, &SpectacleConfig::setIncludeDecorationsChecked); + if (theGrabModes.testFlag(Platform::GrabMode::TransientWithParent)) { + mTransientWithParentAvailable = true; + } + 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")); @@ -213,7 +224,7 @@ void KSWidget::newScreenshotClicked() { int lDelay = mCaptureOnClick->isChecked() ? -1 : (mDelayMsec->value() * 1000); - auto lMode = static_cast(mCaptureArea->currentData().toInt()); + auto lMode = static_cast(mCaptureArea->currentData(ActionRole).toInt()); if (mTransientWithParentAvailable && lMode == Spectacle::CaptureMode::WindowUnderCursor && !(mCaptureTransientOnly->isChecked())) { @@ -237,7 +248,7 @@ SpectacleConfig::instance()->setCaptureMode(theIndex); - Spectacle::CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex).toInt()); + Spectacle::CaptureMode lCaptureMode = static_cast(mCaptureArea->itemData(theIndex, ActionRole).toInt()); switch(lCaptureMode) { case Spectacle::CaptureMode::WindowUnderCursor: mWindowDecorations->setEnabled(true); diff --git a/src/SpectacleConfig.cpp b/src/SpectacleConfig.cpp --- a/src/SpectacleConfig.cpp +++ b/src/SpectacleConfig.cpp @@ -18,6 +18,7 @@ */ #include "SpectacleConfig.h" +#include "SpectacleCommon.h" #include #include @@ -44,26 +45,42 @@ { QAction *action = new QAction(i18n("Launch Spectacle")); action->setObjectName(QStringLiteral("_launch")); + action->setProperty("isConfigurationAction", true); shortCutActions->addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Entire Desktop")); action->setObjectName(QStringLiteral("FullScreenScreenShot")); + action->setProperty("isConfigurationAction", true); + action->setData(Spectacle::CaptureMode::AllScreens); shortCutActions->addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Current Monitor")); action->setObjectName(QStringLiteral("CurrentMonitorScreenShot")); + action->setProperty("isConfigurationAction", true); + action->setData(Spectacle::CaptureMode::CurrentScreen); shortCutActions->addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Active Window")); action->setObjectName(QStringLiteral("ActiveWindowScreenShot")); + action->setProperty("isConfigurationAction", true); + action->setData(Spectacle::CaptureMode::ActiveWindow); + shortCutActions->addAction(action->objectName(), action); + } + { + QAction *action = new QAction(i18n("Capture Window Under Cursor")); + action->setObjectName(QStringLiteral("WindowUnderCursorScreenShot")); + action->setProperty("isConfigurationAction", true); + action->setData(Spectacle::CaptureMode::WindowUnderCursor); shortCutActions->addAction(action->objectName(), action); } { QAction *action = new QAction(i18n("Capture Rectangular Region")); action->setObjectName(QStringLiteral("RectangularRegionScreenShot")); + action->setProperty("isConfigurationAction", true); + action->setData(Spectacle::CaptureMode::RectangularRegion); shortCutActions->addAction(action->objectName(), action); } } @@ -119,7 +136,7 @@ mGeneralConfig.sync(); } -QUrl SpectacleConfig::lastSaveLocation() const +QUrl SpectacleConfig::lastSaveLocation() const { return this->lastSaveFile().adjusted(QUrl::RemoveFilename); } diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp --- a/src/SpectacleCore.cpp +++ b/src/SpectacleCore.cpp @@ -88,6 +88,8 @@ connect(lExportManager, &ExportManager::forceNotify, this, &SpectacleCore::doNotify); connect(mPlatform.get(), &Platform::windowTitleChanged, lExportManager, &ExportManager::setWindowTitle); + setUpShortcuts(); + switch (theStartMode) { case StartMode::DBus: break; @@ -106,7 +108,6 @@ initGui(lGuiConfig.readEntry("includePointer", true), lGuiConfig.readEntry("includeDecorations", true)); break; } - setUpShortcuts(); } void SpectacleCore::setUpShortcuts()