Introduce a config resolver (#2244)

* Add config resolver

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

* Enable resolver even when using systray

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

* Fix bugs

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

* Do not show resolver for shortcut conflicts

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

* Fix build error on MacOS and Windows

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

* Add missing translations

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

* Replace variable i with row

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

* Improve presentation

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

* Disambiguate shortcuts and general settings

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

* Wrap some strings in tr

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

* Update tooltips in ConfigResolver

Signed-off-by: Haris Gušić <harisgusic.dev@gmail.com>
This commit is contained in:
Haris Gušić
2022-01-13 23:01:56 +01:00
committed by GitHub
parent 563a479552
commit 417a1534cf
11 changed files with 385 additions and 141 deletions

View File

@@ -173,19 +173,13 @@ static QMap<QString, QSharedPointer<KeySequence>> recognizedShortcuts = {
// CLASS CONFIGHANDLER
ConfigHandler::ConfigHandler(bool skipInitialErrorCheck)
ConfigHandler::ConfigHandler()
: m_settings(QSettings::IniFormat,
QSettings::UserScope,
qApp->organizationName(),
qApp->applicationName())
{
static bool wasEverChecked = false;
static bool firstInitialization = true;
if (!skipInitialErrorCheck && !wasEverChecked) {
// check for error on initial call
checkAndHandleError();
wasEverChecked = true;
}
if (firstInitialization) {
// check for error every time the file changes
m_configWatcher.reset(new QFileSystemWatcher());
@@ -449,9 +443,6 @@ void ConfigHandler::setValue(const QString& key, const QVariant& value)
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);
@@ -468,6 +459,16 @@ QVariant ConfigHandler::value(const QString& key) const
return handler->value(val);
}
void ConfigHandler::remove(const QString& key)
{
m_settings.remove(key);
}
void ConfigHandler::resetValue(const QString& key)
{
m_settings.setValue(key, valueHandler(key)->fallback());
}
QSet<QString>& ConfigHandler::recognizedGeneralOptions()
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
@@ -492,8 +493,10 @@ QSet<QString>& ConfigHandler::recognizedShortcutNames()
return names;
}
// Return keys from group `group`. Use CONFIG_GROUP_GENERAL (General) for
// general settings.
/**
* @brief Return keys from group `group`.
* Use CONFIG_GROUP_GENERAL (General) for general settings.
*/
QSet<QString> ConfigHandler::keysFromGroup(const QString& group) const
{
QSet<QString> keys;
@@ -515,20 +518,6 @@ bool ConfigHandler::checkForErrors(AbstractLogger* log) const
checkSemantics(log);
}
void ConfigHandler::cleanUnusedKeys(const QString& group,
const QSet<QString>& keys) const
{
for (const QString& key : keys) {
if (group == CONFIG_GROUP_GENERAL && !key.contains('/')) {
m_settings.remove(key);
} else {
m_settings.beginGroup(group);
m_settings.remove(key);
m_settings.endGroup();
}
}
}
/**
* @brief Parse the config to find settings with unrecognized names.
* @return Whether the config passes this check.
@@ -537,7 +526,8 @@ void ConfigHandler::cleanUnusedKeys(const QString& group,
* `recognizedGeneralOptions` or `recognizedShortcutNames` depending on the
* group the option belongs to.
*/
bool ConfigHandler::checkUnrecognizedSettings(AbstractLogger* log) const
bool ConfigHandler::checkUnrecognizedSettings(AbstractLogger* log,
QList<QString>* offenders) const
{
// sort the config keys by group
QSet<QString> generalKeys = keysFromGroup(CONFIG_GROUP_GENERAL),
@@ -549,38 +539,20 @@ bool ConfigHandler::checkUnrecognizedSettings(AbstractLogger* log) const
generalKeys.subtract(recognizedGeneralKeys);
shortcutKeys.subtract(recognizedShortcutKeys);
// automatically clean up unused keys
if (!generalKeys.isEmpty()) {
cleanUnusedKeys(CONFIG_GROUP_GENERAL, generalKeys);
generalKeys = keysFromGroup(CONFIG_GROUP_GENERAL),
generalKeys.subtract(recognizedGeneralKeys);
}
if (!shortcutKeys.isEmpty()) {
cleanUnusedKeys(CONFIG_GROUP_SHORTCUTS, shortcutKeys);
shortcutKeys = keysFromGroup(CONFIG_GROUP_SHORTCUTS);
shortcutKeys.subtract(recognizedShortcutKeys);
}
// clean up unused groups
QStringList settingsGroups = m_settings.childGroups();
for (const auto& group : settingsGroups) {
if (group != QLatin1String(CONFIG_GROUP_SHORTCUTS) &&
group != QLatin1String(CONFIG_GROUP_GENERAL)) {
m_settings.beginGroup(group);
m_settings.remove("");
m_settings.endGroup();
}
}
// what is left are the unrecognized keys - hopefully empty
bool ok = generalKeys.isEmpty() && shortcutKeys.isEmpty();
if (log != nullptr) {
if (log != nullptr || offenders != nullptr) {
for (const QString& key : generalKeys) {
*log << QStringLiteral("Unrecognized setting: '%1'\n").arg(key);
if (log)
*log << tr("Unrecognized setting: '%1'\n").arg(key);
if (offenders)
offenders->append(key);
}
for (const QString& key : shortcutKeys) {
*log
<< QStringLiteral("Unrecognized shortcut name: '%1'.\n").arg(key);
if (log)
*log << tr("Unrecognized shortcut name: '%1'.\n").arg(key);
if (offenders)
offenders->append(CONFIG_GROUP_SHORTCUTS "/" + key);
}
}
return ok;
@@ -619,8 +591,8 @@ bool ConfigHandler::checkShortcutConflicts(AbstractLogger* log) const
!reportedInLog.contains(*key2)) { // log entries
reportedInLog.append(*key1);
reportedInLog.append(*key2);
*log << QStringLiteral("Shortcut conflict: '%1' and '%2' "
"have the same shortcut: %3\n")
*log << tr("Shortcut conflict: '%1' and '%2' "
"have the same shortcut: %3\n")
.arg(*key1)
.arg(*key2)
.arg(value1);
@@ -634,9 +606,12 @@ bool ConfigHandler::checkShortcutConflicts(AbstractLogger* log) const
/**
* @brief Check each config value semantically.
* @param log Destination for error log output.
* @param offenders Destination for the semantically invalid keys.
* @return Whether the config passes this check.
*/
bool ConfigHandler::checkSemantics(AbstractLogger* log) const
bool ConfigHandler::checkSemantics(AbstractLogger* log,
QList<QString>* offenders) const
{
QStringList allKeys = m_settings.allKeys();
bool ok = true;
@@ -650,14 +625,17 @@ bool ConfigHandler::checkSemantics(AbstractLogger* log) const
QVariant val = m_settings.value(key);
auto valueHandler = this->valueHandler(key);
if (val.isValid() && !valueHandler->check(val)) {
// Key does not pass the check
ok = false;
if (log == nullptr) {
if (log == nullptr && offenders == nullptr)
break;
} else {
*log << QStringLiteral("Semantic error in '%1'. Expected: %2\n")
if (log != nullptr) {
*log << tr("Bad value in '%1'. Expected: %2\n")
.arg(key)
.arg(valueHandler->expected());
}
if (offenders != nullptr)
offenders->append(key);
}
}
return ok;
@@ -724,7 +702,8 @@ bool ConfigHandler::hasError() const
/// 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.");
return tr(
"The configuration contains an error. Open configuration to resolve.");
}
void ConfigHandler::ensureFileWatched() const
@@ -801,7 +780,7 @@ QString ConfigHandler::baseName(QString key) const
// STATIC MEMBER DEFINITIONS
bool ConfigHandler::m_hasError = false;
bool ConfigHandler::m_errorCheckPending = false;
bool ConfigHandler::m_errorCheckPending = true;
bool ConfigHandler::m_skipNextErrorCheck = false;
QSharedPointer<QFileSystemWatcher> ConfigHandler::m_configWatcher;