From dfec60a3a53942d4b5c8a154d407ab9582df4606 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 09:40:02 +0200 Subject: [PATCH 1/8] Draw a terminal's label and its number apart, when asked to A terminal's label is written as it is read off the plan, "X1:1", and the symbol draws it as one field. On a long strip that field is long enough to reach into the next terminal, and a user who wants the number next to the screw and the label out of the way has no way to say so. Settings > General > Appearance now has a "Display of the terminal labelling" group with two choices: the label and its terminal number in one field, the way it has always been drawn, or the two split. The first is what every drawing gets, so nothing moves until the user asks for it, and choosing the other one redraws the drawings already open -- nothing needs to be closed and opened again. A symbol made for the split carries a text field bound to the new "Terminal number" information (in the element editor's list, next to the label, and as the %{terminal_number} variable of a composite text): the label field then shows "X1" and the number field "1", each placed where the symbol wants it, and hideable when it must be. A symbol which has no such field -- every symbol drawn until now -- gets the number on its own line inside the label field, so switching the setting on can never make a number vanish, and never needs a symbol to be touched first. A field which becomes, or stops being, the number field tells the label fields of its symbol to draw themselves again. Only the drawing reacts. What the element stores, the numbering, the sorting in the terminal strip editor, the automatic strip import and every export keep the whole label: the split is read where the text is drawn (Element::labelForDisplay() and friends), not where the label is made. The new test covers both choices, with and without the number field, a label with no colon at all, and the way back to the whole label. The full test suite passes (124/124). --- lang/qet_de.ts | 35 +++ .../dynamicelementtextitem.cpp | 100 ++++++-- sources/qetgraphicsitem/element.cpp | 134 +++++++++++ sources/qetgraphicsitem/element.h | 18 ++ sources/qetinformation.cpp | 2 + sources/qetinformation.h | 4 + .../configpage/generalconfigurationpage.cpp | 19 ++ .../ui/configpage/generalconfigurationpage.ui | 42 ++++ sources/utils/qetsettings.cpp | 24 ++ sources/utils/qetsettings.h | 14 ++ tests/qttest/CMakeLists.txt | 7 + tests/qttest/tst_terminallabeldisplay.cpp | 221 ++++++++++++++++++ 12 files changed, 599 insertions(+), 21 deletions(-) create mode 100644 tests/qttest/tst_terminallabeldisplay.cpp diff --git a/lang/qet_de.ts b/lang/qet_de.ts index 747912959..f63cd5473 100644 --- a/lang/qet_de.ts +++ b/lang/qet_de.ts @@ -6942,6 +6942,36 @@ Texte und Zahlen sind ebenso erlaubt. Use gestures touchpad Gesten vom Touchpad aktivieren + + + Display of the terminal labelling + Darstellung der Klemmenbeschriftung + + + + A terminal's label (X1:1) is drawn as a whole, the way it has always been. + Die BMK einer Klemme (X1:1) wird ganz gezeichnet, wie bisher. + + + + Show the label and the terminal number in one field + BMK (Label) und Anschlussnummer in einem Feld anzeigen + + + + The label (X1) is drawn in one field and the terminal number (1) in another, which a terminal symbol may place where it wants -- and hide when it must. A symbol without such a field shows the two, one under the other. + Die BMK (X1) wird in einem Feld und die Anschlussnummer (1) in einem weiteren Feld gezeichnet, das ein Klemmensymbol platzieren kann, wo es will -- und ausblenden, wenn es muss. Ein Symbol ohne ein solches Feld zeigt beides untereinander. + + + + Split the label and the terminal number + BMK und Anschlussnummer auftrennen + + + + Only the drawing changes: what is written in the element, the numbering, the sorting and the exports keep the whole label. + Es ändert sich nur die Darstellung: Was im Bauteil steht, die Nummerierung, die Sortierung und die Exporte behalten das ganze Label. + Use sheet numbers instead of their position in the project @@ -16677,6 +16707,11 @@ Möchten Sie sie ersetzen? Label Betriebsmittelkennzeichen (BMK) + + + Terminal number + Anschlussnummer + PLC Table diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index e07d72294..ffb1535dd 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -46,6 +46,20 @@ #include #include +namespace { + /** + A composite text which uses the terminal number follows the label + as closely as one which uses %{label}: both are drawn from the + label of the element, and from the formula that label is made of. + */ + bool usesLabelVariables(const QString &composite_text) + { + return composite_text.contains(QLatin1String("%{label}")) + || composite_text.contains( + QStringLiteral("%{") + QETInformation::ELMT_TERMINAL_NUMBER + QLatin1String("}")); + } +} + void DynamicElementTextItem::keyPressEvent(QKeyEvent *event) { if (!event->matches(QKeySequence::Paste) || m_text_from != UserText || @@ -401,8 +415,10 @@ Element *DynamicElementTextItem::elementUseForInfo() const */ void DynamicElementTextItem::refreshLabelConnection() { - if ((m_text_from == ElementInfo && m_info_name == "label") || - (m_text_from == CompositeText && m_composite_text.contains("%{label}"))) + if ((m_text_from == ElementInfo && + (m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) || + (m_text_from == CompositeText && usesLabelVariables(m_composite_text))) { if((m_parent_element.data()->linkType() & Element::AllReport) || m_parent_element.data()->linkType() == Element::ConductorDefinition) @@ -450,26 +466,34 @@ void DynamicElementTextItem::setTextFrom(DynamicElementTextItem::TextFrom text_f } else if (m_text_from == ElementInfo && elementUseForInfo()) { - if(m_info_name == "label") + if(m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER) { setupFormulaConnection(); updateLabel(); } else - setPlainText(elementUseForInfo()->elementInformations().value(m_info_name).toString()); + setPlainText(elementUseForInfo()->infoForDisplay(m_info_name)); if(old_text_from == UserText) connect(elementUseForInfo(), &Element::elementInfoChange, this, &DynamicElementTextItem::elementInfoChanged); } else if (m_text_from == CompositeText && elementUseForInfo()) { - if(m_composite_text.contains("%{label}")) + if(usesLabelVariables(m_composite_text)) { setupFormulaConnection(); updateLabel(); } else - setPlainText(autonum::AssignVariables::replaceVariable(m_composite_text, elementUseForInfo()->elementInformations())); + { + //The label and the terminal number are drawn the way the + //settings say, every other information as it is stored + DiagramContext dc = elementUseForInfo()->elementInformations(); + dc.addValue(QETInformation::ELMT_LABEL, elementUseForInfo()->labelForDisplay()); + dc.addValue(QETInformation::ELMT_TERMINAL_NUMBER, elementUseForInfo()->terminalNumberForDisplay()); + setPlainText(autonum::AssignVariables::replaceVariable(m_composite_text, dc)); + } if(old_text_from == UserText) connect(elementUseForInfo(), &Element::elementInfoChange, this, &DynamicElementTextItem::elementInfoChanged); @@ -478,7 +502,16 @@ void DynamicElementTextItem::setTextFrom(DynamicElementTextItem::TextFrom text_f if(m_parent_element.data()->linkType() == Element::Master || m_parent_element.data()->linkType() == Element::Slave) updateXref(); - + + //A field which becomes, or stops being, the field for the + //terminal number changes what the label of the symbol leaves + //to it (@sa Element::labelForDisplay) + if (m_info_name == QETInformation::ELMT_TERMINAL_NUMBER + && (m_text_from == ElementInfo || old_text_from == ElementInfo) + && parentElement()) { + parentElement()->refreshLabelFields(); + } + emit textFromChanged(m_text_from); } @@ -515,7 +548,8 @@ void DynamicElementTextItem::setInfoName(const QString &info_name) m_info_name = info_name; - if(old_info_name == "label") + if(old_info_name == QLatin1String("label") + || old_info_name == QETInformation::ELMT_TERMINAL_NUMBER) { clearFormulaConnection(); updateXref(); @@ -538,14 +572,25 @@ void DynamicElementTextItem::setInfoName(const QString &info_name) conductorPropertiesChanged(); } } - else if (m_info_name == "label" && elementUseForInfo()) + else if ((m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER) + && elementUseForInfo()) { setupFormulaConnection(); updateLabel(); updateXref(); } else if(elementUseForInfo() && m_text_from == DynamicElementTextItem::ElementInfo) { - setPlainText(elementUseForInfo()->elementInformations().value(info_name).toString()); + setPlainText(elementUseForInfo()->infoForDisplay(info_name)); + } + + //A field which becomes, or stops being, the field for the + //terminal number changes what the label of the symbol leaves + //to it (@sa Element::labelForDisplay) + if (parentElement() + && (info_name == QETInformation::ELMT_TERMINAL_NUMBER + || old_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) { + parentElement()->refreshLabelFields(); } emit infoNameChanged(info_name); @@ -1174,6 +1219,12 @@ void DynamicElementTextItem::elementInfoChanged() Element *element = elementUseForInfo(); if(element) { dc = element->elementInformations(); + //The label and the terminal number are drawn the way the + //settings say (@sa QetSettings::terminalLabelSplit): a + //composite text which uses them must see the same values the + //fields bound to them show + dc.addValue(QETInformation::ELMT_LABEL, element->labelForDisplay()); + dc.addValue(QETInformation::ELMT_TERMINAL_NUMBER, element->terminalNumberForDisplay()); } QString final_text; @@ -1183,12 +1234,13 @@ void DynamicElementTextItem::elementInfoChanged() { //If the info is the label, then we must make some connection //if the label is created from a formula - if(m_info_name == "label") + if(m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER) { setupFormulaConnection(); if (element) { - final_text = element->actualLabel(); + final_text = element->infoForDisplay(m_info_name); } } else { @@ -1199,7 +1251,7 @@ void DynamicElementTextItem::elementInfoChanged() { //If the composite have the label variable, we must make some //connection if the label is created from a formula - if (m_composite_text.contains("%{label}")) + if (usesLabelVariables(m_composite_text)) setupFormulaConnection(); //A report's %{label} is where its partner is, not its own @@ -1367,8 +1419,10 @@ void DynamicElementTextItem::removeConnectionForReportFormula(const QString &for */ void DynamicElementTextItem::setupFormulaConnection() { - if ((m_text_from == ElementInfo && m_info_name == "label") || - (m_text_from == CompositeText && m_composite_text.contains("%{label}"))) + if ((m_text_from == ElementInfo && + (m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) || + (m_text_from == CompositeText && usesLabelVariables(m_composite_text))) { clearFormulaConnection(); @@ -1458,8 +1512,10 @@ void DynamicElementTextItem::updateReportText() */ void DynamicElementTextItem::updateLabel() { - if ((m_text_from == ElementInfo && m_info_name == "label") || - (m_text_from == CompositeText && m_composite_text.contains("%{label}"))) + if ((m_text_from == ElementInfo && + (m_info_name == QLatin1String("label") + || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) || + (m_text_from == CompositeText && usesLabelVariables(m_composite_text))) { DiagramContext dc; Element *element = elementUseForInfo(); @@ -1469,19 +1525,21 @@ void DynamicElementTextItem::updateLabel() if(m_text_from == ElementInfo && element) { - QString new_label = element->actualLabel(); + QString new_label = element->infoForDisplay(m_info_name); if (toPlainText() != new_label) { setPlainText(new_label); } } else if (m_text_from == CompositeText) { - // Use actualLabel() to ensure %{label} reflects the current - // resolved label (e.g. after a folio/page-number change). + // Use the label as it must be drawn, so %{label} and + // %{terminal_number} reflect the current resolved label (e.g. + // after a folio/page-number change). // A contact not linked to a coil has no element to read from // (bugtracker #345): %{label} then shows empty, as it did // before actualLabel() was used here. if (element) { - dc.addValue(QStringLiteral("label"), element->actualLabel()); + dc.addValue(QETInformation::ELMT_LABEL, element->labelForDisplay()); + dc.addValue(QETInformation::ELMT_TERMINAL_NUMBER, element->terminalNumberForDisplay()); } setPlainText(autonum::AssignVariables::replaceVariable(m_composite_text, dc)); } diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 8c4676e79..4c22353d4 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -33,6 +33,7 @@ #include "../properties/terminaldata.h" #include "../properties/xrefproperties.h" #include "../qetinformation.h" +#include "../utils/qetsettings.h" #include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/terminal.h" #include "../ui/elementpropertieswidget.h" @@ -1689,6 +1690,13 @@ void Element::addDynamicTextItem(DynamicElementTextItem *deti) m_dynamic_text_list.append(deti); + //A field of its own for the terminal number is what the label + //leaves the number to: the fields which draw the label follow + //when such a field lands on the symbol + if (deti->infoName() == QETInformation::ELMT_TERMINAL_NUMBER) { + refreshLabelFields(); + } + //On a mirrored element, the mirror of the text depends on the size //and the rotation of the text, see keepReadable() connect(deti->document()->documentLayout(), @@ -1724,6 +1732,9 @@ void Element::removeDynamicTextItem(DynamicElementTextItem *deti) m_dynamic_text_list.removeOne(deti); deti->setParentItem(nullptr); forget(); + if (deti->infoName() == QETInformation::ELMT_TERMINAL_NUMBER) { + refreshLabelFields(); + } emit textRemoved(deti); return; } @@ -1736,12 +1747,36 @@ void Element::removeDynamicTextItem(DynamicElementTextItem *deti) m_dynamic_text_list.removeOne(deti); deti->setParentItem(nullptr); forget(); + if (deti->infoName() == QETInformation::ELMT_TERMINAL_NUMBER) { + refreshLabelFields(); + } emit textRemoved(deti); return; } } } +/** + @brief Element::refreshLabelFields + The fields which draw the label draw it again: what they show of the + label has changed under them (a field for the terminal number added + or removed, @sa Element::hasTerminalNumberText), or the terminal + labelling setting was switched (@sa QetSettings::terminalLabelSplit). + Only the fields which draw a label are refreshed, every other field + follows its own information. +*/ +void Element::refreshLabelFields() +{ + for (DynamicElementTextItem *text : m_dynamic_text_list) { + text->refreshLabelConnection(); + } + for (ElementTextItemGroup *group : m_texts_group) { + for (DynamicElementTextItem *text : group->texts()) { + text->refreshLabelConnection(); + } + } +} + /** @brief Element::dynamicTextItems @return all dynamic text items of this element directly child of this element. @@ -2721,6 +2756,105 @@ QString Element::actualLabel() } } +/** + @brief Element::labelForDisplay + @return the label as the drawing must show it, the settings of the + moment being read (@sa QetSettings::terminalLabelSplit): the whole + label ("X1:1") while the labelling is shown in one field; the label + alone ("X1") when it is shown split and this symbol has a text field + of its own for the terminal number; and the two one under the other + when it is split without such a field -- so a symbol drawn before + the setting existed still shows all of its label, only shorter. +*/ +QString Element::labelForDisplay() +{ + const QString label = actualLabel(); + if (!QetSettings::terminalLabelSplit()) { + return label; + } + + const int colon = label.lastIndexOf(QLatin1Char(':')); + if (colon < 0) { + return label; + } + + if (hasTerminalNumberText()) { + return label.left(colon); + } + + return label.left(colon) + QLatin1Char('\n') + label.mid(colon + 1); +} + +/** + @brief Element::terminalNumberForDisplay + @return the part of the label behind its last colon ("1" in "X1:1"), + or an empty string while the labelling is shown in one field: the + number is already there with the label, showing it twice would only + be noise. @sa labelForDisplay +*/ +QString Element::terminalNumberForDisplay() +{ + if (!QetSettings::terminalLabelSplit()) { + return QString(); + } + + const QString label = actualLabel(); + const int colon = label.lastIndexOf(QLatin1Char(':')); + if (colon < 0) { + return QString(); + } + + return label.mid(colon + 1); +} + +/** + @brief Element::hasTerminalNumberText + @return whether this symbol has a text field bound to the terminal + number, the field labelForDisplay() leaves the number to. +*/ +bool Element::hasTerminalNumberText() const +{ + auto is_number_field = [](const DynamicElementTextItem *text) { + return text + && text->textFrom() == DynamicElementTextItem::ElementInfo + && text->infoName() == QETInformation::ELMT_TERMINAL_NUMBER; + }; + + for (const DynamicElementTextItem *text : dynamicTextItems()) { + if (is_number_field(text)) { + return true; + } + } + //The field may just as well live in a text group of the symbol + for (const ElementTextItemGroup *group : m_texts_group) { + for (const DynamicElementTextItem *text : group->texts()) { + if (is_number_field(text)) { + return true; + } + } + } + + return false; +} + +/** + @brief Element::infoForDisplay + @return what a text field bound to @p info_name must show: the label + and the terminal number follow how the labelling is drawn, every + other information is the stored one. @sa labelForDisplay +*/ +QString Element::infoForDisplay(const QString &info_name) +{ + if (info_name == QETInformation::ELMT_LABEL) { + return labelForDisplay(); + } + if (info_name == QETInformation::ELMT_TERMINAL_NUMBER) { + return terminalNumberForDisplay(); + } + + return m_data.m_informations.value(info_name).toString(); +} + /** @brief Element::name @return the human name of this element diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 29c29bb1f..65dc5ce6f 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -165,6 +165,24 @@ class Element : public QetGraphicsItem bool isFreezeLabel() const {return m_freeze_label;} void freezeNewAddedElement(); QString actualLabel(); + /** + What the drawing shows of the label and of the terminal + number, the terminal labelling setting of the moment being + read (@sa QetSettings::terminalLabelSplit). The label itself + is never changed by any of them. + */ + QString labelForDisplay(); + QString terminalNumberForDisplay(); + QString infoForDisplay(const QString &info_name); + bool hasTerminalNumberText() const; + /** + The fields which draw the label draw it again: called when + what they show of the label has changed under them, a + field for the terminal number added or removed, or the + terminal labelling setting switched (@sa + QetSettings::terminalLabelSplit). + */ + void refreshLabelFields(); QString name() const override; ElementsLocation location() const; diff --git a/sources/qetinformation.cpp b/sources/qetinformation.cpp index 80d7db2bd..36498c9c4 100644 --- a/sources/qetinformation.cpp +++ b/sources/qetinformation.cpp @@ -142,6 +142,7 @@ QStringList QETInformation::elementInfoKeys() { QStringList list = { ELMT_FORMULA, ELMT_LABEL, + ELMT_TERMINAL_NUMBER, ELMT_PLANT, ELMT_LOCATION, ELMT_COMMENT, @@ -311,6 +312,7 @@ QString QETInformation::translatedInfoKey(const QString &info) else if (info == PROJECT_SAVED_FILE_PATH) return QObject::tr("Saved file path"); else if (info == ELMT_FORMULA) return QObject::tr("Label formula"); else if (info == ELMT_LABEL) return QObject::tr("Label"); + else if (info == ELMT_TERMINAL_NUMBER) return QObject::tr("Terminal number"); else if (info == ELMT_COMMENT) return QObject::tr("Annotation"); else if (info == ELMT_FUNCTION) return QObject::tr("Function"); else if (info == ELMT_DESCRIPTION) return QObject::tr("Textual description"); diff --git a/sources/qetinformation.h b/sources/qetinformation.h index 26dfb78f2..7da77677c 100644 --- a/sources/qetinformation.h +++ b/sources/qetinformation.h @@ -39,6 +39,10 @@ namespace QETInformation { /** Default information related to element **/ static QString ELMT_LABEL = "label"; + //The part of the label behind its last colon ("1" in "X1:1"), + //shown in a text field of its own when the terminal labelling + //is drawn split (@sa QetSettings::terminalLabelSplit) + static QString ELMT_TERMINAL_NUMBER = "terminal_number"; static QString ELMT_FORMULA = "formula"; /** Uuid of the project's element numbering scheme the element's diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index 6928e8d2b..645dec4dc 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -44,6 +44,7 @@ #include #include "../../diagram.h" +#include "../../qetgraphicsitem/element.h" #include "../../qetproject.h" namespace { @@ -161,6 +162,11 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : enable(ui->m_wiring_rules_cb->isChecked()); connect(ui->m_wiring_rules_cb, &QCheckBox::toggled, this, enable); } + if (QetSettings::terminalLabelSplit()) { + ui->m_terminal_label_split_rb->setChecked(true); + } else { + ui->m_terminal_label_combined_rb->setChecked(true); + } ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool()); ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool()); ui->m_autonumber_pasted->setChecked(settings.value("diagramcommands/autonumber-pasted-elements", true).toBool()); @@ -415,6 +421,19 @@ void GeneralConfigurationPage::applyConf() if (WiringRules::masterEnabled() && WiringRules::turnsRuleOn(before, rules)) { WiringRulesWarning::show(this); } + } + //The labelling setting is read where the label is drawn, so the + //drawings already open are told to draw it again when it changes + const bool label_split = ui->m_terminal_label_split_rb->isChecked(); + if (QetSettings::terminalLabelSplit() != label_split) { + QetSettings::setTerminalLabelSplit(label_split); + for (QETProject *project : QETApp::registeredProjects()) { + for (Diagram *diagram : project->diagrams()) { + for (Element *element : diagram->elements()) { + element->refreshLabelFields(); + } + } + } } settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value()); settings.setValue("projectlock/enabled", ui->m_project_lock->isChecked()); diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui index 5a91496c9..40a87f188 100644 --- a/sources/ui/configpage/generalconfigurationpage.ui +++ b/sources/ui/configpage/generalconfigurationpage.ui @@ -140,6 +140,48 @@ + + + + Display of the terminal labelling + + + + + + A terminal's label (X1:1) is drawn as a whole, the way it has always been. + + + Show the label and the terminal number in one field + + + true + + + + + + + The label (X1) is drawn in one field and the terminal number (1) in another, which a terminal symbol may place where it wants -- and hide when it must. A symbol without such a field shows the two, one under the other. + + + Split the label and the terminal number + + + + + + + Only the drawing changes: what is written in the element, the numbering, the sorting and the exports keep the whole label. + + + true + + + + + + diff --git a/sources/utils/qetsettings.cpp b/sources/utils/qetsettings.cpp index 632d30b52..35ffe8559 100644 --- a/sources/utils/qetsettings.cpp +++ b/sources/utils/qetsettings.cpp @@ -290,4 +290,28 @@ namespace QetSettings QSettings settings; settings.setValue(QStringLiteral("diagrameditor/sheet_background_recent"), colors); } + + /** + * @brief terminalLabelSplit + * @return whether a terminal's label is drawn split, the label and + * the terminal number each in their own text field, instead of the + * whole label ("X1:1") in one field. Off unless the user turned it + * on, so an existing drawing keeps its look. @sa setTerminalLabelSplit + */ + bool terminalLabelSplit() + { + QSettings settings; + return settings.value(QStringLiteral("element/terminal_label_split"), false).toBool(); + } + + /** + * @brief setTerminalLabelSplit + * Store whether a terminal's label is drawn split. @sa terminalLabelSplit + * @param split + */ + void setTerminalLabelSplit(bool split) + { + QSettings settings; + settings.setValue(QStringLiteral("element/terminal_label_split"), split); + } } diff --git a/sources/utils/qetsettings.h b/sources/utils/qetsettings.h index f291caf93..5e4e42c25 100644 --- a/sources/utils/qetsettings.h +++ b/sources/utils/qetsettings.h @@ -76,6 +76,20 @@ namespace QetSettings QStringList sheetBackgroundRecentColors(); void setSheetBackgroundRecentColors(const QStringList &colors); + + /** + @brief How a terminal's own label is drawn. + + The label is stored as it is written ("X1:1"). With the split on, + the drawing shows the label ("X1") and the terminal number ("1") + as two parts, so a terminal symbol can keep the number in its own + text field and place it where it wants. Only the drawing reacts: + what the element stores, the numbering, the sorting in the + terminal strip editor and every export keep the whole label. + Off by default, so a drawing keeps the look it was made with. + */ + bool terminalLabelSplit(); + void setTerminalLabelSplit(bool split); } #endif // QETSETTINGS_H diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 4e8d6c930..c4cc2fadc 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -323,6 +323,13 @@ qet_add_app_test(tst_reportfoliotitle tst_reportfoliotitle.cpp) # what was typed in, and nothing stale is written into the element. qet_add_app_test(tst_terminalcableauto tst_terminalcableauto.cpp) +# A terminal's label is drawn whole in one field, or split into its label +# and its terminal number: in their own fields when the symbol carries a +# field for the number, one under the other when it does not. Only the +# drawing changes; the stored label, the numbering and the exports keep +# the whole label. +qet_add_app_test(tst_terminallabeldisplay tst_terminallabeldisplay.cpp) + # Escape during a picture's resize, rotate or pivot drag puts it back, keeps # it selected and records no undo step (#1414); the key goes to the view. qet_add_app_test(tst_imagehandleescape tst_imagehandleescape.cpp) diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp new file mode 100644 index 000000000..b3f591ec2 --- /dev/null +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + 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 . +*/ +#include "qetapptest.h" +#include "TerminalStrip/realterminal.h" +#include "TerminalStrip/terminalstrip.h" +#include "qetgraphicsitem/dynamicelementtextitem.h" +#include "qetgraphicsitem/element.h" +#include "qetgraphicsitem/terminal.h" +#include "qetinformation.h" +#include "qetproject.h" +#include "utils/qetsettings.h" + +/** + How a terminal's own label is drawn, the setting of the moment being + read (@sa QetSettings::terminalLabelSplit). + + The label stays whole wherever it is used as data: what the element + stores, the numbering, the sorting in the terminal strip editor and + the exports are never touched by the setting. Only the drawing reacts: + with the labelling split, the label ("X1") and the terminal number + ("1") are two parts -- in their own fields when the symbol has a + field for the number, one under the other when it has not. + + fixtures/every_feature.qet has the strip X1 whose terminals are + plain terminal symbols: a field tagged "label", no field for the + number, and -- as every element of the fixture -- a label the test + gives them before it looks at the drawing. +*/ +class tst_terminallabeldisplay : public QObject +{ + Q_OBJECT + + //A terminal element of the fixture's strip X1, given @p label + static Element *terminalWithLabel(QETProject &project, const QString &label) + { + const QVector strips = project.terminalStrip(); + if (strips.isEmpty()) return nullptr; + for (const QSharedPointer &real_t + : strips.first()->realTerminals()) + { + Element *element = real_t ? real_t->element() : nullptr; + if (!element || element->terminals().isEmpty()) continue; + + DiagramContext dc = element->elementInformations(); + dc.addValue(QStringLiteral("label"), label); + element->setElementInformations(dc); + if (element->actualLabel() == label) return element; + } + return nullptr; + } + + //The text field of @p element bound to @p info_name, the field the + //symbol carries for it + static DynamicElementTextItem *fieldBoundTo(Element *element, const QString &info_name) + { + for (DynamicElementTextItem *text : element->dynamicTextItems()) + if (text->infoName() == info_name) return text; + return nullptr; + } + + //A text field as a symbol made for the split would carry one: bound + //to the terminal number, and nothing else + static DynamicElementTextItem *addNumberField(Element *element) + { + DynamicElementTextItem *number = new DynamicElementTextItem(element); + element->addDynamicTextItem(number); + number->setInfoName(QETInformation::ELMT_TERMINAL_NUMBER); + number->setTextFrom(DynamicElementTextItem::ElementInfo); + return number; + } + +private slots: + void cleanup() + { + //Whatever a test left behind, the next one (and the rest of + //the suite) starts from the shipped default + QetSettings::setTerminalLabelSplit(false); + } + + //The shipped default: one field with the whole label, the way it has + //always been drawn + void oneFieldKeepsTheWholeLabel() + { + QetSettings::setTerminalLabelSplit(false); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY2(element, "the fixture has a terminal of the strip X1"); + + QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); + QCOMPARE(element->labelForDisplay(), QStringLiteral("X1:1")); + QVERIFY(element->terminalNumberForDisplay().isEmpty()); + QVERIFY(element->infoForDisplay(QETInformation::ELMT_TERMINAL_NUMBER).isEmpty()); + QVERIFY(!element->hasTerminalNumberText()); + + //The field the symbol carries shows the whole label + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY2(label, "the terminal symbol has a field for its label"); + QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); + } + + //Split, and the symbol has no field for the number: the number stays + //with the label, on its own line, so that switching the setting on + //can never make a number vanish + void splitWithoutNumberFieldShowsTwoLines() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + + QCOMPARE(element->labelForDisplay(), QStringLiteral("X1\n1")); + QCOMPARE(element->terminalNumberForDisplay(), QStringLiteral("1")); + QCOMPARE(element->infoForDisplay(QETInformation::ELMT_TERMINAL_NUMBER), + QStringLiteral("1")); + + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QStringLiteral("X1\n1")); + + //A text which uses the label as a variable follows the same + //rule as the field bound to the label itself + DynamicElementTextItem *composite = new DynamicElementTextItem(element); + composite->setCompositeText(QStringLiteral("%{label}")); + composite->setTextFrom(DynamicElementTextItem::CompositeText); + QCOMPARE(composite->toPlainText(), QStringLiteral("X1\n1")); + } + + //Split, and the symbol has a field of its own for the number: each + //part in its field, the label no longer carrying the number + void splitWithNumberFieldShowsEachInItsOwnField() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + + DynamicElementTextItem *number = addNumberField(element); + QVERIFY(element->hasTerminalNumberText()); + QCOMPARE(number->toPlainText(), QStringLiteral("1")); + + QCOMPARE(element->labelForDisplay(), QStringLiteral("X1")); + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QStringLiteral("X1")); + } + + //A label with no colon -- every label which is not a terminal's, and + //a terminal named by hand -- is drawn as it is stored, whatever the + //setting says + void splitLeavesLabelsWithoutColonAlone() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("S7")); + QVERIFY(element); + + QCOMPARE(element->labelForDisplay(), QStringLiteral("S7")); + QVERIFY(element->terminalNumberForDisplay().isEmpty()); + + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QStringLiteral("S7")); + } + + //The setting is read where it is drawn: the settings page tells the + //drawings already open to draw it again, and the whole label comes + //back without anything to rewrite first + void switchingBackGivesTheWholeLabel() + { + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + QetSettings::setTerminalLabelSplit(true); + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QStringLiteral("X1\n1")); + + QetSettings::setTerminalLabelSplit(false); + element->refreshLabelFields(); + QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); + QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); + } +}; + +QET_APP_TEST_MAIN(tst_terminallabeldisplay) +#include "tst_terminallabeldisplay.moc" From edf761c2b6ab48cef331cd7ed9238a08fe711f3b Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 12:51:50 +0200 Subject: [PATCH 2/8] Add a Show name box to the selection properties of a terminal The box hides the name of the terminal on the sheet only: the element keeps all of its data, and with the labelling split the terminal keeps its number, drawn once - in its own field when the symbol has one, alone in the label field when it has not. Only a terminal may have its name undrawn: the box is shown for the element type Terminal alone, and Element::labelShown() obeys the stored information for a terminal only. The choice is stored as the element information "label_shown" = "false" and only that, so files of every element which still draws its name stay exactly as they are. Multi-selection: the flag appears for terminals only and counts an absent information as "shown", so a mixed selection shows the box ticked only when every terminal has it ticked. Includes four test cases: hidden name keeps the stored data, keeps the number without a number field, leaves the number to its own field, and a non-terminal draws its name whatever the file says. --- sources/qetgraphicsitem/element.cpp | 46 ++++++++- sources/qetgraphicsitem/element.h | 1 + sources/ui/elementinfowidget.cpp | 52 +++++++++- sources/ui/elementinfowidget.h | 2 + sources/ui/multielementinfowidget.cpp | 21 +++- tests/qttest/tst_terminallabeldisplay.cpp | 120 ++++++++++++++++++++++ 6 files changed, 238 insertions(+), 4 deletions(-) diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index 4c22353d4..c978035a7 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -2769,7 +2769,28 @@ QString Element::actualLabel() QString Element::labelForDisplay() { const QString label = actualLabel(); - if (!QetSettings::terminalLabelSplit()) { + const bool split = QetSettings::terminalLabelSplit(); + + //The "Show name" box of the selection properties hides the name + //of this element on the sheet only: the element keeps all of its + //data, and a terminal keeps its number, drawn on its own while + //the labelling is split. + if (!labelShown()) + { + if (!split) { + return QString(); + } + //The number alone in the label field, but only while the + //symbol has no field of its own for it: the number is + //drawn once, wherever it is drawn at all. + const int hidden_colon = label.lastIndexOf(QLatin1Char(':')); + if (hidden_colon < 0 || hasTerminalNumberText()) { + return QString(); + } + return label.mid(hidden_colon + 1); + } + + if (!split) { return label; } @@ -2837,6 +2858,29 @@ bool Element::hasTerminalNumberText() const return false; } +/** + @brief Element::labelShown + @return whether the label of this element must be drawn on the sheet. + The choice belongs to the element itself, set with the "Show name" + box of the selection properties: an element whose name is not shown + keeps all of its data, nothing is only not drawn. An element which + never got the box unticked keeps drawing its name, which is why a + missing information means true. +*/ +bool Element::labelShown() const +{ + //Only a terminal may have its name undrawn: an information + //which says otherwise, from whatever source, is not obeyed. + if (m_data.m_type != ElementData::Terminal) { + return true; + } + if (!m_data.m_informations.contains(QStringLiteral("label_shown"))) { + return true; + } + return QET::infoFlagIsTrue( + m_data.m_informations.value(QStringLiteral("label_shown")).toString()); +} + /** @brief Element::infoForDisplay @return what a text field bound to @p info_name must show: the label diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 65dc5ce6f..a7d5b2ef1 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -175,6 +175,7 @@ class Element : public QetGraphicsItem QString terminalNumberForDisplay(); QString infoForDisplay(const QString &info_name); bool hasTerminalNumberText() const; + bool labelShown() const; /** The fields which draw the label draw it again: called when what they show of the label has changed under them, a diff --git a/sources/ui/elementinfowidget.cpp b/sources/ui/elementinfowidget.cpp index cd1640bb9..0f204331a 100644 --- a/sources/ui/elementinfowidget.cpp +++ b/sources/ui/elementinfowidget.cpp @@ -262,6 +262,9 @@ void ElementInfoWidget::enableLiveEdit() if (m_freeze_cb) { connect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply); } + if (m_label_shown_cb) { + connect(m_label_shown_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply); + } if (m_number_cb) { connect(m_number_cb, QOverload::of(&QComboBox::activated), this, &ElementInfoWidget::apply); } @@ -283,6 +286,9 @@ void ElementInfoWidget::disableLiveEdit() if (m_freeze_cb) { disconnect(m_freeze_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply); } + if (m_label_shown_cb) { + disconnect(m_label_shown_cb, &QCheckBox::clicked, this, &ElementInfoWidget::apply); + } if (m_number_cb) { disconnect(m_number_cb, QOverload::of(&QComboBox::activated), this, &ElementInfoWidget::apply); } @@ -318,6 +324,7 @@ void ElementInfoWidget::buildInterface() setupSchemeRow(); setupNumberRow(); setupFreezeRow(); + setupLabelShownRow(); m_add_custom_property_btn = new QPushButton(tr("Add a custom property"), this); connect(m_add_custom_property_btn, &QPushButton::clicked, this, [this]() { addCustomProperty(); }); @@ -367,7 +374,8 @@ QStringList ElementInfoWidget::predefinedKeys() const keys << QETInformation::ELMT_FORMULA_ID << QStringLiteral("auto_num_locked") << QStringLiteral("potential_isolating") - << QStringLiteral("exclude_from_bom"); + << QStringLiteral("exclude_from_bom") + << QStringLiteral("label_shown"); return keys; } @@ -672,6 +680,9 @@ void ElementInfoWidget::updateUi() m_freeze_cb->setChecked(m_element->isFreezeLabel()); updateFreezeRow(); } + if (m_label_shown_cb) { + m_label_shown_cb->setChecked(m_element->labelShown()); + } refreshNumberRow(); updateSuggestions(); @@ -759,6 +770,13 @@ DiagramContext ElementInfoWidget::currentInfo() const info_.addValue(QStringLiteral("exclude_from_bom"), m_exclude_from_bom_cb->isChecked() ? QStringLiteral("true") : QStringLiteral("false")); } + // The name is drawn unless the element says otherwise: only a name + // which is not drawn needs an information, so that elements which + // draw it keep their files exactly as they are. + if (m_label_shown_cb && !m_label_shown_cb->isChecked()) { + info_.addValue(QStringLiteral("label_shown"), QStringLiteral("false")); + } + //The element keeps following its numbering scheme as long as its //formula is left as it is; a formula edited by hand is its own. const DiagramContext &elmt_info = m_element->elementInformations(); @@ -846,6 +864,38 @@ void ElementInfoWidget::updateFreezeRow() updateNumberRow(); } +/** + @brief ElementInfoWidget::setupLabelShownRow + Add under the freeze box a check box which decides whether the name + of this element is drawn on the sheet: the choice belongs to the + placed element, not to the symbol, so everyone can decide for + himself. +*/ +void ElementInfoWidget::setupLabelShownRow() +{ + //The box means something for a terminal only, so it is drawn + //for a terminal only - the same rule as the function separation + //box just below it. + if (!m_element || m_element->elementData().m_type != ElementData::Terminal) { + return; + } + m_label_shown_cb = new QCheckBox(tr("Show name"), this); + m_label_shown_cb->setObjectName(QStringLiteral("m_label_shown_cb")); + m_label_shown_cb->setToolTip(tr("Untick to not draw the name of this element on the sheet: " + "its data is kept, and a terminal keeps its number.")); + + int row = ui->scroll_vlayout->indexOf(m_freeze_cb); + if (row < 0) { + ElementInfoPartWidget *label = infoPartWidgetForKey(QETInformation::ELMT_LABEL); + row = label ? ui->scroll_vlayout->indexOf(label) : -1; + } + if (row >= 0) { + ui->scroll_vlayout->insertWidget(row + 1, m_label_shown_cb); + } else { + ui->scroll_vlayout->addWidget(m_label_shown_cb); + } +} + /** @brief ElementInfoWidget::setupNumberRow Add under the numbering a list of the numbers the element may be given diff --git a/sources/ui/elementinfowidget.h b/sources/ui/elementinfowidget.h index 2271becf8..f51924d89 100644 --- a/sources/ui/elementinfowidget.h +++ b/sources/ui/elementinfowidget.h @@ -76,6 +76,7 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget void updateSuggestions(); void setupSchemeRow(); void setupFreezeRow(); + void setupLabelShownRow(); void setupNumberRow(); void refreshNumberRow(); void updateNumberRow(); @@ -103,6 +104,7 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget QCheckBox *m_exclude_from_bom_cb = nullptr; QComboBox *m_scheme_cb = nullptr; QCheckBox *m_freeze_cb = nullptr; + QCheckBox *m_label_shown_cb = nullptr; QWidget *m_number_row = nullptr; QComboBox *m_number_cb = nullptr; int m_number_current = -1; ///< the number the element has, -1 if unknown diff --git a/sources/ui/multielementinfowidget.cpp b/sources/ui/multielementinfowidget.cpp index be226cbae..49d31b56a 100644 --- a/sources/ui/multielementinfowidget.cpp +++ b/sources/ui/multielementinfowidget.cpp @@ -75,6 +75,15 @@ quint64 s_last_session = 0; const QString EXCLUDE_FROM_BOM = QStringLiteral("exclude_from_bom"); const QString AUTO_NUM_LOCKED = QStringLiteral("auto_num_locked"); const QString POTENTIAL_ISOLATING = QStringLiteral("potential_isolating"); + //The name is drawn unless the element says otherwise (@sa + //Element::labelShown): an element which never got the box unticked + //has no information for it, and counts as "shown". +const QString LABEL_SHOWN = QStringLiteral("label_shown"); + +bool flagDefaultIsOn(const QString &key) +{ + return key == LABEL_SHOWN; +} QString flagText(bool on) { @@ -449,7 +458,8 @@ void MultiElementInfoWidget::buildInterface() const QList> flags { {EXCLUDE_FROM_BOM, tr("Exclude from the bill of materials")}, {POTENTIAL_ISOLATING, tr("Function separation")}, - {AUTO_NUM_LOCKED, tr("Exclude from automatic numbering")} + {AUTO_NUM_LOCKED, tr("Exclude from automatic numbering")}, + {LABEL_SHOWN, tr("Show name")} }; for (const auto &pair : flags) { @@ -588,12 +598,19 @@ void MultiElementInfoWidget::fillRows() for (Flag *flag : std::as_const(m_flags)) { + //"Show name" means something for a terminal only, so it is + //shown for terminals only - the same rule as the two flags + //before it. flag->check->setVisible(flag->key == EXCLUDE_FROM_BOM || all_terminals); flag->restore->setVisible(flag->check->isVisible()); int on = 0; for (Element *element : list) { - if (QET::infoFlagIsTrue(m_snapshot.value(element).value(flag->key).toString())) { + const QVariant value = m_snapshot.value(element).value(flag->key); + const bool is_on = value.isValid() + ? QET::infoFlagIsTrue(value.toString()) + : flagDefaultIsOn(flag->key); + if (is_on) { ++on; } } diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index b3f591ec2..e52ecc89c 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -19,6 +19,8 @@ #include "qetapptest.h" #include "TerminalStrip/realterminal.h" #include "TerminalStrip/terminalstrip.h" +#include "diagram.h" +#include "properties/elementdata.h" #include "qetgraphicsitem/dynamicelementtextitem.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/terminal.h" @@ -85,6 +87,28 @@ class tst_terminallabeldisplay : public QObject return number; } + //The "Show name" box of the selection properties, unticked: the + //element then stores the information "label_shown" = "false", the + //only thing it stores for it + static void hideName(Element *element) + { + DiagramContext dc = element->elementInformations(); + dc.addValue(QStringLiteral("label_shown"), QStringLiteral("false")); + element->setElementInformations(dc); + } + + //Any element of the fixture which is not a terminal and carries a + //name to draw + static Element *aNonTerminal(QETProject &project) + { + for (Diagram *diagram : project.diagrams()) + for (Element *element : diagram->elements()) + if (element->elementData().m_type != ElementData::Terminal + && !element->actualLabel().isEmpty()) + return element; + return nullptr; + } + private slots: void cleanup() { @@ -215,6 +239,102 @@ private slots: QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); } + + //A terminal whose name must not be drawn draws nothing of it, but + //keeps everything: the stored label is still there, and so is every + //use of it as data -- only the drawing changes + void hiddenNameKeepsEverythingStored() + { + QetSettings::setTerminalLabelSplit(false); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + hideName(element); + + QVERIFY(!element->labelShown()); + QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); + QCOMPARE(element->labelForDisplay(), QString()); + + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QString()); + } + + //Hidden, and the labelling is split: the terminal keeps its number, + //drawn on its own line while the symbol has no field for it, so + //hiding the name can never hide the number + void hiddenNameKeepsTheNumberWithSplit() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + hideName(element); + + QCOMPARE(element->labelForDisplay(), QStringLiteral("1")); + QCOMPARE(element->terminalNumberForDisplay(), QStringLiteral("1")); + QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); + + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QStringLiteral("1")); + } + + //Hidden, split, and the symbol has a field of its own for the + //number: the number is drawn in its own field, the label field + //draws nothing -- the number is never drawn twice + void hiddenNameLeavesTheNumberToItsOwnField() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + DynamicElementTextItem *number = addNumberField(element); + hideName(element); + + QCOMPARE(element->labelForDisplay(), QString()); + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QCOMPARE(label->toPlainText(), QString()); + QCOMPARE(number->toPlainText(), QStringLiteral("1")); + } + + //Only a terminal may have its name undrawn: another element which + //carries the same information still draws its name, whatever the + //file says + void nonTerminalAlwaysDrawsItsName() + { + QetSettings::setTerminalLabelSplit(false); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = aNonTerminal(project); + QVERIFY2(element, "the fixture has an element which is no terminal"); + DiagramContext dc = element->elementInformations(); + dc.addValue(QStringLiteral("label_shown"), QStringLiteral("false")); + element->setElementInformations(dc); + + //Whatever the file says, a non-terminal draws its name: + //the information is not obeyed, and the drawn name is the + //stored one, composed as it always is + QVERIFY(element->labelShown()); + QVERIFY(!element->actualLabel().isEmpty()); + QCOMPARE(element->labelForDisplay(), element->actualLabel()); + } }; QET_APP_TEST_MAIN(tst_terminallabeldisplay) From 337502edfc95160153fb207c50681c2f27ac685f Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 13:18:55 +0200 Subject: [PATCH 3/8] Take the label field off the sheet while the name is hidden An element whose name must not be drawn left its label field behind as an empty frame: selected along with the element, ready to click, drawing nothing. The field now follows the Show name box both ways: - a field bound to the label is hidden while Element::labelShown() says no, and shown again the moment the box is ticked again, both through the same signals which refresh the text (elementInfoChange, the label connection refresh, binding the field to another information), so it can never get stuck on one side; - a hidden field is deselected and gets no resize handles, and one which comes back gets its handles back when its element is still selected; - every other field, composite texts included, is drawn as always, and the data of the element never changes - only the drawing. Adds a test: the field is visible, goes away with the name, and comes back with it. --- .../dynamicelementtextitem.cpp | 43 ++++++++++++++++++- .../qetgraphicsitem/dynamicelementtextitem.h | 1 + sources/qetgraphicsitem/element.cpp | 4 ++ tests/qttest/tst_terminallabeldisplay.cpp | 30 +++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index ffb1535dd..c74a123a1 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -415,6 +415,7 @@ Element *DynamicElementTextItem::elementUseForInfo() const */ void DynamicElementTextItem::refreshLabelConnection() { + refreshLabelShown(); if ((m_text_from == ElementInfo && (m_info_name == QLatin1String("label") || m_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) || @@ -434,6 +435,30 @@ void DynamicElementTextItem::refreshLabelConnection() } } +/** + @brief DynamicElementTextItem::refreshLabelShown + Whether this field is drawn at all: a field bound to the label of + an element whose "Show name" box is unticked (@sa Element::labelShown) + does not stay on the sheet as an empty frame to select -- it is + hidden, and comes back the moment the box is ticked again. Both ways + run through the same signals which refresh the text, so the field + can never get stuck on one side. Every other field is drawn as + always, and a composite text keeps drawing what is not the label. +*/ +void DynamicElementTextItem::refreshLabelShown() +{ + const bool shown = !(m_text_from == ElementInfo + && m_info_name == QETInformation::ELMT_LABEL) + || !m_parent_element + || m_parent_element.data()->labelShown(); + + if (!shown && isSelected()) { + setSelected(false); + } + setVisible(shown); + refreshResizeHandlesVisibility(); +} + /** @brief DynamicElementTextItem::textFrom @return what the final text is created from. @@ -592,7 +617,12 @@ void DynamicElementTextItem::setInfoName(const QString &info_name) || old_info_name == QETInformation::ELMT_TERMINAL_NUMBER)) { parentElement()->refreshLabelFields(); } - + + //A field which becomes, or stops being, the field for the + //label of its element is shown or hidden with the "Show name" + //box (@sa refreshLabelShown) + refreshLabelShown(); + emit infoNameChanged(info_name); } @@ -1001,7 +1031,11 @@ bool DynamicElementTextItem::sceneEventFilter(QGraphicsItem *watched, QEvent *ev */ void DynamicElementTextItem::refreshResizeHandlesVisibility() { - const bool handles_wanted = isSelected() || (m_parent_element && m_parent_element->isSelected()); + //A hidden text has no handles, not even while its element is + //selected: refreshLabelShown() puts them back when the text + //is shown again + const bool handles_wanted = isVisible() + && (isSelected() || (m_parent_element && m_parent_element->isSelected())); if (handles_wanted && !m_left_resize_handle) addResizeHandles(); else if (!handles_wanted && m_left_resize_handle) @@ -1202,6 +1236,11 @@ void DynamicElementTextItem::handlerMouseReleaseEvent(QetGraphicsHandlerItem *ha void DynamicElementTextItem::elementInfoChanged() { + //The "Show name" box of the selection properties was possibly + //ticked or unticked: the field of a hidden name leaves the + //sheet and comes back with the box (@sa refreshLabelShown) + refreshLabelShown(); + //A folio report's label shows where its partner is //(updateReportText()); the label in its information is only where //the partner was when last saved, and changing any other diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.h b/sources/qetgraphicsitem/dynamicelementtextitem.h index 3bc2a0afc..e6fc2134f 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.h +++ b/sources/qetgraphicsitem/dynamicelementtextitem.h @@ -97,6 +97,7 @@ class DynamicElementTextItem : public DiagramTextItem ElementTextItemGroup *parentGroup() const; Element *elementUseForInfo() const; void refreshLabelConnection(); + void refreshLabelShown(); DynamicElementTextItem::TextFrom textFrom() const; void setTextFrom (DynamicElementTextItem::TextFrom text_from); diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index c978035a7..da0f98202 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -1690,6 +1690,10 @@ void Element::addDynamicTextItem(DynamicElementTextItem *deti) m_dynamic_text_list.append(deti); + //An element whose name must not be drawn does not keep the + //field of its label on the sheet either (@sa Element::labelShown) + deti->refreshLabelShown(); + //A field of its own for the terminal number is what the label //leaves the number to: the fields which draw the label follow //when such a field lands on the symbol diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index e52ecc89c..fa07cc4af 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -335,6 +335,36 @@ private slots: QVERIFY(!element->actualLabel().isEmpty()); QCOMPARE(element->labelForDisplay(), element->actualLabel()); } + + //The field of a hidden name does not stay on the sheet as an empty + //frame to select: it leaves the sheet with the box and comes back + //with it, both ways through the same signal + void hiddenNameHidesTheLabelField() + { + QetSettings::setTerminalLabelSplit(false); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY(label); + QVERIFY(label->isVisible()); + + hideName(element); + QVERIFY(!label->isVisible()); + + //The box ticked again: the element drops the information, + //and the field is drawn as it was before + DiagramContext dc = element->elementInformations(); + dc.remove(QStringLiteral("label_shown")); + element->setElementInformations(dc); + + QVERIFY(label->isVisible()); + QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); + } }; QET_APP_TEST_MAIN(tst_terminallabeldisplay) From 790497978a967d101f80aee1c415d70a4b2a53fd Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 18:33:06 +0200 Subject: [PATCH 4/8] Split the labelling of terminal symbols only An element of any other type draws its label as a whole, whatever the labelling setting says: the colon in a label like "-K1:13" is part of that label, not a terminal number waiting to be split off. Only an element whose data says it is a terminal is ever drawn split. The test picks a non-terminal whose own label field draws the label it is given -- not a report (its label is where its partner is), not a slave (its label follows its master) and not a numbered symbol (a formula composes its label): those draw texts of their own the split never touches. --- sources/qetgraphicsitem/element.cpp | 15 ++++-- tests/qttest/tst_terminallabeldisplay.cpp | 63 +++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index da0f98202..cf4a84174 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -2769,11 +2769,14 @@ QString Element::actualLabel() of its own for the terminal number; and the two one under the other when it is split without such a field -- so a symbol drawn before the setting existed still shows all of its label, only shorter. + Only an element of the terminal type is ever drawn split: the colon + in any other label is part of the label ("-K1:13"). */ QString Element::labelForDisplay() { const QString label = actualLabel(); - const bool split = QetSettings::terminalLabelSplit(); + const bool split = m_data.m_type == ElementData::Terminal + && QetSettings::terminalLabelSplit(); //The "Show name" box of the selection properties hides the name //of this element on the sheet only: the element keeps all of its @@ -2813,13 +2816,15 @@ QString Element::labelForDisplay() /** @brief Element::terminalNumberForDisplay @return the part of the label behind its last colon ("1" in "X1:1"), - or an empty string while the labelling is shown in one field: the - number is already there with the label, showing it twice would only - be noise. @sa labelForDisplay + or an empty string while the labelling is shown in one field -- and + for every element which is no terminal, whose colon is part of its + label: the number is already there with the label, showing it twice + would only be noise. @sa labelForDisplay */ QString Element::terminalNumberForDisplay() { - if (!QetSettings::terminalLabelSplit()) { + if (m_data.m_type != ElementData::Terminal + || !QetSettings::terminalLabelSplit()) { return QString(); } diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index fa07cc4af..8d7f4b4ee 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -109,6 +109,45 @@ class tst_terminallabeldisplay : public QObject return nullptr; } + //A non-terminal element of the fixture whose label field draws the + //label it is given, colon and all: a terminal's own kind of drawing + //is what the split changes, so the test needs an element which is + //none of the things drawing a label of their own -- a report (its + //label is where its partner is), a slave (its label follows its + //master) or a numbered symbol (a formula composes its label) + static Element *nonTerminalWithLabel(QETProject &project, const QString &label) + { + for (Diagram *diagram : project.diagrams()) + for (Element *element : diagram->elements()) + { + if (element->elementData().m_type == ElementData::Terminal) { + continue; + } + if (element->linkType() & (Element::Slave | Element::AllReport)) { + continue; + } + DiagramContext dc = element->elementInformations(); + if (!dc.value(QStringLiteral("formula")).toString().isEmpty()) { + continue; + } + dc.addValue(QStringLiteral("label"), label); + element->setElementInformations(dc); + if (element->actualLabel() != label) { + continue; + } + //Only an element whose own label field draws the + //label it stores: everything else draws a text of + //its own, which this test would not understand + DynamicElementTextItem *field = fieldBoundTo(element, + QETInformation::ELMT_LABEL); + if (!field || field->toPlainText() != label) { + continue; + } + return element; + } + return nullptr; + } + private slots: void cleanup() { @@ -336,6 +375,30 @@ private slots: QCOMPARE(element->labelForDisplay(), element->actualLabel()); } + //The labelling is drawn split for an element of the terminal type + //only: the colon in the label of any other element ("-K1:13") is + //part of the label, not a split waiting to happen + void splitLeavesNonTerminalsWithColonAlone() + { + QetSettings::setTerminalLabelSplit(true); + + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = nonTerminalWithLabel(project, QStringLiteral("-K1:13")); + QVERIFY2(element, + "the fixture has an element which is no terminal and draws the label it is given"); + + QCOMPARE(element->actualLabel(), QStringLiteral("-K1:13")); + QCOMPARE(element->labelForDisplay(), QStringLiteral("-K1:13")); + QVERIFY(element->terminalNumberForDisplay().isEmpty()); + + DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); + QVERIFY2(label, "the symbol has a field for its label"); + QCOMPARE(label->toPlainText(), QStringLiteral("-K1:13")); + } + //The field of a hidden name does not stay on the sheet as an empty //frame to select: it leaves the sheet with the box and comes back //with it, both ways through the same signal From 86563850f81f42d2f696810c9a3903197b16ad24 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 18:40:19 +0200 Subject: [PATCH 5/8] Move the terminal labelling setting into the project The review asked for it, and it is the right place: the same file must draw the same way on every machine which opens it, so how a terminal writes its label belongs to the project, not to the application's own settings. The choice lives with the other display options of the sheets, in the project properties (General page), and is saved in the project as -- written only when on, so a project with it off saves byte for byte as before. Reading it is a member of the project now: no QSettings built per text refresh any more, and switching it redraws the labels of the drawings already open. The application setting (QetSettings, the General configuration page) is gone with it, and the test reads the project's own value. --- .../dynamicelementtextitem.cpp | 2 +- sources/qetgraphicsitem/element.cpp | 28 ++++---- sources/qetgraphicsitem/element.h | 8 +-- sources/qetinformation.h | 2 +- sources/qetproject.cpp | 67 +++++++++++++++++++ sources/qetproject.h | 8 +++ .../configpage/generalconfigurationpage.cpp | 19 ------ .../ui/configpage/generalconfigurationpage.ui | 42 ------------ sources/ui/configpage/projectconfigpages.cpp | 39 +++++++++++ sources/ui/configpage/projectconfigpages.h | 5 ++ sources/utils/qetsettings.cpp | 24 ------- sources/utils/qetsettings.h | 14 ---- tests/qttest/tst_terminallabeldisplay.cpp | 45 ++++--------- 13 files changed, 152 insertions(+), 151 deletions(-) diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index c74a123a1..6e7ece16b 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -1259,7 +1259,7 @@ void DynamicElementTextItem::elementInfoChanged() if(element) { dc = element->elementInformations(); //The label and the terminal number are drawn the way the - //settings say (@sa QetSettings::terminalLabelSplit): a + //project says (@sa QETProject::terminalLabelSplit): a //composite text which uses them must see the same values the //fields bound to them show dc.addValue(QETInformation::ELMT_LABEL, element->labelForDisplay()); diff --git a/sources/qetgraphicsitem/element.cpp b/sources/qetgraphicsitem/element.cpp index cf4a84174..238332130 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -33,7 +33,6 @@ #include "../properties/terminaldata.h" #include "../properties/xrefproperties.h" #include "../qetinformation.h" -#include "../utils/qetsettings.h" #include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/terminal.h" #include "../ui/elementpropertieswidget.h" @@ -1765,7 +1764,8 @@ void Element::removeDynamicTextItem(DynamicElementTextItem *deti) The fields which draw the label draw it again: what they show of the label has changed under them (a field for the terminal number added or removed, @sa Element::hasTerminalNumberText), or the terminal - labelling setting was switched (@sa QetSettings::terminalLabelSplit). + labelling setting of the project was switched + (@sa QETProject::terminalLabelSplit). Only the fields which draw a label are refreshed, every other field follows its own information. */ @@ -2762,21 +2762,22 @@ QString Element::actualLabel() /** @brief Element::labelForDisplay - @return the label as the drawing must show it, the settings of the - moment being read (@sa QetSettings::terminalLabelSplit): the whole - label ("X1:1") while the labelling is shown in one field; the label - alone ("X1") when it is shown split and this symbol has a text field - of its own for the terminal number; and the two one under the other - when it is split without such a field -- so a symbol drawn before - the setting existed still shows all of its label, only shorter. - Only an element of the terminal type is ever drawn split: the colon - in any other label is part of the label ("-K1:13"). + @return the label as the drawing must show it, the terminal labelling + of the project being read (@sa QETProject::terminalLabelSplit): the + whole label ("X1:1") while the labelling is shown in one field; the + label alone ("X1") when it is shown split and this symbol has a text + field of its own for the terminal number; and the two one under the + other when it is split without such a field -- so a symbol drawn + before the setting existed still shows all of its label, only + shorter. Only an element of the terminal type is ever drawn split: + the colon in any other label is part of the label ("-K1:13"). */ QString Element::labelForDisplay() { const QString label = actualLabel(); + const Diagram *d = diagram(); const bool split = m_data.m_type == ElementData::Terminal - && QetSettings::terminalLabelSplit(); + && d && d->project() && d->project()->terminalLabelSplit(); //The "Show name" box of the selection properties hides the name //of this element on the sheet only: the element keeps all of its @@ -2823,8 +2824,9 @@ QString Element::labelForDisplay() */ QString Element::terminalNumberForDisplay() { + const Diagram *d = diagram(); if (m_data.m_type != ElementData::Terminal - || !QetSettings::terminalLabelSplit()) { + || !d || !d->project() || !d->project()->terminalLabelSplit()) { return QString(); } diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index a7d5b2ef1..6668e0991 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -167,8 +167,8 @@ class Element : public QetGraphicsItem QString actualLabel(); /** What the drawing shows of the label and of the terminal - number, the terminal labelling setting of the moment being - read (@sa QetSettings::terminalLabelSplit). The label itself + number, the terminal labelling of the project being + read (@sa QETProject::terminalLabelSplit). The label itself is never changed by any of them. */ QString labelForDisplay(); @@ -180,8 +180,8 @@ class Element : public QetGraphicsItem The fields which draw the label draw it again: called when what they show of the label has changed under them, a field for the terminal number added or removed, or the - terminal labelling setting switched (@sa - QetSettings::terminalLabelSplit). + terminal labelling setting of the project switched (@sa + QETProject::terminalLabelSplit). */ void refreshLabelFields(); diff --git a/sources/qetinformation.h b/sources/qetinformation.h index 7da77677c..c49f28e5b 100644 --- a/sources/qetinformation.h +++ b/sources/qetinformation.h @@ -41,7 +41,7 @@ namespace QETInformation static QString ELMT_LABEL = "label"; //The part of the label behind its last colon ("1" in "X1:1"), //shown in a text field of its own when the terminal labelling - //is drawn split (@sa QetSettings::terminalLabelSplit) + //is drawn split (@sa QETProject::terminalLabelSplit) static QString ELMT_TERMINAL_NUMBER = "terminal_number"; static QString ELMT_FORMULA = "formula"; /** diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index c8dd66df0..9837657d7 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2331,6 +2331,39 @@ void QETProject::setUprightSymbolTexts(bool upright) } } +/** + @brief QETProject::terminalLabelSplit + @return whether the terminals of this project draw their label split, + the label ("X1") and the terminal number ("1") each in their own text + field, instead of the whole label ("X1:1") in one field. Off unless + the project says otherwise, so a project saved without it draws as it + always did. This is a setting of the project, not of the application: + the same file draws the same way on every machine which opens it. + @sa setTerminalLabelSplit +*/ +bool QETProject::terminalLabelSplit() const { + return m_terminal_label_split; +} + +/** + @brief QETProject::setTerminalLabelSplit + Set whether the terminals of this project draw their label split, and + tell the labels of the drawings already open to draw it again. + @param split +*/ +void QETProject::setTerminalLabelSplit(bool split) +{ + if (split == m_terminal_label_split) { + return; + } + m_terminal_label_split = split; + for (Diagram *diagram : diagrams()) { + for (Element *element : diagram->elements()) { + element->refreshLabelFields(); + } + } +} + /** @brief QETProject::wiringRules @return how many wires a terminal of this project may take @@ -2569,6 +2602,7 @@ QDomDocument QETProject::toXml(QHash *kept_folios, QStringList writeUsageXml(project_root); writeWireHopsXml(project_root); writeSymbolTextsXml(project_root); + writeTerminalLabelSplitXml(project_root); writeWiringRulesXml(project_root); // Properties for news diagrams @@ -3529,6 +3563,7 @@ void QETProject::readProjectXml(QDomDocument &xml_project) readUsageXml(xml_project); readWireHopsXml(xml_project); readSymbolTextsXml(xml_project); + readTerminalLabelSplitXml(xml_project); readWiringRulesXml(xml_project); //Load the default properties for the new diagrams @@ -3893,6 +3928,21 @@ void QETProject::readSymbolTextsXml(QDomDocument &xml_project) && texts.attribute(QStringLiteral("upright")) == QLatin1String("true"); } +/** + @brief QETProject::readTerminalLabelSplitXml + Read the element of the project, if any. A + project without it was saved before the setting existed, or with it + off, and draws the whole label of each terminal in one field. + @param xml_project : the xml description of the project +*/ +void QETProject::readTerminalLabelSplitXml(QDomDocument &xml_project) +{ + const QDomElement labelling = xml_project.documentElement() + .firstChildElement(QStringLiteral("terminal_labelling")); + m_terminal_label_split = !labelling.isNull() + && labelling.attribute(QStringLiteral("split")) == QLatin1String("true"); +} + /** @brief QETProject::readWiringRulesXml Read the element of the project, if any. @@ -4217,6 +4267,23 @@ void QETProject::writeSymbolTextsXml(QDomElement &xml_element) xml_element.appendChild(texts); } +/** + @brief QETProject::writeTerminalLabelSplitXml + Export whether the terminals draw their label split, as a + child of \a xml_element. Written only when on, + so a project with it off saves exactly as before. +*/ +void QETProject::writeTerminalLabelSplitXml(QDomElement &xml_element) +{ + if (!m_terminal_label_split) { + return; + } + QDomElement labelling = xml_element.ownerDocument() + .createElement(QStringLiteral("terminal_labelling")); + labelling.setAttribute(QStringLiteral("split"), QStringLiteral("true")); + xml_element.appendChild(labelling); +} + /** @brief QETProject::writeWiringRulesXml Export the project's wiring rules as a child of diff --git a/sources/qetproject.h b/sources/qetproject.h index 1675f2627..33cb5c897 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -324,6 +324,8 @@ class QETProject : public QObject void setWireHops(WireHops::Mode mode); bool uprightSymbolTexts() const; void setUprightSymbolTexts(bool upright); + bool terminalLabelSplit() const; + void setTerminalLabelSplit(bool split); WiringRules::Settings wiringRules() const; WiringRules::Settings projectWiringRules() const; void setWiringRules(const WiringRules::Settings &rules); @@ -453,6 +455,7 @@ class QETProject : public QObject void readUsageXml(QDomDocument &xml_project); void readWireHopsXml(QDomDocument &xml_project); void readSymbolTextsXml(QDomDocument &xml_project); + void readTerminalLabelSplitXml(QDomDocument &xml_project); void readWiringRulesXml(QDomDocument &xml_project); void writeProjectPropertiesXml(QDomElement &); @@ -462,6 +465,7 @@ class QETProject : public QObject void writeUsageXml(QDomElement &); void writeWireHopsXml(QDomElement &); void writeSymbolTextsXml(QDomElement &); + void writeTerminalLabelSplitXml(QDomElement &); void writeWiringRulesXml(QDomElement &); void addDiagram(Diagram *diagram, int pos = -1); void detachDiagram(Diagram *diagram); @@ -577,6 +581,10 @@ class QETProject : public QObject /// Texts drawn in a turned symbol stay horizontal (on for a new /// project, off for one saved without it), see uprightSymbolTexts() bool m_upright_symbol_texts = false; + /// Terminals draw their label split: the label ("X1") and the + /// terminal number ("1") each in their own text field (off for + /// a project saved without it), see terminalLabelSplit() + bool m_terminal_label_split = false; WiringRules::Settings m_wiring_rules; XmlElementCollection *m_elements_collection = nullptr; QTimer m_save_backup_timer, diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index 645dec4dc..6928e8d2b 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -44,7 +44,6 @@ #include #include "../../diagram.h" -#include "../../qetgraphicsitem/element.h" #include "../../qetproject.h" namespace { @@ -162,11 +161,6 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) : enable(ui->m_wiring_rules_cb->isChecked()); connect(ui->m_wiring_rules_cb, &QCheckBox::toggled, this, enable); } - if (QetSettings::terminalLabelSplit()) { - ui->m_terminal_label_split_rb->setChecked(true); - } else { - ui->m_terminal_label_combined_rb->setChecked(true); - } ui->m_use_gesture_trackpad->setChecked(settings.value("diagramview/gestures", false).toBool()); ui->m_save_label_paste->setChecked(settings.value("diagramcommands/erase-label-on-copy", true).toBool()); ui->m_autonumber_pasted->setChecked(settings.value("diagramcommands/autonumber-pasted-elements", true).toBool()); @@ -421,19 +415,6 @@ void GeneralConfigurationPage::applyConf() if (WiringRules::masterEnabled() && WiringRules::turnsRuleOn(before, rules)) { WiringRulesWarning::show(this); } - } - //The labelling setting is read where the label is drawn, so the - //drawings already open are told to draw it again when it changes - const bool label_split = ui->m_terminal_label_split_rb->isChecked(); - if (QetSettings::terminalLabelSplit() != label_split) { - QetSettings::setTerminalLabelSplit(label_split); - for (QETProject *project : QETApp::registeredProjects()) { - for (Diagram *diagram : project->diagrams()) { - for (Element *element : diagram->elements()) { - element->refreshLabelFields(); - } - } - } } settings.setValue("diagrameditor/autosave-interval", ui->m_autosave_sb->value()); settings.setValue("projectlock/enabled", ui->m_project_lock->isChecked()); diff --git a/sources/ui/configpage/generalconfigurationpage.ui b/sources/ui/configpage/generalconfigurationpage.ui index 40a87f188..5a91496c9 100644 --- a/sources/ui/configpage/generalconfigurationpage.ui +++ b/sources/ui/configpage/generalconfigurationpage.ui @@ -140,48 +140,6 @@ - - - - Display of the terminal labelling - - - - - - A terminal's label (X1:1) is drawn as a whole, the way it has always been. - - - Show the label and the terminal number in one field - - - true - - - - - - - The label (X1) is drawn in one field and the terminal number (1) in another, which a terminal symbol may place where it wants -- and hide when it must. A symbol without such a field shows the two, one under the other. - - - Split the label and the terminal number - - - - - - - Only the drawing changes: what is written in the element, the numbering, the sorting and the exports keep the whole label. - - - true - - - - - - diff --git a/sources/ui/configpage/projectconfigpages.cpp b/sources/ui/configpage/projectconfigpages.cpp index 50da7433a..29d8c0a74 100644 --- a/sources/ui/configpage/projectconfigpages.cpp +++ b/sources/ui/configpage/projectconfigpages.cpp @@ -191,6 +191,11 @@ void ProjectMainConfigPage::applyProjectConf() modified_project = true; } + if (m_project -> terminalLabelSplit() != terminal_label_split_rb_ -> isChecked()) { + m_project -> setTerminalLabelSplit(terminal_label_split_rb_ -> isChecked()); + modified_project = true; + } + //Following the application's rules stores nothing in the project WiringRules::Settings wiring_rules; if (!use_application_rules_cb_ -> isChecked()) { @@ -266,6 +271,27 @@ void ProjectMainConfigPage::initWidgets() "text. Uncheck to make them turn with the element, as before.", "tooltip")); + //How the terminals draw their label: a setting of this project, + //so the same file draws the same way on every machine which + //opens it, like every other choice of how the sheets are drawn + terminal_label_gb_ = new QGroupBox(tr("Display of the terminal labelling", "group box title")); + terminal_label_combined_rb_ = new QRadioButton( + tr("Show the label and the terminal number in one field", "radio button label")); + terminal_label_combined_rb_ -> setToolTip(tr( + "A terminal's label (X1:1) is drawn as a whole, the way it has always been.", + "tooltip")); + terminal_label_split_rb_ = new QRadioButton(tr("Split the label and the terminal number", + "radio button label")); + terminal_label_split_rb_ -> setToolTip(tr( + "The label (X1) is drawn in one field and the terminal number (1) in another, " + "which a terminal symbol may place where it wants -- and hide when it must. " + "A symbol without such a field shows the two, one under the other.", + "tooltip")); + terminal_label_help_ = new QLabel(tr("Only the drawing changes: what is written in the element, " + "the numbering, the sorting and the exports keep the whole label.", + "informative label")); + terminal_label_help_ -> setWordWrap(true); + //How many wires a terminal may take (discussion #1158) wiring_rules_gb_ = new QGroupBox(tr("Conductors per terminal", "group box title")); use_application_rules_cb_ = new QCheckBox(tr("Use the application settings", "checkbox label")); @@ -335,6 +361,13 @@ void ProjectMainConfigPage::initLayout() main_layout0 -> addWidget(upright_symbol_texts_cb_); main_layout0 -> addSpacing(10); + QVBoxLayout *terminal_label_layout = new QVBoxLayout(terminal_label_gb_); + terminal_label_layout -> addWidget(terminal_label_combined_rb_); + terminal_label_layout -> addWidget(terminal_label_split_rb_); + terminal_label_layout -> addWidget(terminal_label_help_); + main_layout0 -> addWidget(terminal_label_gb_); + main_layout0 -> addSpacing(10); + QVBoxLayout *wiring_rules_layout = new QVBoxLayout(wiring_rules_gb_); wiring_rules_layout -> addWidget(use_application_rules_cb_); QHBoxLayout *max_wires_layout = new QHBoxLayout(); @@ -369,6 +402,11 @@ void ProjectMainConfigPage::readValuesFromProject() const int wire_hops_index = wire_hops_cb_ -> findData(WireHops::toString(m_project -> wireHops())); wire_hops_cb_ -> setCurrentIndex(qMax(0, wire_hops_index)); upright_symbol_texts_cb_ -> setChecked(m_project -> uprightSymbolTexts()); + if (m_project -> terminalLabelSplit()) { + terminal_label_split_rb_ -> setChecked(true); + } else { + terminal_label_combined_rb_ -> setChecked(true); + } //The rules that apply: the project's own or the application's const WiringRules::Settings wiring_rules = m_project -> wiringRules(); @@ -425,6 +463,7 @@ void ProjectMainConfigPage::adjustReadOnly() usage_reset_pb_ -> setDisabled(is_read_only); wire_hops_cb_ -> setDisabled(is_read_only); upright_symbol_texts_cb_ -> setDisabled(is_read_only); + terminal_label_gb_ -> setDisabled(is_read_only); wiring_rules_gb_ -> setDisabled(is_read_only); } diff --git a/sources/ui/configpage/projectconfigpages.h b/sources/ui/configpage/projectconfigpages.h index 39df7702f..03cd6e5c5 100644 --- a/sources/ui/configpage/projectconfigpages.h +++ b/sources/ui/configpage/projectconfigpages.h @@ -26,6 +26,7 @@ class QLabel; class QComboBox; class QLineEdit; class QCheckBox; +class QRadioButton; class QPushButton; class QSpinBox; class QGroupBox; @@ -140,6 +141,10 @@ class ProjectMainConfigPage : public ProjectConfigPage { QLabel *wire_hops_label_; QComboBox *wire_hops_cb_; QCheckBox *upright_symbol_texts_cb_; + QGroupBox *terminal_label_gb_; + QRadioButton *terminal_label_combined_rb_; + QRadioButton *terminal_label_split_rb_; + QLabel *terminal_label_help_; QGroupBox *wiring_rules_gb_; QCheckBox *use_application_rules_cb_; QLabel *max_wires_label_; diff --git a/sources/utils/qetsettings.cpp b/sources/utils/qetsettings.cpp index 35ffe8559..632d30b52 100644 --- a/sources/utils/qetsettings.cpp +++ b/sources/utils/qetsettings.cpp @@ -290,28 +290,4 @@ namespace QetSettings QSettings settings; settings.setValue(QStringLiteral("diagrameditor/sheet_background_recent"), colors); } - - /** - * @brief terminalLabelSplit - * @return whether a terminal's label is drawn split, the label and - * the terminal number each in their own text field, instead of the - * whole label ("X1:1") in one field. Off unless the user turned it - * on, so an existing drawing keeps its look. @sa setTerminalLabelSplit - */ - bool terminalLabelSplit() - { - QSettings settings; - return settings.value(QStringLiteral("element/terminal_label_split"), false).toBool(); - } - - /** - * @brief setTerminalLabelSplit - * Store whether a terminal's label is drawn split. @sa terminalLabelSplit - * @param split - */ - void setTerminalLabelSplit(bool split) - { - QSettings settings; - settings.setValue(QStringLiteral("element/terminal_label_split"), split); - } } diff --git a/sources/utils/qetsettings.h b/sources/utils/qetsettings.h index 5e4e42c25..f291caf93 100644 --- a/sources/utils/qetsettings.h +++ b/sources/utils/qetsettings.h @@ -76,20 +76,6 @@ namespace QetSettings QStringList sheetBackgroundRecentColors(); void setSheetBackgroundRecentColors(const QStringList &colors); - - /** - @brief How a terminal's own label is drawn. - - The label is stored as it is written ("X1:1"). With the split on, - the drawing shows the label ("X1") and the terminal number ("1") - as two parts, so a terminal symbol can keep the number in its own - text field and place it where it wants. Only the drawing reacts: - what the element stores, the numbering, the sorting in the - terminal strip editor and every export keep the whole label. - Off by default, so a drawing keeps the look it was made with. - */ - bool terminalLabelSplit(); - void setTerminalLabelSplit(bool split); } #endif // QETSETTINGS_H diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index 8d7f4b4ee..c21105ffb 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -26,11 +26,10 @@ #include "qetgraphicsitem/terminal.h" #include "qetinformation.h" #include "qetproject.h" -#include "utils/qetsettings.h" /** - How a terminal's own label is drawn, the setting of the moment being - read (@sa QetSettings::terminalLabelSplit). + How a terminal's own label is drawn, the terminal labelling of the + project being read (@sa QETProject::terminalLabelSplit). The label stays whole wherever it is used as data: what the element stores, the numbering, the sorting in the terminal strip editor and @@ -149,19 +148,10 @@ class tst_terminallabeldisplay : public QObject } private slots: - void cleanup() - { - //Whatever a test left behind, the next one (and the rest of - //the suite) starts from the shipped default - QetSettings::setTerminalLabelSplit(false); - } - //The shipped default: one field with the whole label, the way it has //always been drawn void oneFieldKeepsTheWholeLabel() { - QetSettings::setTerminalLabelSplit(false); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); @@ -186,11 +176,10 @@ private slots: //can never make a number vanish void splitWithoutNumberFieldShowsTwoLines() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); QVERIFY(element); @@ -216,11 +205,10 @@ private slots: //part in its field, the label no longer carrying the number void splitWithNumberFieldShowsEachInItsOwnField() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); QVERIFY(element); @@ -240,11 +228,10 @@ private slots: //setting says void splitLeavesLabelsWithoutColonAlone() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("S7")); QVERIFY(element); @@ -266,15 +253,16 @@ private slots: QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); - QetSettings::setTerminalLabelSplit(true); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); QVERIFY(element); DynamicElementTextItem *label = fieldBoundTo(element, QETInformation::ELMT_LABEL); QVERIFY(label); QCOMPARE(label->toPlainText(), QStringLiteral("X1\n1")); - QetSettings::setTerminalLabelSplit(false); - element->refreshLabelFields(); + //The setter itself tells the labels of the drawings already + //open to draw again: nothing else to ask for + project.setTerminalLabelSplit(false); QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); QCOMPARE(element->actualLabel(), QStringLiteral("X1:1")); } @@ -284,8 +272,6 @@ private slots: //use of it as data -- only the drawing changes void hiddenNameKeepsEverythingStored() { - QetSettings::setTerminalLabelSplit(false); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); @@ -308,11 +294,10 @@ private slots: //hiding the name can never hide the number void hiddenNameKeepsTheNumberWithSplit() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); QVERIFY(element); @@ -332,11 +317,10 @@ private slots: //draws nothing -- the number is never drawn twice void hiddenNameLeavesTheNumberToItsOwnField() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); QVERIFY(element); @@ -355,8 +339,6 @@ private slots: //file says void nonTerminalAlwaysDrawsItsName() { - QetSettings::setTerminalLabelSplit(false); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); @@ -380,11 +362,10 @@ private slots: //part of the label, not a split waiting to happen void splitLeavesNonTerminalsWithColonAlone() { - QetSettings::setTerminalLabelSplit(true); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); + project.setTerminalLabelSplit(true); Element *element = nonTerminalWithLabel(project, QStringLiteral("-K1:13")); QVERIFY2(element, @@ -404,8 +385,6 @@ private slots: //with it, both ways through the same signal void hiddenNameHidesTheLabelField() { - QetSettings::setTerminalLabelSplit(false); - const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); QETProject project(fixture); QCOMPARE(project.state(), QETProject::Ok); From 208b5e15705390637befcd77db8a95f45c6a9790 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 22 Oct 2026 18:40:27 +0200 Subject: [PATCH 6/8] Leave the German translation file to lupdate The review asked to keep hand-written entries out of the .ts: the file is generated, and hand edits are lost with the next lupdate run anyway. The strings of the labelling setting pick up their translation with the next translation round. --- lang/qet_de.ts | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/lang/qet_de.ts b/lang/qet_de.ts index f63cd5473..747912959 100644 --- a/lang/qet_de.ts +++ b/lang/qet_de.ts @@ -6942,36 +6942,6 @@ Texte und Zahlen sind ebenso erlaubt. Use gestures touchpad Gesten vom Touchpad aktivieren - - - Display of the terminal labelling - Darstellung der Klemmenbeschriftung - - - - A terminal's label (X1:1) is drawn as a whole, the way it has always been. - Die BMK einer Klemme (X1:1) wird ganz gezeichnet, wie bisher. - - - - Show the label and the terminal number in one field - BMK (Label) und Anschlussnummer in einem Feld anzeigen - - - - The label (X1) is drawn in one field and the terminal number (1) in another, which a terminal symbol may place where it wants -- and hide when it must. A symbol without such a field shows the two, one under the other. - Die BMK (X1) wird in einem Feld und die Anschlussnummer (1) in einem weiteren Feld gezeichnet, das ein Klemmensymbol platzieren kann, wo es will -- und ausblenden, wenn es muss. Ein Symbol ohne ein solches Feld zeigt beides untereinander. - - - - Split the label and the terminal number - BMK und Anschlussnummer auftrennen - - - - Only the drawing changes: what is written in the element, the numbering, the sorting and the exports keep the whole label. - Es ändert sich nur die Darstellung: Was im Bauteil steht, die Nummerierung, die Sortierung und die Exporte behalten das ganze Label. - Use sheet numbers instead of their position in the project @@ -16707,11 +16677,6 @@ Möchten Sie sie ersetzen? Label Betriebsmittelkennzeichen (BMK) - - - Terminal number - Anschlussnummer - PLC Table From f6be424cad1de0a9ebbb9ffd507ef0188ae1aa64 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Fri, 23 Oct 2026 13:02:17 +0200 Subject: [PATCH 7/8] Keep the terminal number out of the export columns Manual testing found that the CSV export of the project offered the terminal number as a column, and failed when it was chosen. The export takes its columns from the element info keys, but the query behind the nomenclature has no terminal_number column, so the query breaks. The widget which offers the columns skips the keys it cannot query (formula and exclude_from_bom) and skips the terminal number with them, with a comment saying why. The key itself stays an element info key: the element editor keeps offering %{terminal_number} as a text variable, and the label keeps splitting into label and number the way it was drawn. --- sources/dataBase/ui/elementquerywidget.cpp | 6 ++++ tests/qttest/tst_terminallabeldisplay.cpp | 33 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/sources/dataBase/ui/elementquerywidget.cpp b/sources/dataBase/ui/elementquerywidget.cpp index cdd6afc83..0258050d8 100644 --- a/sources/dataBase/ui/elementquerywidget.cpp +++ b/sources/dataBase/ui/elementquerywidget.cpp @@ -548,6 +548,12 @@ void ElementQueryWidget::setUpItems() { if (key == "formula" || key == "exclude_from_bom") continue; + // Terminal number is read-only and not a column of the nomenclature + // query, so offering it as an export/query column would break the + // export (query fails with "no such column"). It stays available as a + // text variable (%{terminal_number}) in the element editor. + if (key == QETInformation::ELMT_TERMINAL_NUMBER) + continue; auto item = new QListWidgetItem(QETInformation::translatedInfoKey(key), ui->m_var_list); item->setData(Qt::UserRole, key); diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index c21105ffb..797e959be 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -19,7 +19,9 @@ #include "qetapptest.h" #include "TerminalStrip/realterminal.h" #include "TerminalStrip/terminalstrip.h" +#include "dataBase/ui/elementquerywidget.h" #include "diagram.h" +#include #include "properties/elementdata.h" #include "qetgraphicsitem/dynamicelementtextitem.h" #include "qetgraphicsitem/element.h" @@ -407,6 +409,37 @@ private slots: QVERIFY(label->isVisible()); QCOMPARE(label->toPlainText(), QStringLiteral("X1:1")); } + + //The nomenclature/CSV export builds its columns from the element info + //keys, but its query has no terminal_number column: offering the key + //there made the export fail. The key itself stays an info key, so the + //Bauteileditor keeps offering %{terminal_number} as a text variable + void csvExportDoesNotOfferTerminalNumber() + { + ElementQueryWidget widget; + QListWidget *offer = widget.findChild(QStringLiteral("m_var_list")); + QVERIFY2(offer, "the widget which lists the exportable columns"); + + QStringList offered; + for (int row = 0; row < offer->count(); ++row) + offered.append(offer->item(row)->text()); + + const QString terminal_number = QETInformation::translatedInfoKey( + QETInformation::ELMT_TERMINAL_NUMBER); + QVERIFY2(!offered.contains(terminal_number), + qPrintable(QStringLiteral("The export must not offer \"%1\"") + .arg(terminal_number))); + + //The other columns are still offered... + QVERIFY(offered.contains( + QETInformation::translatedInfoKey(QETInformation::ELMT_LABEL))); + + //...and the variable is still a text variable + QVERIFY(QETInformation::elementInfoKeys().contains( + QETInformation::ELMT_TERMINAL_NUMBER)); + QCOMPARE(QETInformation::elementInfoToVar(QETInformation::ELMT_TERMINAL_NUMBER), + QStringLiteral("%{terminal_number}")); + } }; QET_APP_TEST_MAIN(tst_terminallabeldisplay) From 129648f92a4091b6c4dbccb30f163727ae8baf9c Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Fri, 23 Oct 2026 13:21:29 +0200 Subject: [PATCH 8/8] Keep the terminal number out of the element properties The properties window builds a row for every element info key, so it also offered an editable "Terminal number" row -- both for one element and for several at once. The number is not a property of the element but derived from its label: typing there stored a value nothing reads, and the export has no column for it either (previous commit). Both windows skip the key now, the way they skip exclude_from_bom. The key itself stays an element info key, so the element editor keeps offering %{terminal_number} as a text variable. The test builds both windows for a terminal of the fixture and looks at the rows they offer. --- sources/ui/elementinfowidget.cpp | 7 ++++ sources/ui/multielementinfowidget.cpp | 3 ++ tests/qttest/tst_terminallabeldisplay.cpp | 41 +++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/sources/ui/elementinfowidget.cpp b/sources/ui/elementinfowidget.cpp index 0f204331a..4a4a11a66 100644 --- a/sources/ui/elementinfowidget.cpp +++ b/sources/ui/elementinfowidget.cpp @@ -313,6 +313,13 @@ void ElementInfoWidget::buildInterface() //line that currentInfo() then fills with "true"/"false". keys.removeAll(QStringLiteral("exclude_from_bom")); + //The terminal number is not a property of the element: it is + //derived from its label, so an edit row here would store a value + //nothing reads -- and the nomenclature query has no such column, + //which is why the export does not offer it either. The number + //stays a text variable (%{terminal_number}) of the element editor. + keys.removeAll(QETInformation::ELMT_TERMINAL_NUMBER); + for (auto str : keys) { ElementInfoPartWidget *eipw = new ElementInfoPartWidget(str, QETInformation::translatedInfoKey(str), this); diff --git a/sources/ui/multielementinfowidget.cpp b/sources/ui/multielementinfowidget.cpp index 49d31b56a..b150d36b1 100644 --- a/sources/ui/multielementinfowidget.cpp +++ b/sources/ui/multielementinfowidget.cpp @@ -496,6 +496,9 @@ void MultiElementInfoWidget::buildInterface() QStringList keys = QETInformation::elementInfoKeys(); //It has its own check box above, see ElementInfoWidget::buildInterface keys.removeAll(EXCLUDE_FROM_BOM); + //Not an editable property but derived from the label, see + //ElementInfoWidget::buildInterface + keys.removeAll(QETInformation::ELMT_TERMINAL_NUMBER); int line = 0; for (const QString &key : std::as_const(keys)) diff --git a/tests/qttest/tst_terminallabeldisplay.cpp b/tests/qttest/tst_terminallabeldisplay.cpp index 797e959be..cdba571aa 100644 --- a/tests/qttest/tst_terminallabeldisplay.cpp +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -21,6 +21,10 @@ #include "TerminalStrip/terminalstrip.h" #include "dataBase/ui/elementquerywidget.h" #include "diagram.h" +#include "ui/elementinfopartwidget.h" +#include "ui/elementinfowidget.h" +#include "ui/multielementinfowidget.h" +#include #include #include "properties/elementdata.h" #include "qetgraphicsitem/dynamicelementtextitem.h" @@ -440,6 +444,43 @@ private slots: QCOMPARE(QETInformation::elementInfoToVar(QETInformation::ELMT_TERMINAL_NUMBER), QStringLiteral("%{terminal_number}")); } + + //The properties of a terminal have no row for its number: the number + //is derived from the label, so a row there would offer an edit which + //stores a value nothing reads -- the export has no column for it, and + //the drawing takes the number from the label + void propertiesHaveNoRowForTheTerminalNumber() + { + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + Element *element = terminalWithLabel(project, QStringLiteral("X1:1")); + QVERIFY(element); + + //The properties of one element... + ElementInfoWidget one(element); + QStringList one_rows; + const QList parts = one.findChildren(); + for (const ElementInfoPartWidget *part : parts) + one_rows << part->key(); + QVERIFY2(one_rows.contains(QETInformation::ELMT_LABEL), + "The other properties are still offered"); + QVERIFY2(!one_rows.contains(QETInformation::ELMT_TERMINAL_NUMBER), + "The terminal number is no property of the element"); + + //...and the properties of several elements at once + MultiElementInfoWidget many({element}); + QStringList rows; + const QList labels = many.findChildren(); + for (const QLabel *label : labels) + rows << label->text(); + QVERIFY2(rows.contains(QETInformation::translatedInfoKey(QETInformation::ELMT_LABEL)), + "The other properties are still offered"); + QVERIFY2(!rows.contains( + QETInformation::translatedInfoKey(QETInformation::ELMT_TERMINAL_NUMBER)), + "The terminal number is no property of the element"); + } }; QET_APP_TEST_MAIN(tst_terminallabeldisplay)