//// SettingHelpers.cpp// libraries/shared/src//// Created by Clement on 9/13/16.// Copyright 2016 High Fidelity, Inc.// Copyright 2022-2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "SettingHelpers.h" #include <QDataStream>#include <QDebug>#include <QJsonDocument>#include <QJsonObject>#include <QPoint>#include <QRect>#include <QSettings>#include <QSize>#include <QStringList> #include "SharedLogging.h" const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile); QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map); QString settingsFilename() { return QSettings().fileName();} bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) { QJsonParseError jsonParseError; auto bytesRead = device.readAll(); auto document = QJsonDocument::fromJson(bytesRead, &jsonParseError); if (jsonParseError.error != QJsonParseError::NoError) { qCDebug(shared) << "Error parsing QSettings file:" << jsonParseError.errorString(); return false; } map = jsonDocumentToVariantMap(document); return true;} bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map) { auto document = variantMapToJsonDocument(map); auto jsonByteArray = document.toJson(QJsonDocument::Indented); auto bytesWritten = device.write(jsonByteArray); return bytesWritten == jsonByteArray.size();} void loadOldINIFile(QSettings& settings) { QSettings::setDefaultFormat(QSettings::IniFormat); QSettings iniSettings; if (!iniSettings.allKeys().isEmpty()) { qCDebug(shared) << "No data in json settings file, trying to load old ini settings file."; for (auto key : iniSettings.allKeys()) { auto variant = iniSettings.value(key); if (variant.type() == QVariant::String) { auto string = variant.toString(); if (string == "true") { variant = true; } else if (string == "false") { variant = false; } else { bool ok; double value = string.toDouble(&ok); if (ok) { variant = value; } } } settings.setValue(key, variant); } qCDebug(shared) << "Loaded" << settings.allKeys().size() << "keys from ini settings file."; } QSettings::setDefaultFormat(JSON_FORMAT);} QStringList splitArgs(const QString& string, int idx) { int length = string.length(); Q_ASSERT(length > 0); Q_ASSERT(string.at(idx) == QLatin1Char('(')); Q_ASSERT(string.at(length - 1) == QLatin1Char(')')); QStringList result; QString item; for (++idx; idx < length; ++idx) { QChar c = string.at(idx); if (c == QLatin1Char(')')) { Q_ASSERT(idx == length - 1); result.append(item); } else if (c == QLatin1Char(' ')) { result.append(item); item.clear(); } else { item.append(c); } } return result;} QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { QJsonObject object; for (auto it = map.cbegin(); it != map.cend(); ++it) { auto& key = it.key(); auto& variant = it.value(); auto variantType = variant.type(); // Switch some types so they are readable/modifiable in the json file if (variantType == QVariant(1.0f).type()) { // float variantType = QVariant::Double; } if (variantType == QVariant((quint16)0).type()) { // uint16 variantType = QVariant::UInt; } if (variantType == QVariant::Url) { // QUrl variantType = QVariant::String; } switch (variantType) { // QML has problems with QVariant::Hash case QVariant::Hash: { qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant; Q_ASSERT(false); break; } case QVariant::Invalid: object.insert(key, QJsonValue()); break; case QVariant::LongLong: case QVariant::ULongLong: case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::Double: case QVariant::Map: case QVariant::List: object.insert(key, QJsonValue::fromVariant(variant)); break; case QVariant::String: { QString result = variant.toString(); if (result.startsWith(QLatin1Char('@'))) { result.prepend(QLatin1Char('@')); } object.insert(key, result); break; } case QVariant::ByteArray: { QByteArray a = variant.toByteArray(); QString result = QLatin1String("@ByteArray("); int sz = a.size(); if ( sz > 0 ) { // Work around 'warning: ‘size_t strlen(const char*)’ reading 1 or more bytes from a region of size 0 [-Wstringop-overread]' // size() indeed could be zero bytes, so make sure that can't be the case. result += QString::fromLatin1(a.constData(), sz); } result += QLatin1Char(')'); object.insert(key, result); break; } case QVariant::Rect: { QRect r = qvariant_cast<QRect>(variant); QString result = QLatin1String("@Rect("); result += QString::number(r.x()); result += QLatin1Char(' '); result += QString::number(r.y()); result += QLatin1Char(' '); result += QString::number(r.width()); result += QLatin1Char(' '); result += QString::number(r.height()); result += QLatin1Char(')'); object.insert(key, result); break; } case QVariant::Size: { QSize s = qvariant_cast<QSize>(variant); QString result = QLatin1String("@Size("); result += QString::number(s.width()); result += QLatin1Char(' '); result += QString::number(s.height()); result += QLatin1Char(')'); object.insert(key, result); break; } case QVariant::Point: { QPoint p = qvariant_cast<QPoint>(variant); QString result = QLatin1String("@Point("); result += QString::number(p.x()); result += QLatin1Char(' '); result += QString::number(p.y()); result += QLatin1Char(')'); object.insert(key, result); break; } default: { QByteArray array; { QDataStream stream(&array, QIODevice::WriteOnly); stream.setVersion(QDataStream::Qt_4_0); stream << variant; } QString result = QLatin1String("@Variant("); int sz = array.size(); if ( sz > 0 ) { // See comment in the case handling QVariant::ByteArray result += QString::fromLatin1(array.constData(), sz); } result += QLatin1Char(')'); object.insert(key, result); break; } } } return QJsonDocument(object);} QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document) { if (!document.isObject()) { qWarning() << "Settings file does not contain a JSON object"; return QSettings::SettingsMap(); } auto object = document.object(); QSettings::SettingsMap map; for (auto it = object.begin(); it != object.end(); ++it) { QVariant result; if (!it->isString()) { result = it->toVariant(); } else { auto string = it->toString(); if (string.startsWith(QLatin1String("@@"))) { // Standard string starting with '@' result = QVariant(string.mid(1)); } else if (string.startsWith(QLatin1Char('@'))) { // Custom type to string if (string.endsWith(QLatin1Char(')'))) { if (string.startsWith(QLatin1String("@ByteArray("))) { result = QVariant(string.toLatin1().mid(11, string.size() - 12)); } else if (string.startsWith(QLatin1String("@Variant("))) { QByteArray a(string.toLatin1().mid(9)); QDataStream stream(&a, QIODevice::ReadOnly); stream.setVersion(QDataStream::Qt_4_0); stream >> result; } else if (string.startsWith(QLatin1String("@Rect("))) { QStringList args = splitArgs(string, 5); if (args.size() == 4) { result = QRect(args[0].toInt(), args[1].toInt(), args[2].toInt(), args[3].toInt()); } } else if (string.startsWith(QLatin1String("@Size("))) { QStringList args = splitArgs(string, 5); if (args.size() == 2) { result = QSize(args[0].toInt(), args[1].toInt()); } } else if (string.startsWith(QLatin1String("@Point("))) { QStringList args = splitArgs(string, 6); if (args.size() == 2) { result = QPoint(args[0].toInt(), args[1].toInt()); } } } } else { // Standard string result = string; } } map.insert(it.key(), result); } return map;}
//
// SettingHelpers.cpp
// libraries/shared/src
//
// Created by Clement on 9/13/16.
// Copyright 2016 High Fidelity, Inc.
// Copyright 2022-2023 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// SPDX-License-Identifier: Apache-2.0
//
#include "SettingHelpers.h"
#include <QDataStream>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QPoint>
#include <QRect>
#include <QSettings>
#include <QSize>
#include <QStringList>
#include "SharedLogging.h"
const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);
QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map);
QString settingsFilename() {
return QSettings().fileName();
}
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) {
QJsonParseError jsonParseError;
auto bytesRead = device.readAll();
auto document = QJsonDocument::fromJson(bytesRead, &jsonParseError);
if (jsonParseError.error != QJsonParseError::NoError) {
qCDebug(shared) << "Error parsing QSettings file:" << jsonParseError.errorString();
return false;
}
map = jsonDocumentToVariantMap(document);
return true;
}
bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map) {
auto document = variantMapToJsonDocument(map);
auto jsonByteArray = document.toJson(QJsonDocument::Indented);
auto bytesWritten = device.write(jsonByteArray);
return bytesWritten == jsonByteArray.size();
}
void loadOldINIFile(QSettings& settings) {
QSettings::setDefaultFormat(QSettings::IniFormat);
QSettings iniSettings;
if (!iniSettings.allKeys().isEmpty()) {
qCDebug(shared) << "No data in json settings file, trying to load old ini settings file.";
for (auto key : iniSettings.allKeys()) {
auto variant = iniSettings.value(key);
if (variant.type() == QVariant::String) {
auto string = variant.toString();
if (string == "true") {
variant = true;
} else if (string == "false") {
variant = false;
} else {
bool ok;
double value = string.toDouble(&ok);
if (ok) {
variant = value;
}
}
}
settings.setValue(key, variant);
}
qCDebug(shared) << "Loaded" << settings.allKeys().size() << "keys from ini settings file.";
}
QSettings::setDefaultFormat(JSON_FORMAT);
}
QStringList splitArgs(const QString& string, int idx) {
int length = string.length();
Q_ASSERT(length > 0);
Q_ASSERT(string.at(idx) == QLatin1Char('('));
Q_ASSERT(string.at(length - 1) == QLatin1Char(')'));
QStringList result;
QString item;
for (++idx; idx < length; ++idx) {
QChar c = string.at(idx);
if (c == QLatin1Char(')')) {
Q_ASSERT(idx == length - 1);
result.append(item);
} else if (c == QLatin1Char(' ')) {
result.append(item);
item.clear();
} else {
item.append(c);
}
}
return result;
}
QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) {
QJsonObject object;
for (auto it = map.cbegin(); it != map.cend(); ++it) {
auto& key = it.key();
auto& variant = it.value();
auto variantType = variant.type();
// Switch some types so they are readable/modifiable in the json file
if (variantType == QVariant(1.0f).type()) { // float
variantType = QVariant::Double;
}
if (variantType == QVariant((quint16)0).type()) { // uint16
variantType = QVariant::UInt;
}
if (variantType == QVariant::Url) { // QUrl
variantType = QVariant::String;
}
switch (variantType) {
// QML has problems with QVariant::Hash
case QVariant::Hash: {
qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant;
Q_ASSERT(false);
break;
}
case QVariant::Invalid:
object.insert(key, QJsonValue());
break;
case QVariant::LongLong:
case QVariant::ULongLong:
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
case QVariant::Double:
case QVariant::Map:
case QVariant::List:
object.insert(key, QJsonValue::fromVariant(variant));
break;
case QVariant::String: {
QString result = variant.toString();
if (result.startsWith(QLatin1Char('@'))) {
result.prepend(QLatin1Char('@'));
}
object.insert(key, result);
break;
}
case QVariant::ByteArray: {
QByteArray a = variant.toByteArray();
QString result = QLatin1String("@ByteArray(");
int sz = a.size();
if ( sz > 0 ) {
// Work around 'warning: ‘size_t strlen(const char*)’ reading 1 or more bytes from a region of size 0 [-Wstringop-overread]'
// size() indeed could be zero bytes, so make sure that can't be the case.
result += QString::fromLatin1(a.constData(), sz);
}
result += QLatin1Char(')');
object.insert(key, result);
break;
}
case QVariant::Rect: {
QRect r = qvariant_cast<QRect>(variant);
QString result = QLatin1String("@Rect(");
result += QString::number(r.x());
result += QLatin1Char(' ');
result += QString::number(r.y());
result += QLatin1Char(' ');
result += QString::number(r.width());
result += QLatin1Char(' ');
result += QString::number(r.height());
result += QLatin1Char(')');
object.insert(key, result);
break;
}
case QVariant::Size: {
QSize s = qvariant_cast<QSize>(variant);
QString result = QLatin1String("@Size(");
result += QString::number(s.width());
result += QLatin1Char(' ');
result += QString::number(s.height());
result += QLatin1Char(')');
object.insert(key, result);
break;
}
case QVariant::Point: {
QPoint p = qvariant_cast<QPoint>(variant);
QString result = QLatin1String("@Point(");
result += QString::number(p.x());
result += QLatin1Char(' ');
result += QString::number(p.y());
result += QLatin1Char(')');
object.insert(key, result);
break;
}
default: {
QByteArray array;
{
QDataStream stream(&array, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_4_0);
stream << variant;
}
QString result = QLatin1String("@Variant(");
int sz = array.size();
if ( sz > 0 ) {
// See comment in the case handling QVariant::ByteArray
result += QString::fromLatin1(array.constData(), sz);
}
result += QLatin1Char(')');
object.insert(key, result);
break;
}
}
}
return QJsonDocument(object);
}
QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document) {
if (!document.isObject()) {
qWarning() << "Settings file does not contain a JSON object";
return QSettings::SettingsMap();
}
auto object = document.object();
QSettings::SettingsMap map;
for (auto it = object.begin(); it != object.end(); ++it) {
QVariant result;
if (!it->isString()) {
result = it->toVariant();
} else {
auto string = it->toString();
if (string.startsWith(QLatin1String("@@"))) { // Standard string starting with '@'
result = QVariant(string.mid(1));
} else if (string.startsWith(QLatin1Char('@'))) { // Custom type to string
if (string.endsWith(QLatin1Char(')'))) {
if (string.startsWith(QLatin1String("@ByteArray("))) {
result = QVariant(string.toLatin1().mid(11, string.size() - 12));
} else if (string.startsWith(QLatin1String("@Variant("))) {
QByteArray a(string.toLatin1().mid(9));
QDataStream stream(&a, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_4_0);
stream >> result;
} else if (string.startsWith(QLatin1String("@Rect("))) {
QStringList args = splitArgs(string, 5);
if (args.size() == 4) {
result = QRect(args[0].toInt(), args[1].toInt(),
args[2].toInt(), args[3].toInt());
}
} else if (string.startsWith(QLatin1String("@Size("))) {
QStringList args = splitArgs(string, 5);
if (args.size() == 2) {
result = QSize(args[0].toInt(), args[1].toInt());
}
} else if (string.startsWith(QLatin1String("@Point("))) {
QStringList args = splitArgs(string, 6);
if (args.size() == 2) {
result = QPoint(args[0].toInt(), args[1].toInt());
}
}
}
} else { // Standard string
result = string;
}
}
map.insert(it.key(), result);
}
return map;
}