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/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index e07d72294..6e7ece16b 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,11 @@ 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}"))) + refreshLabelShown(); + 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) @@ -418,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. @@ -450,26 +491,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 +527,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 +573,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,16 +597,32 @@ 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(); + } + + //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); } @@ -956,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) @@ -1157,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 @@ -1174,6 +1258,12 @@ void DynamicElementTextItem::elementInfoChanged() Element *element = elementUseForInfo(); if(element) { dc = element->elementInformations(); + //The label and the terminal number are drawn the way the + //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()); + dc.addValue(QETInformation::ELMT_TERMINAL_NUMBER, element->terminalNumberForDisplay()); } QString final_text; @@ -1183,12 +1273,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 +1290,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 +1458,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 +1551,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 +1564,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/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 a6c4d769e..fe95904c3 100644 --- a/sources/qetgraphicsitem/element.cpp +++ b/sources/qetgraphicsitem/element.cpp @@ -1689,6 +1689,17 @@ 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 + 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 +1735,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 +1750,37 @@ 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 of the project was switched + (@sa QETProject::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. @@ -2735,6 +2774,156 @@ QString Element::actualLabel() } } +/** + @brief Element::labelForDisplay + @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 + && 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 + //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; + } + + 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 -- 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() +{ + const Diagram *d = diagram(); + if (m_data.m_type != ElementData::Terminal + || !d || !d->project() || !d->project()->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::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 + 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 2cb097b9a..2c6ad164c 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -166,6 +166,25 @@ 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 of the project being + read (@sa QETProject::terminalLabelSplit). The label itself + is never changed by any of them. + */ + QString labelForDisplay(); + 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 + field for the terminal number added or removed, or the + terminal labelling setting of the project switched (@sa + QETProject::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..c49f28e5b 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 QETProject::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/qetproject.cpp b/sources/qetproject.cpp index 742ff9494..4ffb4f250 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -2316,6 +2316,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 @@ -2554,6 +2587,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 @@ -3525,6 +3559,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 @@ -3889,6 +3924,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. @@ -4213,6 +4263,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 8e4e17eb6..2196d1cb6 100644 --- a/sources/qetproject.h +++ b/sources/qetproject.h @@ -325,6 +325,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); @@ -454,6 +456,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 &); @@ -463,6 +466,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); @@ -572,6 +576,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/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/ui/elementinfowidget.cpp b/sources/ui/elementinfowidget.cpp index cd1640bb9..4a4a11a66 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); } @@ -307,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); @@ -318,6 +331,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 +381,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 +687,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 +777,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 +871,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..b150d36b1 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) { @@ -486,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)) @@ -588,12 +601,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/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 64ab7f836..62e5d26ac 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..cdba571aa --- /dev/null +++ b/tests/qttest/tst_terminallabeldisplay.cpp @@ -0,0 +1,487 @@ +// 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 "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" +#include "qetgraphicsitem/element.h" +#include "qetgraphicsitem/terminal.h" +#include "qetinformation.h" +#include "qetproject.h" + +/** + 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 + 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; + } + + //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; + } + + //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: + //The shipped default: one field with the whole label, the way it has + //always been drawn + void oneFieldKeepsTheWholeLabel() + { + 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() + { + 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); + + 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() + { + 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); + + 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() + { + 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); + + 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); + + 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")); + + //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")); + } + + //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() + { + 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() + { + 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); + 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() + { + 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); + 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() + { + 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()); + } + + //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() + { + 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, + "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 + void hiddenNameHidesTheLabelField() + { + 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")); + } + + //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}")); + } + + //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) +#include "tst_terminallabeldisplay.moc"