diff --git a/lang/qet_en.ts b/lang/qet_en.ts
index e76b18c19..4368b8d93 100644
--- a/lang/qet_en.ts
+++ b/lang/qet_en.ts
@@ -1936,7 +1936,7 @@ Note: these options DO NOT allow or block auto numberings, only their update pol
<Shift> to move
-<Shift> to move
+Hold Shift and drag to move this text on its own
diff --git a/lang/qet_fr.ts b/lang/qet_fr.ts
index 6a0a85ece..01e15a8c4 100644
--- a/lang/qet_fr.ts
+++ b/lang/qet_fr.ts
@@ -1935,7 +1935,7 @@ Remarque: Ces options n'autorisent ou bloquent l'auto numérotation, s
<Shift> to move
-<Shift> pour déplacer
+Maintenir Maj et faire glisser pour déplacer ce texte seul
diff --git a/sources/ElementsCollection/elementcollectionitem.cpp b/sources/ElementsCollection/elementcollectionitem.cpp
index 3e63c8c8c..ac63291b6 100644
--- a/sources/ElementsCollection/elementcollectionitem.cpp
+++ b/sources/ElementsCollection/elementcollectionitem.cpp
@@ -18,6 +18,9 @@
#include "elementcollectionitem.h"
+#include
+#include
+
/**
@brief ElementCollectionItem::ElementCollectionItem
Constructor
@@ -247,6 +250,73 @@ QList ElementCollectionItem::items() const
return list;
}
+/**
+ @brief onGuiThread
+ @return true when called from the thread the application lives in
+*/
+static bool onGuiThread()
+{
+ return QThread::currentThread() == QCoreApplication::instance()->thread();
+}
+
+/**
+ @brief ElementCollectionItem::setData
+ ElementsCollectionModel::loadCollections() runs setUpData() for every
+ item on worker threads (QtConcurrent::map), while the items are
+ already in the model. QStandardItem::setData() updates the model and
+ emits its dataChanged() signal, which is not safe from a worker
+ thread. So a value set from a worker thread is kept on the item, and
+ the model applies it on the GUI thread with applyDeferredData() once
+ every item is set up. setText(), setFlags(), setToolTip() and setIcon()
+ all end up here.
+ @param value
+ @param role
+*/
+void ElementCollectionItem::setData(const QVariant &value, int role)
+{
+ if (role == Qt::EditRole)
+ role = Qt::DisplayRole;
+
+ if (onGuiThread())
+ QStandardItem::setData(value, role);
+ else
+ m_deferred_data.insert(role, value);
+}
+
+/**
+ @brief ElementCollectionItem::data
+ On a worker thread, a value set by setData() but not applied yet is
+ returned, so setUpData() reads back what it has just set
+ (localName() tests text() for example).
+ @param role
+ @return
+*/
+QVariant ElementCollectionItem::data(int role) const
+{
+ if (role == Qt::EditRole)
+ role = Qt::DisplayRole;
+
+ if (!onGuiThread())
+ {
+ const auto it = m_deferred_data.constFind(role);
+ if (it != m_deferred_data.constEnd())
+ return it.value();
+ }
+ return QStandardItem::data(role);
+}
+
+/**
+ @brief ElementCollectionItem::applyDeferredData
+ Apply the values set from a worker thread, see setData().
+ Must be called on the GUI thread, after the worker is done.
+*/
+void ElementCollectionItem::applyDeferredData()
+{
+ for (auto it = m_deferred_data.constBegin() ; it != m_deferred_data.constEnd() ; ++it)
+ QStandardItem::setData(it.value(), it.key());
+ m_deferred_data.clear();
+}
+
void setUpData(ElementCollectionItem *eci) {
eci->setUpData();
}
diff --git a/sources/ElementsCollection/elementcollectionitem.h b/sources/ElementsCollection/elementcollectionitem.h
index ad12477c1..057215662 100644
--- a/sources/ElementsCollection/elementcollectionitem.h
+++ b/sources/ElementsCollection/elementcollectionitem.h
@@ -18,6 +18,7 @@
#ifndef ELEMENTCOLLECTIONITEM2_H
#define ELEMENTCOLLECTIONITEM2_H
+#include
#include
/**
@@ -56,6 +57,16 @@ class ElementCollectionItem : public QStandardItem
QList elementsChild() const;
QList directoriesChild() const;
QList items() const;
+
+ QVariant data(int role = Qt::UserRole + 1) const override;
+ void setData(const QVariant &value, int role = Qt::UserRole + 1) override;
+ void applyDeferredData();
+
+ private:
+ /// Values set by setUpData() while it runs on a worker thread,
+ /// see setData(). Only ever touched by that one worker thread,
+ /// until applyDeferredData() empties it on the GUI thread.
+ QHash m_deferred_data;
};
void setUpData(ElementCollectionItem *eci);
diff --git a/sources/ElementsCollection/elementscollectionmodel.cpp b/sources/ElementsCollection/elementscollectionmodel.cpp
index 00ef0d2eb..440f10417 100644
--- a/sources/ElementsCollection/elementscollectionmodel.cpp
+++ b/sources/ElementsCollection/elementscollectionmodel.cpp
@@ -312,8 +312,15 @@ void ElementsCollectionModel::loadCollections(bool common_collection,
this, &ElementsCollectionModel::loadingProgressValueChanged);
connect(watcher, &QFutureWatcher::progressRangeChanged,
this, &ElementsCollectionModel::loadingProgressRangeChanged);
- connect(watcher, &QFutureWatcher::finished,
- this, &ElementsCollectionModel::loadingFinished);
+ //setUpData() ran on worker threads, which only kept the values on
+ //the items (ElementCollectionItem::setData()): apply them here, on
+ //the GUI thread, before anyone is told the loading is finished.
+ connect(watcher, &QFutureWatcher::finished, this, [this]()
+ {
+ for (ElementCollectionItem *eci : std::as_const(m_items_list_to_setUp))
+ eci->applyDeferredData();
+ emit loadingFinished();
+ });
connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater);
diff --git a/sources/ElementsCollection/ui/renamedialog.cpp b/sources/ElementsCollection/ui/renamedialog.cpp
index fe8558b14..c7e761b22 100644
--- a/sources/ElementsCollection/ui/renamedialog.cpp
+++ b/sources/ElementsCollection/ui/renamedialog.cpp
@@ -20,12 +20,14 @@
#include
#include "ui_renamedialog.h"
+#include "../../qet.h"
RenameDialog::RenameDialog(QString path, QWidget *parent) :
QDialog(parent),
ui(new Ui::RenameDialog),
m_path(std::move(path))
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_name = m_path.split("/").last();
if (m_name.endsWith(".elmt")) m_name.remove(".elmt");
ui->m_label->setText(tr("L'élément « %1 » existe déjà. Que souhaitez-vous faire ?").arg(m_path));
diff --git a/sources/NameList/ui/namelistdialog.cpp b/sources/NameList/ui/namelistdialog.cpp
index bb942536b..71e606a4b 100644
--- a/sources/NameList/ui/namelistdialog.cpp
+++ b/sources/NameList/ui/namelistdialog.cpp
@@ -19,6 +19,7 @@
#include "ui_namelistdialog.h"
#include "namelistwidget.h"
+#include "../../qet.h"
#include
#include
@@ -27,6 +28,7 @@ NameListDialog::NameListDialog(QWidget *parent) :
ui(new Ui::NameListDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_namelist_widget = new NameListWidget(this);
ui->m_main_layout->insertWidget(1, m_namelist_widget);
diff --git a/sources/PropertiesEditor/propertieseditordialog.h b/sources/PropertiesEditor/propertieseditordialog.h
index 50cb96aef..b9e3bae86 100644
--- a/sources/PropertiesEditor/propertieseditordialog.h
+++ b/sources/PropertiesEditor/propertieseditordialog.h
@@ -18,6 +18,8 @@
#ifndef PROPERTIESEDITORDIALOG_H
#define PROPERTIESEDITORDIALOG_H
+#include "../qet.h"
+
#include
#include
#include
@@ -74,6 +76,14 @@ class PropertiesEditorDialog : public QDialog
this);
vlayout->addWidget(button_box);
+ // Keyed on the wrapped editor's own class, not this
+ // dialog's -- every instantiation of this constructor
+ // template is the same PropertiesEditorDialog class,
+ // so using its class name here would have every kind
+ // of wrapped editor (conductor properties, element
+ // properties, ...) fight over one saved size.
+ QET::trackDialogGeometry(this, editor->metaObject()->className());
+
//Setup connection between button box and the editor
connect(button_box,
&QDialogButtonBox::clicked,
diff --git a/sources/SearchAndReplace/ui/replaceadvanceddialog.cpp b/sources/SearchAndReplace/ui/replaceadvanceddialog.cpp
index 81f9f9e83..a728ab374 100644
--- a/sources/SearchAndReplace/ui/replaceadvanceddialog.cpp
+++ b/sources/SearchAndReplace/ui/replaceadvanceddialog.cpp
@@ -21,6 +21,7 @@
#include "../../qetinformation.h"
#include "ui_replaceadvanceddialog.h"
+#include "../../qet.h"
#include
/**
@@ -33,6 +34,7 @@ replaceAdvancedDialog::replaceAdvancedDialog(advancedReplaceStruct advanced, QWi
ui(new Ui::replaceAdvancedDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
connect(ui->m_button_box, &QDialogButtonBox::clicked, [this](QAbstractButton *button_) {
this->done(ui->m_button_box->buttonRole(button_));
diff --git a/sources/SearchAndReplace/ui/replaceconductordialog.cpp b/sources/SearchAndReplace/ui/replaceconductordialog.cpp
index 112452846..9b8fd7017 100644
--- a/sources/SearchAndReplace/ui/replaceconductordialog.cpp
+++ b/sources/SearchAndReplace/ui/replaceconductordialog.cpp
@@ -20,6 +20,7 @@
#include "../searchandreplaceworker.h"
#include "ui_replaceconductordialog.h"
+#include "../../qet.h"
#include
#include
@@ -37,6 +38,7 @@ ReplaceConductorDialog::ReplaceConductorDialog(
ui(new Ui::ReplaceConductorDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
initWidget();
setProperties(properties);
}
diff --git a/sources/SearchAndReplace/ui/replaceelementdialog.cpp b/sources/SearchAndReplace/ui/replaceelementdialog.cpp
index d1fa0e34b..ad5dfbf3f 100644
--- a/sources/SearchAndReplace/ui/replaceelementdialog.cpp
+++ b/sources/SearchAndReplace/ui/replaceelementdialog.cpp
@@ -24,6 +24,7 @@
#include "replaceelementdialog.h"
#include "ui_replaceelementdialog.h"
+#include "../../qet.h"
#include
ReplaceElementDialog::ReplaceElementDialog(DiagramContext context, QWidget *parent) :
@@ -31,6 +32,7 @@ ReplaceElementDialog::ReplaceElementDialog(DiagramContext context, QWidget *pare
ui(new Ui::ReplaceElementDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
buildWidget();
setContext(context);
}
diff --git a/sources/SearchAndReplace/ui/replacefoliowidget.cpp b/sources/SearchAndReplace/ui/replacefoliowidget.cpp
index ea75b87cf..be11c4352 100644
--- a/sources/SearchAndReplace/ui/replacefoliowidget.cpp
+++ b/sources/SearchAndReplace/ui/replacefoliowidget.cpp
@@ -18,6 +18,7 @@
#include "replacefoliowidget.h"
#include "../../ui/diagramcontextwidget.h"
+#include "../../qet.h"
#include "../searchandreplaceworker.h"
#include "ui_replacefoliowidget.h"
@@ -132,6 +133,8 @@ ReplaceFolioDialog::ReplaceFolioDialog(QWidget *parent) :
{
this->done(m_button_box->buttonRole(button_));
});
+
+ QET::trackDialogGeometry(this);
}
ReplaceFolioDialog::~ReplaceFolioDialog()
diff --git a/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp b/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp
index c61b67ad6..fe4c77d8b 100644
--- a/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp
+++ b/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp
@@ -23,6 +23,7 @@
#include "../GraphicsItem/terminalstripitem.h"
#include "../../diagram.h"
+#include "../../qet.h"
void AddTerminalStripItemDialog::openDialog(Diagram *diagram, QWidget *parent)
{
AddTerminalStripItemDialog d(diagram->project(), parent);
@@ -46,6 +47,7 @@ AddTerminalStripItemDialog::AddTerminalStripItemDialog(QETProject *project, QWid
ui{new Ui::AddTerminalStripItemDialog}
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
fillComboBox();
}
diff --git a/sources/TerminalStrip/ui/terminalstripcreatordialog.cpp b/sources/TerminalStrip/ui/terminalstripcreatordialog.cpp
index 52363065e..3cf5d7e4c 100644
--- a/sources/TerminalStrip/ui/terminalstripcreatordialog.cpp
+++ b/sources/TerminalStrip/ui/terminalstripcreatordialog.cpp
@@ -20,6 +20,7 @@
#include "../terminalstrip.h"
#include "../../qetproject.h"
+#include "../../qet.h"
/**
* @brief TerminalStripCreatorDialog::TerminalStripCreatorDialog
* @param project : Project to add a new terminal strip
@@ -31,6 +32,7 @@ TerminalStripCreatorDialog::TerminalStripCreatorDialog(QETProject *project, QWid
m_project(project)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp b/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp
index b2e2aa1db..fd81bd61e 100644
--- a/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp
+++ b/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp
@@ -62,7 +62,9 @@ void TerminalStripTreeDockWidget::setProject(QETProject *project)
}
m_project = project;
if (m_project) {
- m_project_destroy_connection = connect(m_project, &QObject::destroyed, [this](){
+ //`this` as context: this dock can be deleted before the project
+ //(with the editor window that owns it), and the connection must go with it
+ m_project_destroy_connection = connect(m_project, &QObject::destroyed, this, [this](){
this->m_current_strip.clear();
this->reload();
});
diff --git a/sources/autoNum/ui/renumberelementsdialog.cpp b/sources/autoNum/ui/renumberelementsdialog.cpp
index a8c4c9dd1..7d9cfe48a 100644
--- a/sources/autoNum/ui/renumberelementsdialog.cpp
+++ b/sources/autoNum/ui/renumberelementsdialog.cpp
@@ -17,6 +17,7 @@
*/
#include "renumberelementsdialog.h"
+#include "../../qet.h"
#include
#include
@@ -60,6 +61,8 @@ RenumberElementsDialog::RenumberElementsDialog(const QStringList &scheme_titles,
connect(m_all_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
connect(m_one_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
+
+ QET::trackDialogGeometry(this);
}
QString RenumberElementsDialog::selectedSchemeTitle() const
diff --git a/sources/configdialog.cpp b/sources/configdialog.cpp
index 10a0d0e0e..1e4f2bbe2 100644
--- a/sources/configdialog.cpp
+++ b/sources/configdialog.cpp
@@ -19,6 +19,7 @@
#include "configdialog.h"
#include "ui/configpage/configpages.h"
#include "qetapp.h"
+#include "qet.h"
#include "machine_info.h"
@@ -89,6 +90,8 @@ ConfigDialog::ConfigDialog(QWidget *parent) : QDialog(parent) {
setWindowFlags(Qt::Sheet);
}
#endif
+
+ QET::trackDialogGeometry(this);
}
/// Destructeur
diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp
index b4c5cf126..d74e524b4 100644
--- a/sources/diagramview.cpp
+++ b/sources/diagramview.cpp
@@ -45,13 +45,20 @@
#include "ElementsCollection/xmlelementcollection.h"
#include "NameList/nameslist.h"
#include "elementdialog.h"
+#include "qetapp.h"
+#include "qetgraphicsitem/element.h"
+#include "qetinformation.h"
+#include "qetversion.h"
#include
#include
#include
#include
#include
+#include
#include
+static bool hasCabinetInfo(const DiagramContext &infos);
+
/**
Constructeur
@param diagram Schema a afficher ; si diagram vaut 0, un nouveau Diagram est utilise
@@ -103,6 +110,10 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
m_create_template = new QAction(tr("Créer un template", "context menu action"), this);
connect(m_create_template, &QAction::triggered, this, &DiagramView::createTemplateFromSelection);
+ //Setup the action to generate cabinet placement thumbnails (discussion #602)
+ m_generate_cabinet_thumbnail = new QAction(tr("Générer une vignette d'armoire", "context menu action"), this);
+ connect(m_generate_cabinet_thumbnail, &QAction::triggered, this, &DiagramView::generateCabinetThumbnails);
+
//Filled each time the context menu opens, see updateFolioReportMenu()
m_folio_report_menu = new QMenu(tr("Renvoi de folio"), this);
@@ -1765,7 +1776,8 @@ QList DiagramView::contextMenuActions() const
QList list;
if (QETDiagramEditor *qde = diagramEditor())
{
- if (m_diagram->selectedItems().isEmpty())
+ const QList selected_items = m_diagram->selectedItems();
+ if (selected_items.isEmpty())
{
//Drawing comes first. The row and column actions change
//the folio's layout and are rarely wanted, so they sit one
@@ -1786,6 +1798,15 @@ QList DiagramView::contextMenuActions() const
list << m_multi_paste;
list << m_separators.at(0);
list << m_create_template; // Add the create template action
+ //Disabled, and so left out below, unless a selected element
+ //has both a manufacturer and a manufacturer reference.
+ m_generate_cabinet_thumbnail->setEnabled(std::any_of(
+ selected_items.cbegin(), selected_items.cend(),
+ [](QGraphicsItem *qgi) {
+ const Element *element = qgraphicsitem_cast(qgi);
+ return element && hasCabinetInfo(element->elementInformations());
+ }));
+ list << m_generate_cabinet_thumbnail;
list << qde->m_conductor_reset;
list << m_separators.at(1);
list << qde->m_selection_actions_group.actions();
@@ -2073,6 +2094,166 @@ void DiagramView::createTemplateFromSelection()
}
}
+/**
+ @brief hasCabinetInfo
+ @return true if @a infos has both a manufacturer and a manufacturer
+ reference, the two values a cabinet thumbnail shows.
+*/
+static bool hasCabinetInfo(const DiagramContext &infos)
+{
+ return !infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed().isEmpty()
+ && !infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed().isEmpty();
+}
+
+/**
+ @brief cabinetThumbnailDefinition
+ @return the definition of a thumbnail element, a 120x30 frame showing
+ the manufacturer and the manufacturer reference in a dynamic text.
+ The two values are also stored in the definition's element
+ information, so the thumbnail shows them as soon as it is placed.
+*/
+static QDomElement cabinetThumbnailDefinition(QDomDocument &doc,
+ const QString &display_name,
+ const QString &manufacturer,
+ const QString &reference)
+{
+ QDomElement definition = doc.createElement(QStringLiteral("definition"));
+ definition.setAttribute(QStringLiteral("version"), QetVersion::currentVersion().toString());
+ definition.setAttribute(QStringLiteral("type"), QStringLiteral("element"));
+ definition.setAttribute(QStringLiteral("link_type"), QStringLiteral("thumbnail"));
+ definition.setAttribute(QStringLiteral("width"), 120);
+ definition.setAttribute(QStringLiteral("height"), 30);
+ definition.setAttribute(QStringLiteral("hotspot_x"), 60);
+ definition.setAttribute(QStringLiteral("hotspot_y"), 15);
+ doc.appendChild(definition);
+
+ QDomElement uuid = doc.createElement(QStringLiteral("uuid"));
+ uuid.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString());
+ definition.appendChild(uuid);
+
+ NamesList names;
+ names.addName(QStringLiteral("en"), display_name);
+ definition.appendChild(names.toXml(doc));
+
+ QDomElement informations = doc.createElement(QStringLiteral("elementInformations"));
+ DiagramContext infos;
+ infos.addValue(QETInformation::ELMT_MANUFACTURER, manufacturer);
+ infos.addValue(QETInformation::ELMT_MANUFACTURER_REF, reference);
+ infos.toXml(informations, QStringLiteral("elementInformation"));
+ definition.appendChild(informations);
+
+ QDomElement description = doc.createElement(QStringLiteral("description"));
+
+ QDomElement rect = doc.createElement(QStringLiteral("rect"));
+ rect.setAttribute(QStringLiteral("x"), -60);
+ rect.setAttribute(QStringLiteral("y"), -15);
+ rect.setAttribute(QStringLiteral("width"), 120);
+ rect.setAttribute(QStringLiteral("height"), 30);
+ rect.setAttribute(QStringLiteral("rx"), 0);
+ rect.setAttribute(QStringLiteral("ry"), 0);
+ rect.setAttribute(QStringLiteral("style"),
+ QStringLiteral("line-style:normal;line-weight:normal;filling:none;color:black"));
+ rect.setAttribute(QStringLiteral("antialias"), QStringLiteral("false"));
+ description.appendChild(rect);
+
+ QDomElement text = doc.createElement(QStringLiteral("dynamic_text"));
+ text.setAttribute(QStringLiteral("x"), -57);
+ text.setAttribute(QStringLiteral("y"), -14);
+ text.setAttribute(QStringLiteral("z"), 1);
+ text.setAttribute(QStringLiteral("text_width"), 114);
+ text.setAttribute(QStringLiteral("Halignment"), QStringLiteral("AlignLeft"));
+ text.setAttribute(QStringLiteral("Valignment"), QStringLiteral("AlignTop"));
+ text.setAttribute(QStringLiteral("frame"), QStringLiteral("false"));
+ text.setAttribute(QStringLiteral("rotation"), 0);
+ text.setAttribute(QStringLiteral("keep_visual_rotation"), QStringLiteral("false"));
+ text.setAttribute(QStringLiteral("text_from"), QStringLiteral("CompositeText"));
+ text.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString());
+ text.setAttribute(QStringLiteral("font"), QETApp::dynamicTextsItemFont(7).toString());
+
+ QDomElement text_cache = doc.createElement(QStringLiteral("text"));
+ text_cache.appendChild(doc.createTextNode(manufacturer + QLatin1Char('\n') + reference));
+ text.appendChild(text_cache);
+
+ QDomElement composite_text = doc.createElement(QStringLiteral("composite_text"));
+ composite_text.appendChild(doc.createTextNode(QStringLiteral("%{manufacturer}\n%{manufacturer_reference}")));
+ text.appendChild(composite_text);
+
+ description.appendChild(text);
+ definition.appendChild(description);
+ return definition;
+}
+
+/**
+ @brief DiagramView::generateCabinetThumbnails
+ Context menu action (discussion #602). For every selected element with
+ both a manufacturer and a manufacturer reference, add a thumbnail
+ element showing them to the folder "Cabinet thumbnails" of this
+ project's embedded collection, from where it can be placed on a
+ cabinet layout folio like any other element. A device that already
+ has a thumbnail there is skipped, so placing it several times does
+ not pile up copies.
+*/
+void DiagramView::generateCabinetThumbnails()
+{
+ QETProject *project = m_diagram->project();
+ if (!project || project->isReadOnly()) {
+ return;
+ }
+ XmlElementCollection *collection = project->embeddedElementCollection();
+ if (!collection) {
+ return;
+ }
+
+ const QString dir_name = QStringLiteral("Cabinet thumbnails");
+ const QString dir_path = QStringLiteral("import/") + dir_name;
+ if (!collection->exist(dir_path))
+ {
+ NamesList dir_names;
+ dir_names.addName(QStringLiteral("en"), dir_name);
+ //Stored per language, like an element's names: not tr(), which
+ //would store the interface's language under "fr"
+ dir_names.addName(QStringLiteral("fr"), QStringLiteral("Vignettes d'armoire"));
+ if (!collection->createDir(QStringLiteral("import"), dir_name, dir_names)) {
+ return;
+ }
+ }
+
+ QStringList existing = collection->elementsNames(collection->directory(dir_path));
+ int created = 0;
+ const QList selected_items = m_diagram->selectedItems();
+ for (QGraphicsItem *qgi : selected_items)
+ {
+ const Element *element = qgraphicsitem_cast(qgi);
+ if (!element || !hasCabinetInfo(element->elementInformations())) {
+ continue;
+ }
+ const DiagramContext infos = element->elementInformations();
+ const QString manufacturer = infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed();
+ const QString reference = infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed();
+
+ //The display name keeps the values as typed; the file name
+ //cannot hold characters such as '/', which would be read as
+ //a folder.
+ const QString display_name = manufacturer + QLatin1Char(' ') + reference;
+ const QString file_name = QET::stringToFileName(display_name) + QStringLiteral(".elmt");
+ if (existing.contains(file_name)) {
+ continue;
+ }
+
+ QDomDocument doc;
+ const QDomElement definition = cabinetThumbnailDefinition(doc, display_name, manufacturer, reference);
+ if (collection->addElementDefinition(dir_path, file_name, definition))
+ {
+ existing << file_name;
+ ++created;
+ }
+ }
+
+ if (created) {
+ project->setModified(true);
+ }
+}
+
/**
@return l'editeur de schemas parent ou 0
*/
diff --git a/sources/diagramview.h b/sources/diagramview.h
index 9ac5a5ad8..11f53ddf2 100644
--- a/sources/diagramview.h
+++ b/sources/diagramview.h
@@ -58,6 +58,7 @@ class DiagramView : public PaletteGraphicsView
QAction *m_paste_here = nullptr;
QAction *m_multi_paste = nullptr;
QAction *m_create_template = nullptr;
+ QAction *m_generate_cabinet_thumbnail = nullptr;
QMenu *m_folio_report_menu = nullptr;
QPoint m_paste_here_pos;
QPoint m_press_pos;
@@ -197,5 +198,6 @@ class DiagramView : public PaletteGraphicsView
void adjustGridToZoom();
void applyReadOnly();
void createTemplateFromSelection();
+ void generateCabinetThumbnails();
};
#endif
diff --git a/sources/editor/elementscene.cpp b/sources/editor/elementscene.cpp
index c0105ae83..1ae830123 100644
--- a/sources/editor/elementscene.cpp
+++ b/sources/editor/elementscene.cpp
@@ -18,6 +18,7 @@
#include "elementscene.h"
#include "../ElementsCollection/terminaluuids.h"
+#include "../borderproperties.h"
#include "../NameList/ui/namelistdialog.h"
#include "../NameList/ui/namelistwidget.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
@@ -72,6 +73,49 @@ ElementScene::ElementScene(QETElementEditor *editor, QObject *parent) :
m_decorator_lock = new QMutex();
connect(&m_undo_stack, &QUndoStack::indexChanged, this, &ElementScene::managePrimitivesGroups);
connect(this, &ElementScene::selectionChanged, this, &ElementScene::managePrimitivesGroups);
+
+ QSettings settings;
+ m_background_frame_visible = settings.value(
+ QStringLiteral("elementeditor/background_frame_visible"), false).toBool();
+ BorderProperties bp = BorderProperties::defaultProperties();
+ m_background_frame_size = QSizeF(
+ settings.value(QStringLiteral("elementeditor/background_frame_width"),
+ bp.columns_count * bp.columns_width).toReal(),
+ settings.value(QStringLiteral("elementeditor/background_frame_height"),
+ bp.rows_count * bp.rows_height).toReal());
+}
+
+/**
+ @brief ElementScene::setBackgroundFrameVisible
+ Toggle the visual-only background frame used to proportion this
+ element's drawing against a representative folio surface. Like the
+ hotspot indicator, this frame is never written to the saved .elmt file.
+ @param visible
+*/
+void ElementScene::setBackgroundFrameVisible(bool visible)
+{
+ if (m_background_frame_visible == visible) {
+ return;
+ }
+ m_background_frame_visible = visible;
+ QSettings().setValue(QStringLiteral("elementeditor/background_frame_visible"), visible);
+ update();
+}
+
+/**
+ @brief ElementScene::setBackgroundFrameSize
+ @param size the new size (in scene/grid units) of the background frame
+*/
+void ElementScene::setBackgroundFrameSize(const QSizeF &size)
+{
+ if (m_background_frame_size == size) {
+ return;
+ }
+ m_background_frame_size = size;
+ QSettings settings;
+ settings.setValue(QStringLiteral("elementeditor/background_frame_width"), size.width());
+ settings.setValue(QStringLiteral("elementeditor/background_frame_height"), size.height());
+ update();
}
/**
diff --git a/sources/editor/elementscene.h b/sources/editor/elementscene.h
index a53afff6f..e4f086078 100644
--- a/sources/editor/elementscene.h
+++ b/sources/editor/elementscene.h
@@ -92,6 +92,8 @@ class ElementScene : public QGraphicsScene
QPointer m_single_selected_item;
bool m_hotspot_visible = true;
+ bool m_background_frame_visible = false;
+ QSizeF m_background_frame_size;
// methods
public:
@@ -142,6 +144,11 @@ class ElementScene : public QGraphicsScene
/// not part of the drawn symbol.
void setHotspotVisible(bool visible) {m_hotspot_visible = visible;}
bool hotspotVisible() const {return m_hotspot_visible;}
+
+ bool backgroundFrameVisible() const {return m_background_frame_visible;}
+ void setBackgroundFrameVisible(bool visible);
+ QSizeF backgroundFrameSize() const {return m_background_frame_size;}
+ void setBackgroundFrameSize(const QSizeF &size);
protected:
void mouseMoveEvent (QGraphicsSceneMouseEvent *) override;
diff --git a/sources/editor/elementview.cpp b/sources/editor/elementview.cpp
index c9d254f70..6eed40457 100644
--- a/sources/editor/elementview.cpp
+++ b/sources/editor/elementview.cpp
@@ -606,6 +606,19 @@ void ElementView::drawBackground(QPainter *p, const QRectF &r) {
}
}
}
+
+ if (m_scene->backgroundFrameVisible()) {
+ const QSizeF frame_size = m_scene->backgroundFrameSize();
+ const QRectF frame_rect(-frame_size.width() / 2.0, -frame_size.height() / 2.0,
+ frame_size.width(), frame_size.height());
+ QPen frame_pen(Qt::blue);
+ frame_pen.setCosmetic(true);
+ frame_pen.setStyle(Qt::DashLine);
+ p -> setPen(frame_pen);
+ p -> setBrush(Qt::NoBrush);
+ p -> drawRect(frame_rect);
+ }
+
p -> restore();
}
diff --git a/sources/editor/ui/elementpropertieseditorwidget.cpp b/sources/editor/ui/elementpropertieseditorwidget.cpp
index 6e16ffa1b..78386abd1 100644
--- a/sources/editor/ui/elementpropertieseditorwidget.cpp
+++ b/sources/editor/ui/elementpropertieseditorwidget.cpp
@@ -22,6 +22,7 @@
#include "../../qetinformation.h"
#include "ui_elementpropertieseditorwidget.h"
+#include "../../qet.h"
#include
#include
#include
@@ -95,6 +96,7 @@ ElementPropertiesEditorWidget::ElementPropertiesEditorWidget(ElementData data, Q
m_data(data)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
setUpInterface();
upDateInterface();
}
diff --git a/sources/editor/ui/qetelementeditor.cpp b/sources/editor/ui/qetelementeditor.cpp
index 5428ec1b9..8489f8def 100644
--- a/sources/editor/ui/qetelementeditor.cpp
+++ b/sources/editor/ui/qetelementeditor.cpp
@@ -59,6 +59,10 @@
#include
#include
#include
+#include
+#include
+#include
+#include
/**
* @brief QETElementEditor::QETElementEditor
@@ -1138,6 +1142,47 @@ void QETElementEditor::setupActions()
parts_toolbar -> setObjectName("parts");
parts_toolbar -> addActions(m_add_part_action_grp -> actions());
addToolBar(Qt::LeftToolBarArea, parts_toolbar);
+
+ //Background frame action: a visual-only reference rectangle, never
+ //written to the saved .elmt file, to help proportion the drawing
+ //against a representative folio surface.
+ auto *toggle_background_frame_action = new QAction(QET::Icons::DocumentPrintFrame, tr("Afficher le cadre de fond"), this);
+ toggle_background_frame_action -> setCheckable(true);
+ toggle_background_frame_action -> setChecked(m_elmt_scene -> backgroundFrameVisible());
+ connect(toggle_background_frame_action, &QAction::toggled, m_elmt_scene, &ElementScene::setBackgroundFrameVisible);
+ ShortcutManager::instance().registerAction(toggle_background_frame_action, "elementeditor.toggle_background_frame", tr("Éditeur d'élément"), QKeySequence());
+ ui->m_display_menu->addAction(toggle_background_frame_action);
+ ui->m_view_toolbar->addAction(toggle_background_frame_action);
+
+ auto *configure_background_frame_action = new QAction(tr("Taille du cadre de fond..."), this);
+ connect(configure_background_frame_action, &QAction::triggered, this, [this]() {
+ QDialog dialog(this);
+ dialog.setWindowTitle(tr("Taille du cadre de fond"));
+ auto *layout = new QFormLayout(&dialog);
+
+ auto *width_spin = new QDoubleSpinBox(&dialog);
+ width_spin -> setRange(1.0, 100000.0);
+ width_spin -> setSuffix(tr(" px"));
+ width_spin -> setValue(m_elmt_scene -> backgroundFrameSize().width());
+ layout -> addRow(tr("Largeur"), width_spin);
+
+ auto *height_spin = new QDoubleSpinBox(&dialog);
+ height_spin -> setRange(1.0, 100000.0);
+ height_spin -> setSuffix(tr(" px"));
+ height_spin -> setValue(m_elmt_scene -> backgroundFrameSize().height());
+ layout -> addRow(tr("Hauteur"), height_spin);
+
+ auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
+ connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+ layout -> addRow(buttons);
+
+ if (dialog.exec() == QDialog::Accepted) {
+ m_elmt_scene -> setBackgroundFrameSize(QSizeF(width_spin -> value(), height_spin -> value()));
+ }
+ });
+ ShortcutManager::instance().registerAction(configure_background_frame_action, "elementeditor.configure_background_frame", tr("Éditeur d'élément"), QKeySequence());
+ ui->m_display_menu->addAction(configure_background_frame_action);
}
/**
diff --git a/sources/elementdialog.cpp b/sources/elementdialog.cpp
index 503aefc6b..64d62bd46 100644
--- a/sources/elementdialog.cpp
+++ b/sources/elementdialog.cpp
@@ -20,6 +20,7 @@
#include "ElementsCollection/elementcollectionitem.h"
#include "ElementsCollection/elementscollectionmodel.h"
#include "ElementsCollection/elementstreeview.h"
+#include "qet.h"
#include "qetapp.h"
#include "qetmessagebox.h"
#include "qfilenameedit.h"
@@ -41,6 +42,7 @@ ElementDialog::ElementDialog(uint mode, QWidget *parent) :
{
setUpWidget();
setUpConnection();
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/elementscategoryeditor.cpp b/sources/elementscategoryeditor.cpp
index 600b7e28e..455f4e115 100644
--- a/sources/elementscategoryeditor.cpp
+++ b/sources/elementscategoryeditor.cpp
@@ -41,6 +41,7 @@ ElementsCategoryEditor::ElementsCategoryEditor(const ElementsLocation &location,
m_location(location)
{
setUpWidget();
+ QET::trackDialogGeometry(this);
if (m_location.isElement()) {
QET::QetMessageBox::warning(this,
diff --git a/sources/exportdialog.cpp b/sources/exportdialog.cpp
index 95d0f8fb6..4242d726d 100644
--- a/sources/exportdialog.cpp
+++ b/sources/exportdialog.cpp
@@ -22,6 +22,7 @@
#include "dxfexport.h"
#include "exportpropertieswidget.h"
#include "factory/elementpicturefactory.h"
+#include "qet.h"
#include "qetgraphicsitem/ViewItem/qetgraphicstableitem.h"
#include "dxfpaintdevice.h"
#include "qetgraphicsitem/conductor.h"
@@ -108,6 +109,8 @@ ExportDialog::ExportDialog(
// ajustement des extensions des fichiers
slot_changeFilesExtension(true);
+
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/factory/ui/addtabledialog.cpp b/sources/factory/ui/addtabledialog.cpp
index b2de24aad..6ea094a01 100644
--- a/sources/factory/ui/addtabledialog.cpp
+++ b/sources/factory/ui/addtabledialog.cpp
@@ -22,6 +22,7 @@
#include "../../utils/qetutils.h"
#include "ui_addtabledialog.h"
+#include "../../qet.h"
#include
/**
@@ -35,6 +36,7 @@ AddTableDialog::AddTableDialog(QWidget *content_widget, QWidget *parent) :
ui(new Ui::AddTableDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
ui->m_header_font_pb->setText(m_header_font.family());
ui->m_table_font_pb->setText(m_table_font.family());
m_content_widget = content_widget;
diff --git a/sources/logging/ui/diagnosticsreportdialog.cpp b/sources/logging/ui/diagnosticsreportdialog.cpp
index 538b289f8..badfea564 100644
--- a/sources/logging/ui/diagnosticsreportdialog.cpp
+++ b/sources/logging/ui/diagnosticsreportdialog.cpp
@@ -17,6 +17,7 @@
*/
#include "diagnosticsreportdialog.h"
+#include "../../qet.h"
#include "../../qetmessagebox.h"
#include
@@ -63,6 +64,8 @@ DiagnosticsReportDialog::DiagnosticsReportDialog(
// holds a QString copy but we save the original UTF-8 bytes to avoid
// any round-trip surprises.
setProperty("qet_report_content", content);
+
+ QET::trackDialogGeometry(this);
}
void DiagnosticsReportDialog::saveToFile()
diff --git a/sources/materiallist/materialentrydialog.cpp b/sources/materiallist/materialentrydialog.cpp
index 2795df12f..477dd0805 100644
--- a/sources/materiallist/materialentrydialog.cpp
+++ b/sources/materiallist/materialentrydialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "materialentrydialog.h"
+#include "../qet.h"
#include "../qetmessagebox.h"
@@ -92,6 +93,8 @@ MaterialEntryDialog::MaterialEntryDialog(const QStringList &columns, QWidget *pa
if (!m_edits.isEmpty()) {
m_edits.first()->setFocus();
}
+
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/qet.cpp b/sources/qet.cpp
index 8f9b79908..4e5db31d8 100644
--- a/sources/qet.cpp
+++ b/sources/qet.cpp
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -272,6 +273,27 @@ bool QET::infoFlagIsTrue(const QString &value)
|| v == QLatin1String("on");
}
+/**
+ @brief QET::trackDialogGeometry
+ @see the declaration in qet.h for the rationale.
+*/
+void QET::trackDialogGeometry(QDialog *dialog, const QString &key)
+{
+ const QString settings_key = QStringLiteral("dialoggeometry/%1").arg(
+ key.isEmpty() ? QString::fromLatin1(dialog->metaObject()->className()) : key);
+
+ QSettings settings;
+ const QVariant geometry = settings.value(settings_key);
+ if (geometry.isValid()) {
+ dialog->restoreGeometry(geometry.toByteArray());
+ }
+
+ QObject::connect(dialog, &QDialog::finished, dialog, [dialog, settings_key]() {
+ QSettings settings;
+ settings.setValue(settings_key, dialog->saveGeometry());
+ });
+}
+
/**
@brief QET::ElementsAndConductorsSentence
Permet de composer rapidement la proposition "x elements et y conducteurs"
diff --git a/sources/qet.h b/sources/qet.h
index cb5a8a0a8..11438d2c2 100644
--- a/sources/qet.h
+++ b/sources/qet.h
@@ -24,6 +24,7 @@
#include
class QActionGroup;
+class QDialog;
/**
This file provides useful functions and enums that may be used from
anywhere else within the QElectroTech application.
@@ -204,6 +205,18 @@ namespace QET {
QActionGroup *depthActionGroup(QObject *parent = nullptr);
void saveCustomColors();
void loadCustomColors();
+
+ /**
+ Restore a dialog's last-used size/position from QSettings, and
+ save it back whenever the dialog closes (accepted, rejected, or
+ via the window's close button). @a key identifies the dialog in
+ QSettings; defaults to the dialog's class name, which is enough
+ to distinguish dialogs one-to-one -- pass an explicit @a key for
+ a dialog class reused for several different kinds of content
+ (e.g. a generic properties-editor wrapper), so each kind gets
+ its own remembered size instead of fighting over one entry.
+ */
+ void trackDialogGeometry(QDialog *dialog, const QString &key = QString());
}
Q_DECLARE_METATYPE(QET::DepthOption)
diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp
index 568cca5de..615428f07 100644
--- a/sources/qetapp.cpp
+++ b/sources/qetapp.cpp
@@ -30,6 +30,7 @@
#include "qetpalette.h"
#include "qetstyle.h"
#include "utils/qetutils.h"
+#include "utils/configprofile.h"
#include "qetmessagebox.h"
#include "qetproject.h"
#include "qtextorientationspinboxwidget.h"
@@ -55,10 +56,12 @@
#include
#define QUOTE(x) STRINGIFY(x)
#define STRINGIFY(x) #x
+#include
#include
#include
#include
#include
+#include
#include
#include
#ifdef BUILD_WITHOUT_KF
@@ -2246,6 +2249,109 @@ void QETApp::configureQET()
#endif
}
+/**
+ @brief QETApp::exportConfiguration
+ Save the settings of QElectroTech to a file the user chooses, to keep
+ them as a named profile or copy them to another computer (discussion
+ #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.
+*/
+void QETApp::exportConfiguration()
+{
+ QWidget *parent_widget = qApp->activeWindow();
+
+ QString path = QFileDialog::getSaveFileName(
+ parent_widget,
+ tr("Enregistrer la configuration sous...", "dialog title"),
+ QString(),
+ tr("Configurations QElectroTech (*.conf)", "file dialog filter"));
+ if (path.isEmpty()) {
+ return;
+ }
+ if (!path.endsWith(QLatin1String(".conf"), Qt::CaseInsensitive)) {
+ path += QLatin1String(".conf");
+ }
+
+ QSettings live_settings;
+ QSettings file_settings(path, QSettings::IniFormat);
+ ConfigProfile::exportTo(live_settings, file_settings);
+
+ if (file_settings.status() != QSettings::NoError) {
+ QET::QetMessageBox::critical(
+ parent_widget,
+ tr("Erreur", "message box title"),
+ tr("Impossible d'enregistrer la configuration dans « %1 ».").arg(path));
+ }
+}
+
+/**
+ @brief QETApp::importConfiguration
+ Replace the settings of QElectroTech with a file saved by
+ exportConfiguration(), then close QElectroTech.
+
+ 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
+ new settings are applied by starting QElectroTech again. That restart
+ is left to the user: a second copy started from here would find this
+ one still running, hand its arguments over to it and exit (main.cpp).
+*/
+void QETApp::importConfiguration()
+{
+ QWidget *parent_widget = qApp->activeWindow();
+
+ const QString path = QFileDialog::getOpenFileName(
+ parent_widget,
+ tr("Charger une configuration...", "dialog title"),
+ QString(),
+ tr("Configurations QElectroTech (*.conf)", "file dialog filter"));
+ if (path.isEmpty()) {
+ return;
+ }
+
+ QSettings file_settings(path, QSettings::IniFormat);
+ if (file_settings.status() != QSettings::NoError
+ || !ConfigProfile::isProfile(file_settings))
+ {
+ QET::QetMessageBox::critical(
+ parent_widget,
+ tr("Erreur", "message box title"),
+ tr("« %1 » n'est pas une configuration enregistrée par QElectroTech.").arg(path));
+ return;
+ }
+
+ //Said before anything closes: once the last window is closed,
+ //QElectroTech quits by itself (checkRemainingWindows()).
+ const auto answer = QET::QetMessageBox::question(
+ parent_widget,
+ tr("Charger une configuration", "message box title"),
+ tr("Cette configuration va remplacer vos réglages actuels, sauf la "
+ "disposition des fenêtres et la liste des fichiers récents.\n\n"
+ "QElectroTech va ensuite se fermer. Relancez-le pour utiliser "
+ "la nouvelle configuration.\n\n"
+ "Voulez-vous continuer ?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (answer != QMessageBox::Yes) {
+ 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
+ //cancelled close leaves them untouched.
+ if (!closeEveryEditor()) {
+ return;
+ }
+
+ QSettings live_settings;
+ ConfigProfile::importFrom(file_settings, live_settings);
+ //~QETApp() saves the colour dialog's custom colours on exit: load
+ //the profile's into it first, or the old ones overwrite them
+ QET::loadCustomColors();
+ qApp->quit();
+}
+
/**
@brief QETApp::aboutQET
Open the dialog about qet.
diff --git a/sources/qetapp.h b/sources/qetapp.h
index 5c0b15106..2c2fa1d3e 100644
--- a/sources/qetapp.h
+++ b/sources/qetapp.h
@@ -291,6 +291,10 @@ class QETApp : public QObject
void openTitleBlockTemplate(const QString &);
void openTitleBlockTemplateFiles(const QStringList &);
void configureQET();
+ /// Save the settings to a file (discussion #610)
+ void exportConfiguration();
+ /// Replace the settings with a saved file, then close QElectroTech
+ void importConfiguration();
void aboutQET();
void showDiagnosticsReport();
void receiveMessage(int instanceId, QByteArray message);
diff --git a/sources/qetgraphicsitem/conductor.cpp b/sources/qetgraphicsitem/conductor.cpp
index 4eb9009bc..a82dc5ced 100644
--- a/sources/qetgraphicsitem/conductor.cpp
+++ b/sources/qetgraphicsitem/conductor.cpp
@@ -1099,8 +1099,10 @@ bool Conductor::fromXml(QDomElement &dom_element)
m_uuid = QUuid::createUuid();
}
- setPos(dom_element.attribute("x", nullptr).toDouble(),
- dom_element.attribute("y", nullptr).toDouble());
+ //"nan" and "inf" parse as numbers; a non-finite position is kept at 0
+ const qreal x = dom_element.attribute("x", nullptr).toDouble();
+ const qreal y = dom_element.attribute("y", nullptr).toDouble();
+ setPos(qIsFinite(x) ? x : 0, qIsFinite(y) ? y : 0);
bool retval = pathFromXml(dom_element);
@@ -1229,7 +1231,10 @@ bool Conductor::pathFromXml(const QDomElement &e) {
// cette longueur doit etre un reel
bool ok;
qreal segment_length = current_segment.attribute("length").toDouble(&ok);
- if (!ok) continue;
+ // toDouble() accepts "nan" and "inf". A NaN length would also pass
+ // the coherence check below (every comparison with NaN is false)
+ // and turn the whole path into NaN.
+ if (!ok || !qIsFinite(segment_length)) continue;
if (current_segment.attribute("orientation") == "horizontal") {
segments_x << segment_length;
diff --git a/sources/qetgraphicsitem/conductortextitem.cpp b/sources/qetgraphicsitem/conductortextitem.cpp
index 624b5ea77..a3e3127f0 100644
--- a/sources/qetgraphicsitem/conductortextitem.cpp
+++ b/sources/qetgraphicsitem/conductortextitem.cpp
@@ -69,13 +69,17 @@ Conductor *ConductorTextItem::parentConductor() const
@param e
*/
void ConductorTextItem::fromXml(const QDomElement &e) {
- if (e.hasAttribute("userx")) {
- setPos(e.attribute("userx").toDouble(),
- e.attribute("usery").toDouble());
+ //A non-finite ("nan", "inf") position or rotation is ignored,
+ //as if the text had never been moved or rotated
+ const qreal x = e.attribute("userx").toDouble();
+ const qreal y = e.attribute("usery").toDouble();
+ if (e.hasAttribute("userx") && qIsFinite(x) && qIsFinite(y)) {
+ setPos(x, y);
moved_by_user_ = true;
}
- if (e.hasAttribute("rotation")) {
- setRotation(e.attribute("rotation").toDouble());
+ const qreal rotation = e.attribute("rotation").toDouble();
+ if (e.hasAttribute("rotation") && qIsFinite(rotation)) {
+ setRotation(rotation);
rotate_by_user_ = true;
}
}
diff --git a/sources/qetmainwindow.cpp b/sources/qetmainwindow.cpp
index 1436f2668..36fccebf8 100644
--- a/sources/qetmainwindow.cpp
+++ b/sources/qetmainwindow.cpp
@@ -92,6 +92,16 @@ void QETMainWindow::initCommonActions()
}
});
+ export_config_action_ = new QAction(QET::Icons::DocumentExport, tr("Enregistrer la configuration sous..."), this);
+ export_config_action_ -> setStatusTip(tr("Enregistre les réglages de QElectroTech dans un fichier", "status bar tip"));
+ connect(export_config_action_, &QAction::triggered, qet_app, &QETApp::exportConfiguration);
+ ShortcutManager::instance().registerAction(export_config_action_, "mainwindow.export_configuration", tr("Général"), QKeySequence());
+
+ import_config_action_ = new QAction(QET::Icons::DocumentImport, tr("Charger une configuration..."), this);
+ import_config_action_ -> setStatusTip(tr("Remplace les réglages de QElectroTech par ceux d'un fichier, puis ferme QElectroTech", "status bar tip"));
+ connect(import_config_action_, &QAction::triggered, qet_app, &QETApp::importConfiguration);
+ ShortcutManager::instance().registerAction(import_config_action_, "mainwindow.import_configuration", tr("Général"), QKeySequence());
+
fullscreen_action_ = new QAction(this);
updateFullScreenAction();
connect(fullscreen_action_, &QAction::triggered, this, &QETMainWindow::toggleFullScreen);
@@ -170,6 +180,9 @@ void QETMainWindow::initCommonMenus()
settings_menu_ = new QMenu(tr("&Configuration", "window menu"), this);
settings_menu_ -> addAction(fullscreen_action_);
settings_menu_ -> addAction(configure_action_);
+ settings_menu_ -> addSeparator();
+ settings_menu_ -> addAction(export_config_action_);
+ settings_menu_ -> addAction(import_config_action_);
connect(settings_menu_, &QMenu::aboutToShow, this, &QETMainWindow::checkToolbarsmenu);
help_menu_ = new QMenu(tr("&Aide", "window menu"), this);
diff --git a/sources/qetmainwindow.h b/sources/qetmainwindow.h
index c3820c4bb..fe76fb7e0 100644
--- a/sources/qetmainwindow.h
+++ b/sources/qetmainwindow.h
@@ -56,6 +56,8 @@ class QETMainWindow : public QMainWindow {
// attributes
protected:
QAction *configure_action_; ///< Launch the QElectroTech configuration dialog
+ QAction *export_config_action_; ///< Save the settings to a file
+ QAction *import_config_action_; ///< Replace the settings with a saved file
QAction *fullscreen_action_; ///< Toggle full screen
QAction *whatsthis_action_; ///< Toggle "What's this" mode
QAction *about_qet_; ///< Launch the "About QElectroTech" dialog
diff --git a/sources/richtext/richtexteditor.cpp b/sources/richtext/richtexteditor.cpp
index 9c8b9b0a6..a982db81f 100644
--- a/sources/richtext/richtexteditor.cpp
+++ b/sources/richtext/richtexteditor.cpp
@@ -48,6 +48,7 @@
#include "richtexteditor_p.h"
#include "ui_addlinkdialog.h"
#include "../shortcutmanager.h"
+#include "../qet.h"
//#include
@@ -242,6 +243,7 @@ AddLinkDialog::AddLinkDialog(RichTextEditor *editor, QWidget *parent) :
m_ui(new Ui::AddLinkDialog)
{
m_ui->setupUi(this);
+ QET::trackDialogGeometry(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -862,6 +864,7 @@ RichTextEditorDialog::RichTextEditorDialog(QWidget *parent) :
m_editor->setFocus();
resize(600, 400);
+ QET::trackDialogGeometry(this);
}
RichTextEditorDialog::~RichTextEditorDialog()
diff --git a/sources/titleblock/dimensionwidget.cpp b/sources/titleblock/dimensionwidget.cpp
index 228b9ad57..6e2566c06 100644
--- a/sources/titleblock/dimensionwidget.cpp
+++ b/sources/titleblock/dimensionwidget.cpp
@@ -17,6 +17,8 @@
*/
#include "dimensionwidget.h"
+#include "../qet.h"
+
/**
Constructor
@param complete True for this dialog to show the radio buttons that allow
@@ -31,6 +33,7 @@ TitleBlockDimensionWidget::TitleBlockDimensionWidget(bool complete, QWidget *par
{
initWidgets();
initLayouts();
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/ui/aboutqetdialog.cpp b/sources/ui/aboutqetdialog.cpp
index afec0e258..37911e122 100644
--- a/sources/ui/aboutqetdialog.cpp
+++ b/sources/ui/aboutqetdialog.cpp
@@ -34,6 +34,7 @@ AboutQETDialog::AboutQETDialog(QWidget *parent) :
ui(new Ui::AboutQETDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
setAbout();
setAuthors();
setTranslators();
diff --git a/sources/ui/aiassistantdialog.cpp b/sources/ui/aiassistantdialog.cpp
index 6d8542d7e..7e0284bdd 100644
--- a/sources/ui/aiassistantdialog.cpp
+++ b/sources/ui/aiassistantdialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "aiassistantdialog.h"
+#include "../qet.h"
#include
#include
@@ -157,6 +158,8 @@ AiAssistantDialog::AiAssistantDialog(QWidget *parent) :
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
refresh();
+
+ QET::trackDialogGeometry(this);
}
void AiAssistantDialog::refresh()
diff --git a/sources/ui/alignmenttextdialog.cpp b/sources/ui/alignmenttextdialog.cpp
index e3331800f..f732d9177 100644
--- a/sources/ui/alignmenttextdialog.cpp
+++ b/sources/ui/alignmenttextdialog.cpp
@@ -20,11 +20,13 @@
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "ui_alignmenttextdialog.h"
+#include "../qet.h"
AlignmentTextDialog::AlignmentTextDialog(Qt::Alignment alignment, QWidget *parent) :
QDialog(parent),
ui(new Ui::AlignmentTextDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
if(alignment == (Qt::AlignTop|Qt::AlignLeft))
ui->top_left->setChecked(true);
diff --git a/sources/ui/bomexportdialog.cpp b/sources/ui/bomexportdialog.cpp
index da2be53ca..703b9e3c6 100644
--- a/sources/ui/bomexportdialog.cpp
+++ b/sources/ui/bomexportdialog.cpp
@@ -24,6 +24,7 @@
#include "../qetproject.h"
#include "ui_bomexportdialog.h"
+#include "../qet.h"
#include
#include
#include
@@ -40,6 +41,7 @@ BOMExportDialog::BOMExportDialog(QETProject *project, QWidget *parent) :
m_project(project)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_query_widget = new ElementQueryWidget(this);
ui->m_main_layout->insertWidget(0, m_query_widget);
diff --git a/sources/ui/compositetexteditdialog.cpp b/sources/ui/compositetexteditdialog.cpp
index 3e0deca34..ac4e9c89b 100644
--- a/sources/ui/compositetexteditdialog.cpp
+++ b/sources/ui/compositetexteditdialog.cpp
@@ -5,6 +5,7 @@
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "../qetgraphicsitem/element.h"
#include "../qetinformation.h"
+#include "../qet.h"
#include "ui_compositetexteditdialog.h"
#include
@@ -15,6 +16,7 @@ CompositeTextEditDialog::CompositeTextEditDialog(DynamicElementTextItem *text, Q
m_text(text)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_default_text = m_text->compositeText();
ui->m_plain_text_edit->setPlainText(m_default_text);
ui->m_plain_text_edit->setPlaceholderText(tr("Entrée votre texte composé ici, en vous aidant des variables disponible"));
@@ -31,6 +33,7 @@ CompositeTextEditDialog::CompositeTextEditDialog(QString text, bool report, QWid
ui(new Ui::CompositeTextEditDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_default_text = std::move(text);
ui->m_plain_text_edit->setPlainText(m_default_text);
ui->m_plain_text_edit->setPlaceholderText(tr("Entrée votre texte composé ici, en vous aidant des variables disponible"));
diff --git a/sources/ui/conductorpropertiesdialog.cpp b/sources/ui/conductorpropertiesdialog.cpp
index 329cf83a9..1973eb188 100644
--- a/sources/ui/conductorpropertiesdialog.cpp
+++ b/sources/ui/conductorpropertiesdialog.cpp
@@ -24,6 +24,7 @@
#include "conductorpropertieswidget.h"
#include "ui_conductorpropertiesdialog.h"
+#include "../qet.h"
/**
@brief ConductorPropertiesDialog::ConductorPropertiesDialog
Constructor
@@ -36,6 +37,7 @@ ConductorPropertiesDialog::ConductorPropertiesDialog(
ui(new Ui::ConductorPropertiesDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_cpw = new ConductorPropertiesWidget(conductor->properties());
m_cpw -> setHiddenOneTextPerFolio(true);
m_cpw->setHiddenAvailableAutonum(true);
diff --git a/sources/ui/contactgroupselectiondialog.cpp b/sources/ui/contactgroupselectiondialog.cpp
index d1c88a002..e681a79cb 100644
--- a/sources/ui/contactgroupselectiondialog.cpp
+++ b/sources/ui/contactgroupselectiondialog.cpp
@@ -17,6 +17,8 @@
*/
#include "contactgroupselectiondialog.h"
+#include "../qet.h"
+
#include
#include
#include
@@ -207,6 +209,8 @@ ContactGroupSelectionDialog::ContactGroupSelectionDialog(
accept();
}
});
+
+ QET::trackDialogGeometry(this);
}
int ContactGroupSelectionDialog::selectedIndex() const
diff --git a/sources/ui/diagrampropertiesdialog.cpp b/sources/ui/diagrampropertiesdialog.cpp
index e07de4a6a..75f05fa70 100644
--- a/sources/ui/diagrampropertiesdialog.cpp
+++ b/sources/ui/diagrampropertiesdialog.cpp
@@ -89,6 +89,8 @@ DiagramPropertiesDialog::DiagramPropertiesDialog(Diagram *diagram, QWidget *pare
vlayout.addLayout(glayout);
vlayout.addWidget(&boutons);
+ QET::trackDialogGeometry(this);
+
// if dialog is accepted
if (this -> exec() == QDialog::Accepted && !diagram_is_read_only)
{
diff --git a/sources/ui/dialogwaiting.cpp b/sources/ui/dialogwaiting.cpp
index b203d5d96..498504e1e 100644
--- a/sources/ui/dialogwaiting.cpp
+++ b/sources/ui/dialogwaiting.cpp
@@ -18,6 +18,7 @@
#include "dialogwaiting.h"
#include "ui_dialogwaiting.h"
+#include "../qet.h"
#include
@@ -31,6 +32,7 @@ DialogWaiting::DialogWaiting(QWidget *parent) :
ui(new Ui::DialogWaiting)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
setTitle( "..." );
setDetail( "..." );
}
diff --git a/sources/ui/duplicateoffsetdialog.cpp b/sources/ui/duplicateoffsetdialog.cpp
index 906bc97cd..86668d570 100644
--- a/sources/ui/duplicateoffsetdialog.cpp
+++ b/sources/ui/duplicateoffsetdialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "duplicateoffsetdialog.h"
+#include "../qet.h"
#include
#include
@@ -76,6 +77,8 @@ DuplicateOffsetDialog::DuplicateOffsetDialog(QWidget *parent) :
auto *layout = new QVBoxLayout(this);
layout->addLayout(form);
layout->addWidget(buttons);
+
+ QET::trackDialogGeometry(this);
}
QPoint DuplicateOffsetDialog::stepOffset() const
diff --git a/sources/ui/formulaassistantdialog.cpp b/sources/ui/formulaassistantdialog.cpp
index 228da7aff..0f921f10b 100644
--- a/sources/ui/formulaassistantdialog.cpp
+++ b/sources/ui/formulaassistantdialog.cpp
@@ -18,6 +18,7 @@
#include "formulaassistantdialog.h"
#include "ui_formulaassistantdialog.h"
+#include "../qet.h"
#include
FormulaAssistantDialog::FormulaAssistantDialog(QWidget *parent) :
@@ -25,6 +26,7 @@ FormulaAssistantDialog::FormulaAssistantDialog(QWidget *parent) :
ui(new Ui::FormulaAssistantDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
}
FormulaAssistantDialog::~FormulaAssistantDialog()
diff --git a/sources/ui/imagetransparentcolordialog.cpp b/sources/ui/imagetransparentcolordialog.cpp
index ed9f5362c..0318acbee 100644
--- a/sources/ui/imagetransparentcolordialog.cpp
+++ b/sources/ui/imagetransparentcolordialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "imagetransparentcolordialog.h"
+#include "../qet.h"
#include
#include
@@ -141,6 +142,8 @@ ImageTransparentColorDialog::ImageTransparentColorDialog(const QPixmap &basePixm
rebuildSwatches(); // shows existingColors immediately, if any
updatePreview(); // and the preview already reflects them too, rather than only appearing after the next pick
+
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/ui/importelementdialog.cpp b/sources/ui/importelementdialog.cpp
index 47d59220c..65f131f92 100644
--- a/sources/ui/importelementdialog.cpp
+++ b/sources/ui/importelementdialog.cpp
@@ -17,6 +17,7 @@
*/
#include "importelementdialog.h"
#include "ui_importelementdialog.h"
+#include "../qet.h"
#include
ImportElementDialog::ImportElementDialog(QWidget *parent) :
@@ -24,6 +25,7 @@ ImportElementDialog::ImportElementDialog(QWidget *parent) :
ui(new Ui::ImportElementDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
setUpWidget();
}
diff --git a/sources/ui/importelementtextpatterndialog.cpp b/sources/ui/importelementtextpatterndialog.cpp
index 4c4429d47..4581fa865 100644
--- a/sources/ui/importelementtextpatterndialog.cpp
+++ b/sources/ui/importelementtextpatterndialog.cpp
@@ -18,10 +18,12 @@
#include "importelementtextpatterndialog.h"
#include "ui_importelementtextpatterndialog.h"
+#include "../qet.h"
ImportElementTextPatternDialog::ImportElementTextPatternDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ImportElementTextPatternDialog) {
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
}
ImportElementTextPatternDialog::~ImportElementTextPatternDialog()
diff --git a/sources/ui/jumptoelementdialog.cpp b/sources/ui/jumptoelementdialog.cpp
index 65b2615b3..566a93305 100644
--- a/sources/ui/jumptoelementdialog.cpp
+++ b/sources/ui/jumptoelementdialog.cpp
@@ -20,6 +20,7 @@
#include "../diagram.h"
#include "../diagramview.h"
#include "../qetproject.h"
+#include "../qet.h"
#include "../qetgraphicsitem/element.h"
#include
@@ -59,6 +60,8 @@ JumpToElementDialog::JumpToElementDialog(Diagram *diagram, QWidget *parent) :
buildCandidates();
updateFilteredList(QString());
m_filter_edit->setFocus();
+
+ QET::trackDialogGeometry(this);
}
JumpToElementDialog::~JumpToElementDialog()
diff --git a/sources/ui/marginseditdialog.cpp b/sources/ui/marginseditdialog.cpp
index 65a8ac1a4..2177cb7d0 100644
--- a/sources/ui/marginseditdialog.cpp
+++ b/sources/ui/marginseditdialog.cpp
@@ -18,6 +18,7 @@
#include "marginseditdialog.h"
#include "ui_marginseditdialog.h"
+#include "../qet.h"
#include
MarginsEditDialog::MarginsEditDialog(QMargins margins, QWidget *parent) :
@@ -25,6 +26,7 @@ MarginsEditDialog::MarginsEditDialog(QMargins margins, QWidget *parent) :
ui(new Ui::MarginsEditDialog)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
ui->m_top_sb->setValue(margins.top());
ui->m_left_sb->setValue(margins.left());
ui->m_right_sb->setValue(margins.right());
diff --git a/sources/ui/masterpropertieswidget.cpp b/sources/ui/masterpropertieswidget.cpp
index 94d56e145..5d1d5a1ac 100644
--- a/sources/ui/masterpropertieswidget.cpp
+++ b/sources/ui/masterpropertieswidget.cpp
@@ -605,7 +605,6 @@ void MasterPropertiesWidget::updateUi()
// Type combo (read-only — type is defined in the editor)
auto *type_cb = new QComboBox(m_plc_table);
type_cb->setEnabled(false);
- type_cb->setStyleSheet("QComboBox { background-color: #f0f0f0; }");
QStringList plc_types = ElementData::plcIOTypeList();
for (int t = 0; t < plc_types.size(); ++t) {
type_cb->addItem(plc_types.at(t), t);
diff --git a/sources/ui/multipastedialog.cpp b/sources/ui/multipastedialog.cpp
index 953b5bccd..c4c8dda4f 100644
--- a/sources/ui/multipastedialog.cpp
+++ b/sources/ui/multipastedialog.cpp
@@ -26,6 +26,7 @@
#include "../qetgraphicsitem/conductor.h"
#include "../ui_multipastedialog.h"
+#include "../qet.h"
#include
#include
@@ -35,6 +36,7 @@ MultiPasteDialog::MultiPasteDialog(Diagram *diagram, QWidget *parent) :
m_diagram(diagram)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
connect(ui->m_x_sb, static_cast(&QSpinBox::valueChanged), this, &MultiPasteDialog::updatePreview);
connect(ui->m_y_sb, static_cast(&QSpinBox::valueChanged), this, &MultiPasteDialog::updatePreview);
diff --git a/sources/ui/pdfpagesdialog.cpp b/sources/ui/pdfpagesdialog.cpp
index 00a460862..586887b40 100644
--- a/sources/ui/pdfpagesdialog.cpp
+++ b/sources/ui/pdfpagesdialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "pdfpagesdialog.h"
+#include "../qet.h"
// Whole file is a no-op unless QtPdf is available (see pdfpagesdialog.h).
#ifdef QET_HAS_QTPDF
@@ -125,6 +126,8 @@ PdfPagesDialog::PdfPagesDialog(QPdfDocument &document, QWidget *parent)
// Render initial preview
updatePreview();
+
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/ui/potentialselectordialog.cpp b/sources/ui/potentialselectordialog.cpp
index 2a39252c7..7122676a0 100644
--- a/sources/ui/potentialselectordialog.cpp
+++ b/sources/ui/potentialselectordialog.cpp
@@ -19,6 +19,7 @@
#include "potentialselectordialog.h"
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
+#include "../qet.h"
#include "../autoNum/assignvariables.h"
#include "../diagram.h"
#include "../qetgraphicsitem/conductor.h"
@@ -270,6 +271,7 @@ PotentialSelectorDialog::PotentialSelectorDialog(Conductor *conductor,
m_parent_undo(parent_undo)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_potential_selector = new NewConductorPotentialSelector(conductor);
buildWidget();
}
@@ -292,6 +294,7 @@ PotentialSelectorDialog::PotentialSelectorDialog(Element *report,
m_parent_undo(parent_undo)
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
m_potential_selector = new LinkReportPotentialSelector(report);
buildWidget();
}
diff --git a/sources/ui/terminalnumberingdialog.cpp b/sources/ui/terminalnumberingdialog.cpp
index 85f299214..fde1df6da 100644
--- a/sources/ui/terminalnumberingdialog.cpp
+++ b/sources/ui/terminalnumberingdialog.cpp
@@ -5,6 +5,7 @@
#include "../diagram.h"
#include "../qetgraphicsitem/element.h"
#include "../undocommand/changeelementinformationcommand.h"
+#include "../qet.h"
#include
#include
#include
@@ -67,6 +68,8 @@ TerminalNumberingDialog::TerminalNumberingDialog(QWidget *parent, QETProject *pr
m_stripCheckboxes.insert(prefix, cb);
}
}
+
+ QET::trackDialogGeometry(this);
}
/**
diff --git a/sources/ui/thirdpartybinaryinstalldialog.cpp b/sources/ui/thirdpartybinaryinstalldialog.cpp
index 80754a269..0eb82fc07 100644
--- a/sources/ui/thirdpartybinaryinstalldialog.cpp
+++ b/sources/ui/thirdpartybinaryinstalldialog.cpp
@@ -18,6 +18,7 @@
#include "thirdpartybinaryinstalldialog.h"
#include "ui_thirdpartybinaryinstalldialog.h"
+#include "../qet.h"
#include
#include
#include
@@ -32,6 +33,7 @@ ThirdPartyBinaryInstallDialog::ThirdPartyBinaryInstallDialog(const QString &text
ui{new Ui::ThirdPartyBinaryInstallDialog}
{
ui->setupUi(this);
+ QET::trackDialogGeometry(this);
ui->m_label->setText(text);
connect(ui->m_download_pb, &QPushButton::clicked, [downloadLink](){
diff --git a/sources/ui/wiringlistdialog.cpp b/sources/ui/wiringlistdialog.cpp
index a28f2425e..da2e7746c 100644
--- a/sources/ui/wiringlistdialog.cpp
+++ b/sources/ui/wiringlistdialog.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "wiringlistdialog.h"
+#include "../qet.h"
#include "../dataBase/projectdatabase.h"
#include "../qetproject.h"
@@ -111,4 +112,6 @@ WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) :
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttons);
+
+ QET::trackDialogGeometry(this);
}
diff --git a/sources/utils/configprofile.h b/sources/utils/configprofile.h
new file mode 100644
index 000000000..b235bc507
--- /dev/null
+++ b/sources/utils/configprofile.h
@@ -0,0 +1,98 @@
+/*
+ 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
+
+/**
+ 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"), and
+ the recent-files lists ("...-recentfiles/..."). They are not written to
+ the file, and loading a profile keeps the current ones.
+*/
+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.
+ inline const QString marker_key{QStringLiteral("qelectrotech-configuration/format")};
+
+ /// 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;
+ }
+
+ /// True if @a file was exported by exportTo().
+ inline bool isProfile(const QSettings &file)
+ {
+ return file.value(marker_key).toInt() == 1;
+ }
+
+ /// 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)
+ {
+ file.clear();
+ int written = 0;
+ const QStringList keys = live.allKeys();
+ for (const QString &key : keys) {
+ if (isLocalKey(key)) {
+ continue;
+ }
+ file.setValue(key, live.value(key));
+ ++written;
+ }
+ file.setValue(marker_key, 1);
+ 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.
+ inline void importFrom(const QSettings &file, QSettings &live)
+ {
+ const QStringList live_keys = live.allKeys();
+ for (const QString &key : live_keys) {
+ if (!isLocalKey(key)) {
+ live.remove(key);
+ }
+ }
+ const QStringList file_keys = file.allKeys();
+ for (const QString &key : file_keys) {
+ if (!isLocalKey(key)) {
+ live.setValue(key, file.value(key));
+ }
+ }
+ live.sync();
+ }
+}
+
+#endif // CONFIGPROFILE_H
diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt
index c8c159b15..29afaa992 100644
--- a/tests/qttest/CMakeLists.txt
+++ b/tests/qttest/CMakeLists.txt
@@ -111,6 +111,13 @@ add_test(NAME tst_alignment COMMAND tst_alignment)
target_include_directories(tst_alignment PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_alignment 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)
+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)
+
# itemgroups.cpp keeps group membership and the selection rule apart from
# Diagram, so it is tested here on a plain scene of rectangles.
add_executable(tst_itemgroups tst_itemgroups.cpp ${QET_DIR}/sources/itemgroups.cpp)
diff --git a/tests/qttest/tst_configprofile.cpp b/tests/qttest/tst_configprofile.cpp
new file mode 100644
index 000000000..e005e6f8d
--- /dev/null
+++ b/tests/qttest/tst_configprofile.cpp
@@ -0,0 +1,100 @@
+#include
+#include
+
+#include "utils/configprofile.h"
+
+class tst_configprofile : public QObject
+{
+ Q_OBJECT
+
+ QTemporaryDir m_dir;
+
+ QString path(const QString &name) const { return m_dir.filePath(name); }
+
+private slots:
+ void localKeys()
+ {
+ QVERIFY(ConfigProfile::isLocalKey("diagrameditor/geometry"));
+ QVERIFY(ConfigProfile::isLocalKey("elementeditor/state"));
+ QVERIFY(ConfigProfile::isLocalKey("projects-recentfiles/file1"));
+ QVERIFY(ConfigProfile::isLocalKey(ConfigProfile::marker_key));
+ QVERIFY(!ConfigProfile::isLocalKey("diagrameditor/Xgrid"));
+ QVERIFY(!ConfigProfile::isLocalKey("shortcuts/mainwindow.fullscreen"));
+ QVERIFY(!ConfigProfile::isLocalKey("masterpropertieswidget/plc-table-header-state"));
+ }
+
+ // Export leaves out window layout and recent files, and marks the file.
+ void exportLeavesOutLocalKeys()
+ {
+ QSettings live(path("live1.conf"), QSettings::IniFormat);
+ live.setValue("diagrameditor/Xgrid", 7);
+ live.setValue("lang", "de");
+ live.setValue("diagrameditor/geometry", QByteArray("xyz"));
+ live.setValue("projects-recentfiles/file1", "/home/a/b.qet");
+
+ QSettings file(path("profile1.conf"), QSettings::IniFormat);
+ QCOMPARE(ConfigProfile::exportTo(live, file), 2);
+ QVERIFY(ConfigProfile::isProfile(file));
+ QCOMPARE(file.value("diagrameditor/Xgrid").toInt(), 7);
+ QVERIFY(!file.contains("diagrameditor/geometry"));
+ QVERIFY(!file.contains("projects-recentfiles/file1"));
+ }
+
+ // Import replaces every setting, removes the ones the profile does not
+ // have, keeps window layout and recent files, and never copies the marker.
+ void importReplacesButKeepsLocal()
+ {
+ QSettings file(path("profile2.conf"), QSettings::IniFormat);
+ file.setValue("diagrameditor/Xgrid", 5);
+ file.setValue("diagrameditor/geometry", QByteArray("from-file"));
+ file.setValue(ConfigProfile::marker_key, 1);
+
+ QSettings live(path("live2.conf"), QSettings::IniFormat);
+ live.setValue("diagrameditor/Xgrid", 10);
+ live.setValue("lang", "fr");
+ live.setValue("diagrameditor/geometry", QByteArray("mine"));
+ live.setValue("projects-recentfiles/file1", "/home/a/b.qet");
+
+ ConfigProfile::importFrom(file, live);
+ QCOMPARE(live.value("diagrameditor/Xgrid").toInt(), 5);
+ QVERIFY(!live.contains("lang"));
+ QCOMPARE(live.value("diagrameditor/geometry").toByteArray(), QByteArray("mine"));
+ QCOMPARE(live.value("projects-recentfiles/file1").toString(), QString("/home/a/b.qet"));
+ QVERIFY(!live.contains(ConfigProfile::marker_key));
+ }
+
+ // Values survive the round trip with their types, including arrays.
+ void roundTrip()
+ {
+ QSettings a(path("a.conf"), QSettings::IniFormat);
+ a.setValue("print/default/fitinpage", true);
+ a.setValue("diagrameditor/sheet_background_color", QColor(12, 34, 56));
+ a.beginWriteArray("diagrameditor/defaultguides", 2);
+ a.setArrayIndex(0); a.setValue("pos", 100);
+ a.setArrayIndex(1); a.setValue("pos", 250);
+ a.endArray();
+
+ QSettings file(path("profile3.conf"), QSettings::IniFormat);
+ ConfigProfile::exportTo(a, file);
+ QSettings b(path("b.conf"), QSettings::IniFormat);
+ b.setValue("stray", 1);
+ ConfigProfile::importFrom(file, b);
+
+ QStringList ka = a.allKeys(), kb = b.allKeys();
+ ka.sort(); kb.sort();
+ QCOMPARE(kb, ka);
+ for (const QString &k : std::as_const(ka))
+ QCOMPARE(b.value(k), a.value(k));
+ }
+
+ void plainFileIsNotAProfile()
+ {
+ QSettings other(path("other.conf"), QSettings::IniFormat);
+ other.setValue("some/key", 1);
+ QVERIFY(!ConfigProfile::isProfile(other));
+ }
+};
+
+QTEST_GUILESS_MAIN(tst_configprofile)
+
+#include "tst_configprofile.moc"