Add a terminal name check when saving an element

Discussion #1157. IEC 61666 §4.1 requires each terminal to be
identified unambiguously within its object, so two terminals of one
element must not share a name.

On save, the element editor now:
- refuses to save when two terminals share a name, lists the names
  ("N ×3") and selects those terminals;
- warns, and still saves, when a terminal has no name. Folio reports,
  conductor definitions and thumbnails are skipped.

Names are compared after trimming surrounding spaces, case sensitive.

Settings > General > Editor has a new checkbox, on by default, that
turns both checks off (elementeditor/check-terminal-names).

--check-elements applies the same rule: repeated names are a FAIL,
missing names a WARN. It ignores the setting, since it is an explicit
check. On the shipped collection this reports 82 FAILs, the elements
that repeat a terminal name today.

The rule lives in the header-only editor/terminalnamecheck.h, shared
by both, and is unit tested by tst_terminalnamecheck.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-30 07:24:55 +13:00
parent f15736bff7
commit bd4d942d71
7 changed files with 242 additions and 1 deletions
+48
View File
@@ -40,6 +40,7 @@
#include "../esevent/eseventadddynamictextfield.h"
#include "../../elementdialog.h"
#include "../graphicspart/partterminal.h"
#include "../terminalnamecheck.h"
#include "../arceditor.h"
#include "ellipseeditor.h"
#include "lineeditor.h"
@@ -835,6 +836,53 @@ bool QETElementEditor::checkElement()
}
}
// Check terminal names: repeated names are an error, missing names a warning
if (QSettings().value(TerminalNameCheck::settings_key, true).toBool())
{
QList<PartTerminal *> terminals;
QStringList names;
for (auto qgi : m_elmt_scene -> items()) {
if (auto terminal = qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminals << terminal;
names << terminal -> terminalName();
}
}
const auto repeated = TerminalNameCheck::repeatedNames(names);
if (!repeated.isEmpty())
{
errors << qMakePair (tr("Noms de bornes en double"),
tr("<br><b>Erreur</b> :"
"<br>Plusieurs bornes portent le même nom : %1."
"<br><b>Solution</b> :"
"<br>Donner un nom unique à chaque borne, par exemple N.1 et N.2."
" Les bornes concernées sont sélectionnées.")
.arg(TerminalNameCheck::describe(repeated).toHtmlEscaped()));
m_elmt_scene -> clearSelection();
for (auto terminal : terminals) {
for (const auto &entry : repeated) {
if (terminal -> terminalName().trimmed() == entry.first) {
terminal -> setSelected(true);
}
}
}
}
const int unnamed = TerminalNameCheck::unnamedCount(names);
if (unnamed &&
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition &&
m_elmt_scene->elementData().m_type != ElementData::Thumbnail)
{
warnings << qMakePair (tr("Bornes sans nom"),
tr("<br>%n borne(s) sans nom. Sans noms de bornes uniques,"
" la liste de câblage (qui relie quoi à quoi) ne peut pas"
" désigner chaque borne, et ne peut donc pas servir à"
" câbler l'armoire en atelier.", "", unnamed));
}
}
if (!errors.count() && !warnings.count()) {
return(true);
}