diff --git a/flameshot.example.ini b/flameshot.example.ini index db2501fb..9a6c086c 100644 --- a/flameshot.example.ini +++ b/flameshot.example.ini @@ -1,73 +1,73 @@ -[General] -; Configure which buttons to show after drawing a selection -; Not easy to set by hand +;[General] +;; Configure which buttons to show after drawing a selection +;; Not easy to set by hand ;buttons=@Variant(\0\0\0\x7f\0\0\0\vQList\0\0\0\0\x14\0\0\0\0\0\0\0\x1\0\0\0\x2\0\0\0\x3\0\0\0\x4\0\0\0\x5\0\0\0\x6\0\0\0\x12\0\0\0\xf\0\0\0\x13\0\0\0\a\0\0\0\b\0\0\0\t\0\0\0\x10\0\0\0\n\0\0\0\v\0\0\0\f\0\0\0\r\0\0\0\xe\0\0\0\x11) - -; List of colors for color picker -; The colors are arranged counter-clockwise with the first being set to the right of the cursor -; Colors are any valid hex code or W3C color name -; "picker" adds a custom color picker +; +;; List of colors for color picker +;; The colors are arranged counter-clockwise with the first being set to the right of the cursor +;; Colors are any valid hex code or W3C color name +;; "picker" adds a custom color picker ;userColors=#800000, #ff0000, #ffff00, #00ff00, #008000, #00ffff, #0000ff, #ff00ff, #800080, picker - -; Image Save Path -;savePath= - -; Whether the savePath is a fixed path (bool) +; +;; Image Save Path +;savePath=/tmp +; +;; Whether the savePath is a fixed path (bool) ;savePathFixed=false - -; Main UI color -; Color is any valid hex code or W3C color name +; +;; Main UI color +;; Color is any valid hex code or W3C color name ;uiColor=#740096 - -; Contrast UI color -; Color is any valid hex code or W3C color name +; +;; Contrast UI color +;; Color is any valid hex code or W3C color name ;contrastUiColor=#270032 - -; Last used color -; Color is any valid hex code or W3C color name -;drawColor= - -; Show the help screen on startup (bool) +; +;; Last used color +;; Color is any valid hex code or W3C color name +;drawColor=#ff0000 +; +;; Show the help screen on startup (bool) ;showHelp=true - -; Show the side panel button (bool) +; +;; Show the side panel button (bool) ;showSidePanelButton=true - -; Ignore updates to versions less than this value +; +;; Ignore updates to versions less than this value ;ignoreUpdateToVersion= - -; Show desktop notifications (bool) +; +;; Show desktop notifications (bool) ;showDesktopNotification=true - -; Filename pattern using C++ strftime formatting +; +;; Filename pattern using C++ strftime formatting ;filenamePattern=%F_%H-%M - -; Whether the tray icon is disabled (bool) +; +;; Whether the tray icon is disabled (bool) ;disabledTrayIcon=false - -; Last used tool thickness (int) -;drawThickness=0 - -; Keep the App Launcher open after selecting an app (bool) +; +;; Last used tool thickness (int) +;drawThickness=1 +; +;; Keep the App Launcher open after selecting an app (bool) ;keepOpenAppLauncher=false - -; Launch at startup (bool) -;startupLaunch= - -; Opacity of area outside selection (int in range 0-255) +; +;; Launch at startup (bool) +;startupLaunch=true +; +;; Opacity of area outside selection (int in range 0-255) ;contrastOpacity=190 - -; Save image after copy (bool) +; +;; Save image after copy (bool) ;saveAfterCopy=false - -; Copy path to image after save (bool) +; +;; Copy path to image after save (bool) ;copyPathAfterSave=false - -; Use JPG format instead of PNG (bool) +; +;; Use JPG format instead of PNG (bool) ;useJpgForClipboard=false - -; Shortcut Settings for all tools -[Shortcuts] +; +;; Shortcut Settings for all tools +;[Shortcuts] ;TYPE_ARROW=A ;TYPE_CIRCLE=C ;TYPE_CIRCLECOUNT= diff --git a/src/config/buttonlistview.cpp b/src/config/buttonlistview.cpp index bb585eda..df2354bd 100644 --- a/src/config/buttonlistview.cpp +++ b/src/config/buttonlistview.cpp @@ -59,7 +59,7 @@ void ButtonListView::updateActiveButtons(QListWidgetItem* item) CaptureToolButton::getPriorityByButton(b); }); } else { - m_listButtons.remove(m_listButtons.indexOf(bType)); + m_listButtons.removeOne(bType); } ConfigHandler().setButtons(m_listButtons); } @@ -85,7 +85,7 @@ void ButtonListView::selectAll() void ButtonListView::updateComponents() { - m_listButtons = ConfigHandler().getButtons(); + m_listButtons = ConfigHandler().buttons(); auto listTypes = CaptureToolButton::getIterableButtonTypes(); for (int i = 0; i < this->count(); ++i) { QListWidgetItem* item = this->item(i); diff --git a/src/config/buttonlistview.h b/src/config/buttonlistview.h index db79b2cf..eef646c8 100644 --- a/src/config/buttonlistview.h +++ b/src/config/buttonlistview.h @@ -22,7 +22,7 @@ protected: void initButtonList(); private: - QVector m_listButtons; + QList m_listButtons; QMap m_buttonTypeByName; void updateActiveButtons(QListWidgetItem*); diff --git a/src/config/configwindow.cpp b/src/config/configwindow.cpp index 1ce53f0c..81025de3 100644 --- a/src/config/configwindow.cpp +++ b/src/config/configwindow.cpp @@ -11,10 +11,17 @@ #include "src/utils/confighandler.h" #include "src/utils/globalvalues.h" #include "src/utils/pathinfo.h" +#include +#include +#include #include #include #include +#include +#include #include +#include +#include #include // ConfigWindow contains the menus where you can configure the application @@ -24,25 +31,18 @@ ConfigWindow::ConfigWindow(QWidget* parent) { // We wrap QTabWidget in a QWidget because of a Qt bug auto layout = new QVBoxLayout(this); - m_tabs = new QTabWidget(this); - m_tabs->tabBar()->setUsesScrollButtons(false); - layout->addWidget(m_tabs); + m_tabWidget = new QTabWidget(this); + m_tabWidget->tabBar()->setUsesScrollButtons(false); + layout->addWidget(m_tabWidget); setAttribute(Qt::WA_DeleteOnClose); setWindowIcon(QIcon(":img/app/flameshot.svg")); setWindowTitle(tr("Configuration")); - auto changedSlot = [this](QString s) { - QStringList files = m_configWatcher->files(); - if (!files.contains(s)) { - this->m_configWatcher->addPath(s); - } - emit updateChildren(); - }; - m_configWatcher = new QFileSystemWatcher(this); - m_configWatcher->addPath(ConfigHandler().configFilePath()); - connect( - m_configWatcher, &QFileSystemWatcher::fileChanged, this, changedSlot); + connect(ConfigHandler::getInstance(), + &ConfigHandler::fileChanged, + this, + &ConfigWindow::updateChildren); QColor background = this->palette().window().color(); bool isDark = ColorUtils::colorIsDark(background); @@ -51,24 +51,40 @@ ConfigWindow::ConfigWindow(QWidget* parent) // visuals m_visuals = new VisualsEditor(); - m_tabs->addTab( - m_visuals, QIcon(modifier + "graphics.svg"), tr("Interface")); + m_visualsTab = new QWidget(); + QVBoxLayout* visualsLayout = new QVBoxLayout(m_visualsTab); + m_visualsTab->setLayout(visualsLayout); + visualsLayout->addWidget(m_visuals); + m_tabWidget->addTab( + m_visualsTab, QIcon(modifier + "graphics.svg"), tr("Interface")); // filename m_filenameEditor = new FileNameEditor(); - m_tabs->addTab(m_filenameEditor, - QIcon(modifier + "name_edition.svg"), - tr("Filename Editor")); + m_filenameEditorTab = new QWidget(); + QVBoxLayout* filenameEditorLayout = new QVBoxLayout(m_filenameEditorTab); + m_filenameEditorTab->setLayout(filenameEditorLayout); + filenameEditorLayout->addWidget(m_filenameEditor); + m_tabWidget->addTab(m_filenameEditorTab, + QIcon(modifier + "name_edition.svg"), + tr("Filename Editor")); // general m_generalConfig = new GeneralConf(); - m_tabs->addTab( - m_generalConfig, QIcon(modifier + "config.svg"), tr("General")); + m_generalConfigTab = new QWidget(); + QVBoxLayout* generalConfigLayout = new QVBoxLayout(m_generalConfigTab); + m_generalConfigTab->setLayout(generalConfigLayout); + generalConfigLayout->addWidget(m_generalConfig); + m_tabWidget->addTab( + m_generalConfigTab, QIcon(modifier + "config.svg"), tr("General")); // shortcuts m_shortcuts = new ShortcutsWidget(); - m_tabs->addTab( - m_shortcuts, QIcon(modifier + "shortcut.svg"), tr("Shortcuts")); + m_shortcutsTab = new QWidget(); + QVBoxLayout* shortcutsLayout = new QVBoxLayout(m_shortcutsTab); + m_shortcutsTab->setLayout(shortcutsLayout); + shortcutsLayout->addWidget(m_shortcuts); + m_tabWidget->addTab( + m_shortcutsTab, QIcon(modifier + "shortcut.svg"), tr("Shortcuts")); // connect update sigslots connect(this, @@ -83,6 +99,12 @@ ConfigWindow::ConfigWindow(QWidget* parent) &ConfigWindow::updateChildren, m_generalConfig, &GeneralConf::updateComponents); + + // Error indicator (this must come last) + initErrorIndicator(m_visualsTab, m_visuals); + initErrorIndicator(m_filenameEditorTab, m_filenameEditor); + initErrorIndicator(m_generalConfigTab, m_generalConfig); + initErrorIndicator(m_shortcutsTab, m_shortcuts); } void ConfigWindow::keyPressEvent(QKeyEvent* e) @@ -91,3 +113,84 @@ void ConfigWindow::keyPressEvent(QKeyEvent* e) close(); } } + +void ConfigWindow::initErrorIndicator(QWidget* tab, QWidget* widget) +{ + QLabel* label = new QLabel(tab); + QPushButton* btnShowErrors = new QPushButton("Show errors", tab); + QHBoxLayout* btnLayout = new QHBoxLayout(tab); + + // Set up label + label->setText(tr( + "Configuration file has errors. Resolve them before continuing.")); + label->setStyleSheet(QStringLiteral(":disabled { color: %1; }") + .arg(qApp->palette().color(QPalette::Text).name())); + label->setVisible(ConfigHandler().hasError()); + + // Set up "Show errors" button + btnShowErrors->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + btnLayout->addWidget(btnShowErrors); + btnShowErrors->setVisible(ConfigHandler().hasError()); + + widget->setEnabled(!ConfigHandler().hasError()); + + // Add label and button to the parent widget's layout + QBoxLayout* layout = static_cast(tab->layout()); + if (layout != nullptr) { + layout->insertWidget(0, label); + layout->insertLayout(1, btnLayout); + } else { + widget->layout()->addWidget(label); + widget->layout()->addWidget(btnShowErrors); + } + + // Sigslots + connect(ConfigHandler::getInstance(), &ConfigHandler::error, widget, [=]() { + widget->setEnabled(false); + label->show(); + btnShowErrors->show(); + }); + connect(ConfigHandler::getInstance(), + &ConfigHandler::errorResolved, + widget, + [=]() { + widget->setEnabled(true); + label->hide(); + btnShowErrors->hide(); + }); + connect(btnShowErrors, &QPushButton::clicked, this, [this]() { + // Generate error log message + QString str; + QTextStream stream(&str); + ConfigHandler().checkForErrors(&stream); + + // Set up dialog + QDialog dialog; + dialog.setWindowTitle(QStringLiteral("Configuration errors")); + dialog.setLayout(new QVBoxLayout(&dialog)); + + // Add text display + QTextEdit* textDisplay = new QTextEdit(&dialog); + textDisplay->setPlainText(str); + textDisplay->setReadOnly(true); + dialog.layout()->addWidget(textDisplay); + + // Add Ok button + using BBox = QDialogButtonBox; + BBox* buttons = new BBox(BBox::Ok); + dialog.layout()->addWidget(buttons); + connect(buttons, &QDialogButtonBox::clicked, this, [&dialog]() { + dialog.close(); + }); + + dialog.show(); + + qApp->processEvents(); + QPoint center = dialog.geometry().center(); + QRect dialogRect(0, 0, 400, 400); + dialogRect.moveCenter(center); + dialog.setGeometry(dialogRect); + + dialog.exec(); + }); +} diff --git a/src/config/configwindow.h b/src/config/configwindow.h index 905bc5b7..53b2e988 100644 --- a/src/config/configwindow.h +++ b/src/config/configwindow.h @@ -10,6 +10,7 @@ class ShortcutsWidget; class GeneralConf; class QFileSystemWatcher; class VisualsEditor; +class QWidget; class ConfigWindow : public QWidget { @@ -24,10 +25,19 @@ protected: void keyPressEvent(QKeyEvent*); private: - QTabWidget* m_tabs; + QTabWidget* m_tabWidget; + FileNameEditor* m_filenameEditor; + QWidget* m_filenameEditorTab; + ShortcutsWidget* m_shortcuts; + QWidget* m_shortcutsTab; + GeneralConf* m_generalConfig; + QWidget* m_generalConfigTab; + VisualsEditor* m_visuals; - QFileSystemWatcher* m_configWatcher; + QWidget* m_visualsTab; + + void initErrorIndicator(QWidget* tab, QWidget* widget); }; diff --git a/src/config/filenameeditor.cpp b/src/config/filenameeditor.cpp index 3e4c812a..62f3c435 100644 --- a/src/config/filenameeditor.cpp +++ b/src/config/filenameeditor.cpp @@ -103,7 +103,7 @@ void FileNameEditor::showParsedPattern(const QString& p) void FileNameEditor::resetName() { - m_nameEditor->setText(ConfigHandler().filenamePatternValue()); + m_nameEditor->setText(ConfigHandler().filenamePattern()); } void FileNameEditor::addToNameEditor(QString s) @@ -114,6 +114,6 @@ void FileNameEditor::addToNameEditor(QString s) void FileNameEditor::updateComponents() { - m_nameEditor->setText(ConfigHandler().filenamePatternValue()); + m_nameEditor->setText(ConfigHandler().filenamePattern()); m_outputLabel->setText(m_nameHandler->parsedPattern()); } diff --git a/src/config/generalconf.cpp b/src/config/generalconf.cpp index 097d6fd1..4fba65f1 100644 --- a/src/config/generalconf.cpp +++ b/src/config/generalconf.cpp @@ -42,7 +42,7 @@ GeneralConf::GeneralConf(QWidget* parent) initCopyPathAfterSave(); initUseJpgForClipboard(); initSaveAfterCopy(); - initUploadHistoryMaxSize(); + inituploadHistoryMax(); initUndoLimit(); m_layout->addStretch(); @@ -55,31 +55,27 @@ GeneralConf::GeneralConf(QWidget* parent) void GeneralConf::_updateComponents(bool allowEmptySavePath) { ConfigHandler config; - m_helpMessage->setChecked(config.showHelpValue()); - m_sidePanelButton->setChecked(config.showSidePanelButtonValue()); - m_sysNotifications->setChecked(config.desktopNotificationValue()); - m_autostart->setChecked(config.startupLaunchValue()); - m_copyAndCloseAfterUpload->setChecked( - config.copyAndCloseAfterUploadEnabled()); - m_saveAfterCopy->setChecked(config.saveAfterCopyValue()); - m_copyPathAfterSave->setChecked(config.copyPathAfterSaveEnabled()); + m_helpMessage->setChecked(config.showHelp()); + m_sidePanelButton->setChecked(config.showSidePanelButton()); + m_sysNotifications->setChecked(config.showDesktopNotification()); + m_autostart->setChecked(config.startupLaunch()); + m_copyAndCloseAfterUpload->setChecked(config.copyAndCloseAfterUpload()); + m_saveAfterCopy->setChecked(config.saveAfterCopy()); + m_copyPathAfterSave->setChecked(config.copyPathAfterSave()); m_useJpgForClipboard->setChecked(config.useJpgForClipboard()); m_historyConfirmationToDelete->setChecked( config.historyConfirmationToDelete()); m_checkForUpdates->setChecked(config.checkForUpdates()); m_showStartupLaunchMessage->setChecked(config.showStartupLaunchMessage()); m_screenshotPathFixedCheck->setChecked(config.savePathFixed()); - m_uploadHistoryMaxSize->setValue(config.uploadHistoryMaxSizeValue()); + m_uploadHistoryMax->setValue(config.uploadHistoryMax()); m_undoLimit->setValue(config.undoLimit()); if (allowEmptySavePath || !config.savePath().isEmpty()) { m_savePath->setText(config.savePath()); - } else { - ConfigHandler().setSavePath( - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); } #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) - m_showTray->setChecked(!config.disabledTrayIconValue()); + m_showTray->setChecked(!config.disabledTrayIcon()); #endif } @@ -100,7 +96,7 @@ void GeneralConf::showSidePanelButtonChanged(bool checked) void GeneralConf::showDesktopNotificationChanged(bool checked) { - ConfigHandler().setDesktopNotification(checked); + ConfigHandler().setShowDesktopNotification(checked); } void GeneralConf::showTrayIconChanged(bool checked) @@ -344,7 +340,7 @@ void GeneralConf::initCopyAndCloseAfterUpload() m_scrollAreaLayout->addWidget(m_copyAndCloseAfterUpload); connect(m_copyAndCloseAfterUpload, &QCheckBox::clicked, [](bool checked) { - ConfigHandler().setCopyAndCloseAfterUploadEnabled(checked); + ConfigHandler().setCopyAndCloseAfterUpload(checked); }); } @@ -368,10 +364,6 @@ void GeneralConf::initSaveAfterCopy() QHBoxLayout* pathLayout = new QHBoxLayout(); QString path = ConfigHandler().savePath(); - if (path.isEmpty()) { - path = - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - } m_savePath = new QLineEdit(path, this); m_savePath->setDisabled(true); QString foreground = this->palette().windowText().color().name(); @@ -401,7 +393,7 @@ void GeneralConf::historyConfirmationToDelete(bool checked) ConfigHandler().setHistoryConfirmationToDelete(checked); } -void GeneralConf::initUploadHistoryMaxSize() +void GeneralConf::inituploadHistoryMax() { QGroupBox* box = new QGroupBox(tr("Latest Uploads Max Size")); box->setFlat(true); @@ -410,22 +402,22 @@ void GeneralConf::initUploadHistoryMaxSize() QVBoxLayout* vboxLayout = new QVBoxLayout(); box->setLayout(vboxLayout); - m_uploadHistoryMaxSize = new QSpinBox(this); - m_uploadHistoryMaxSize->setMaximum(50); + m_uploadHistoryMax = new QSpinBox(this); + m_uploadHistoryMax->setMaximum(50); QString foreground = this->palette().windowText().color().name(); - m_uploadHistoryMaxSize->setStyleSheet( + m_uploadHistoryMax->setStyleSheet( QStringLiteral("color: %1").arg(foreground)); - connect(m_uploadHistoryMaxSize, + connect(m_uploadHistoryMax, SIGNAL(valueChanged(int)), this, - SLOT(uploadHistoryMaxSizeChanged(int))); - vboxLayout->addWidget(m_uploadHistoryMaxSize); + SLOT(uploadHistoryMaxChanged(int))); + vboxLayout->addWidget(m_uploadHistoryMax); } -void GeneralConf::uploadHistoryMaxSizeChanged(int max) +void GeneralConf::uploadHistoryMaxChanged(int max) { - ConfigHandler().setUploadHistoryMaxSize(max); + ConfigHandler().setUploadHistoryMax(max); } void GeneralConf::initUndoLimit() @@ -479,10 +471,6 @@ void GeneralConf::saveAfterCopyChanged(bool checked) void GeneralConf::changeSavePath() { QString path = ConfigHandler().savePath(); - if (path.isEmpty()) { - path = - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - } path = chooseFolder(path); if (!path.isEmpty()) { m_savePath->setText(path); @@ -496,7 +484,7 @@ void GeneralConf::initCopyPathAfterSave() m_copyPathAfterSave->setToolTip(tr("Copy file path after save")); m_scrollAreaLayout->addWidget(m_copyPathAfterSave); connect(m_copyPathAfterSave, &QCheckBox::clicked, [](bool checked) { - ConfigHandler().setCopyPathAfterSaveEnabled(checked); + ConfigHandler().setCopyPathAfterSave(checked); }); } diff --git a/src/config/generalconf.h b/src/config/generalconf.h index d467b0bf..c9c5c458 100644 --- a/src/config/generalconf.h +++ b/src/config/generalconf.h @@ -30,7 +30,7 @@ private slots: void checkForUpdatesChanged(bool checked); void autostartChanged(bool checked); void historyConfirmationToDelete(bool checked); - void uploadHistoryMaxSizeChanged(int max); + void uploadHistoryMaxChanged(int max); void undoLimit(int limit); void saveAfterCopyChanged(bool checked); void changeSavePath(); @@ -49,7 +49,7 @@ private: void initShowDesktopNotification(); void initShowTrayIcon(); void initHistoryConfirmationToDelete(); - void initUploadHistoryMaxSize(); + void inituploadHistoryMax(); void initUndoLimit(); void initConfigButtons(); void initCheckForUpdates(); @@ -84,6 +84,6 @@ private: QCheckBox* m_screenshotPathFixedCheck; QCheckBox* m_historyConfirmationToDelete; QCheckBox* m_useJpgForClipboard; - QSpinBox* m_uploadHistoryMaxSize; + QSpinBox* m_uploadHistoryMax; QSpinBox* m_undoLimit; }; diff --git a/src/config/shortcutswidget.cpp b/src/config/shortcutswidget.cpp index a857e635..f47da3d9 100644 --- a/src/config/shortcutswidget.cpp +++ b/src/config/shortcutswidget.cpp @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: 2020 Yurii Puchkov at Namecheap & Contributors #include "shortcutswidget.h" +#include "capturetool.h" #include "setshortcutwidget.h" #include "src/core/qguiappcurrentscreen.h" -#include "src/utils/configshortcuts.h" #include #include #include @@ -37,13 +37,12 @@ ShortcutsWidget::ShortcutsWidget(QWidget* parent) m_layout = new QVBoxLayout(this); m_layout->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); - m_shortcuts = ConfigShortcuts().captureShortcutsDefault( - CaptureToolButton::getIterableButtonTypes()); + initShortcuts(); initInfoTable(); show(); } -const QVector& ShortcutsWidget::shortcuts() +const QList& ShortcutsWidget::shortcuts() { return m_shortcuts; } @@ -158,6 +157,71 @@ void ShortcutsWidget::slotShortcutCellClicked(int row, int col) } } +void ShortcutsWidget::initShortcuts() +{ + auto buttons = CaptureToolButton::getIterableButtonTypes(); + + // get shortcuts names from capture buttons + for (const CaptureToolButton::ButtonType& t : buttons) { + CaptureToolButton* b = new CaptureToolButton(t, nullptr); + QString shortcutName = QVariant::fromValue(t).toString(); + if (shortcutName != "TYPE_IMAGEUPLOADER") { + appendShortcut(shortcutName, b->tool()->description()); + } + delete b; + } + + // additional tools that don't have their own buttons + appendShortcut("TYPE_TOGGLE_PANEL", "Toggle side panel"); + appendShortcut("TYPE_RESIZE_LEFT", "Resize selection left 1px"); + appendShortcut("TYPE_RESIZE_RIGHT", "Resize selection right 1px"); + appendShortcut("TYPE_RESIZE_UP", "Resize selection up 1px"); + appendShortcut("TYPE_RESIZE_DOWN", "Resize selection down 1px"); + appendShortcut("TYPE_SELECT_ALL", "Select entire screen"); + appendShortcut("TYPE_MOVE_LEFT", "Move selection left 1px"); + appendShortcut("TYPE_MOVE_RIGHT", "Move selection right 1px"); + appendShortcut("TYPE_MOVE_UP", "Move selection up 1px"); + appendShortcut("TYPE_MOVE_DOWN", "Move selection down 1px"); + appendShortcut("TYPE_COMMIT_CURRENT_TOOL", "Commit text in text area"); + appendShortcut("TYPE_DELETE_CURRENT_TOOL", "Delete current tool"); + + // non-editable shortcuts have an empty shortcut name + + m_shortcuts << (QStringList() << "" << QObject::tr("Quit capture") + << QKeySequence(Qt::Key_Escape).toString()); + + // Global hotkeys +#if defined(Q_OS_MACOS) + m_shortcuts << (QStringList() + << "" << QObject::tr("Screenshot history") << "⇧⌘⌥H"); + m_shortcuts << (QStringList() + << "" << QObject::tr("Capture screen") << "⇧⌘⌥4"); +#elif defined(Q_OS_WIN) + m_shortcuts << (QStringList() << "" << QObject::tr("Screenshot history") + << "Shift+Print Screen"); + m_shortcuts << (QStringList() + << "" << QObject::tr("Capture screen") << "Print Screen"); +#else + // TODO - Linux doesn't support global shortcuts for (XServer and Wayland), + // possibly it will be solved in the QHotKey library later. So it is + // disabled for now. +#endif + m_shortcuts << (QStringList() + << "" << QObject::tr("Show color picker") << "Right Click"); + m_shortcuts << (QStringList() + << "" << QObject::tr("Change the tool's thickness") + << "Mouse Wheel"); +} + +void ShortcutsWidget::appendShortcut(const QString& shortcutName, + const QString& description) +{ + m_shortcuts << (QStringList() + << shortcutName + << QObject::tr(description.toStdString().c_str()) + << ConfigHandler().shortcut(shortcutName)); +} + #if defined(Q_OS_MACOS) const QString& ShortcutsWidget::nativeOSHotKeyText(const QString& text) { diff --git a/src/config/shortcutswidget.h b/src/config/shortcutswidget.h index f441f77c..3afeae18 100644 --- a/src/config/shortcutswidget.h +++ b/src/config/shortcutswidget.h @@ -18,7 +18,7 @@ class ShortcutsWidget : public QWidget Q_OBJECT public: explicit ShortcutsWidget(QWidget* parent = nullptr); - const QVector& shortcuts(); + const QList& shortcuts(); private: void initInfoTable(); @@ -38,7 +38,11 @@ private: ConfigHandler m_config; QTableWidget* m_table; QVBoxLayout* m_layout; - QVector m_shortcuts; + QList m_shortcuts; + + void initShortcuts(); + void appendShortcut(const QString& shortcutName, + const QString& description); }; #endif // HOTKEYSCONFIG_H diff --git a/src/config/uicoloreditor.cpp b/src/config/uicoloreditor.cpp index 0edc79d3..03276a20 100644 --- a/src/config/uicoloreditor.cpp +++ b/src/config/uicoloreditor.cpp @@ -37,8 +37,8 @@ UIcolorEditor::UIcolorEditor(QWidget* parent) void UIcolorEditor::updateComponents() { ConfigHandler config; - m_uiColor = config.uiMainColorValue(); - m_contrastColor = config.uiContrastColorValue(); + m_uiColor = config.uiColor(); + m_contrastColor = config.contrastUiColor(); m_buttonContrast->setColor(m_contrastColor); m_buttonMainColor->setColor(m_uiColor); if (m_lastButtonPressed == m_buttonMainColor) { @@ -53,9 +53,9 @@ void UIcolorEditor::updateUIcolor() { ConfigHandler config; if (m_lastButtonPressed == m_buttonMainColor) { - config.setUIMainColor(m_uiColor); + config.setUiColor(m_uiColor); } else { - config.setUIContrastColor(m_contrastColor); + config.setContrastUiColor(m_contrastColor); } } diff --git a/src/config/visualseditor.cpp b/src/config/visualseditor.cpp index b67637b5..2c5d20a8 100644 --- a/src/config/visualseditor.cpp +++ b/src/config/visualseditor.cpp @@ -21,7 +21,7 @@ void VisualsEditor::updateComponents() { m_buttonList->updateComponents(); m_colorEditor->updateComponents(); - int opacity = ConfigHandler().contrastOpacityValue(); + int opacity = ConfigHandler().contrastOpacity(); m_opacitySlider->setMapedValue(0, opacity, 255); } @@ -50,7 +50,7 @@ void VisualsEditor::initOpacitySlider() m_layout->addWidget(label); m_layout->addLayout(localLayout); - int opacity = ConfigHandler().contrastOpacityValue(); + int opacity = ConfigHandler().contrastOpacity(); m_opacitySlider->setMapedValue(0, opacity, 255); } diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 28a4400f..10783957 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -66,7 +66,7 @@ Controller::Controller() // init tray icon #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) - if (!ConfigHandler().disabledTrayIconValue()) { + if (!ConfigHandler().disabledTrayIcon()) { enableTrayIcon(); } #elif defined(Q_OS_WIN) diff --git a/src/main.cpp b/src/main.cpp index 43f312b4..56748c44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -165,6 +165,8 @@ int main(int argc, char* argv[]) { "a", "autostart" }, QObject::tr("Enable or disable run at startup"), QStringLiteral("bool")); + CommandOption checkOption( + "check", QObject::tr("Check the configuration for errors")); CommandOption showHelpOption( { "s", "showhelp" }, QObject::tr("Show the help message in the capture mode"), @@ -270,7 +272,8 @@ int main(int argc, char* argv[]) trayOption, showHelpOption, mainColorOption, - contrastColorOption }, + contrastColorOption, + checkOption }, configArgument); // Parse if (!parser.parse(app.arguments())) { @@ -447,8 +450,19 @@ int main(int argc, char* argv[]) bool help = parser.isSet(showHelpOption); bool mainColor = parser.isSet(mainColorOption); bool contrastColor = parser.isSet(contrastColorOption); + bool check = parser.isSet(checkOption); bool someFlagSet = - (filename || tray || help || mainColor || contrastColor); + (filename || tray || help || mainColor || contrastColor || check); + if (check) { + QTextStream stream(stderr); + bool ok = ConfigHandler(true).checkForErrors(&stream); + if (ok) { + stream << QStringLiteral("No errors detected.\n"); + goto finish; + } else { + return 1; + } + } ConfigHandler config; if (autostart) { QDBusMessage m = QDBusMessage::createMethodCall( @@ -506,12 +520,12 @@ int main(int argc, char* argv[]) if (mainColor) { QString colorCode = parser.value(mainColorOption); QColor parsedColor(colorCode); - config.setUIMainColor(parsedColor); + config.setUiColor(parsedColor); } if (contrastColor) { QString colorCode = parser.value(contrastColorOption); QColor parsedColor(colorCode); - config.setUIContrastColor(parsedColor); + config.setContrastUiColor(parsedColor); } // Open gui when no options diff --git a/src/tools/imgur/imguruploader.cpp b/src/tools/imgur/imguruploader.cpp index 4611d8bb..06de4d64 100644 --- a/src/tools/imgur/imguruploader.cpp +++ b/src/tools/imgur/imguruploader.cpp @@ -47,7 +47,7 @@ ImgurUploader::ImgurUploader(const QPixmap& capture, QWidget* parent) #endif m_spinner = new LoadSpinner(this); - m_spinner->setColor(ConfigHandler().uiMainColorValue()); + m_spinner->setColor(ConfigHandler().uiColor()); m_spinner->start(); m_infoLabel = new QLabel(tr("Uploading Image")); @@ -94,7 +94,7 @@ void ImgurUploader::handleReply(QNetworkReply* reply) imageName = history.packFileName("imgur", deleteToken, imageName); history.save(m_pixmap, imageName); - if (ConfigHandler().copyAndCloseAfterUploadEnabled()) { + if (ConfigHandler().copyAndCloseAfterUpload()) { SystemNotification().sendMessage( QObject::tr("URL copied to clipboard.")); QApplication::clipboard()->setText(m_imageURL.toString()); diff --git a/src/tools/launcher/applauncherwidget.cpp b/src/tools/launcher/applauncherwidget.cpp index 7817523a..14443adc 100644 --- a/src/tools/launcher/applauncherwidget.cpp +++ b/src/tools/launcher/applauncherwidget.cpp @@ -42,7 +42,7 @@ AppLauncherWidget::AppLauncherWidget(const QPixmap& p, QWidget* parent) setWindowIcon(QIcon(":img/app/flameshot.svg")); setWindowTitle(tr("Open With")); - m_keepOpen = ConfigHandler().keepOpenAppLauncherValue(); + m_keepOpen = ConfigHandler().keepOpenAppLauncher(); QString dirLocal = QDir::homePath() + "/.local/share/applications/"; QDir appsDirLocal(dirLocal); @@ -57,7 +57,7 @@ AppLauncherWidget::AppLauncherWidget(const QPixmap& p, QWidget* parent) m_terminalCheckbox = new QCheckBox(tr("Launch in terminal"), this); m_keepOpenCheckbox = new QCheckBox(tr("Keep open after selection"), this); - m_keepOpenCheckbox->setChecked(ConfigHandler().keepOpenAppLauncherValue()); + m_keepOpenCheckbox->setChecked(ConfigHandler().keepOpenAppLauncher()); connect(m_keepOpenCheckbox, &QCheckBox::clicked, this, diff --git a/src/tools/pin/pinwidget.cpp b/src/tools/pin/pinwidget.cpp index 4a554808..c9a6c4aa 100644 --- a/src/tools/pin/pinwidget.cpp +++ b/src/tools/pin/pinwidget.cpp @@ -19,8 +19,8 @@ PinWidget::PinWidget(const QPixmap& pixmap, QWidget* parent) setAttribute(Qt::WA_TranslucentBackground); ConfigHandler conf; - m_baseColor = conf.uiMainColorValue(); - m_hoverColor = conf.uiContrastColorValue(); + m_baseColor = conf.uiColor(); + m_hoverColor = conf.contrastUiColor(); m_layout = new QVBoxLayout(this); const int margin = this->margin(); diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index e96feaa2..afb3c29a 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -5,7 +5,7 @@ target_sources( filenamehandler.h screengrabber.h systemnotification.h - configshortcuts.h + valuehandler.h request.h strfparse.h ) @@ -16,6 +16,7 @@ target_sources( screengrabber.cpp confighandler.cpp systemnotification.cpp + valuehandler.cpp screenshotsaver.cpp dbusutils.cpp globalvalues.cpp @@ -24,7 +25,6 @@ target_sources( pathinfo.cpp colorutils.cpp history.cpp - configshortcuts.cpp strfparse.cpp request.cpp ) diff --git a/src/utils/confighandler.cpp b/src/utils/confighandler.cpp index 353948ee..17a9cf2e 100644 --- a/src/utils/confighandler.cpp +++ b/src/utils/confighandler.cpp @@ -3,357 +3,27 @@ #include "confighandler.h" #include "src/tools/capturetool.h" -#include "src/utils/configshortcuts.h" +#include "systemnotification.h" +#include "valuehandler.h" #include #include #include #include +#include #include +#include +#include #include +#include +#include #include #if defined(Q_OS_MACOS) #include #endif -ConfigHandler::ConfigHandler() -{ - m_settings.setDefaultFormat(QSettings::IniFormat); -} +// HELPER FUNCTIONS -QVector ConfigHandler::getButtons() -{ - QVector buttons; - if (m_settings.contains(QStringLiteral("buttons"))) { - // TODO: remove toList in v1.0 - QVector buttonsInt = m_settings.value(QStringLiteral("buttons")) - .value>() - .toVector(); - bool modified = normalizeButtons(buttonsInt); - if (modified) { - m_settings.setValue(QStringLiteral("buttons"), - QVariant::fromValue(buttonsInt.toList())); - } - buttons = fromIntToButton(buttonsInt); - } else { - // Default tools - buttons = CaptureToolButton::getIterableButtonTypes(); - buttons.removeOne(CaptureToolButton::TYPE_SIZEDECREASE); - buttons.removeOne(CaptureToolButton::TYPE_SIZEINCREASE); - } - - using bt = CaptureToolButton::ButtonType; - std::sort(buttons.begin(), buttons.end(), [](bt a, bt b) { - return CaptureToolButton::getPriorityByButton(a) < - CaptureToolButton::getPriorityByButton(b); - }); - return buttons; -} - -void ConfigHandler::setButtons( - const QVector& buttons) -{ - QVector l = fromButtonToInt(buttons); - normalizeButtons(l); - // TODO: remove toList in v1.0 - m_settings.setValue(QStringLiteral("buttons"), - QVariant::fromValue(l.toList())); -} - -QVector ConfigHandler::getUserColors() -{ - QVector colors; - const QVector& defaultColors = { - Qt::darkRed, Qt::red, Qt::yellow, Qt::green, Qt::darkGreen, - Qt::cyan, Qt::blue, Qt::magenta, Qt::darkMagenta, QColor() - }; - - if (m_settings.contains(QStringLiteral("userColors"))) { - for (const QString& hex : - m_settings.value(QStringLiteral("userColors")).toStringList()) { - if (QColor::isValidColor(hex)) { - colors.append(QColor(hex)); - } else if (hex == QStringLiteral("picker")) { - colors.append(QColor()); - } - } - - if (colors.isEmpty()) { - colors = defaultColors; - } - } else { - colors = defaultColors; - } - - return colors; -} - -QString ConfigHandler::savePath() -{ - return m_settings.value(QStringLiteral("savePath")).toString(); -} - -void ConfigHandler::setSavePath(const QString& savePath) -{ - m_settings.setValue(QStringLiteral("savePath"), savePath); -} - -bool ConfigHandler::savePathFixed() -{ - if (!m_settings.contains(QStringLiteral("savePathFixed"))) { - m_settings.setValue(QStringLiteral("savePathFixed"), false); - } - return m_settings.value(QStringLiteral("savePathFixed")).toBool(); -} - -void ConfigHandler::setSavePathFixed(bool savePathFixed) -{ - m_settings.setValue(QStringLiteral("savePathFixed"), savePathFixed); -} - -QColor ConfigHandler::uiMainColorValue() -{ - QColor res = QColor(116, 0, 150); - - if (m_settings.contains(QStringLiteral("uiColor"))) { - QString hex = m_settings.value(QStringLiteral("uiColor")).toString(); - - if (QColor::isValidColor(hex)) { - res = QColor(hex); - } - } - return res; -} - -void ConfigHandler::setUIMainColor(const QColor& c) -{ - m_settings.setValue(QStringLiteral("uiColor"), c.name()); -} - -QColor ConfigHandler::uiContrastColorValue() -{ - QColor res = QColor(39, 0, 50); - - if (m_settings.contains(QStringLiteral("contrastUiColor"))) { - QString hex = - m_settings.value(QStringLiteral("contrastUiColor")).toString(); - - if (QColor::isValidColor(hex)) { - res = QColor(hex); - } - } - - return res; -} - -void ConfigHandler::setUIContrastColor(const QColor& c) -{ - m_settings.setValue(QStringLiteral("contrastUiColor"), c.name()); -} - -QColor ConfigHandler::drawColorValue() -{ - QColor res(Qt::red); - - if (m_settings.contains(QStringLiteral("drawColor"))) { - QString hex = m_settings.value(QStringLiteral("drawColor")).toString(); - - if (QColor::isValidColor(hex)) { - res = QColor(hex); - } - } - - return res; -} - -void ConfigHandler::setDrawColor(const QColor& c) -{ - m_settings.setValue(QStringLiteral("drawColor"), c.name()); -} - -void ConfigHandler::setFontFamily(const QString& fontFamily) -{ - m_settings.setValue(QStringLiteral("fontFamily"), fontFamily); -} - -const QString& ConfigHandler::fontFamily() -{ - m_strRes.clear(); - if (m_settings.contains(QStringLiteral("fontFamily"))) { - m_strRes = m_settings.value(QStringLiteral("fontFamily")).toString(); - } - return m_strRes; -} - -bool ConfigHandler::showHelpValue() -{ - bool res = true; - if (m_settings.contains(QStringLiteral("showHelp"))) { - res = m_settings.value(QStringLiteral("showHelp")).toBool(); - } - return res; -} - -void ConfigHandler::setShowHelp(const bool showHelp) -{ - m_settings.setValue(QStringLiteral("showHelp"), showHelp); -} - -bool ConfigHandler::showSidePanelButtonValue() -{ - return m_settings.value(QStringLiteral("showSidePanelButton"), true) - .toBool(); -} - -void ConfigHandler::setShowSidePanelButton(const bool showSidePanelButton) -{ - m_settings.setValue(QStringLiteral("showSidePanelButton"), - showSidePanelButton); -} - -void ConfigHandler::setIgnoreUpdateToVersion(const QString& text) -{ - m_settings.setValue(QStringLiteral("ignoreUpdateToVersion"), text); -} - -QString ConfigHandler::ignoreUpdateToVersion() -{ - return m_settings.value(QStringLiteral("ignoreUpdateToVersion")).toString(); -} - -void ConfigHandler::setUndoLimit(int value) -{ - m_settings.setValue(QStringLiteral("undoLimit"), value); -} - -int ConfigHandler::undoLimit() -{ - int limit = 100; - if (m_settings.contains(QStringLiteral("undoLimit"))) { - limit = m_settings.value(QStringLiteral("undoLimit")).toInt(); - limit = qBound(1, limit, 999); - } - return limit; -} - -bool ConfigHandler::desktopNotificationValue() -{ - bool res = true; - if (m_settings.contains(QStringLiteral("showDesktopNotification"))) { - res = - m_settings.value(QStringLiteral("showDesktopNotification")).toBool(); - } - return res; -} - -void ConfigHandler::setDesktopNotification(const bool showDesktopNotification) -{ - m_settings.setValue(QStringLiteral("showDesktopNotification"), - showDesktopNotification); -} - -QString ConfigHandler::filenamePatternDefault() -{ - m_strRes = QLatin1String("%F_%H-%M"); - return m_strRes; -} - -QString ConfigHandler::filenamePatternValue() -{ - m_strRes = m_settings.value(QStringLiteral("filenamePattern")).toString(); - if (m_strRes.isEmpty()) { - m_strRes = filenamePatternDefault(); - } - return m_strRes; -} - -void ConfigHandler::setFilenamePattern(const QString& pattern) -{ - return m_settings.setValue(QStringLiteral("filenamePattern"), pattern); -} - -bool ConfigHandler::disabledTrayIconValue() -{ - bool res = false; - if (m_settings.contains(QStringLiteral("disabledTrayIcon"))) { - res = m_settings.value(QStringLiteral("disabledTrayIcon")).toBool(); - } - return res; -} - -void ConfigHandler::setDisabledTrayIcon(const bool disabledTrayIcon) -{ - m_settings.setValue(QStringLiteral("disabledTrayIcon"), disabledTrayIcon); -} - -int ConfigHandler::drawThicknessValue() -{ - int res = 3; - if (m_settings.contains(QStringLiteral("drawThickness"))) { - res = m_settings.value(QStringLiteral("drawThickness")).toInt(); - } - return res; -} - -void ConfigHandler::setDrawThickness(const int thickness) -{ - m_settings.setValue(QStringLiteral("drawThickness"), thickness); -} - -int ConfigHandler::drawFontSizeValue() -{ - int res = 8; - if (m_settings.contains(QStringLiteral("drawFontSize"))) { - res = m_settings.value(QStringLiteral("drawFontSize")).toInt(); - } - return res; -} - -void ConfigHandler::setDrawFontSize(const int fontSize) -{ - m_settings.setValue(QStringLiteral("drawFontSize"), fontSize); -} - -bool ConfigHandler::keepOpenAppLauncherValue() -{ - return m_settings.value(QStringLiteral("keepOpenAppLauncher")).toBool(); -} - -void ConfigHandler::setKeepOpenAppLauncher(const bool keepOpen) -{ - m_settings.setValue(QStringLiteral("keepOpenAppLauncher"), keepOpen); -} - -bool ConfigHandler::checkForUpdates() -{ - bool res = true; - if (m_settings.contains(QStringLiteral("checkForUpdates"))) { - res = m_settings.value(QStringLiteral("checkForUpdates")).toBool(); - } - return res; -} - -void ConfigHandler::setCheckForUpdates(const bool checkForUpdates) -{ - m_settings.setValue(QStringLiteral("checkForUpdates"), checkForUpdates); -} - -bool ConfigHandler::startupLaunchValue() -{ -#if defined(Q_OS_MACOS) - bool res = false; -#else - bool res = true; -#endif - if (m_settings.contains(QStringLiteral("startupLaunch"))) { - res = m_settings.value(QStringLiteral("startupLaunch")).toBool(); - } - if (res != verifyLaunchFile()) { - setStartupLaunch(res); - } - return res; -} - -bool ConfigHandler::verifyLaunchFile() +bool verifyLaunchFile() { #if defined(Q_OS_LINUX) || defined(Q_OS_UNIX) QString path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, @@ -372,12 +42,184 @@ bool ConfigHandler::verifyLaunchFile() return res; } +// VALUE HANDLING + +/** + * Use this to declare a setting with a type that is either unrecognized by + * QVariant or if you need to place additional constraints on its value. + * @param KEY Name of the setting as in the config file + * (a C-style string literal) + * @param TYPE An instance of a `ValueHandler` derivative. This must be + * specified in the form of a constructor, or the macro will + * misbehave. + */ +#define OPTION(KEY, TYPE) \ + { \ + QStringLiteral(KEY), QSharedPointer(new TYPE) \ + } + +#define SHORTCUT(NAME, DEFAULT_VALUE) \ + { \ + QStringLiteral(NAME), QSharedPointer(new KeySequence( \ + QKeySequence(QLatin1String(DEFAULT_VALUE)))) \ + } + +/** + * This map contains all the information that is needed to parse, verify and + * preprocess each configuration option in the General section. + * NOTE: Please keep it well structured + */ +// clang-format off +static QMap> + recognizedGeneralOptions = { +// KEY TYPE DEFAULT_VALUE + OPTION("showHelp" ,Bool ( true )), + OPTION("showSidePanelButton" ,Bool ( true )), + OPTION("showDesktopNotification" ,Bool ( true )), + OPTION("disabledTrayIcon" ,Bool ( false )), + OPTION("historyConfirmationToDelete" ,Bool ( true )), + OPTION("checkForUpdates" ,Bool ( true )), +#if defined(Q_OS_MACOS) + OPTION("startupLaunch" ,Bool ( false )), +#else + OPTION("startupLaunch" ,Bool ( true )), +#endif + OPTION("showStartupLaunchMessage" ,Bool ( true )), + OPTION("copyAndCloseAfterUpload" ,Bool ( true )), + OPTION("copyPathAfterSave" ,Bool ( false )), +#if !defined(Q_OS_MACOS) + OPTION("useJpgForClipboard" ,Bool ( false )), +#endif + OPTION("saveAfterCopy" ,Bool ( false )), + OPTION("savePath" ,ExistingDir ( )), + OPTION("savePathFixed" ,Bool ( false )), + OPTION("uploadHistoryMax" ,LowerBoundedInt(0 , 25 )), + OPTION("undoLimit" ,BoundedInt(0, 999 , 100 )), + // Interface tab + OPTION("uiColor" ,Color ( {116, 0, 150} )), + OPTION("contrastUiColor" ,Color ( {39, 0, 50} )), + OPTION("contrastOpacity" ,BoundedInt(0, 255 , 190 )), + OPTION("buttons" ,ButtonList ( {} )), + // Filename Editor tab + OPTION("filenamePattern" ,FilenamePattern ( {} )), + // Others + OPTION("drawThickness" ,LowerBoundedInt(1 , 3 )), + OPTION("drawColor" ,Color ( Qt::red )), + OPTION("userColors" ,UserColors ( )), + OPTION("drawFontSize" ,LowerBoundedInt(1 , 8 )), + OPTION("ignoreUpdateToVersion" ,String ( "" )), + OPTION("keepOpenAppLauncher" ,Bool ( false )), + OPTION("fontFamily" ,String ( "" )), + OPTION("setSaveAsFileExtension" ,String ( "" )), + }; + +static QMap> recognizedShortcuts = { +// NAME DEFAULT_SHORTCUT + SHORTCUT("TYPE_PENCIL" , "P" ), + SHORTCUT("TYPE_DRAWER" , "D" ), + SHORTCUT("TYPE_ARROW" , "A" ), + SHORTCUT("TYPE_SELECTION" , "S" ), + SHORTCUT("TYPE_RECTANGLE" , "R" ), + SHORTCUT("TYPE_CIRCLE" , "C" ), + SHORTCUT("TYPE_MARKER" , "M" ), + SHORTCUT("TYPE_MOVESELECTION" , "Ctrl+M" ), + SHORTCUT("TYPE_UNDO" , "Ctrl+Z" ), + SHORTCUT("TYPE_COPY" , "Ctrl+C" ), + SHORTCUT("TYPE_SAVE" , "Ctrl+S" ), + SHORTCUT("TYPE_EXIT" , "Ctrl+Q" ), + SHORTCUT("TYPE_IMAGEUPLOADER" , "Ctrl+U" ), +#if !defined(Q_OS_MACOS) + SHORTCUT("TYPE_OPEN_APP" , "Ctrl+O" ), +#endif + SHORTCUT("TYPE_PIXELATE" , "B" ), + SHORTCUT("TYPE_INVERT" , "I" ), + SHORTCUT("TYPE_REDO" , "Ctrl+Shift+Z" ), + SHORTCUT("TYPE_TEXT" , "T" ), + SHORTCUT("TYPE_TOGGLE_PANEL" , "Space" ), + SHORTCUT("TYPE_RESIZE_LEFT" , "Shift+Left" ), + SHORTCUT("TYPE_RESIZE_RIGHT" , "Shift+Right" ), + SHORTCUT("TYPE_RESIZE_UP" , "Shift+Up" ), + SHORTCUT("TYPE_RESIZE_DOWN" , "Shift+Down" ), + SHORTCUT("TYPE_SELECT_ALL" , "Ctrl+A" ), + SHORTCUT("TYPE_MOVE_LEFT" , "Left" ), + SHORTCUT("TYPE_MOVE_RIGHT" , "Right" ), + SHORTCUT("TYPE_MOVE_UP" , "Up" ), + SHORTCUT("TYPE_MOVE_DOWN" , "Down" ), + SHORTCUT("TYPE_COMMIT_CURRENT_TOOL" , "Ctrl+Return" ), +#if defined(Q_OS_MACOS) + SHORTCUT("TYPE_DELETE_CURRENT_TOOL" , "Backspace" ), +#else + SHORTCUT("TYPE_DELETE_CURRENT_TOOL" , "Delete" ), +#endif + SHORTCUT("TYPE_PIN" , ), + SHORTCUT("TYPE_SELECTIONINDICATOR" , ), + SHORTCUT("TYPE_SIZEINCREASE" , ), + SHORTCUT("TYPE_SIZEDECREASE" , ), + SHORTCUT("TYPE_CIRCLECOUNT" , ), +}; +// clang-format on + +// CLASS CONFIGHANDLER + +ConfigHandler::ConfigHandler(bool skipInitialErrorCheck) +{ + m_settings.setDefaultFormat(QSettings::IniFormat); + + if (m_configWatcher == nullptr && qApp != nullptr) { + if (!skipInitialErrorCheck) { + // check for error on initial call + checkAndHandleError(); + } + // check for error every time the file changes + m_configWatcher.reset(new QFileSystemWatcher()); + ensureFileWatched(); + QObject::connect(m_configWatcher.data(), + &QFileSystemWatcher::fileChanged, + [](const QString& fileName) { + emit getInstance()->fileChanged(); + + if (QFile(fileName).exists()) { + m_configWatcher->addPath(fileName); + } + if (m_skipNextErrorCheck) { + m_skipNextErrorCheck = false; + return; + } + ConfigHandler().checkAndHandleError(); + if (!QFile(fileName).exists()) { + // File watcher stops watching a deleted file. + // Next time the config is accessed, force it + // to check for errors (and watch again). + m_errorCheckPending = true; + } + }); + } +} + +/// Serves as an object to which slots can be connected. +ConfigHandler* ConfigHandler::getInstance() +{ + static ConfigHandler config; + return &config; +} + +// SPECIAL CASES + +bool ConfigHandler::startupLaunch() +{ + bool res = value(QStringLiteral("startupLaunch")).toBool(); + if (res != verifyLaunchFile()) { + setStartupLaunch(res); + } + return res; +} + void ConfigHandler::setStartupLaunch(const bool start) { - if (start == m_settings.value(QStringLiteral("startupLaunch")).toBool()) { + if (start == value(QStringLiteral("startupLaunch")).toBool()) { return; } - m_settings.setValue(QStringLiteral("startupLaunch"), start); + setValue(QStringLiteral("startupLaunch"), start); #if defined(Q_OS_MACOS) /* TODO - there should be more correct way via API, but didn't find it without extra dependencies, there should be something like that: @@ -456,137 +298,31 @@ void ConfigHandler::setStartupLaunch(const bool start) #endif } -bool ConfigHandler::showStartupLaunchMessage() +QString ConfigHandler::saveAsFileExtension() { - if (!m_settings.contains(QStringLiteral("showStartupLaunchMessage"))) { - m_settings.setValue(QStringLiteral("showStartupLaunchMessage"), true); - } - return m_settings.value(QStringLiteral("showStartupLaunchMessage")) - .toBool(); + // TODO If the name of the option changes in the future, remove this + // function and use the macro CONFIG_GETTER_SETTER instead. + return value("setSaveAsFileExtension").toString(); } -void ConfigHandler::setShowStartupLaunchMessage( - const bool showStartupLaunchMessage) +void ConfigHandler::setAllTheButtons() { - m_settings.setValue(QStringLiteral("showStartupLaunchMessage"), - showStartupLaunchMessage); + QList buttons = + CaptureToolButton::getIterableButtonTypes(); + setValue(QStringLiteral("buttons"), QVariant::fromValue(buttons)); } -int ConfigHandler::contrastOpacityValue() -{ - int opacity = 190; - if (m_settings.contains(QStringLiteral("contrastOpacity"))) { - opacity = m_settings.value(QStringLiteral("contrastOpacity")).toInt(); - opacity = qBound(0, opacity, 255); - } - return opacity; -} +// DEFAULTS -void ConfigHandler::setContrastOpacity(const int transparency) +QString ConfigHandler::filenamePatternDefault() { - m_settings.setValue(QStringLiteral("contrastOpacity"), transparency); -} - -bool ConfigHandler::copyAndCloseAfterUploadEnabled() -{ - bool res = true; - if (m_settings.contains(QStringLiteral("copyAndCloseAfterUpload"))) { - res = - m_settings.value(QStringLiteral("copyAndCloseAfterUpload")).toBool(); - } - return res; -} - -void ConfigHandler::setCopyAndCloseAfterUploadEnabled(const bool value) -{ - m_settings.setValue(QStringLiteral("copyAndCloseAfterUpload"), value); -} - -bool ConfigHandler::historyConfirmationToDelete() -{ - bool res = true; - if (m_settings.contains(QStringLiteral("historyConfirmationToDelete"))) { - res = m_settings.value(QStringLiteral("historyConfirmationToDelete")) - .toBool(); - } - return res; -} - -void ConfigHandler::setHistoryConfirmationToDelete(const bool check) -{ - m_settings.setValue(QStringLiteral("historyConfirmationToDelete"), check); -} - -int ConfigHandler::uploadHistoryMaxSizeValue() -{ - int max = 25; - if (m_settings.contains(QStringLiteral("uploadHistoryMax"))) { - max = m_settings.value(QStringLiteral("uploadHistoryMax")).toInt(); - } - return max; -} - -void ConfigHandler::setUploadHistoryMaxSize(const int max) -{ - m_settings.setValue(QStringLiteral("uploadHistoryMax"), max); -} - -bool ConfigHandler::saveAfterCopyValue() -{ - return m_settings.value(QStringLiteral("saveAfterCopy")).toBool(); -} - -void ConfigHandler::setSaveAfterCopy(const bool save) -{ - m_settings.setValue(QStringLiteral("saveAfterCopy"), save); -} - -bool ConfigHandler::copyPathAfterSaveEnabled() -{ - bool res = false; - if (m_settings.contains(QStringLiteral("copyPathAfterSave"))) { - res = m_settings.value(QStringLiteral("copyPathAfterSave")).toBool(); - } - return res; -} - -void ConfigHandler::setCopyPathAfterSaveEnabled(const bool value) -{ - m_settings.setValue(QStringLiteral("copyPathAfterSave"), value); -} - -bool ConfigHandler::useJpgForClipboard() const -{ -#if !defined(Q_OS_MACOS) - // FIXME - temporary fix to disable option for MacOS - if (m_settings.contains(QStringLiteral("useJpgForClipboard"))) { - return m_settings.value(QStringLiteral("useJpgForClipboard")).toBool(); - } -#endif - return false; -} - -void ConfigHandler::setUseJpgForClipboard(const bool value) -{ - m_settings.setValue(QStringLiteral("useJpgForClipboard"), value); -} - -void ConfigHandler::setSaveAsFileExtension(const QString& extension) -{ - m_settings.setValue(QStringLiteral("setSaveAsFileExtension"), extension); -} - -QString ConfigHandler::getSaveAsFileExtension() -{ - return m_settings - .value(QStringLiteral("setSaveAsFileExtension"), QString(".png")) - .toString(); + return QStringLiteral("%F_%H-%M"); } void ConfigHandler::setDefaultSettings() { foreach (const QString& key, m_settings.allKeys()) { - if (key.startsWith("Shortcuts/")) { + if (isShortcut(key)) { // Do not reset Shortcuts continue; } @@ -595,52 +331,12 @@ void ConfigHandler::setDefaultSettings() m_settings.sync(); } -void ConfigHandler::setAllTheButtons() -{ - QVector buttons = - fromButtonToInt(CaptureToolButton::getIterableButtonTypes()); - // TODO: remove toList in v1.0 - m_settings.setValue(QStringLiteral("buttons"), - QVariant::fromValue(buttons.toList())); -} - QString ConfigHandler::configFilePath() const { return m_settings.fileName(); } -bool ConfigHandler::normalizeButtons(QVector& buttons) -{ - QVector listTypesInt = - fromButtonToInt(CaptureToolButton::getIterableButtonTypes()); - - bool hasChanged = false; - for (int i = 0; i < buttons.size(); i++) { - if (!listTypesInt.contains(buttons.at(i))) { - buttons.remove(i); - hasChanged = true; - } - } - return hasChanged; -} - -QVector ConfigHandler::fromIntToButton( - const QVector& l) -{ - QVector buttons; - for (auto const i : l) - buttons << static_cast(i); - return buttons; -} - -QVector ConfigHandler::fromButtonToInt( - const QVector& l) -{ - QVector buttons; - for (auto const i : l) - buttons << static_cast(i); - return buttons; -} +// GENERIC GETTERS AND SETTERS bool ConfigHandler::setShortcut(const QString& shortcutName, const QString& shortutValue) @@ -659,7 +355,7 @@ bool ConfigHandler::setShortcut(const QString& shortcutName, #endif if (shortutValue.isEmpty()) { - m_settings.setValue(shortcutName, ""); + setValue(shortcutName, ""); } else if (reservedShortcuts.contains(QKeySequence(shortutValue))) { // do not allow to set reserved shortcuts error = true; @@ -672,54 +368,331 @@ bool ConfigHandler::setShortcut(const QString& shortcutName, // do not allow to set overlapped shortcuts foreach (auto currentShortcutName, m_settings.allKeys()) { - if (m_settings.value(currentShortcutName) == shortcutItem) { - m_settings.setValue(shortcutName, ""); + if (value(currentShortcutName) == shortcutItem) { + setValue(shortcutName, ""); error = true; break; } } if (!error) { - m_settings.setValue(shortcutName, shortcutItem); + setValue(shortcutName, shortcutItem); } } m_settings.endGroup(); return !error; } -const QString& ConfigHandler::shortcut(const QString& shortcutName) +QString ConfigHandler::shortcut(const QString& shortcutName) { + return value(QStringLiteral("Shortcuts/") + shortcutName).toString(); +} + +void ConfigHandler::setValue(const QString& key, const QVariant& value) +{ + assertKeyRecognized(key); + if (!hasError()) { + m_skipNextErrorCheck = true; + auto val = valueHandler(key)->representation(value); + m_settings.setValue(key, val); + } +} + +QVariant ConfigHandler::value(const QString& key) const +{ + assertKeyRecognized(key); + // Perform check on entire config if due. Please make sure that this + // function is called in all scenarios - best to keep it on top. + hasError(); + + auto val = m_settings.value(key); + + auto handler = valueHandler(key); + + // Check the value for semantic errors + if (val.isValid() && !handler->check(val)) { + setErrorState(true); + } + if (m_hasError) { + return handler->fallback(); + } + + return handler->value(val); +} + +const QSet& ConfigHandler::recognizedGeneralOptions() const +{ + static QSet options = + QSet::fromList(::recognizedGeneralOptions.keys()); + return options; +} + +const QSet& ConfigHandler::recognizedShortcutNames() const +{ + static QSet names = + QSet::fromList(recognizedShortcuts.keys()); + return names; +} + +/// Return keys from group `group`. Use "General" for general settings. +QSet ConfigHandler::keysFromGroup(const QString& group) const +{ + QSet keys; + for (const QString& key : m_settings.allKeys()) { + if (group == "General" && !key.contains('/')) { + keys.insert(key); + } else if (key.startsWith(group + "/")) { + keys.insert(baseName(key)); + } + } + return keys; +} + +// ERROR HANDLING + +bool ConfigHandler::checkForErrors(QTextStream* log) const +{ + return checkUnrecognizedSettings(log) & checkShortcutConflicts(log) & + checkSemantics(log); +} + +/** + * @brief Parse the config to find settings with unrecognized names. + * @return Whether the config passes this check. + * + * @note An unrecognized option is one that is not included in + * `recognizedGeneralOptions` or `recognizedShortcutNames` depending on the + * group the option belongs to. + */ +bool ConfigHandler::checkUnrecognizedSettings(QTextStream* log) const +{ + // sort the config keys by group + QSet generalKeys = keysFromGroup("General"), + shortcutKeys = keysFromGroup("Shortcuts"), + recognizedGeneralKeys = recognizedGeneralOptions(), + recognizedShortcutKeys = recognizedShortcutNames(); + + // subtract recognized keys + generalKeys.subtract(recognizedGeneralKeys); + shortcutKeys.subtract(recognizedShortcutKeys); + + // what is left are the unrecognized keys - hopefully empty + bool ok = generalKeys.isEmpty() && shortcutKeys.isEmpty(); + if (log != nullptr) { + for (const QString& key : generalKeys) { + *log << QStringLiteral("Unrecognized setting: '%1'\n").arg(key); + } + for (const QString& key : shortcutKeys) { + *log + << QStringLiteral("Unrecognized shortcut name: '%1'.\n").arg(key); + } + } + return ok; +} + +/** + * @brief Check if there are multiple shortcuts with the same key binding. + * @return Whether the config passes this check. + */ +bool ConfigHandler::checkShortcutConflicts(QTextStream* log) const +{ + bool ok = true; m_settings.beginGroup("Shortcuts"); - if (m_settings.contains(shortcutName)) { - m_strRes = m_settings.value(shortcutName).toString(); - } else { - m_strRes = - ConfigShortcuts().captureShortcutDefault(shortcutName).toString(); + QStringList shortcuts = m_settings.allKeys(); + QStringList reportedInLog; + for (auto key1 = shortcuts.begin(); key1 != shortcuts.end(); ++key1) { + for (auto key2 = key1 + 1; key2 != shortcuts.end(); ++key2) { + // values stored in variables are useful when running debugger + QString value1 = m_settings.value(*key1).toString(), + value2 = m_settings.value(*key2).toString(); + if (!value1.isEmpty() && value1 == value2) { + ok = false; + if (log == nullptr) { + break; + } else if (!reportedInLog.contains(*key1) && + !reportedInLog.contains(*key2)) { + reportedInLog.append(*key1); + reportedInLog.append(*key2); + *log << QStringLiteral("Shortcut conflict: '%1' and '%2' " + "have the same shortcut: %3\n") + .arg(*key1) + .arg(*key2) + .arg(value1); + } + } + } } m_settings.endGroup(); - return m_strRes; + return ok; } -void ConfigHandler::setValue(const QString& group, - const QString& key, - const QVariant& value) +/** + * @brief Check each config value semantically. + * @return Whether the config passes this check. + */ +bool ConfigHandler::checkSemantics(QTextStream* log) const { - if (!group.isEmpty()) { - m_settings.beginGroup(group); + QStringList allKeys = m_settings.allKeys(); + bool ok = true; + for (const QString& key : allKeys) { + if (!recognizedGeneralOptions().contains(key) && + !recognizedShortcutNames().contains(baseName(key))) { + continue; + } + QVariant val = m_settings.value(key); + auto valueHandler = this->valueHandler(key); + if (val.isValid() && !valueHandler->check(val)) { + ok = false; + if (log == nullptr) { + break; + } else { + *log << QStringLiteral("Semantic error in '%1'. Expected: %2\n") + .arg(key) + .arg(valueHandler->expected()); + } + } } - m_settings.setValue(key, value); - if (!group.isEmpty()) { - m_settings.endGroup(); + return ok; +} + +/** + * @brief Parse the configuration to find any errors in it. + * + * If the error state changes as a result of the check, it will perform the + * appropriate action, e.g. notify the user. + * + * @see ConfigHandler::setErrorState for all the actions. + */ +void ConfigHandler::checkAndHandleError() const +{ + if (!QFile(m_settings.fileName()).exists()) { + setErrorState(false); + } else { + setErrorState(!checkForErrors()); + } + + ensureFileWatched(); +} + +/** + * @brief Update the tracked error state of the config. + * @param error The new error state. + * + * The error state is tracked so that signals are not emitted and the user is + * not spammed every time the config file changes. Instead, only changes in + * error state get reported. + */ +void ConfigHandler::setErrorState(bool error) const +{ + bool hadError = m_hasError; + m_hasError = error; + // Notify user every time m_hasError changes + if (!hadError && m_hasError) { + QString msg = errorMessage(); + SystemNotification().sendMessage(msg); + emit getInstance()->error(); + } else if (hadError && !m_hasError) { + auto msg = + tr("You have successfully resolved the configuration error."); + SystemNotification().sendMessage(msg); + emit getInstance()->errorResolved(); } } -QVariant& ConfigHandler::value(const QString& group, const QString& key) +/** + * @brief Return if the config contains an error. + * + * If an error check is due, it will be performed. + */ +bool ConfigHandler::hasError() const { - if (!group.isEmpty()) { - m_settings.beginGroup(group); + if (m_errorCheckPending) { + checkAndHandleError(); + m_errorCheckPending = false; } - m_varRes = m_settings.value(key); - if (!group.isEmpty()) { - m_settings.endGroup(); - } - return m_varRes; + return m_hasError; } + +/// Error message that can be used by other classes as well +QString ConfigHandler::errorMessage() const +{ + return tr("The configuration contains an error. Falling back to default."); +} + +void ConfigHandler::ensureFileWatched() const +{ + QFile file(m_settings.fileName()); + if (!file.exists()) { + file.open(QFileDevice::WriteOnly); + file.close(); + } + if (m_configWatcher != nullptr && m_configWatcher->files().isEmpty() && + qApp != nullptr // ensures that the organization name can be accessed + ) { + m_configWatcher->addPath(m_settings.fileName()); + } +} + +/** + * @brief Obtain a `ValueHandler` for the config option with the given key. + * @return Smart pointer to the handler. + * + * @note If the key is from the "General" group, the `recognizedGeneralOptions` + * map is looked up. If it is from "Shortcuts", a generic `KeySequence` value + * handler is returned. + */ +QSharedPointer ConfigHandler::valueHandler( + const QString& key) const +{ + QSharedPointer handler; + if (isShortcut(key)) { + QString _key = key; + _key.replace("Shortcuts/", ""); + handler = recognizedShortcuts.value( + _key, QSharedPointer(new KeySequence())); + } else { // General group + handler = ::recognizedGeneralOptions.value(key); + } + return handler; +} + +/** + * This is used so that we can check if there is a mismatch between a config key + * and its getter function. + * Debug: throw an exception; Release: set error state + */ +void ConfigHandler::assertKeyRecognized(const QString& key) const +{ + bool recognized = isShortcut(key) + ? recognizedShortcutNames().contains(baseName(key)) + : ::recognizedGeneralOptions.contains(key); + if (!recognized) { +#if defined(QT_DEBUG) + // This should never happen, but just in case + throw std::logic_error( + tr("Bad config key '%1' in ConfigHandler. Please report " + "this as a bug.") + .arg(key) + .toStdString()); +#else + setErrorState(true); +#endif + } +} + +bool ConfigHandler::isShortcut(const QString& key) const +{ + return m_settings.group() == QStringLiteral("Shortcuts") || + key.startsWith(QStringLiteral("Shortcuts/")); +} + +QString ConfigHandler::baseName(QString key) const +{ + return QFileInfo(key).baseName(); +} + +// STATIC MEMBER DEFINITIONS + +bool ConfigHandler::m_hasError = false; +bool ConfigHandler::m_errorCheckPending = false; +bool ConfigHandler::m_skipNextErrorCheck = false; +QSharedPointer ConfigHandler::m_configWatcher; diff --git a/src/utils/confighandler.h b/src/utils/confighandler.h index d15ef9e4..ec79cb93 100644 --- a/src/utils/confighandler.h +++ b/src/utils/confighandler.h @@ -9,121 +9,141 @@ #include #include -class ConfigHandler +class QFileSystemWatcher; +class ValueHandler; +template +class QSharedPointer; +class QTextStream; + +/** + * Declare and implement a getter for a config option. `KEY` is the option key + * as it appears in the config file, `TYPE` is the C++ type. At the same time + * `KEY` is the name of the generated getter function. + */ +#define CONFIG_GETTER(KEY, TYPE) \ + TYPE KEY() { return value(QStringLiteral(#KEY)).value(); } + +/** + * Declare and implement a setter for a config option. `FUNC` is the name of the + * generated function, `KEY` is the option key as it appears in the config file + * and `TYPE` is the C++ type. + */ +#define CONFIG_SETTER(FUNC, KEY, TYPE) \ + void FUNC(const TYPE& value) \ + { \ + setValue(QStringLiteral(#KEY), QVariant::fromValue(value)); \ + } + +/** + * Combines the functionality of `CONFIG_GETTER` and `CONFIG_SETTER`. `GETFUNC` + * is simultaneously the name of the getter function and the option key as it + * appears in the config file. `SETFUNC` is the name of the setter function. + * `TYPE` is the C++ type of the value. + */ +#define CONFIG_GETTER_SETTER(GETFUNC, SETFUNC, TYPE) \ + CONFIG_GETTER(GETFUNC, TYPE) \ + CONFIG_SETTER(SETFUNC, GETFUNC, TYPE) + +class ConfigHandler : public QObject { + Q_OBJECT + public: - explicit ConfigHandler(); + explicit ConfigHandler(bool skipInitialErrorCheck = false); - QVector getButtons(); - void setButtons(const QVector&); + static ConfigHandler* getInstance(); - QVector getUserColors(); + // Definitions of getters and setters for config options + // Some special cases are implemented regularly, without the macro + // NOTE: When adding new options, make sure to add an entry in + // recognizedGeneralOptions in the cpp file. + CONFIG_GETTER_SETTER(userColors, setUserColors, QVector); + CONFIG_GETTER_SETTER(savePath, setSavePath, QString) + CONFIG_GETTER_SETTER(savePathFixed, setSavePathFixed, bool) + CONFIG_GETTER_SETTER(uiColor, setUiColor, QColor) + CONFIG_GETTER_SETTER(contrastUiColor, setContrastUiColor, QColor) + CONFIG_GETTER_SETTER(drawColor, setDrawColor, QColor) + CONFIG_GETTER_SETTER(fontFamily, setFontFamily, QString) + CONFIG_GETTER_SETTER(showHelp, setShowHelp, bool) + CONFIG_GETTER_SETTER(showSidePanelButton, setShowSidePanelButton, bool) + CONFIG_GETTER_SETTER(showDesktopNotification, + setShowDesktopNotification, + bool) + CONFIG_GETTER_SETTER(filenamePattern, setFilenamePattern, QString) + CONFIG_GETTER_SETTER(disabledTrayIcon, setDisabledTrayIcon, bool) + CONFIG_GETTER_SETTER(drawThickness, setDrawThickness, int) + CONFIG_GETTER_SETTER(drawFontSize, setDrawFontSize, int) + CONFIG_GETTER_SETTER(keepOpenAppLauncher, setKeepOpenAppLauncher, bool) + CONFIG_GETTER_SETTER(checkForUpdates, setCheckForUpdates, bool) + CONFIG_GETTER_SETTER(showStartupLaunchMessage, + setShowStartupLaunchMessage, + bool) + CONFIG_GETTER_SETTER(contrastOpacity, setContrastOpacity, int) + CONFIG_GETTER_SETTER(copyAndCloseAfterUpload, + setCopyAndCloseAfterUpload, + bool) + CONFIG_GETTER_SETTER(historyConfirmationToDelete, + setHistoryConfirmationToDelete, + bool) + CONFIG_GETTER_SETTER(uploadHistoryMax, setUploadHistoryMax, int) + CONFIG_GETTER_SETTER(saveAfterCopy, setSaveAfterCopy, bool) + CONFIG_GETTER_SETTER(copyPathAfterSave, setCopyPathAfterSave, bool) + CONFIG_GETTER_SETTER(useJpgForClipboard, setUseJpgForClipboard, bool) + CONFIG_GETTER_SETTER(ignoreUpdateToVersion, + setIgnoreUpdateToVersion, + QString) + CONFIG_GETTER_SETTER(undoLimit, setUndoLimit, int) + CONFIG_GETTER_SETTER(buttons, + setButtons, + QList) - QString savePath(); - void setSavePath(const QString&); - - bool savePathFixed(); - void setSavePathFixed(bool); - - QColor uiMainColorValue(); - void setUIMainColor(const QColor&); - - QColor uiContrastColorValue(); - void setUIContrastColor(const QColor&); - - QColor drawColorValue(); - void setDrawColor(const QColor&); - - void setFontFamily(const QString&); - const QString& fontFamily(); - - bool showHelpValue(); - void setShowHelp(const bool); - - bool showSidePanelButtonValue(); - void setShowSidePanelButton(const bool); - - bool desktopNotificationValue(); - void setDesktopNotification(const bool); - - QString filenamePatternDefault(); - QString filenamePatternValue(); - void setFilenamePattern(const QString&); - - bool disabledTrayIconValue(); - void setDisabledTrayIcon(const bool); - - int drawThicknessValue(); - void setDrawThickness(const int); - - int drawFontSizeValue(); - void setDrawFontSize(const int); - - bool keepOpenAppLauncherValue(); - void setKeepOpenAppLauncher(const bool); - - bool checkForUpdates(); - void setCheckForUpdates(const bool); - - bool verifyLaunchFile(); - bool startupLaunchValue(); + // SPECIAL CASES + bool startupLaunch(); void setStartupLaunch(const bool); - - bool showStartupLaunchMessage(); - void setShowStartupLaunchMessage(const bool); - - int contrastOpacityValue(); - void setContrastOpacity(const int); - - bool copyAndCloseAfterUploadEnabled(); - void setCopyAndCloseAfterUploadEnabled(const bool); - - bool historyConfirmationToDelete(); - void setHistoryConfirmationToDelete(const bool save); - - int uploadHistoryMaxSizeValue(); - void setUploadHistoryMaxSize(const int); - - bool saveAfterCopyValue(); - void setSaveAfterCopy(const bool); - - bool copyPathAfterSaveEnabled(); - void setCopyPathAfterSaveEnabled(const bool); - - bool useJpgForClipboard() const; - void setUseJpgForClipboard(const bool); - void setSaveAsFileExtension(const QString& extension); - QString getSaveAsFileExtension(); - - void setDefaultSettings(); + QString saveAsFileExtension(); + CONFIG_SETTER(setSaveAsFileExtension, setSaveAsFileExtension, QString) void setAllTheButtons(); - void setIgnoreUpdateToVersion(const QString& text); - QString ignoreUpdateToVersion(); - - void setUndoLimit(int value); - int undoLimit(); - - bool setShortcut(const QString&, const QString&); - const QString& shortcut(const QString&); - + // DEFAULTS + QString filenamePatternDefault(); + void setDefaultSettings(); QString configFilePath() const; - void setValue(const QString& group, - const QString& key, - const QVariant& value); - QVariant& value(const QString& group, const QString& key); + // GENERIC GETTERS AND SETTERS + bool setShortcut(const QString&, const QString&); + QString shortcut(const QString&); + void setValue(const QString& key, const QVariant& value); + QVariant value(const QString& key) const; + + // INFO + const QSet& recognizedGeneralOptions() const; + const QSet& recognizedShortcutNames() const; + QSet keysFromGroup(const QString& group) const; + + // ERROR HANDLING + bool checkForErrors(QTextStream* log = nullptr) const; + bool checkUnrecognizedSettings(QTextStream* log = nullptr) const; + bool checkShortcutConflicts(QTextStream* log = nullptr) const; + bool checkSemantics(QTextStream* log = nullptr) const; + void checkAndHandleError() const; + void setErrorState(bool error) const; + bool hasError() const; + QString errorMessage() const; + +signals: + void error() const; + void errorResolved() const; + void fileChanged() const; private: - QString m_strRes; - QVariant m_varRes; - QSettings m_settings; - QVector m_shortcuts; + mutable QSettings m_settings; - bool normalizeButtons(QVector&); + static bool m_hasError, m_errorCheckPending, m_skipNextErrorCheck; + static QSharedPointer m_configWatcher; - QVector fromIntToButton( - const QVector& l); - QVector fromButtonToInt( - const QVector& l); + void ensureFileWatched() const; + QSharedPointer valueHandler(const QString& key) const; + void assertKeyRecognized(const QString& key) const; + bool isShortcut(const QString& key) const; + QString baseName(QString key) const; }; diff --git a/src/utils/configshortcuts.h b/src/utils/configshortcuts.h deleted file mode 100644 index cbe77a1a..00000000 --- a/src/utils/configshortcuts.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef CONFIGSHORTCUTS_H -#define CONFIGSHORTCUTS_H - -#include "src/widgets/capture/capturetoolbutton.h" -#include -#include -#include -#include - -class ConfigShortcuts -{ -public: - ConfigShortcuts(); - - const QVector& captureShortcutsDefault( - const QVector& buttons); - const QKeySequence& captureShortcutDefault(const QString& buttonType); - -private: - QVector m_shortcuts; - QKeySequence m_ks; - - void addShortcut(const QString& shortcutName, const QString& description); -}; - -#endif // CONFIGSHORTCUTS_H diff --git a/src/utils/filenamehandler.cpp b/src/utils/filenamehandler.cpp index f678bd1a..7959538d 100644 --- a/src/utils/filenamehandler.cpp +++ b/src/utils/filenamehandler.cpp @@ -27,7 +27,7 @@ FileNameHandler::FileNameHandler(QObject* parent) QString FileNameHandler::parsedPattern() { - return parseFilename(ConfigHandler().filenamePatternValue()); + return parseFilename(ConfigHandler().filenamePattern()); } QString FileNameHandler::parseFilename(const QString& name) diff --git a/src/utils/history.cpp b/src/utils/history.cpp index 9e20d6e1..e1af3318 100644 --- a/src/utils/history.cpp +++ b/src/utils/history.cpp @@ -57,7 +57,7 @@ const QList& History::history() QDir::Files, QDir::Time); int cnt = 0; - int max = ConfigHandler().uploadHistoryMaxSizeValue(); + int max = ConfigHandler().uploadHistoryMax(); m_thumbs.clear(); foreach (QString fileName, images) { if (++cnt <= max) { diff --git a/src/utils/screenshotsaver.cpp b/src/utils/screenshotsaver.cpp index 1c37f6a6..c16e5d42 100644 --- a/src/utils/screenshotsaver.cpp +++ b/src/utils/screenshotsaver.cpp @@ -58,7 +58,7 @@ void ScreenshotSaver::saveToClipboard(const QPixmap& capture) { // If we are able to properly save the file, save the file and copy to // clipboard. - if ((ConfigHandler().saveAfterCopyValue()) && + if ((ConfigHandler().saveAfterCopy()) && (!ConfigHandler().savePath().isEmpty())) { saveToFilesystem(capture, ConfigHandler().savePath(), @@ -126,7 +126,7 @@ QString ScreenshotSaver::ShowSaveFileDialog(QWidget* parent, mimeTypeList.append(mimeType); dialog.setMimeTypeFilters(mimeTypeList); - QString suffix = ConfigHandler().getSaveAsFileExtension(); + QString suffix = ConfigHandler().saveAsFileExtension(); QString defaultMimeType = QMimeDatabase().mimeTypeForFile("image" + suffix).name(); dialog.selectMimeTypeFilter(defaultMimeType); @@ -179,7 +179,7 @@ bool ScreenshotSaver::saveToFilesystemGUI(const QPixmap& capture) QString msg = QObject::tr("Capture saved as ") + savePath; - if (config.copyPathAfterSaveEnabled()) { + if (config.copyPathAfterSave()) { msg = QObject::tr("Capture is saved and copied to the clipboard as ") + savePath; @@ -190,7 +190,7 @@ bool ScreenshotSaver::saveToFilesystemGUI(const QPixmap& capture) Controller::getInstance()->sendCaptureSaved( m_id, QFileInfo(savePath).canonicalFilePath()); - if (config.copyPathAfterSaveEnabled()) { + if (config.copyPathAfterSave()) { QApplication::clipboard()->setText(savePath); } diff --git a/src/utils/systemnotification.cpp b/src/utils/systemnotification.cpp index 6dabfc24..2081c106 100644 --- a/src/utils/systemnotification.cpp +++ b/src/utils/systemnotification.cpp @@ -35,7 +35,7 @@ void SystemNotification::sendMessage(const QString& text, const QString& savePath, const int timeout) { - if (!ConfigHandler().desktopNotificationValue()) { + if (!ConfigHandler().showDesktopNotification()) { return; } diff --git a/src/utils/valuehandler.cpp b/src/utils/valuehandler.cpp new file mode 100644 index 00000000..951dce38 --- /dev/null +++ b/src/utils/valuehandler.cpp @@ -0,0 +1,413 @@ +#include "valuehandler.h" +#include "confighandler.h" +#include +#include +#include +#include + +// VALUE HANDLER + +QVariant ValueHandler::value(const QVariant& val) +{ + if (!val.isValid() || !check(val)) { + return fallback(); + } else { + return process(val); + } +} + +QVariant ValueHandler::fallback() +{ + return QVariant(); +} + +QVariant ValueHandler::representation(const QVariant& val) +{ + return val.toString(); +} + +QString ValueHandler::expected() +{ + return {}; +} + +QVariant ValueHandler::process(const QVariant& val) +{ + return val; +} + +// BOOL + +Bool::Bool(bool def) + : m_def(def) +{} + +bool Bool::check(const QVariant& val) +{ + QString str = val.toString(); + if (str != "true" && str != "false") { + return false; + } + return true; +} + +QVariant Bool::fallback() +{ + return m_def; +} + +QString Bool::expected() +{ + return QStringLiteral("true or false"); +} + +// STRING + +String::String(const QString& def) + : m_def(def) +{} + +bool String::check(const QVariant&) +{ + return true; +} + +QVariant String::fallback() +{ + return m_def; +} + +QString String::expected() +{ + return QStringLiteral("string"); +} + +// COLOR + +Color::Color(const QColor& def) + : m_def(def) +{} + +bool Color::check(const QVariant& val) +{ + QString str = val.toString(); + // Disable #RGB, #RRRGGGBBB and #RRRRGGGGBBBB formats that QColor supports + return QColor::isValidColor(str) && + (str[0] != '#' || + (str.length() != 4 && str.length() != 10 && str.length() != 13)); +} + +QVariant Color::process(const QVariant& val) +{ + QString str = val.toString(); + QColor color(str); + if (str.length() == 9 && str[0] == '#') { + // Convert #RRGGBBAA (flameshot) to #AARRGGBB (QColor) + int blue = color.blue(); + color.setBlue(color.green()); + color.setGreen(color.red()); + color.setRed(color.alpha()); + color.setAlpha(blue); + } + return color; +} + +QVariant Color::fallback() +{ + return m_def; +} + +QVariant Color::representation(const QVariant& val) +{ + QString str = val.toString(); + QColor color(str); + if (str.length() == 9 && str[0] == '#') { + // Convert #AARRGGBB (QColor) to #RRGGBBAA (flameshot) + int alpha = color.alpha(); + color.setAlpha(color.red()); + color.setRed(color.green()); + color.setGreen(color.blue()); + color.setBlue(alpha); + } + return color.name(); +} + +QString Color::expected() +{ + return QStringLiteral("color name or hex value"); +} + +// BOUNDED INT + +BoundedInt::BoundedInt(int min, int max, int def) + : m_min(min) + , m_max(max) + , m_def(def) +{} + +bool BoundedInt::check(const QVariant& val) +{ + QString str = val.toString(); + bool conversionOk; + int num = str.toInt(&conversionOk); + return conversionOk && (m_max < m_min || num <= m_max); +} + +QVariant BoundedInt::fallback() +{ + return m_def; +} + +QString BoundedInt::expected() +{ + return QStringLiteral("number between %1 and %2").arg(m_min).arg(m_max); +} + +// LOWER BOUNDED INT + +LowerBoundedInt::LowerBoundedInt(int min, int def) + : m_min(min) + , m_def(def) +{} + +bool LowerBoundedInt::check(const QVariant& val) +{ + QString str = val.toString(); + bool conversionOk; + int num = str.toInt(&conversionOk); + return conversionOk && num >= m_min; +} + +QVariant LowerBoundedInt::fallback() +{ + return m_def; +} + +QString LowerBoundedInt::expected() +{ + return QStringLiteral("number >= %1").arg(m_min); +} + +// KEY SEQUENCE + +KeySequence::KeySequence(const QKeySequence& fallback) + : m_fallback(fallback) +{} + +bool KeySequence::check(const QVariant& val) +{ + QString str = val.toString(); + if (!str.isEmpty() && QKeySequence(str).toString().isEmpty()) { + return false; + } + return true; +} + +QVariant KeySequence::fallback() +{ + return m_fallback; +} + +QString KeySequence::expected() +{ + return QStringLiteral("keyboard shortcut"); +} + +// EXISTING DIR + +bool ExistingDir::check(const QVariant& val) +{ + if (!val.canConvert(QVariant::String) || val.toString().isEmpty()) { + return false; + } + QFileInfo info(val.toString()); + return info.isDir() && info.exists(); +} + +QVariant ExistingDir::fallback() +{ + using SP = QStandardPaths; + for (auto location : + { SP::PicturesLocation, SP::HomeLocation, SP::TempLocation }) { + QString path = SP::writableLocation(location); + if (QFileInfo(path).isDir()) { + return path; + } + } + return {}; +} + +QString ExistingDir::expected() +{ + return QStringLiteral("existing directory"); +} + +// FILENAME PATTERN + +bool FilenamePattern::check(const QVariant&) +{ + return true; +} + +QVariant FilenamePattern::fallback() +{ + return ConfigHandler().filenamePatternDefault(); +} + +QVariant FilenamePattern::process(const QVariant& val) +{ + QString str = val.toString(); + return !str.isEmpty() ? val : fallback(); +} + +QString FilenamePattern::expected() +{ + return QStringLiteral("please edit using the GUI"); +} + +// BUTTON LIST + +using BType = CaptureToolButton::ButtonType; +using BList = QList; + +bool ButtonList::check(const QVariant& val) +{ + using CTB = CaptureToolButton; + auto allButtons = CTB::getIterableButtonTypes(); + for (int btn : val.value>()) { + if (!allButtons.contains(static_cast(btn))) { + return false; + } + } + return true; +} + +// Helper +void sortButtons(BList& buttons) +{ + std::sort(buttons.begin(), buttons.end(), [](BType a, BType b) { + return CaptureToolButton::getPriorityByButton(a) < + CaptureToolButton::getPriorityByButton(b); + }); +} + +QVariant ButtonList::process(const QVariant& val) +{ + QList intButtons = val.value>(); + auto buttons = ButtonList::fromIntList(intButtons); + sortButtons(buttons); + return QVariant::fromValue(buttons); +} + +QVariant ButtonList::fallback() +{ + auto buttons = CaptureToolButton::getIterableButtonTypes(); + buttons.removeOne(CaptureToolButton::TYPE_SIZEDECREASE); + buttons.removeOne(CaptureToolButton::TYPE_SIZEINCREASE); + sortButtons(buttons); + return QVariant::fromValue(buttons); +} + +QVariant ButtonList::representation(const QVariant& val) +{ + auto intList = toIntList(val.value()); + normalizeButtons(intList); + return QVariant::fromValue(intList); +} + +QString ButtonList::expected() +{ + return QStringLiteral("please don't edit by hand"); +} + +QList ButtonList::fromIntList( + const QList& l) +{ + QList buttons; + buttons.reserve(l.size()); + for (auto const i : l) + buttons << static_cast(i); + return buttons; +} + +QList ButtonList::toIntList(const QList& l) +{ + QList buttons; + buttons.reserve(l.size()); + for (auto const i : l) + buttons << static_cast(i); + return buttons; +} + +bool ButtonList::normalizeButtons(QList& buttons) +{ + QList listTypesInt = + toIntList(CaptureToolButton::getIterableButtonTypes()); + + bool hasChanged = false; + for (int i = 0; i < buttons.size(); i++) { + if (!listTypesInt.contains(buttons.at(i))) { + buttons.removeAt(i); + hasChanged = true; + } + } + return hasChanged; +} + +// USER COLORS + +bool UserColors::check(const QVariant& val) +{ + if (!val.isValid()) { + return true; + } + if (!val.canConvert(QVariant::StringList)) { + return false; + } + for (const QString& str : val.toStringList()) { + if (!QColor::isValidColor(str) && str != "picker") { + return false; + } + } + return true; +} + +QVariant UserColors::process(const QVariant& val) +{ + QStringList strColors = val.toStringList(); + if (strColors.isEmpty()) { + return fallback(); + } + + QVector colors; + colors.reserve(strColors.size()); + + for (const QString& str : strColors) { + if (str != "picker") { + colors.append(QColor(str)); + } else { + colors.append(QColor()); + } + } + + return QVariant::fromValue(colors); +} + +QVariant UserColors::fallback() +{ + return QVariant::fromValue(QVector{ Qt::darkRed, + Qt::red, + Qt::yellow, + Qt::green, + Qt::darkGreen, + Qt::cyan, + Qt::blue, + Qt::magenta, + Qt::darkMagenta, + QColor() }); +} + +QString UserColors::expected() +{ + return QStringLiteral("list of colors separated by comma"); +} diff --git a/src/utils/valuehandler.h b/src/utils/valuehandler.h new file mode 100644 index 00000000..5f6de375 --- /dev/null +++ b/src/utils/valuehandler.h @@ -0,0 +1,197 @@ +#pragma once + +#include "src/widgets/capture/capturetoolbutton.h" + +#include +#include +#include + +class QVariant; + +/** + * @brief Handles the value of a configuration option (abstract class). + * + * Each configuration option is represented as a `QVariant`. If the option was + * not specified in a config file, the `QVariant` will be invalid. + * + * Each option will usually be handled in three different ways: + * - have its value checked for semantic errors (type, format, etc). + * @see ValueHandler::check + * - have its value (that was taken from the config file) adapted for proper + * use. + * @see ValueHandler::value + * - provided a fallback value in case: the config does not explicitly specify + * it, or the config contains an error and is globally falling back to + * defaults. + * @see ValueHandler::fallback. + * - some options may want to be stored in the config file in a different way + * than the default one provided by `QVariant`. + * @see ValueHandler::representation + * + * @note Please see the documentation of the functions to learn when you should + * override each. + * + */ +class ValueHandler +{ +public: + /** + * @brief Check the value semantically. + * @param val The value that was read from the config file + * @return Whether the value is correct + * @note The function should presume that `val.isValid()` is true. + */ + virtual bool check(const QVariant& val) = 0; + /** + * @brief Adapt the value for proper use. + * @param val The value that was read from the config file + * @return The modified value + * + * If the value is invalid (unspecified in the config) or does not pass + * `check`, the fallback will be returned. Otherwise the value is processed + * by `process` and then returned. + * + * @note Cannot be overriden + * @see fallback, process + */ + QVariant value(const QVariant& val); + /** + * @brief Fallback value (default value). + */ + virtual QVariant fallback(); + /** + * @brief Return the representaion of the value in the config file. + * + * Override this if you want to write the value in a different format than + * the one provided by `QVariant`. + */ + virtual QVariant representation(const QVariant& val); + /** + * @brief The expected value (descriptive). + * Used when reporting configuration errors. + */ + virtual QString expected(); + +protected: + /** + * @brief Process a value, presuming it is a valid `QVariant`. + * @param val The value that was read from the config file + * @return The processed value + * @note You will usually want to override this. In rare cases, you may want + * to override `value`. + */ + virtual QVariant process(const QVariant& val); +}; + +class Bool : public ValueHandler +{ +public: + Bool(bool def); + bool check(const QVariant& val) override; + QVariant fallback() override; + QString expected() override; + +private: + bool m_def; +}; + +class String : public ValueHandler +{ +public: + String(const QString& def); + bool check(const QVariant&) override; + QVariant fallback() override; + QString expected() override; + +private: + QString m_def; +}; + +class Color : public ValueHandler +{ +public: + Color(const QColor& def); + bool check(const QVariant& val) override; + QVariant process(const QVariant& val) override; + QVariant fallback() override; + QVariant representation(const QVariant& val) override; + QString expected() override; + +private: + QColor m_def; +}; + +class BoundedInt : public ValueHandler +{ +public: + BoundedInt(int min, int max, int def); + + bool check(const QVariant& val) override; + virtual QVariant fallback() override; + QString expected() override; + +private: + int m_min, m_max, m_def; +}; + +class LowerBoundedInt : public ValueHandler +{ +public: + LowerBoundedInt(int min, int def); + bool check(const QVariant& val) override; + QVariant fallback() override; + QString expected() override; + +private: + int m_min, m_def; +}; + +class KeySequence : public ValueHandler +{ +public: + KeySequence(const QKeySequence& fallback = {}); + bool check(const QVariant& val) override; + QVariant fallback() override; + QString expected() override; + +private: + QKeySequence m_fallback; +}; + +class ExistingDir : public ValueHandler +{ + bool check(const QVariant& val) override; + QVariant fallback() override; + QString expected() override; +}; + +class FilenamePattern : public ValueHandler +{ + bool check(const QVariant&) override; + QVariant fallback() override; + QVariant process(const QVariant&) override; + QString expected() override; +}; + +class ButtonList : public ValueHandler +{ +public: + bool check(const QVariant& val) override; + QVariant process(const QVariant& val) override; + QVariant fallback() override; + QVariant representation(const QVariant& val) override; + QString expected() override; + + // UTILITY FUNCTIONS + static QList fromIntList(const QList&); + static QList toIntList(const QList& l); + static bool normalizeButtons(QList& buttons); +}; + +class UserColors : public ValueHandler +{ + bool check(const QVariant& val) override; + QVariant process(const QVariant& val) override; + QVariant fallback() override; + QString expected() override; +}; diff --git a/src/widgets/capture/capturebutton.cpp b/src/widgets/capture/capturebutton.cpp index 2dabc26d..7127de23 100644 --- a/src/widgets/capture/capturebutton.cpp +++ b/src/widgets/capture/capturebutton.cpp @@ -72,4 +72,4 @@ void CaptureButton::setColor(const QColor& c) setStyleSheet(styleSheet()); } -QColor CaptureButton::m_mainColor = ConfigHandler().uiMainColorValue(); +QColor CaptureButton::m_mainColor = ConfigHandler().uiColor(); diff --git a/src/widgets/capture/capturetoolbutton.cpp b/src/widgets/capture/capturetoolbutton.cpp index 3048f1d5..29edbbb7 100644 --- a/src/widgets/capture/capturetoolbutton.cpp +++ b/src/widgets/capture/capturetoolbutton.cpp @@ -81,8 +81,7 @@ void CaptureToolButton::updateIcon() setIconSize(size() * 0.6); } -QVector -CaptureToolButton::getIterableButtonTypes() +QList CaptureToolButton::getIterableButtonTypes() { return iterableButtonTypes; } @@ -123,7 +122,7 @@ void CaptureToolButton::setColor(const QColor& c) updateIcon(); } -QColor CaptureToolButton::m_mainColor = ConfigHandler().uiMainColorValue(); +QColor CaptureToolButton::m_mainColor = ConfigHandler().uiColor(); static std::map buttonTypeOrder { @@ -163,31 +162,30 @@ int CaptureToolButton::getPriorityByButton(CaptureToolButton::ButtonType b) : it->second; } -QVector - CaptureToolButton::iterableButtonTypes = { - CaptureToolButton::TYPE_PENCIL, - CaptureToolButton::TYPE_DRAWER, - CaptureToolButton::TYPE_ARROW, - CaptureToolButton::TYPE_SELECTION, - CaptureToolButton::TYPE_RECTANGLE, - CaptureToolButton::TYPE_CIRCLE, - CaptureToolButton::TYPE_MARKER, - CaptureToolButton::TYPE_TEXT, - CaptureToolButton::TYPE_PIXELATE, - CaptureToolButton::TYPE_INVERT, - CaptureToolButton::TYPE_CIRCLECOUNT, - CaptureToolButton::TYPE_SELECTIONINDICATOR, - CaptureToolButton::TYPE_MOVESELECTION, - CaptureToolButton::TYPE_UNDO, - CaptureToolButton::TYPE_REDO, - CaptureToolButton::TYPE_COPY, - CaptureToolButton::TYPE_SAVE, - CaptureToolButton::TYPE_EXIT, - CaptureToolButton::TYPE_IMAGEUPLOADER, +QList CaptureToolButton::iterableButtonTypes = { + CaptureToolButton::TYPE_PENCIL, + CaptureToolButton::TYPE_DRAWER, + CaptureToolButton::TYPE_ARROW, + CaptureToolButton::TYPE_SELECTION, + CaptureToolButton::TYPE_RECTANGLE, + CaptureToolButton::TYPE_CIRCLE, + CaptureToolButton::TYPE_MARKER, + CaptureToolButton::TYPE_TEXT, + CaptureToolButton::TYPE_PIXELATE, + CaptureToolButton::TYPE_INVERT, + CaptureToolButton::TYPE_SELECTIONINDICATOR, + CaptureToolButton::TYPE_MOVESELECTION, + CaptureToolButton::TYPE_UNDO, + CaptureToolButton::TYPE_REDO, + CaptureToolButton::TYPE_COPY, + CaptureToolButton::TYPE_SAVE, + CaptureToolButton::TYPE_EXIT, + CaptureToolButton::TYPE_IMAGEUPLOADER, #if !defined(Q_OS_MACOS) - CaptureToolButton::TYPE_OPEN_APP, + CaptureToolButton::TYPE_OPEN_APP, #endif - CaptureToolButton::TYPE_PIN, - CaptureToolButton::TYPE_SIZEINCREASE, - CaptureToolButton::TYPE_SIZEDECREASE, - }; + CaptureToolButton::TYPE_PIN, + CaptureToolButton::TYPE_CIRCLECOUNT, + CaptureToolButton::TYPE_SIZEINCREASE, + CaptureToolButton::TYPE_SIZEDECREASE, +}; diff --git a/src/widgets/capture/capturetoolbutton.h b/src/widgets/capture/capturetoolbutton.h index d76d9283..bf456dbd 100644 --- a/src/widgets/capture/capturetoolbutton.h +++ b/src/widgets/capture/capturetoolbutton.h @@ -51,7 +51,7 @@ public: explicit CaptureToolButton(const ButtonType, QWidget* parent = nullptr); ~CaptureToolButton(); - static QVector getIterableButtonTypes(); + static QList getIterableButtonTypes(); static int getPriorityByButton(CaptureToolButton::ButtonType); QString name() const; @@ -64,7 +64,7 @@ public: protected: void mousePressEvent(QMouseEvent* e) override; - static QVector iterableButtonTypes; + static QList iterableButtonTypes; CaptureTool* m_tool; diff --git a/src/widgets/capture/capturewidget.cpp b/src/widgets/capture/capturewidget.cpp index 6fe2727d..d67f182f 100644 --- a/src/widgets/capture/capturewidget.cpp +++ b/src/widgets/capture/capturewidget.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -56,6 +58,8 @@ CaptureWidget::CaptureWidget(uint id, , m_captureDone(false) , m_previewEnabled(true) , m_adjustmentButtonPressed(false) + , m_configError(false) + , m_configErrorResolved(false) , m_activeButton(nullptr) , m_activeTool(nullptr) , m_toolWidget(nullptr) @@ -85,9 +89,9 @@ CaptureWidget::CaptureWidget(uint id, this, &CaptureWidget::childLeave); setAttribute(Qt::WA_DeleteOnClose); - m_opacity = m_config.contrastOpacityValue(); - m_uiColor = m_config.uiMainColorValue(); - m_contrastUiColor = m_config.uiContrastColorValue(); + m_opacity = m_config.contrastOpacity(); + m_uiColor = m_config.uiColor(); + m_contrastUiColor = m_config.contrastUiColor(); setMouseTracking(true); initContext(savePath, fullScreen); initShortcuts(); @@ -200,10 +204,25 @@ CaptureWidget::CaptureWidget(uint id, initPanel(); + m_config.checkAndHandleError(); + if (m_config.hasError()) { + m_configError = true; + } + connect(ConfigHandler::getInstance(), &ConfigHandler::error, this, [=]() { + m_configError = true; + m_configErrorResolved = false; + update(); + }); + connect( + ConfigHandler::getInstance(), &ConfigHandler::errorResolved, this, [=]() { + m_configError = false; + m_configErrorResolved = true; + update(); + }); OverlayMessage::init(this, QGuiAppCurrentScreen().currentScreen()->geometry()); - if (m_config.showHelpValue()) { + if (m_config.showHelp()) { OverlayMessage::push( tr("Select an area with the mouse, or press Esc to exit." "\nPress Enter to capture the screen." @@ -225,7 +244,7 @@ CaptureWidget::~CaptureWidget() void CaptureWidget::initButtons() { auto allButtonTypes = CaptureToolButton::getIterableButtonTypes(); - auto visibleButtonTypes = m_config.getButtons(); + auto visibleButtonTypes = m_config.buttons(); QVector vectorButtons; // Add all buttons but hide those that were disabled in the Interface config @@ -376,6 +395,10 @@ void CaptureWidget::paintEvent(QPaintEvent* paintEvent) // draw inactive region drawInactiveRegion(&painter); + + if (m_configError || m_configErrorResolved) { + drawConfigErrorMessage(&painter); + } } void CaptureWidget::showColorPicker(const QPoint& pos) @@ -683,8 +706,7 @@ void CaptureWidget::mouseMoveEvent(QMouseEvent* e) m_buttonHandler->show(); } } - } else if (m_activeButton && m_activeButton->tool() && - m_activeButton->tool()->showMousePreview()) { + } else if (m_activeButton && m_activeButton->tool()) { update(); } else { if (!m_selection->isVisible()) { @@ -705,7 +727,7 @@ void CaptureWidget::mouseReleaseEvent(QMouseEvent* e) } m_colorPicker->hide(); if (!m_context.color.isValid()) { - m_context.color = ConfigHandler().drawColorValue(); + m_context.color = ConfigHandler().drawColor(); m_panel->show(); } } else if (m_mouseIsClicked) { @@ -907,11 +929,11 @@ void CaptureWidget::moveEvent(QMoveEvent* e) void CaptureWidget::initContext(const QString& savePath, bool fullscreen) { - m_context.color = m_config.drawColorValue(); + m_context.color = m_config.drawColor(); m_context.savePath = savePath; m_context.widgetOffset = mapToGlobal(QPoint(0, 0)); m_context.mousePos = mapFromGlobal(QCursor::pos()); - m_context.thickness = m_config.drawThicknessValue(); + m_context.thickness = m_config.drawThickness(); m_context.fullscreen = fullscreen; } @@ -934,7 +956,7 @@ void CaptureWidget::initPanel() #endif } - if (ConfigHandler().showSidePanelButtonValue()) { + if (ConfigHandler().showSidePanelButton()) { auto* panelToggleButton = new OrientablePushButton(tr("Tool Settings"), this); makeChild(panelToggleButton); @@ -1107,9 +1129,9 @@ void CaptureWidget::loadDrawThickness() if ((m_activeButton && m_activeButton->tool() && m_activeButton->tool()->type() == ToolType::TEXT) || (m_activeTool && m_activeTool->type() == ToolType::TEXT)) { - m_context.thickness = m_config.drawFontSizeValue(); + m_context.thickness = m_config.drawFontSize(); } else { - m_context.thickness = m_config.drawThicknessValue(); + m_context.thickness = m_config.drawThickness(); } emit m_sidePanel->thicknessChanged(m_context.thickness); } @@ -1670,6 +1692,28 @@ QRect CaptureWidget::extendedRect(const QRect& r) const r.height() * devicePixelRatio); } +void CaptureWidget::drawConfigErrorMessage(QPainter* painter) +{ + QString msg; + if (m_configError) { + msg = ConfigHandler().errorMessage(); + } else if (m_configErrorResolved) { + msg = tr("Configuration error resolved. Launch `flameshot " + "gui` again to apply it."); + } + + auto textRect = painter->fontMetrics().boundingRect(msg); + int w = textRect.width(), h = textRect.height(); + textRect = { size().width() - w, size().height() - h, w + 100, h + 100 }; + QScreen* currentScreen = QGuiAppCurrentScreen().currentScreen(); + + if (!textRect.contains(QCursor::pos(currentScreen))) { + QColor textColor(Qt::white); + painter->setPen(textColor); + painter->drawText(textRect, msg); + } +} + void CaptureWidget::drawInactiveRegion(QPainter* painter) { QColor overlayColor(0, 0, 0, m_opacity); diff --git a/src/widgets/capture/capturewidget.h b/src/widgets/capture/capturewidget.h index 3d9b4796..3c2cf7ed 100644 --- a/src/widgets/capture/capturewidget.h +++ b/src/widgets/capture/capturewidget.h @@ -22,6 +22,7 @@ #include #include +class QLabel; class QPaintEvent; class QResizeEvent; class QMouseEvent; @@ -131,6 +132,7 @@ private: QRect extendedSelection() const; QRect extendedRect(const QRect& r) const; + void drawConfigErrorMessage(QPainter* painter); void drawInactiveRegion(QPainter* painter); void drawToolsData(bool updateLayersPanel = true, bool drawSelection = false); @@ -161,6 +163,8 @@ private: bool m_captureDone; bool m_previewEnabled; bool m_adjustmentButtonPressed; + bool m_configError; + bool m_configErrorResolved; UpdateNotificationWidget* m_updateNotificationWidget; quint64 m_lastMouseWheel; diff --git a/src/widgets/capture/colorpicker.cpp b/src/widgets/capture/colorpicker.cpp index 74a77b95..da1a9ec9 100644 --- a/src/widgets/capture/colorpicker.cpp +++ b/src/widgets/capture/colorpicker.cpp @@ -11,12 +11,12 @@ ColorPicker::ColorPicker(QWidget* parent) : QWidget(parent) { ConfigHandler config; - m_colorList = config.getUserColors(); + m_colorList = config.userColors(); m_colorAreaSize = GlobalValues::buttonBaseSize() * 0.6; setMouseTracking(true); // save the color values in member variables for faster access - m_uiColor = config.uiMainColorValue(); - m_drawColor = config.drawColorValue(); + m_uiColor = config.uiColor(); + m_drawColor = config.drawColor(); // extraSize represents the extra space needed for the highlight of the // selected color. const int extraSize = 6; diff --git a/src/widgets/capture/notifierbox.cpp b/src/widgets/capture/notifierbox.cpp index e6b778d9..9873d572 100644 --- a/src/widgets/capture/notifierbox.cpp +++ b/src/widgets/capture/notifierbox.cpp @@ -16,7 +16,7 @@ NotifierBox::NotifierBox(QWidget* parent) m_timer->setSingleShot(true); m_timer->setInterval(600); connect(m_timer, &QTimer::timeout, this, &NotifierBox::hide); - m_bgColor = ConfigHandler().uiMainColorValue(); + m_bgColor = ConfigHandler().uiColor(); m_foregroundColor = (ColorUtils::colorIsDark(m_bgColor) ? Qt::white : Qt::black); m_bgColor.setAlpha(180); diff --git a/src/widgets/capture/overlaymessage.cpp b/src/widgets/capture/overlaymessage.cpp index 0fa6a5df..4edbba4c 100644 --- a/src/widgets/capture/overlaymessage.cpp +++ b/src/widgets/capture/overlaymessage.cpp @@ -66,7 +66,7 @@ void OverlayMessage::paintEvent(QPaintEvent*) QRectF bRect = boundingRect(); bRect.moveTo(0, 0); - QColor rectColor(ConfigHandler().uiMainColorValue()); + QColor rectColor(ConfigHandler().uiColor()); rectColor.setAlpha(180); QColor textColor( (ColorUtils::colorIsDark(rectColor) ? Qt::white : Qt::black));