File indexing completed on 2025-02-02 04:26:11
0001 /* SPDX-FileCopyrightText: 2022 Noah Davis <noahadvs@gmail.com> 0002 * SPDX-License-Identifier: LGPL-2.0-or-later 0003 */ 0004 0005 #include "OptionsMenu.h" 0006 0007 #include "CaptureModeModel.h" 0008 #include "Gui/SettingsDialog/SettingsDialog.h" 0009 #include "SpectacleCore.h" 0010 #include "WidgetWindowUtils.h" 0011 #include "settings.h" 0012 0013 #include <KLocalizedString> 0014 #include <KStandardAction> 0015 0016 #include <QStyle> 0017 0018 using namespace Qt::StringLiterals; 0019 0020 static QPointer<OptionsMenu> s_instance = nullptr; 0021 0022 OptionsMenu::OptionsMenu(QWidget *parent) 0023 : SpectacleMenu(parent) 0024 , captureModeSection(new QAction(this)) 0025 , captureModeGroup(new QActionGroup(this)) // exclusive by default) 0026 , captureSettingsSection(new QAction(this)) 0027 , includeMousePointerAction(new QAction(this)) 0028 , includeWindowDecorationsAction(new QAction(this)) 0029 , includeWindowShadowAction(new QAction(this)) 0030 , onlyCapturePopupAction(new QAction(this)) 0031 , quitAfterSaveAction(new QAction(this)) 0032 , captureOnClickAction(new QAction(this)) 0033 , delayAction(new QWidgetAction(this)) 0034 , delayWidget(new QWidget(this)) 0035 , delayLayout(new QHBoxLayout(delayWidget.get())) 0036 , delayLabel(new QLabel(delayWidget.get())) 0037 , delaySpinBox(new SmartSpinBox(delayWidget.get())) 0038 { 0039 addAction(KStandardAction::preferences(this, &OptionsMenu::showPreferencesDialog, this)); 0040 0041 // QMenu::addSection just adds an action with text and separator mode enabled 0042 captureModeSection->setText(i18n("Capture Mode")); 0043 captureModeSection->setSeparator(true); 0044 addAction(captureModeSection.get()); 0045 0046 // Add capture mode actions. 0047 // This cannot be done in the constructor because captureModeModel will be null at this time. 0048 connect(this, &OptionsMenu::aboutToShow, 0049 this, &OptionsMenu::updateCaptureModes); 0050 0051 // make capture mode actions do things 0052 connect(captureModeGroup.get(), &QActionGroup::triggered, this, [](QAction *action){ 0053 int mode = action->data().toInt(); 0054 Settings::setCaptureMode(mode); 0055 }); 0056 connect(Settings::self(), &Settings::captureModeChanged, this, [this](){ 0057 int mode = Settings::captureMode(); 0058 if (captureModeGroup->checkedAction() && mode == captureModeGroup->checkedAction()->data().toInt()) { 0059 return; 0060 } 0061 for (auto action : std::as_const(captureModeActions)) { 0062 if (mode == action->data().toInt()) { 0063 action->setChecked(true); 0064 } 0065 } 0066 }); 0067 0068 captureSettingsSection->setText(i18n("Capture Settings")); 0069 captureSettingsSection->setSeparator(true); 0070 addAction(captureSettingsSection.get()); 0071 0072 includeMousePointerAction->setText(i18n("Include mouse pointer")); 0073 includeMousePointerAction->setToolTip(i18n("Show the mouse cursor in the screenshot image")); 0074 includeMousePointerAction->setCheckable(true); 0075 includeMousePointerAction->setChecked(Settings::includePointer()); 0076 connect(includeMousePointerAction.get(), &QAction::toggled, this, [](bool checked){ 0077 Settings::setIncludePointer(checked); 0078 }); 0079 connect(Settings::self(), &Settings::includePointerChanged, this, [this](){ 0080 includeMousePointerAction->setChecked(Settings::includePointer()); 0081 }); 0082 addAction(includeMousePointerAction.get()); 0083 0084 includeWindowDecorationsAction->setText(i18n("Include window titlebar and borders")); 0085 includeWindowDecorationsAction->setToolTip(i18n("Show the window title bar, the minimize/maximize/close buttons, and the window border")); 0086 includeWindowDecorationsAction->setCheckable(true); 0087 includeWindowDecorationsAction->setChecked(Settings::includeDecorations()); 0088 connect(includeWindowDecorationsAction.get(), &QAction::toggled, this, [](bool checked){ 0089 Settings::setIncludeDecorations(checked); 0090 }); 0091 connect(Settings::self(), &Settings::includeDecorationsChanged, this, [this](){ 0092 includeWindowDecorationsAction->setChecked(Settings::includeDecorations()); 0093 }); 0094 addAction(includeWindowDecorationsAction.get()); 0095 0096 includeWindowShadowAction->setText(i18n("Include window shadow")); 0097 includeWindowShadowAction->setToolTip(i18n("Show the window shadow")); 0098 includeWindowShadowAction->setCheckable(true); 0099 includeWindowShadowAction->setChecked(Settings::includeShadow()); 0100 connect(includeWindowShadowAction.get(), &QAction::toggled, this, [](bool checked) { 0101 Settings::setIncludeShadow(checked); 0102 }); 0103 connect(Settings::self(), &Settings::includeShadowChanged, this, [this]() { 0104 includeWindowShadowAction->setChecked(Settings::includeShadow()); 0105 }); 0106 addAction(includeWindowShadowAction.get()); 0107 0108 onlyCapturePopupAction->setText(i18n("Capture the current pop-up only")); 0109 onlyCapturePopupAction->setToolTip( 0110 i18n("Capture only the current pop-up window (like a menu, tooltip etc).\n" 0111 "If disabled, the pop-up is captured along with the parent window")); 0112 onlyCapturePopupAction->setCheckable(true); 0113 onlyCapturePopupAction->setChecked(Settings::transientOnly()); 0114 connect(onlyCapturePopupAction.get(), &QAction::toggled, this, [](bool checked){ 0115 Settings::setTransientOnly(checked); 0116 }); 0117 connect(Settings::self(), &Settings::transientOnlyChanged, this, [this](){ 0118 onlyCapturePopupAction->setChecked(Settings::transientOnly()); 0119 }); 0120 addAction(onlyCapturePopupAction.get()); 0121 0122 quitAfterSaveAction->setText(i18n("Quit after manual Save or Copy")); 0123 quitAfterSaveAction->setToolTip(i18n("Quit Spectacle after manually saving or copying the image")); 0124 quitAfterSaveAction->setCheckable(true); 0125 quitAfterSaveAction->setChecked(Settings::quitAfterSaveCopyExport()); 0126 connect(quitAfterSaveAction.get(), &QAction::toggled, this, [](bool checked){ 0127 Settings::setQuitAfterSaveCopyExport(checked); 0128 }); 0129 connect(Settings::self(), &Settings::quitAfterSaveCopyExportChanged, this, [this](){ 0130 quitAfterSaveAction->setChecked(Settings::quitAfterSaveCopyExport()); 0131 }); 0132 addAction(quitAfterSaveAction.get()); 0133 0134 addSeparator(); 0135 0136 // add capture on click 0137 captureOnClickAction->setText(i18n("Capture On Click")); 0138 captureOnClickAction->setCheckable(true); 0139 captureOnClickAction->setChecked(Settings::captureOnClick()); 0140 connect(captureOnClickAction.get(), &QAction::toggled, this, [this](bool checked){ 0141 Settings::setCaptureOnClick(checked); 0142 delayAction->setEnabled(!checked); 0143 }); 0144 connect(Settings::self(), &Settings::captureOnClickChanged, this, [this](){ 0145 captureOnClickAction->setChecked(Settings::captureOnClick()); 0146 }); 0147 addAction(captureOnClickAction.get()); 0148 0149 // set up delay widget 0150 auto spinbox = delaySpinBox.get(); 0151 auto label = delayLabel.get(); 0152 label->setText(i18n("Delay:")); 0153 spinbox->setDecimals(1); 0154 spinbox->setSingleStep(1.0); 0155 spinbox->setMinimum(0.0); 0156 spinbox->setMaximum(999); 0157 spinbox->setSpecialValueText(i18n("No Delay")); 0158 delayActionLayoutUpdate(); 0159 connect(spinbox, qOverload<double>(&SmartSpinBox::valueChanged), this, [this](){ 0160 if (updatingDelayActionLayout) { 0161 return; 0162 } 0163 Settings::setCaptureDelay(delaySpinBox->value()); 0164 }); 0165 connect(Settings::self(), &Settings::captureDelayChanged, this, [this](){ 0166 delaySpinBox->setValue(Settings::captureDelay()); 0167 }); 0168 delayWidget->setLayout(delayLayout.get()); 0169 delayLayout->addWidget(label); 0170 delayLayout->addWidget(spinbox); 0171 delayLayout->setAlignment(Qt::AlignLeft); 0172 delayAction->setDefaultWidget(delayWidget.get()); 0173 delayAction->setEnabled(!captureOnClickAction->isChecked()); 0174 addAction(delayAction.get()); 0175 } 0176 0177 OptionsMenu *OptionsMenu::instance() 0178 { 0179 // We need to create it here instead of using Q_GLOBAL_STATIC like the other menus 0180 // to prevent a segfault. 0181 if (!s_instance) { 0182 s_instance = new OptionsMenu; 0183 } 0184 return s_instance; 0185 } 0186 0187 void OptionsMenu::showPreferencesDialog() 0188 { 0189 KConfigDialog *dialog = KConfigDialog::exists(u"settings"_s); 0190 if (!dialog) { 0191 dialog = new SettingsDialog; 0192 dialog->setAttribute(Qt::WA_DeleteOnClose); 0193 } 0194 0195 // properly set the transientparent chain 0196 setWidgetTransientParent(dialog, getWidgetTransientParent(this)); 0197 0198 // HACK: how to make it appear on top of the fullscreen window? dialog->setWindowFlags(Qt::Popup); is an ugly way 0199 0200 dialog->show(); 0201 } 0202 0203 void OptionsMenu::setCaptureModeOptionsEnabled(bool enabled) 0204 { 0205 captureModeOptionsEnabled = enabled; 0206 } 0207 0208 void OptionsMenu::changeEvent(QEvent *event) 0209 { 0210 switch(event->type()) { 0211 case QEvent::FontChange: 0212 case QEvent::LayoutDirectionChange: 0213 case QEvent::StyleChange: 0214 delayActionLayoutUpdate(); 0215 break; 0216 default: break; 0217 } 0218 QWidget::changeEvent(event); 0219 } 0220 0221 void OptionsMenu::delayActionLayoutUpdate() 0222 { 0223 // We can't block signals while doing this to prevent unnecessary 0224 // processing because the spinbox has internal connections that need 0225 // to work in order to get the correct size. 0226 // We use our own guarding variable instead. 0227 updatingDelayActionLayout = true; 0228 delaySpinBox->setValue(delaySpinBox->maximum()); 0229 delaySpinBox->setMinimumWidth(delaySpinBox->sizeHint().width()); 0230 delaySpinBox->setValue(Settings::captureDelay()); 0231 updatingDelayActionLayout = false; 0232 0233 int menuHMargin = style()->pixelMetric(QStyle::PM_MenuHMargin); 0234 int menuVMargin = style()->pixelMetric(QStyle::PM_MenuVMargin); 0235 if (layoutDirection() == Qt::RightToLeft) { 0236 delayLabel->setContentsMargins(0, 0, menuHMargin + delayLabel->fontMetrics().descent(), 0); 0237 } else { 0238 delayLabel->setContentsMargins(menuHMargin + delayLabel->fontMetrics().descent(), 0, 0, 0); 0239 } 0240 delayLayout->setContentsMargins(0, menuVMargin, 0, 0); 0241 } 0242 0243 void OptionsMenu::updateCaptureModes() 0244 { 0245 captureModeSection->setVisible(captureModeOptionsEnabled); 0246 if (!captureModeOptionsEnabled) { 0247 for (auto action : std::as_const(captureModeActions)) { 0248 captureModeGroup->removeAction(action); 0249 removeAction(action); 0250 action->deleteLater(); 0251 } 0252 captureModeActions.clear(); 0253 return; 0254 } 0255 auto captureModeModel = SpectacleCore::instance()->captureModeModel(); 0256 if (captureModeModel == nullptr) { 0257 qWarning() << Q_FUNC_INFO << "captureModeModel is null, not updating actions"; 0258 return; 0259 } 0260 // Only make this conneciton once. 0261 // Can't be done in the constructor because captureModeModel is null at that time. 0262 if (!captureModesInitialized) { 0263 connect(captureModeModel, &CaptureModeModel::captureModesChanged, this, [this](){ 0264 shouldUpdateCaptureModes = true; 0265 }); 0266 captureModesInitialized = true; 0267 } 0268 // avoid unnecessarily resetting actions 0269 if (!shouldUpdateCaptureModes) { 0270 return; 0271 } 0272 shouldUpdateCaptureModes = false; 0273 for (auto action : std::as_const(captureModeActions)) { 0274 captureModeGroup->removeAction(action); 0275 removeAction(action); 0276 action->deleteLater(); 0277 } 0278 captureModeActions.clear(); 0279 for (int i = 0; i < captureModeModel->rowCount(); ++i) { 0280 auto index = captureModeModel->index(i); 0281 auto action = new QAction(this); 0282 captureModeActions.append(action); 0283 action->setText(captureModeModel->data(index, Qt::DisplayRole).toString()); 0284 action->setData(captureModeModel->data(index, CaptureModeModel::CaptureModeRole)); 0285 action->setCheckable(true); 0286 captureModeGroup->addAction(action); 0287 if (action->data().toInt() == Settings::captureMode()) { 0288 action->setChecked(true); 0289 } 0290 insertAction(captureSettingsSection.get(), action); 0291 } 0292 } 0293 0294 #include "moc_OptionsMenu.cpp"