mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 04:24:14 +02:00
Add saving and loading part of the settings (discussion #1405)
Save settings as... now asks which parts to save: toolbars, keyboard shortcuts and mouse gestures; defaults for new projects (folio, title block, wires, numbering); everything else. All three ticked writes the same complete file as before (format 1, collection folders included). A file with only some parts is format 2 and lists its parts. Loading it replaces those parts and keeps every other setting. The folders of the collections are never in such a file: they rarely exist on another computer. A QElectroTech that only knows format 1 refuses it rather than loading it as complete and losing every other setting. A file holding only the defaults for new projects loads without closing QElectroTech, since QETProject reads them again for every new project. Which part a key belongs to is ConfigProfile::partOf(); a key it does not list falls in "everything else", so a new setting can never be left out of every part. The test checks that what BorderProperties writes for the folio defaults lands in the new-project part. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+143
-14
@@ -18,6 +18,7 @@
|
||||
#ifndef CONFIGPROFILE_H
|
||||
#define CONFIGPROFILE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
@@ -31,68 +32,196 @@
|
||||
sizes, positions and dock layouts (".../geometry", ".../state"), 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.
|
||||
/// 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 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<Part> 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.contains(QLatin1String("-recentfiles/"))
|
||||
|| key == marker_key;
|
||||
|| 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")};
|
||||
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)
|
||||
{
|
||||
return file.value(marker_key).toInt() == 1;
|
||||
const int format = file.value(marker_key).toInt();
|
||||
return format == 1 || format == 2;
|
||||
}
|
||||
|
||||
/// Replace the contents of @a file with every key of @a live,
|
||||
/// except the local ones. Returns the number of keys written.
|
||||
inline int exportTo(const QSettings &live, QSettings &file)
|
||||
/// The parts @a file holds: every part for a complete profile.
|
||||
inline QList<Part> partsOf(const QSettings &file)
|
||||
{
|
||||
const QList<Part> 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<Part> 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<Part> 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)) {
|
||||
if (isLocalKey(key) || !parts.contains(partOf(key))) {
|
||||
continue;
|
||||
}
|
||||
file.setValue(key, live.value(key));
|
||||
++written;
|
||||
}
|
||||
file.setValue(marker_key, 1);
|
||||
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, except the local ones, with the
|
||||
/// keys of @a file. A key that is not in @a file is removed rather
|
||||
/// than kept: loading a profile must not leave behind a setting
|
||||
/// the previous profile made. Local keys in @a file are ignored.
|
||||
/// 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<Part> parts = partsOf(file);
|
||||
const QStringList live_keys = live.allKeys();
|
||||
for (const QString &key : live_keys) {
|
||||
if (!isLocalKey(key)) {
|
||||
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)) {
|
||||
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<Part> &parts)
|
||||
{
|
||||
for (Part part : parts) {
|
||||
if (part != Part::NewProject) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIGPROFILE_H
|
||||
|
||||
Reference in New Issue
Block a user