/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see .
*/
#ifndef CONFIGPROFILE_H
#define CONFIGPROFILE_H
#include
#include
#include
#include
/**
Configuration profiles (discussion #610): the settings of QElectroTech
saved to a file, and loaded back from one.
Two kinds of keys stay out of a profile, because they describe this
computer rather than how the user wants QElectroTech to behave: window
sizes, positions and dock layouts (".../geometry", ".../state",
"dialoggeometry/..."), and the recent-files lists ("...-recentfiles/..."). They are not written to
the file, and loading a profile keeps the current ones.
A file can hold every other setting (a complete profile), or only some
parts of them (discussion #1405), for example the defaults for new
projects alone, to hand to someone else. Loading a file replaces only
the parts it holds and keeps every other setting.
*/
namespace ConfigProfile
{
/// Key written to every exported file, so that loading refuses a
/// file that was not exported by QElectroTech. Never copied into
/// the live settings. 1 = a complete profile, 2 = some parts only.
/// A QElectroTech that only knows 1 refuses a file of some parts:
/// it would load it as a complete one and lose every other setting.
inline const QString marker_key{QStringLiteral("qelectrotech-configuration/format")};
/// Key listing the parts a file of format 2 holds, see partName().
inline const QString parts_key{QStringLiteral("qelectrotech-configuration/parts")};
/// The parts the settings are split into.
enum class Part {
Controls, ///< toolbars, keyboard shortcuts, shortcut bar, mouse and trackpad gestures
NewProject, ///< defaults for new projects: folio, title block, wires, numbering...
Other, ///< everything else: appearance, grid, language...
Folders ///< folders of the collections, title blocks and macros
};
/// The parts a user can choose to save. Folders are not one of
/// them: they are only saved in a complete profile, because they
/// rarely exist on another computer.
inline QList choosableParts()
{
return {Part::Controls, Part::NewProject, Part::Other};
}
/// True for a key that stays out of a profile (see above).
inline bool isLocalKey(const QString &key)
{
return key.endsWith(QLatin1String("/geometry"))
|| key.endsWith(QLatin1String("/state"))
|| key.startsWith(QLatin1String("dialoggeometry/"))
|| key.contains(QLatin1String("-recentfiles/"))
|| key == marker_key
|| key == parts_key;
}
/// The part @a key belongs to. Every key belongs to exactly one
/// part: a key not listed here is Other.
inline Part partOf(const QString &key)
{
static const QStringList controls{
QStringLiteral("shortcuts/"),
QStringLiteral("toolbars/"),
QStringLiteral("diagrameditor/toolbars/"),
QStringLiteral("diagrameditor/custom_toolbars/"),
QStringLiteral("diagrameditor/shortcut_bar/"),
QStringLiteral("diagrameditor/gestures/"),
QStringLiteral("diagrameditor/mouse_gestures"),
QStringLiteral("diagrameditor/context_toolbar"),
QStringLiteral("diagramview/gestures")};
static const QStringList new_project{
//BorderProperties, TitleBlockProperties, ConductorProperties,
//report, cross-reference and guide defaults all start so
QStringLiteral("diagrameditor/default"),
QStringLiteral("autonum/")};
for (const QString &prefix : controls) {
if (key.startsWith(prefix)) return Part::Controls;
}
for (const QString &prefix : new_project) {
if (key.startsWith(prefix)) return Part::NewProject;
}
if (key.startsWith(QLatin1String("elements-collections/"))
&& key.endsWith(QLatin1String("-path"))) {
return Part::Folders;
}
return Part::Other;
}
/// Name of @a part in the parts_key of a file.
inline QString partName(Part part)
{
switch (part) {
case Part::Controls: return QStringLiteral("controls");
case Part::NewProject: return QStringLiteral("newproject");
case Part::Other: return QStringLiteral("other");
case Part::Folders: return QStringLiteral("folders");
}
return QString();
}
/// True if @a file was exported by exportTo().
inline bool isProfile(const QSettings &file)
{
const int format = file.value(marker_key).toInt();
return format == 1 || format == 2;
}
/// The parts @a file holds: every part for a complete profile.
inline QList partsOf(const QSettings &file)
{
const QList all{Part::Controls, Part::NewProject,
Part::Other, Part::Folders};
if (file.value(marker_key).toInt() != 2) {
return all;
}
const QStringList names = file.value(parts_key).toString()
.split(QLatin1Char(','), Qt::SkipEmptyParts);
QList parts;
for (Part part : all) {
if (names.contains(partName(part))) {
parts << part;
}
}
return parts;
}
/// Replace the contents of @a file with the keys of @a live that
/// belong to @a parts, except the local ones. With every choosable
/// part, the file is a complete profile, folders included. Returns
/// the number of keys written.
inline int exportTo(const QSettings &live, QSettings &file,
QList parts = choosableParts())
{
const bool complete = parts.contains(Part::Controls)
&& parts.contains(Part::NewProject)
&& parts.contains(Part::Other);
if (complete) {
parts << Part::Folders;
} else {
parts.removeAll(Part::Folders);
}
file.clear();
int written = 0;
const QStringList keys = live.allKeys();
for (const QString &key : keys) {
if (isLocalKey(key) || !parts.contains(partOf(key))) {
continue;
}
file.setValue(key, live.value(key));
++written;
}
if (complete) {
file.setValue(marker_key, 1);
} else {
QStringList names;
for (Part part : std::as_const(parts)) {
names << partName(part);
}
file.setValue(marker_key, 2);
file.setValue(parts_key, names.join(QLatin1Char(',')));
}
file.sync();
return written;
}
/// Replace every key of @a live that belongs to a part @a file
/// holds, except the local ones, with the keys of @a file. A key
/// of such a part that is not in @a file is removed rather than
/// kept: loading a profile must not leave behind a setting the
/// previous profile made. Keys of the other parts are kept. Local
/// keys in @a file are ignored.
inline void importFrom(const QSettings &file, QSettings &live)
{
const QList parts = partsOf(file);
const QStringList live_keys = live.allKeys();
for (const QString &key : live_keys) {
if (!isLocalKey(key) && parts.contains(partOf(key))) {
live.remove(key);
}
}
const QStringList file_keys = file.allKeys();
for (const QString &key : file_keys) {
if (!isLocalKey(key) && parts.contains(partOf(key))) {
live.setValue(key, file.value(key));
}
}
live.sync();
}
/// True if loading @a parts needs QElectroTech to start again.
/// The defaults for new projects are read again for every new
/// project; every other part is read once, at start.
inline bool needsRestart(const QList &parts)
{
for (Part part : parts) {
if (part != Part::NewProject) {
return true;
}
}
return false;
}
}
#endif // CONFIGPROFILE_H