mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-09 13:34:14 +02:00
Merge pull request #1410 from ispyisail/feature/settings-profile-parts
Add saving and loading only part of the settings
This commit is contained in:
+143
-10
@@ -61,14 +61,19 @@
|
||||
#include <iostream>
|
||||
#define QUOTE(x) STRINGIFY(x)
|
||||
#define STRINGIFY(x) #x
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QFontDatabase>
|
||||
#include <QLabel>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QStyleFactory>
|
||||
#include <QStyleHints>
|
||||
#include <QVBoxLayout>
|
||||
#ifdef BUILD_WITHOUT_KF
|
||||
# include "ui/nokde/kautosavefile.h"
|
||||
#else
|
||||
@@ -2291,6 +2296,88 @@ void QETApp::customizeQET(int tab)
|
||||
dialog.setParent(nullptr, dialog.windowFlags());
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@brief partLabel
|
||||
@return the name of @a part shown to the user
|
||||
*/
|
||||
QString partLabel(ConfigProfile::Part part)
|
||||
{
|
||||
switch (part) {
|
||||
case ConfigProfile::Part::Controls:
|
||||
return QETApp::tr("Toolbars, keyboard shortcuts, mouse and trackpad gestures");
|
||||
case ConfigProfile::Part::NewProject:
|
||||
return QETApp::tr("Defaults for new projects: folio, title block, "
|
||||
"wires, numbering");
|
||||
case ConfigProfile::Part::Other:
|
||||
return QETApp::tr("All other settings: appearance, grid, language...");
|
||||
case ConfigProfile::Part::Folders:
|
||||
return QETApp::tr("Folders of the collections");
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief choosePartsToSave
|
||||
Ask which parts of the settings to save, all of them ticked to begin
|
||||
with.
|
||||
@return the parts chosen, or an empty list if the user cancelled
|
||||
*/
|
||||
QList<ConfigProfile::Part> choosePartsToSave(QWidget *parent)
|
||||
{
|
||||
QDialog dialog(parent);
|
||||
dialog.setWindowTitle(QETApp::tr("Save settings as...", "dialog title"));
|
||||
|
||||
auto *layout = new QVBoxLayout(&dialog);
|
||||
layout->addWidget(new QLabel(QETApp::tr("Settings to save:"), &dialog));
|
||||
|
||||
QMap<ConfigProfile::Part, QCheckBox *> boxes;
|
||||
for (ConfigProfile::Part part : ConfigProfile::choosableParts()) {
|
||||
auto *box = new QCheckBox(partLabel(part), &dialog);
|
||||
box->setChecked(true);
|
||||
layout->addWidget(box);
|
||||
boxes.insert(part, box);
|
||||
}
|
||||
|
||||
auto *note = new QLabel(QETApp::tr("The folders of your collections are "
|
||||
"saved only when every part is ticked."),
|
||||
&dialog);
|
||||
note->setWordWrap(true);
|
||||
layout->addWidget(note);
|
||||
|
||||
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok
|
||||
| QDialogButtonBox::Cancel,
|
||||
&dialog);
|
||||
layout->addWidget(buttons);
|
||||
QObject::connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
|
||||
//Nothing ticked, nothing to save
|
||||
auto update_ok = [&boxes, buttons]() {
|
||||
bool any = false;
|
||||
for (QCheckBox *box : std::as_const(boxes)) {
|
||||
any = any || box->isChecked();
|
||||
}
|
||||
buttons->button(QDialogButtonBox::Ok)->setEnabled(any);
|
||||
};
|
||||
for (QCheckBox *box : std::as_const(boxes)) {
|
||||
QObject::connect(box, &QCheckBox::toggled, &dialog, update_ok);
|
||||
}
|
||||
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<ConfigProfile::Part> parts;
|
||||
for (ConfigProfile::Part part : ConfigProfile::choosableParts()) {
|
||||
if (boxes.value(part)->isChecked()) {
|
||||
parts << part;
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETApp::exportConfiguration
|
||||
Save the settings of QElectroTech to a file the user chooses, to keep
|
||||
@@ -2298,11 +2385,19 @@ void QETApp::customizeQET(int tab)
|
||||
#610). The file is always written in the ini format, whatever the
|
||||
platform stores its live settings in, so a profile saved on Windows
|
||||
loads on Linux and macOS. See ConfigProfile for the keys left out.
|
||||
|
||||
The user first chooses which parts of the settings to save, to share
|
||||
the defaults for new projects alone for example (discussion #1405).
|
||||
*/
|
||||
void QETApp::exportConfiguration()
|
||||
{
|
||||
QWidget *parent_widget = qApp->activeWindow();
|
||||
|
||||
const QList<ConfigProfile::Part> parts = choosePartsToSave(parent_widget);
|
||||
if (parts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = QFileDialog::getSaveFileName(
|
||||
parent_widget,
|
||||
tr("Save settings as...", "dialog title"),
|
||||
@@ -2317,7 +2412,7 @@ void QETApp::exportConfiguration()
|
||||
|
||||
QSettings live_settings;
|
||||
QSettings file_settings(path, QSettings::IniFormat);
|
||||
ConfigProfile::exportTo(live_settings, file_settings);
|
||||
ConfigProfile::exportTo(live_settings, file_settings, parts);
|
||||
|
||||
if (file_settings.status() != QSettings::NoError) {
|
||||
QET::QetMessageBox::critical(
|
||||
@@ -2330,7 +2425,10 @@ void QETApp::exportConfiguration()
|
||||
/**
|
||||
@brief QETApp::importConfiguration
|
||||
Replace the settings of QElectroTech with a file saved by
|
||||
exportConfiguration(), then close QElectroTech.
|
||||
exportConfiguration(), then close QElectroTech. A file holding only
|
||||
some parts of the settings replaces only those parts. A file holding
|
||||
only the defaults for new projects needs no restart: they are read
|
||||
again for every new project.
|
||||
|
||||
The settings are read by each part of QElectroTech when it starts, and
|
||||
there is no signal telling all of them that a setting changed, so the
|
||||
@@ -2352,8 +2450,10 @@ void QETApp::importConfiguration()
|
||||
}
|
||||
|
||||
QSettings file_settings(path, QSettings::IniFormat);
|
||||
const QList<ConfigProfile::Part> parts = ConfigProfile::partsOf(file_settings);
|
||||
if (file_settings.status() != QSettings::NoError
|
||||
|| !ConfigProfile::isProfile(file_settings))
|
||||
|| !ConfigProfile::isProfile(file_settings)
|
||||
|| parts.isEmpty())
|
||||
{
|
||||
QET::QetMessageBox::critical(
|
||||
parent_widget,
|
||||
@@ -2362,24 +2462,57 @@ void QETApp::importConfiguration()
|
||||
return;
|
||||
}
|
||||
|
||||
const bool complete = parts.contains(ConfigProfile::Part::Folders);
|
||||
const bool restart = ConfigProfile::needsRestart(parts);
|
||||
QString question;
|
||||
if (complete) {
|
||||
question = tr("These settings will replace your current ones, except the "
|
||||
"window layout and the list of recent files.\n"
|
||||
"\n"
|
||||
"QElectroTech will then close. Start it again to use the new "
|
||||
"settings.\n"
|
||||
"\n"
|
||||
"Do you want to continue?");
|
||||
} else {
|
||||
QStringList labels;
|
||||
for (ConfigProfile::Part part : parts) {
|
||||
labels << QStringLiteral("• ") + partLabel(part);
|
||||
}
|
||||
question = tr("These settings will replace your current ones for:\n"
|
||||
"%1\n"
|
||||
"All your other settings are kept.")
|
||||
.arg(labels.join(QLatin1Char('\n')));
|
||||
question += QStringLiteral("\n\n");
|
||||
question += restart
|
||||
? tr("QElectroTech will then close. Start it again to use the new "
|
||||
"settings.")
|
||||
: tr("Projects already open keep their own defaults.");
|
||||
question += QStringLiteral("\n\n") + tr("Do you want to continue?");
|
||||
}
|
||||
|
||||
//Said before anything closes: once the last window is closed,
|
||||
//QElectroTech quits by itself (checkRemainingWindows()).
|
||||
const auto answer = QET::QetMessageBox::question(
|
||||
parent_widget,
|
||||
tr("Load settings", "message box title"),
|
||||
tr("These settings will replace your current ones, except the "
|
||||
"window layout and the list of recent files.\n"
|
||||
"\n"
|
||||
"QElectroTech will then close. Start it again to use the new "
|
||||
"settings.\n"
|
||||
"\n"
|
||||
"Do you want to continue?"),
|
||||
question,
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No);
|
||||
if (answer != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!restart) {
|
||||
QSettings live_settings;
|
||||
ConfigProfile::importFrom(file_settings, live_settings);
|
||||
QET::QetMessageBox::information(
|
||||
parent_widget,
|
||||
tr("Load settings", "message box title"),
|
||||
tr("The settings are loaded. They are used for the next "
|
||||
"new project."));
|
||||
return;
|
||||
}
|
||||
|
||||
//Asks to save any modified project, as quitQET() does. The settings
|
||||
//are only replaced once every editor is closed, so that the editors
|
||||
//saving their own settings on close cannot overwrite them, and a
|
||||
|
||||
+147
-16
@@ -18,6 +18,7 @@
|
||||
#ifndef CONFIGPROFILE_H
|
||||
#define CONFIGPROFILE_H
|
||||
|
||||
#include <QList>
|
||||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
@@ -28,71 +29,201 @@
|
||||
|
||||
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"), and
|
||||
the recent-files lists ("...-recentfiles/..."). They are not written to
|
||||
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.
|
||||
/// 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<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.startsWith(QLatin1String("dialoggeometry/"))
|
||||
|| 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"),
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -187,10 +187,13 @@ target_link_libraries(tst_symbolscale PRIVATE Qt::Test)
|
||||
|
||||
# configprofile.h is header-only: which settings a saved configuration
|
||||
# profile carries, and how loading one replaces the live settings.
|
||||
add_executable(tst_configprofile tst_configprofile.cpp)
|
||||
# borderproperties.cpp: the real writer of the folio size defaults, to check
|
||||
# that what it writes falls in the part for new projects.
|
||||
add_executable(tst_configprofile tst_configprofile.cpp
|
||||
${QET_DIR}/sources/borderproperties.cpp)
|
||||
add_test(NAME tst_configprofile COMMAND tst_configprofile)
|
||||
target_include_directories(tst_configprofile PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_configprofile PRIVATE Qt::Test Qt::Gui)
|
||||
target_link_libraries(tst_configprofile PRIVATE Qt::Test Qt::Gui Qt::Xml)
|
||||
|
||||
# itemgroups.cpp keeps group membership and the selection rule apart from
|
||||
# Diagram, so it is tested here on a plain scene of rectangles.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "utils/configprofile.h"
|
||||
#include "borderproperties.h"
|
||||
|
||||
class tst_configprofile : public QObject
|
||||
{
|
||||
@@ -17,6 +18,7 @@ private slots:
|
||||
QVERIFY(ConfigProfile::isLocalKey("diagrameditor/geometry"));
|
||||
QVERIFY(ConfigProfile::isLocalKey("elementeditor/state"));
|
||||
QVERIFY(ConfigProfile::isLocalKey("projects-recentfiles/file1"));
|
||||
QVERIFY(ConfigProfile::isLocalKey("dialoggeometry/ConfigDialog"));
|
||||
QVERIFY(ConfigProfile::isLocalKey(ConfigProfile::marker_key));
|
||||
QVERIFY(!ConfigProfile::isLocalKey("diagrameditor/Xgrid"));
|
||||
QVERIFY(!ConfigProfile::isLocalKey("shortcuts/mainwindow.fullscreen"));
|
||||
@@ -31,6 +33,7 @@ private slots:
|
||||
live.setValue("lang", "de");
|
||||
live.setValue("diagrameditor/geometry", QByteArray("xyz"));
|
||||
live.setValue("projects-recentfiles/file1", "/home/a/b.qet");
|
||||
live.setValue("dialoggeometry/ConfigDialog", QByteArray("xyz"));
|
||||
|
||||
QSettings file(path("profile1.conf"), QSettings::IniFormat);
|
||||
QCOMPARE(ConfigProfile::exportTo(live, file), 2);
|
||||
@@ -38,6 +41,7 @@ private slots:
|
||||
QCOMPARE(file.value("diagrameditor/Xgrid").toInt(), 7);
|
||||
QVERIFY(!file.contains("diagrameditor/geometry"));
|
||||
QVERIFY(!file.contains("projects-recentfiles/file1"));
|
||||
QVERIFY(!file.contains("dialoggeometry/ConfigDialog"));
|
||||
}
|
||||
|
||||
// Import replaces every setting, removes the ones the profile does not
|
||||
@@ -87,6 +91,134 @@ private slots:
|
||||
QCOMPARE(b.value(k), a.value(k));
|
||||
}
|
||||
|
||||
// Every key belongs to one part; keys nobody listed are Other.
|
||||
void partOfKeys()
|
||||
{
|
||||
using P = ConfigProfile::Part;
|
||||
QCOMPARE(ConfigProfile::partOf("shortcuts/mainwindow.fullscreen"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("toolbars/icon_size"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/toolbars/diagram"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/custom_toolbars/names"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/shortcut_bar/canvas"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/gestures/directions"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/mouse_gestures"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/context_toolbar"), P::Controls);
|
||||
QCOMPARE(ConfigProfile::partOf("diagramview/gestures"), P::Controls);
|
||||
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/defaultconductortype"), P::NewProject);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/defaultreportlabel"), P::NewProject);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/defaultxrefcoil"), P::NewProject);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/defaultguides/1/position"), P::NewProject);
|
||||
QCOMPARE(ConfigProfile::partOf("autonum/conductor/current"), P::NewProject);
|
||||
|
||||
QCOMPARE(ConfigProfile::partOf("elements-collections/custom-collection-path"), P::Folders);
|
||||
QCOMPARE(ConfigProfile::partOf("elements-collections/macros-path"), P::Folders);
|
||||
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/Xgrid"), P::Other);
|
||||
QCOMPARE(ConfigProfile::partOf("diagrameditor/auto_break_conductor"), P::Other);
|
||||
QCOMPARE(ConfigProfile::partOf("lang"), P::Other);
|
||||
QCOMPARE(ConfigProfile::partOf("customColors"), P::Other);
|
||||
}
|
||||
|
||||
// What the New project page writes for the folio size is the
|
||||
// NewProject part, whatever key names BorderProperties uses.
|
||||
void folioDefaultsAreNewProject()
|
||||
{
|
||||
QSettings live(path("live_border.conf"), QSettings::IniFormat);
|
||||
BorderProperties().toSettings(live, "diagrameditor/default");
|
||||
const QStringList keys = live.allKeys();
|
||||
QVERIFY(!keys.isEmpty());
|
||||
for (const QString &key : keys)
|
||||
QCOMPARE(ConfigProfile::partOf(key), ConfigProfile::Part::NewProject);
|
||||
}
|
||||
|
||||
// A file of some parts holds only their keys, never the folders, and
|
||||
// is format 2 so that an older QElectroTech refuses it.
|
||||
void exportSomeParts()
|
||||
{
|
||||
QSettings live(path("live4.conf"), QSettings::IniFormat);
|
||||
live.setValue("shortcuts/mainwindow.fullscreen", "F11");
|
||||
live.setValue("diagrameditor/defaultconductortype", "Multi");
|
||||
live.setValue("diagrameditor/Xgrid", 7);
|
||||
live.setValue("elements-collections/custom-collection-path", "/home/a/sym");
|
||||
|
||||
QSettings file(path("profile4.conf"), QSettings::IniFormat);
|
||||
QCOMPARE(ConfigProfile::exportTo(live, file, {ConfigProfile::Part::NewProject}), 1);
|
||||
QCOMPARE(file.value(ConfigProfile::marker_key).toInt(), 2);
|
||||
QVERIFY(ConfigProfile::isProfile(file));
|
||||
QCOMPARE(ConfigProfile::partsOf(file), QList<ConfigProfile::Part>{ConfigProfile::Part::NewProject});
|
||||
QVERIFY(file.contains("diagrameditor/defaultconductortype"));
|
||||
QVERIFY(!file.contains("shortcuts/mainwindow.fullscreen"));
|
||||
QVERIFY(!file.contains("diagrameditor/Xgrid"));
|
||||
QVERIFY(!file.contains("elements-collections/custom-collection-path"));
|
||||
|
||||
//Other without the two others: still no folders
|
||||
QSettings file2(path("profile4b.conf"), QSettings::IniFormat);
|
||||
ConfigProfile::exportTo(live, file2, {ConfigProfile::Part::Other});
|
||||
QVERIFY(file2.contains("diagrameditor/Xgrid"));
|
||||
QVERIFY(!file2.contains("elements-collections/custom-collection-path"));
|
||||
}
|
||||
|
||||
// Every choosable part is a complete profile: format 1, folders
|
||||
// included, readable by the QElectroTech that only knows format 1.
|
||||
void exportEveryPartIsComplete()
|
||||
{
|
||||
QSettings live(path("live5.conf"), QSettings::IniFormat);
|
||||
live.setValue("diagrameditor/Xgrid", 7);
|
||||
live.setValue("elements-collections/custom-collection-path", "/home/a/sym");
|
||||
|
||||
QSettings file(path("profile5.conf"), QSettings::IniFormat);
|
||||
ConfigProfile::exportTo(live, file, ConfigProfile::choosableParts());
|
||||
QCOMPARE(file.value(ConfigProfile::marker_key).toInt(), 1);
|
||||
QVERIFY(!file.contains(ConfigProfile::parts_key));
|
||||
QVERIFY(file.contains("elements-collections/custom-collection-path"));
|
||||
QCOMPARE(ConfigProfile::partsOf(file).size(), 4);
|
||||
}
|
||||
|
||||
// Loading some parts replaces those parts only, removes their keys the
|
||||
// file lacks, and keeps every other setting, folders included.
|
||||
void importSomePartsKeepsTheRest()
|
||||
{
|
||||
QSettings file(path("profile6.conf"), QSettings::IniFormat);
|
||||
file.setValue("diagrameditor/defaultconductortype", "Single");
|
||||
file.setValue("diagrameditor/Xgrid", 99); // not in its parts: ignored
|
||||
file.setValue(ConfigProfile::marker_key, 2);
|
||||
file.setValue(ConfigProfile::parts_key, "newproject");
|
||||
|
||||
QSettings live(path("live6.conf"), QSettings::IniFormat);
|
||||
live.setValue("diagrameditor/defaultconductortype", "Multi");
|
||||
live.setValue("autonum/folio/current", "mine");
|
||||
live.setValue("diagrameditor/Xgrid", 10);
|
||||
live.setValue("shortcuts/mainwindow.fullscreen", "F11");
|
||||
live.setValue("elements-collections/custom-collection-path", "/home/me/sym");
|
||||
|
||||
ConfigProfile::importFrom(file, live);
|
||||
QCOMPARE(live.value("diagrameditor/defaultconductortype").toString(), QString("Single"));
|
||||
QVERIFY(!live.contains("autonum/folio/current"));
|
||||
QCOMPARE(live.value("diagrameditor/Xgrid").toInt(), 10);
|
||||
QCOMPARE(live.value("shortcuts/mainwindow.fullscreen").toString(), QString("F11"));
|
||||
QCOMPARE(live.value("elements-collections/custom-collection-path").toString(),
|
||||
QString("/home/me/sym"));
|
||||
QVERIFY(!live.contains(ConfigProfile::parts_key));
|
||||
}
|
||||
|
||||
void restartOnlyWhenNeeded()
|
||||
{
|
||||
using P = ConfigProfile::Part;
|
||||
QVERIFY(!ConfigProfile::needsRestart({P::NewProject}));
|
||||
QVERIFY(ConfigProfile::needsRestart({P::NewProject, P::Controls}));
|
||||
QVERIFY(ConfigProfile::needsRestart({P::Other}));
|
||||
}
|
||||
|
||||
// A file of format 2 naming no known part is refused by the caller.
|
||||
void unknownPartsGiveNothing()
|
||||
{
|
||||
QSettings file(path("profile7.conf"), QSettings::IniFormat);
|
||||
file.setValue(ConfigProfile::marker_key, 2);
|
||||
file.setValue(ConfigProfile::parts_key, "colours");
|
||||
QVERIFY(ConfigProfile::partsOf(file).isEmpty());
|
||||
}
|
||||
|
||||
void plainFileIsNotAProfile()
|
||||
{
|
||||
QSettings other(path("other.conf"), QSettings::IniFormat);
|
||||
|
||||
Reference in New Issue
Block a user