mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 06:34:14 +02:00
Keep wires attached when a project's copy of a symbol is replaced
A wire whose terminals have uuids is saved against them, and on load it is reattached to a terminal with that uuid or dropped, with only a qDebug line. Re-importing a changed symbol and choosing "replace" swapped the project's definition for one whose terminal uuids differ; the placed symbols kept the old ones until the project was reopened, so every wire on them was lost at the next open, silently, and gone for good at the next save. - XmlElementCollection::copyElement(), where an embedded definition is overwritten, carries each old terminal uuid onto the new terminal at the same place and orientation (TerminalUuids::keep()). Terminals that moved, and new ones, keep their own. - Diagram::fromXml() records wires it could not reattach, logs them, and the editor lists them in one warning after opening a project. Measured on 2612_ats_singlephase.qet with the stored splice's terminal uuids made to differ from the collection's: replace, save, reopen loads 34 of 131 wires on master, 131 with this change (GUI, both arms). tst_terminaluuids covers keep() and runs the real loader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "terminaluuids.h"
|
||||
|
||||
#include <QDomElement>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@return the <terminal> of the definition held by the embedded
|
||||
collection element @p collection_element
|
||||
*/
|
||||
QList<QDomElement> terminalsOf(const QDomElement &collection_element)
|
||||
{
|
||||
QList<QDomElement> terminals;
|
||||
const QDomElement description = collection_element
|
||||
.firstChildElement(QStringLiteral("definition"))
|
||||
.firstChildElement(QStringLiteral("description"));
|
||||
for (QDomElement t = description.firstChildElement(QStringLiteral("terminal"));
|
||||
!t.isNull();
|
||||
t = t.nextSiblingElement(QStringLiteral("terminal"))) {
|
||||
terminals << t;
|
||||
}
|
||||
return terminals;
|
||||
}
|
||||
|
||||
//Place and orientation, "10" and "10.0" being the same place
|
||||
QString terminalPlace(const QDomElement &terminal)
|
||||
{
|
||||
return QStringLiteral("%1|%2|%3")
|
||||
.arg(QString::number(terminal.attribute(QStringLiteral("x")).toDouble()),
|
||||
QString::number(terminal.attribute(QStringLiteral("y")).toDouble()),
|
||||
terminal.attribute(QStringLiteral("orientation")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief TerminalUuids::keep
|
||||
Give each terminal of @p new_element the uuid of the terminal of
|
||||
@p old_element at the same place and orientation. Both are <element>
|
||||
of an embedded collection.
|
||||
|
||||
A wire is saved against the uuids of the terminals it joins, and on
|
||||
loading it is reattached to a terminal with that uuid or not at all.
|
||||
The symbols already on the folios keep the terminals of the definition
|
||||
they were built from, so when the project's definition is replaced by
|
||||
one whose terminal uuids differ -- another copy of the same symbol, or
|
||||
one saved by a different version of the collection -- every wire on
|
||||
those symbols was lost the next time the project was opened.
|
||||
|
||||
Terminals that moved, and new ones, keep their own uuid. Where the old
|
||||
definition has two terminals at one place, they are matched in order.
|
||||
*/
|
||||
void TerminalUuids::keep(const QDomElement &old_element, QDomElement &new_element)
|
||||
{
|
||||
QHash<QString, QStringList> old_uuids;
|
||||
for (const QDomElement &t : terminalsOf(old_element)) {
|
||||
const QString uuid = t.attribute(QStringLiteral("uuid"));
|
||||
if (!uuid.isEmpty()) {
|
||||
old_uuids[terminalPlace(t)] << uuid;
|
||||
}
|
||||
}
|
||||
if (old_uuids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (QDomElement t : terminalsOf(new_element)) {
|
||||
QStringList &uuids = old_uuids[terminalPlace(t)];
|
||||
if (!uuids.isEmpty()) {
|
||||
t.setAttribute(QStringLiteral("uuid"), uuids.takeFirst());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef TERMINALUUIDS_H
|
||||
#define TERMINALUUIDS_H
|
||||
|
||||
class QDomElement;
|
||||
|
||||
namespace TerminalUuids
|
||||
{
|
||||
void keep(const QDomElement &old_element, QDomElement &new_element);
|
||||
}
|
||||
|
||||
#endif // TERMINALUUIDS_H
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "../qetproject.h"
|
||||
#include "../qetxml.h"
|
||||
#include "elementslocation.h"
|
||||
#include "terminaluuids.h"
|
||||
|
||||
/**
|
||||
@brief XmlElementCollection::XmlElementCollection
|
||||
@@ -914,6 +915,7 @@ ElementsLocation XmlElementCollection::copyElement(
|
||||
% "/" % new_elmt_name);
|
||||
bool removed = false;
|
||||
if (!element.isNull()) {
|
||||
TerminalUuids::keep(element, elmt_dom);
|
||||
element.parentNode().removeChild(element);
|
||||
removed = true;
|
||||
}
|
||||
|
||||
@@ -773,6 +773,18 @@ QUuid Diagram::uuid()
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::wiresNotReconnected
|
||||
@return one line per wire of the loaded file that was left out because
|
||||
a terminal it joins could not be found, for example after the symbol's
|
||||
definition in the project was replaced by one whose terminals differ.
|
||||
Empty for a folio that loaded every wire.
|
||||
*/
|
||||
QStringList Diagram::wiresNotReconnected() const
|
||||
{
|
||||
return m_wires_not_reconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Diagram::uuidUsedByOtherDiagram
|
||||
A hand-edited or merged project file can contain two folios with the same
|
||||
@@ -1837,6 +1849,8 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
}
|
||||
|
||||
// Load conductor
|
||||
if (consider_informations)
|
||||
m_wires_not_reconnected.clear();
|
||||
QList<Conductor *> added_conductors;
|
||||
for (auto f : QET::findInDomElement(root,
|
||||
QStringLiteral("conductors"),
|
||||
@@ -1849,6 +1863,33 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
Terminal* p1 = findTerminal(1, f, table_adr_id, added_elements);
|
||||
Terminal* p2 = findTerminal(2, f, table_adr_id, added_elements);
|
||||
|
||||
//Keep a trace of the wire, it will be missing from the next save
|
||||
if ((!p1 || !p2) && consider_informations)
|
||||
{
|
||||
//The symbol's label, else its name. For an end not found,
|
||||
//only the uuid form of a wire says which symbol it is on.
|
||||
auto end_label = [&f, &added_elements](const QString &index,
|
||||
Terminal *found) {
|
||||
Element *element = found ? found->parentElement() : nullptr;
|
||||
const QUuid uuid(f.attribute(QStringLiteral("element") + index));
|
||||
for (int i = 0 ; !element && !uuid.isNull()
|
||||
&& i < added_elements.size() ; ++i) {
|
||||
if (added_elements.at(i)->uuid() == uuid)
|
||||
element = added_elements.at(i);
|
||||
}
|
||||
if (!element)
|
||||
return QStringLiteral("?");
|
||||
const QString label = element->actualLabel();
|
||||
return label.isEmpty() ? element->name() : label;
|
||||
};
|
||||
QString wire = QStringLiteral("%1 - %2").arg(end_label(QStringLiteral("1"), p1),
|
||||
end_label(QStringLiteral("2"), p2));
|
||||
const QString num = f.attribute(QStringLiteral("num"));
|
||||
if (!num.isEmpty() && num != QLatin1String("_"))
|
||||
wire += QStringLiteral(" (%1)").arg(num);
|
||||
m_wires_not_reconnected << wire;
|
||||
}
|
||||
|
||||
if (p1 && p2 && p1 != p2)
|
||||
{
|
||||
Conductor *c = new Conductor(p1, p2);
|
||||
@@ -1884,6 +1925,12 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
delete c;
|
||||
}
|
||||
}
|
||||
if (!m_wires_not_reconnected.isEmpty()) {
|
||||
qWarning().noquote() << "Diagram::fromXml():"
|
||||
<< m_wires_not_reconnected.size()
|
||||
<< "wire(s) not loaded, a terminal they join was not found:"
|
||||
<< m_wires_not_reconnected.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
//Filling of falculatory lists
|
||||
if (content_ptr) {
|
||||
|
||||
+5
-1
@@ -148,7 +148,10 @@ class Diagram : public QGraphicsScene
|
||||
|
||||
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
|
||||
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
|
||||
|
||||
|
||||
//Wires of the loaded file whose ends could not be found
|
||||
QStringList m_wires_not_reconnected;
|
||||
|
||||
// METHODS
|
||||
protected:
|
||||
void drawBackground(QPainter *, const QRectF &) override;
|
||||
@@ -171,6 +174,7 @@ class Diagram : public QGraphicsScene
|
||||
void correctTextPos(Element* elmt);
|
||||
void restoreText(Element* elmt);
|
||||
QUuid uuid();
|
||||
QStringList wiresNotReconnected() const;
|
||||
void setEventInterface (DiagramEventInterface *event_interface);
|
||||
void clearEventInterface();
|
||||
|
||||
|
||||
@@ -1672,6 +1672,34 @@ bool QETDiagramEditor::openAndAddProject(
|
||||
);
|
||||
}
|
||||
|
||||
//Report wires left out of the load because a terminal they join
|
||||
//was not found: they would otherwise vanish on the next save
|
||||
//without the user ever being told.
|
||||
QStringList lost_wires;
|
||||
for (Diagram *diagram : project->diagrams()) {
|
||||
for (const QString &wire : diagram->wiresNotReconnected()) {
|
||||
lost_wires << tr("Folio %1 : %2").arg(diagram->folioIndex() + 1).arg(wire);
|
||||
}
|
||||
}
|
||||
if (interactive && !lost_wires.isEmpty())
|
||||
{
|
||||
QMessageBox box(QMessageBox::Warning,
|
||||
tr("Conducteurs non chargés", "message box title"),
|
||||
tr("%n conducteur(s) n'ont pas pu être reliés à leurs"
|
||||
" bornes et n'ont pas été chargés. La définition de"
|
||||
" l'élément dans le projet a probablement été remplacée"
|
||||
" par une autre dont les bornes diffèrent.\n\n"
|
||||
"Si vous enregistrez le projet, ces conducteurs"
|
||||
" disparaîtront du fichier. Fermez-le sans enregistrer pour"
|
||||
" conserver le fichier tel quel.",
|
||||
"message box content",
|
||||
lost_wires.size()),
|
||||
QMessageBox::Ok,
|
||||
this);
|
||||
box.setDetailedText(lost_wires.join(QLatin1Char('\n')));
|
||||
box.exec();
|
||||
}
|
||||
|
||||
BackupDialog backup_dialog(this);
|
||||
if (backup_dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user