Implement config checking (#1859)

* Add error handling functions to ConfigHandler

Refurbished functions setValue and value which were previously unused.
These functions now set/get a setting with error handling.
Currently recognizes only errors recognizable by QSettings.

* Make use of value and setValue in ConfigHandler

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add checker for unrecognized general options

Extraneous config options in [General] will be reported as errors.
Added some placeholder functions to be implemented in future commits.

* Introduce keysFromGroup function

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Check shortcut names for duplicates

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix notification spam

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Implement shortcut conflict checking

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix reading of fallbacks on error

If there is a config error, some values would not be loaded correctly.
Using the newly implemented function ConfigHandler::contains instead of
QSettings::contains solves this issue.

These changes reveal u bug that causes a crash on startup.

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix crashes introduced in previous commit

Because ConfigHandler is a dependency of most other classes,
calling functions from those classes inside ConfigHandler caused
infinite recursions in some cases.

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add config file watcher

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add missing config options

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix bug in shortcut conflict detection

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add error resolved notification

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add GUI error message overlay

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add indicator in config window

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Use ConfigHandler::fileChanged in ConfigWindow

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix watcher sometimes not firing

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Improve config file watching performance

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add new way to handle config

This is only a fundamental implementation. Future commits will replace
everything with this new paradigm.

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix getButtons and related functions

Also refactored related code to use QList instead of QVector because
QSettings does not work well with QVector.

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Make good use of the new way

* Implement proper checking for basic types

Everything is covered, apart from KeySequence.

* Move fallback path to ExistingDir value handler

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Use consistent naming scheme in ConfigHandler

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Implement config getters/setters via macro

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Surround text with tr and clang-format

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix colors being saved obfuscated

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add ValueHandler::represenation

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Move ValueHandler to separate files

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* confighandler.cpp: rename macro CUSTOM to OPTION

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix bug with shortcut conflict checker

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Update docs and fix setAllTheButtons

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Handle filenamePattern properly

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix failing build due to wrong function name

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix QSet error due to Qt version mismatch

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Replace QSharedPointer::get with data for older Qt versions

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix failing build on MacOS and ubuntu 18.04

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add column headers to recognizedGeneralOptions map

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix ubuntu 18.04 error

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix false positive when shortcuts empty

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix wrong shortcut group prefix

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Implement proper shortcut checking

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add shortcut map in ConfigHandler

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Move ConfigShortcuts functions to ShortcutsWidget

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix minor bugs

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add fallback scheme: Pictures, HOME, TMP

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add config --check CLI option

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Add config error log to GUI

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Rename ValueHandler::description to expected

* Convert Qt's #AARRGGBB to #RRGGBBAA and vice versa

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Remove obsolete `saveAfterCopyPath`

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Fix errors in example config

Also added an additional ; in front of actual comments to differentiate
them from commented options.

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Allow special value 'picker' in userColors

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>

* Allow only name, #RRGGBB, and #RRGGBBAA color formats

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>
This commit is contained in:
Haris Gušić
2021-09-15 18:56:01 +02:00
committed by GitHub
parent a7e88e60de
commit d1428889b9
35 changed files with 1665 additions and 859 deletions

413
src/utils/valuehandler.cpp Normal file
View File

@@ -0,0 +1,413 @@
#include "valuehandler.h"
#include "confighandler.h"
#include <QColor>
#include <QFileInfo>
#include <QStandardPaths>
#include <QVariant>
// 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<BType>;
bool ButtonList::check(const QVariant& val)
{
using CTB = CaptureToolButton;
auto allButtons = CTB::getIterableButtonTypes();
for (int btn : val.value<QList<int>>()) {
if (!allButtons.contains(static_cast<CTB::ButtonType>(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<int> intButtons = val.value<QList<int>>();
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<BList>());
normalizeButtons(intList);
return QVariant::fromValue(intList);
}
QString ButtonList::expected()
{
return QStringLiteral("please don't edit by hand");
}
QList<CaptureToolButton::ButtonType> ButtonList::fromIntList(
const QList<int>& l)
{
QList<CaptureToolButton::ButtonType> buttons;
buttons.reserve(l.size());
for (auto const i : l)
buttons << static_cast<CaptureToolButton::ButtonType>(i);
return buttons;
}
QList<int> ButtonList::toIntList(const QList<CaptureToolButton::ButtonType>& l)
{
QList<int> buttons;
buttons.reserve(l.size());
for (auto const i : l)
buttons << static_cast<int>(i);
return buttons;
}
bool ButtonList::normalizeButtons(QList<int>& buttons)
{
QList<int> 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<QColor> 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<QColor>{ 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");
}