From c1cc9a5b98b0aaaf18e4548e513b00b3411e1901 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Thu, 24 Sep 2026 09:59:21 +0200 Subject: [PATCH] Show all master-defined slaves in the contact comb behind a new option Add a new cross reference setting per xref type (coil, protection, commutator, PLC), labelled "Afficher tous les esclaves definis par le maitre" and persisted as showallconfiguredslaves. When it is enabled, the contacts display is selected and the master declares contact groups, the contact comb draws every contact group of the master in the master's own order, even when no slave is linked to it yet. Masters without declared contact groups and the option turned off keep the previous behaviour exactly: linked slaves only, sorted by position. - XRefProperties: new property stored in the settings and in the project XML (attribute showallconfiguredslaves, absent means false so old files are unaffected), included in operator==. - XRefPropertiesWidget: new checkbox placed after the terminal names one, enabled only while "Afficher en contacts" is selected; its enabled state is now also set explicitly when a type is loaded (a radio button that does not change emits no toggled()). - For the PLC type the contacts/cross radios, the two display checkboxes and the cross options group are hidden: a PLC master is always drawn as its IO table, those settings have no effect there. Positioning and label settings, which the table really uses, stay. - CrossRefItem: free slots draw the symbol of the group plus the terminal names the master defines (pairs swapped for a single pole NO/NC contact, labels of a changeover contact rotated one step counter-clockwise), without position text and without hover/click. Linked slaves keep drawing from their own data at their assigned group position; links without a group are appended at the end in position order. - Xref lifecycle: the item is created and kept without linked slaves for snap-to-bottom (MasterElement::mustShowXrefWithoutSlave) and for snap-to-label (DynamicElementTextItem::updateXref and ElementTextItemGroup::updateXref, which now also run when the element lands on the scene and re-establish their project connection), so a freshly placed master shows its comb immediately instead of only after the next settings change. updateLabel() resets its geometry when the option is turned off again, so no stale ghost stays. --- sources/properties/xrefproperties.cpp | 6 + sources/properties/xrefproperties.h | 4 + sources/qetgraphicsitem/crossrefitem.cpp | 321 ++++++++++++++---- sources/qetgraphicsitem/crossrefitem.h | 15 +- .../dynamicelementtextitem.cpp | 14 +- .../qetgraphicsitem/elementtextitemgroup.cpp | 48 ++- .../qetgraphicsitem/elementtextitemgroup.h | 3 + sources/qetgraphicsitem/masterelement.cpp | 32 ++ sources/qetgraphicsitem/masterelement.h | 1 + sources/ui/xrefpropertieswidget.cpp | 23 ++ sources/ui/xrefpropertieswidget.ui | 11 + 11 files changed, 406 insertions(+), 72 deletions(-) diff --git a/sources/properties/xrefproperties.cpp b/sources/properties/xrefproperties.cpp index c14c7c643..601fb02ef 100644 --- a/sources/properties/xrefproperties.cpp +++ b/sources/properties/xrefproperties.cpp @@ -30,6 +30,7 @@ XRefProperties::XRefProperties() { m_show_power_ctc = true; m_show_terminal_name = true; + m_show_all_configured_slaves = false; m_display = Cross; m_snap_to = Bottom; m_prefix_keys << "power" << "delay" << "switch"; @@ -51,6 +52,7 @@ void XRefProperties::toSettings(QSettings &settings, { settings.setValue(prefix % "showpowerctc", m_show_power_ctc); settings.setValue(prefix % "showterminalname", m_show_terminal_name); + settings.setValue(prefix % "showallconfiguredslaves", m_show_all_configured_slaves); QString display = m_display == Cross? "cross" : "contacts"; settings.setValue(prefix % "displayhas", display); QString snap = m_snap_to == Bottom? "bottom" : "label"; @@ -84,6 +86,7 @@ void XRefProperties::fromSettings(const QSettings &settings, { m_show_power_ctc = settings.value(prefix % "showpowerctc", true).toBool(); m_show_terminal_name = settings.value(prefix % "showterminalname", true).toBool(); + m_show_all_configured_slaves = settings.value(prefix % "showallconfiguredslaves", false).toBool(); QString display = settings.value(prefix % "displayhas", "cross").toString(); display == "cross"? m_display = Cross : m_display = Contacts; QString snap = settings.value(prefix % "snapto", "label").toString(); @@ -115,6 +118,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const xml_element.setAttribute("showpowerctc", m_show_power_ctc? "true" : "false"); xml_element.setAttribute("showterminalname", m_show_terminal_name? "true" : "false"); + xml_element.setAttribute("showallconfiguredslaves", m_show_all_configured_slaves? "true" : "false"); QString display = m_display == Cross? "cross" : "contacts"; xml_element.setAttribute("displayhas", display); QString snap = m_snap_to == Bottom? "bottom" : "label"; @@ -147,6 +151,7 @@ QDomElement XRefProperties::toXml(QDomDocument &xml_document) const bool XRefProperties::fromXml(const QDomElement &xml_element) { m_show_power_ctc = xml_element.attribute("showpowerctc") == "true"; m_show_terminal_name = xml_element.attribute("showterminalname", "true") == "true"; + m_show_all_configured_slaves = xml_element.attribute("showallconfiguredslaves", "false") == "true"; QString display = xml_element.attribute("displayhas", "cross"); display == "cross"? m_display = Cross : m_display = Contacts; QString snap = xml_element.attribute("snapto", "label"); @@ -200,6 +205,7 @@ QHash XRefProperties::defaultProperties() bool XRefProperties::operator ==(const XRefProperties &xrp) const{ return (m_show_power_ctc == xrp.m_show_power_ctc && m_show_terminal_name == xrp.m_show_terminal_name + && m_show_all_configured_slaves == xrp.m_show_all_configured_slaves && m_display == xrp.m_display && m_snap_to == xrp.m_snap_to && m_prefix == xrp.m_prefix diff --git a/sources/properties/xrefproperties.h b/sources/properties/xrefproperties.h index 572481f65..e2faa015e 100644 --- a/sources/properties/xrefproperties.h +++ b/sources/properties/xrefproperties.h @@ -60,6 +60,9 @@ class XRefProperties : public PropertiesInterface void setShowTerminalName (const bool a) {m_show_terminal_name = a;} bool showTerminalName () const {return m_show_terminal_name;} + void setShowAllConfiguredSlaves (const bool a) {m_show_all_configured_slaves = a;} + bool showAllConfiguredSlaves () const {return m_show_all_configured_slaves;} + void setDisplayHas (const DisplayHas dh) {m_display = dh;} DisplayHas displayHas () const {return m_display;} @@ -88,6 +91,7 @@ class XRefProperties : public PropertiesInterface private: bool m_show_power_ctc; bool m_show_terminal_name; + bool m_show_all_configured_slaves; DisplayHas m_display; SnapTo m_snap_to; Qt::AlignmentFlag m_xref_pos; diff --git a/sources/qetgraphicsitem/crossrefitem.cpp b/sources/qetgraphicsitem/crossrefitem.cpp index d0c0c9706..d5f89f0c7 100644 --- a/sources/qetgraphicsitem/crossrefitem.cpp +++ b/sources/qetgraphicsitem/crossrefitem.cpp @@ -30,6 +30,8 @@ #include "terminal.h" #include "../properties/elementdata.h" +#include + //define the height of the header. static int header = 5; //define the minimal height of the cross (without header) @@ -194,6 +196,37 @@ QString CrossRefItem::elementPositionText( return txt; } +/** + @brief CrossRefItem::showAllConfiguredSlaves + @param elmt : the element displaying the cross reference + @param xrp : xref properties of that element + @return true when the contact comb must show every slave contact the + master defines, even those no slave is linked to yet. That is the case + when the user asked for it, when the comb (contacts) display is the + one in use, and when the master really declares contact groups -- + an element which declares none behaves exactly as before. +*/ +bool CrossRefItem::showAllConfiguredSlaves( + const Element *elmt, + const XRefProperties &xrp) +{ + if (!elmt) return false; + if (!xrp.showAllConfiguredSlaves()) return false; + if (xrp.displayHas() != XRefProperties::Contacts) return false; + + return !elmt->elementData().m_slave_contact_groups.isEmpty(); +} + +/** + @brief CrossRefItem::mustDrawAllConfiguredSlaves + @return showAllConfiguredSlaves for the element of this item and the + current properties. +*/ +bool CrossRefItem::mustDrawAllConfiguredSlaves() const +{ + return showAllConfiguredSlaves(m_element, m_properties); +} + /** @brief CrossRefItem::updateProperties update the current properties @@ -256,8 +289,11 @@ void CrossRefItem::updateLabel() QTimer::singleShot(0, this, [this]{ update(); }); return; } - //Draw cross or contact, only if master element is linked. - else if (! m_element->linkedElements().isEmpty()) + //Draw cross or contact, if master element is linked, or if the user + //asks for the contact comb to show the contact groups of the master + //even before they get a slave. + else if (! m_element->linkedElements().isEmpty() + || mustDrawAllConfiguredSlaves()) { m_update_map = true; XRefProperties::DisplayHas dh = m_properties.displayHas(); @@ -358,7 +394,8 @@ void CrossRefItem::paint( if (m_element->elementData().m_master_type == ElementData::PLC) return; - if (m_element->linkedElements().isEmpty()) return; + if (m_element->linkedElements().isEmpty() + && !mustDrawAllConfiguredSlaves()) return; QPen pen_; pen_.setWidthF(0.5); @@ -727,6 +764,41 @@ void CrossRefItem::drawAsCross(QPainter &painter) fillCrossRef(painter); } +namespace { + /** + @brief contactOption + Map the contact group declared by a master onto the CONTACTS flags + used by CrossRefItem::drawContact, so a group no slave is linked to + yet is drawn like the slave it waits for. + @param group : the contact group of the master + @return the flags describing the contact to draw + */ + int contactOption(const ElementData::SlaveContactGroup &group) + { + int option = 0; + + switch (group.type) + { + case ElementData::NO: option = CrossRefItem::NO; break; + case ElementData::NC: option = CrossRefItem::NC; break; + case ElementData::SW: option = CrossRefItem::SW; break; + case ElementData::Other: option = CrossRefItem::Other; break; + } + + switch (group.subtype) + { + case ElementData::Power: option += CrossRefItem::Power; break; + case ElementData::DelayOn: option += CrossRefItem::DelayOn; break; + case ElementData::DelayOff: option += CrossRefItem::DelayOff; break; + case ElementData::delayOnOff: option += CrossRefItem::DelayOnOff; break; + case ElementData::SSimple: + case ElementData::PLCSlave: break; + } + + return option; + } +} + /** @brief CrossRefItem::drawAsContacts Draw this crossref with symbolic contacts @@ -734,37 +806,81 @@ void CrossRefItem::drawAsCross(QPainter &painter) */ void CrossRefItem::drawAsContacts(QPainter &painter) { - if (m_element -> isFree()) + if (m_element -> isFree() && !mustDrawAllConfiguredSlaves()) return; m_drawed_contacts = 0; if (m_update_map) m_hovered_contacts_map.clear(); QRectF bounding_rect; - //Draw each linked contact - foreach (Element *elmt, m_element->linkedElements()) + //Draw every contact group of the master, in the order the master + //defines them, when the user asked for it and the master declares + //contact groups. Otherwise the comb keeps its historical behavior: + //linked slaves only, in position order. + if (mustDrawAllConfiguredSlaves()) { - DiagramContext info = elmt->kindInformations(); + const QVector groups = + m_element->elementData().m_slave_contact_groups; - for (int i=0; i slotted; + QList unassigned; + foreach (Element *elmt, m_element->linkedElements()) //position order { - int option = 0; - - QString state = info["state"].toString(); - if (state == "NO") option = NO; - else if (state == "NC") option = NC; - else if (state == "SW") option = SW; - else if (state == "Other") option = Other; - - QString type = info["type"].toString(); - if (type == "power") option += Power; - else if (type == "delayOn") option += DelayOn; - else if (type == "delayOff") option += DelayOff; - else if (type == "delayOnOff") option += DelayOnOff; - - QRectF br = drawContact(painter, option, elmt, i); - bounding_rect = bounding_rect.united(br); + const int index = m_element->groupIndexForElement(elmt); + if (index >= 0 && index < groups.size() && !slotted.contains(index)) + slotted.insert(index, elmt); + else + unassigned << elmt; } + + for (int i = 0; i < groups.size(); ++i) + { + if (Element *slave = slotted.value(i, nullptr)) + bounding_rect = bounding_rect.united( + drawLinkedSlaveContacts(painter, slave)); + else + { + //No slave is linked to this group yet: the contact + //symbol of the group is drawn with the terminal names + //the master defines for it, there is no cross reference + //to show for it. + const int option = contactOption(groups.at(i)); + const int poles = qMax(1, groups.at(i).contactCount); + QStringList labels = groups.at(i).labels; + + //A single pole simple contact (NO or NC) reads its two + //numbers the other way round; changeover contacts and + //groups with several poles keep the order the master + //defines, they are already right. + if (poles == 1 && (option & NOC)) + std::reverse(labels.begin(), labels.end()); + + for (int pole = 0; pole < poles; ++pole) + bounding_rect = bounding_rect.united( + drawContact(painter, + option, + nullptr, + pole, + labels)); + } + } + + //Slaves the master doesn't assign to one of its groups (a link + //made before the master declared groups, for example) keep their + //usual place: the end of the comb, in position order. + foreach (Element *elmt, unassigned) + bounding_rect = bounding_rect.united( + drawLinkedSlaveContacts(painter, elmt)); + } + else + { + //Draw each linked contact, in position order + foreach (Element *elmt, m_element->linkedElements()) + bounding_rect = bounding_rect.united( + drawLinkedSlaveContacts(painter, elmt)); } bounding_rect.adjust(-30, -4, 4, 4); @@ -773,17 +889,61 @@ void CrossRefItem::drawAsContacts(QPainter &painter) m_shape_path.addRect(bounding_rect); } +/** + @brief CrossRefItem::drawLinkedSlaveContacts + Draw the contact symbols of one slave linked to this master. + @param painter : painter to use + @param elmt : the slave element to draw + @return the bounding rect of the draw +*/ +QRectF CrossRefItem::drawLinkedSlaveContacts(QPainter &painter, Element *elmt) +{ + QRectF bounding_rect; + DiagramContext info = elmt->kindInformations(); + + for (int i=0; ikindInformations()["type"].toString() == "power"; + (flags & Power) + || (elmt && elmt->kindInformations()["type"].toString() == "power"); const bool is_sw = (flags & SW) && !(flags & NOC); // Check if SW terminals have explicit No/Nc/Common types bool sw_has_typed_terminals = false; - if (is_sw) { + if (is_sw && elmt) { for (Terminal *t : elmt->terminals()) { if (!t) continue; if (t->terminalType() == TerminalData::No || @@ -808,14 +969,43 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in } } - for (Terminal *t : elmt->terminals()) { - if (!t) continue; - const QString tname = t->name(); - if (!tname.isEmpty()) - terminal_names << tname; + if (elmt) { + for (Terminal *t : elmt->terminals()) { + if (!t) continue; + const QString tname = t->name(); + if (!tname.isEmpty()) + terminal_names << tname; + } + } else if (!master_labels.isEmpty()) { + //Empty slot of the contact comb: the slave is missing but the + //master already declares the terminal names of the group, so the + //slot shows them instead of staying mute. The labels are a flat + //list covering every terminal of the group, in the order a linked + //slave would receive them: two per contact (or three for a switch). + if (is_sw) { + //The labels are stored in terminal order (for a typical + //changeover contact: common, NC, NO, i.e. 11, 12, 14), while + //the symbol draws NC bottom-left, NO top-left and the common + //on the right: one step counter-clockwise puts every number + //beside its own contact. + const int base = pole_index * 3; + QStringList trio; + for (int k = 0; k < 3; ++k) + if (base + k < master_labels.size()) + trio << master_labels.at(base + k); + if (trio.size() == 3) + terminal_names << trio.at(1) << trio.at(2) << trio.at(0); + else + terminal_names << trio; //incomplete set: keep stored order + } else { + const int base = pole_index * 2; + for (int k = 0; k < 2; ++k) + if (base + k < master_labels.size()) + terminal_names << master_labels.at(base + k); + } } - if (is_power_ctc) { + if (elmt && is_power_ctc) { // Sort terminals alphanumerically so names like "R1","R2"... or "1","2"... // are ordered correctly. Extract trailing digits for numeric comparison; // fall back to full string comparison when no digits are found. @@ -860,7 +1050,7 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in QRectF bounding_rect = QRectF(0, offset, 24, 10); QPen pen = painter.pen(); - m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black); + elmt && m_hovered_contact == elmt ? pen.setColor(Qt::blue) :pen.setColor(Qt::black); painter.setPen(pen); //Draw NO or NC contact @@ -956,12 +1146,15 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in } } - QRectF text_rect = painter.boundingRect(QRectF(30, offset, 5, 10), Qt::AlignLeft | Qt::AlignVCenter, str); - painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); - bounding_rect = bounding_rect.united(text_rect); + if (!str.isEmpty()) + { + QRectF text_rect = painter.boundingRect(QRectF(30, offset, 5, 10), Qt::AlignLeft | Qt::AlignVCenter, str); + painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); + bounding_rect = bounding_rect.united(text_rect); - if (m_update_map) - m_hovered_contacts_map.insert(elmt, text_rect); + if (m_update_map) + m_hovered_contacts_map.insert(elmt, text_rect); + } ++m_drawed_contacts; } @@ -1034,17 +1227,20 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in } //Draw position text - QRectF text_rect = painter.boundingRect( - QRectF(30, offset+4, 5, 10), - Qt::AlignLeft | Qt::AlignVCenter, - str); - painter.drawText(text_rect, - Qt::AlignLeft | Qt::AlignVCenter, - str); - bounding_rect = bounding_rect.united(text_rect); + if (!str.isEmpty()) + { + QRectF text_rect = painter.boundingRect( + QRectF(30, offset+4, 5, 10), + Qt::AlignLeft | Qt::AlignVCenter, + str); + painter.drawText(text_rect, + Qt::AlignLeft | Qt::AlignVCenter, + str); + bounding_rect = bounding_rect.united(text_rect); - if (m_update_map) - m_hovered_contacts_map.insert(elmt, text_rect); + if (m_update_map) + m_hovered_contacts_map.insert(elmt, text_rect); + } //a switch contact take place of two normal contact m_drawed_contacts += 2; @@ -1066,17 +1262,20 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in painter.drawArc(r2, 160*16, 300*16); //Draw position text - QRectF text_rect = painter.boundingRect( - QRectF(30, offset, 5, 10), - Qt::AlignLeft | Qt::AlignVCenter, - str); - painter.drawText(text_rect, - Qt::AlignLeft | Qt::AlignVCenter, - str); - bounding_rect = bounding_rect.united(text_rect); + if (!str.isEmpty()) + { + QRectF text_rect = painter.boundingRect( + QRectF(30, offset, 5, 10), + Qt::AlignLeft | Qt::AlignVCenter, + str); + painter.drawText(text_rect, + Qt::AlignLeft | Qt::AlignVCenter, + str); + bounding_rect = bounding_rect.united(text_rect); - if (m_update_map) - m_hovered_contacts_map.insert(elmt, text_rect); + if (m_update_map) + m_hovered_contacts_map.insert(elmt, text_rect); + } ++m_drawed_contacts; } return bounding_rect; diff --git a/sources/qetgraphicsitem/crossrefitem.h b/sources/qetgraphicsitem/crossrefitem.h index ec3cf7070..bff9c13e8 100644 --- a/sources/qetgraphicsitem/crossrefitem.h +++ b/sources/qetgraphicsitem/crossrefitem.h @@ -63,6 +63,13 @@ class CrossRefItem : public QGraphicsObject enum { Type = UserType + 1009 }; int type() const override { return Type; } + /// Returns true when \a xrp asks the contact comb of \a elmt to show + /// every slave contact the master defines, even the ones no slave is + /// linked to yet. \a elmt must be a master element. + static bool showAllConfiguredSlaves( + const Element *elmt, + const XRefProperties &xrp); + /** @brief The CONTACTS enum */ @@ -121,7 +128,13 @@ class CrossRefItem : public QGraphicsObject void drawAsCross(QPainter &painter); void drawAsContacts(QPainter &painter); void drawAsPlcTable(QPainter &painter); - QRectF drawContact(QPainter &painter, int flags, Element *elmt, int pole_index = 0); + bool mustDrawAllConfiguredSlaves() const; + QRectF drawLinkedSlaveContacts(QPainter &painter, Element *elmt); + QRectF drawContact(QPainter &painter, + int flags, + Element *elmt, + int pole_index = 0, + const QStringList &master_labels = QStringList()); void fillCrossRef(QPainter &painter); void AddExtraInfo(QPainter &painter, const QString&); QList NOElements() const; diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index 53b43ca8b..a42dd5442 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -822,8 +822,12 @@ QVariant DynamicElementTextItem::itemChange(QGraphicsItem::GraphicsItemChange ch connect(m_parent_element.data(), &Element::linkedElementChanged, this, &DynamicElementTextItem::updateXref); if(m_parent_element.data()->diagram()) connect(m_parent_element.data()->diagram()->project(), &QETProject::XRefPropertiesChanged, this, &DynamicElementTextItem::updateXref); - if(!m_parent_element.data()->linkedElements().isEmpty()) - updateXref(); + //Also call updateXref for a master without any linked slave: + //when the contact comb must show every contact group the master + //defines, the cross ref is expected the moment the element lands + //on the diagram, not only after the first link or the first + //settings change. + updateXref(); } m_first_scene_change = false; @@ -1587,8 +1591,10 @@ void DynamicElementTextItem::updateXref() if(m_text_from == DynamicElementTextItem::ElementInfo && m_info_name == "label" && - !m_parent_element.data()->linkedElements().isEmpty() && - xrp.snapTo() == XRefProperties::Label) + xrp.snapTo() == XRefProperties::Label && + (!m_parent_element.data()->linkedElements().isEmpty() + || CrossRefItem::showAllConfiguredSlaves( + m_parent_element.data(), xrp))) { //For add a Xref, this text must not be in a group if(!parentGroup()) diff --git a/sources/qetgraphicsitem/elementtextitemgroup.cpp b/sources/qetgraphicsitem/elementtextitemgroup.cpp index a9cdeb6c0..253c10146 100644 --- a/sources/qetgraphicsitem/elementtextitemgroup.cpp +++ b/sources/qetgraphicsitem/elementtextitemgroup.cpp @@ -60,12 +60,48 @@ ElementTextItemGroup::ElementTextItemGroup(const QString &name, this, &ElementTextItemGroup::updateXref); if(parent->diagram()) - connect(parent->diagram()->project(), + m_project_xref_connection = connect( + parent->diagram()->project(), &QETProject::XRefPropertiesChanged, this, &ElementTextItemGroup::updateXref); } +/** + @brief ElementTextItemGroup::itemChange + The group is very often built while its element is not on a scene yet + (project or element loading): the connection to the project was then + impossible and the first updateXref() ran without a diagram, so the + cross ref of a master waiting for its slaves stayed invisible until an + unrelated settings change happened to refresh it. Do both here, the + moment the group really reaches the scene. + @param change + @param value + @return +*/ +QVariant ElementTextItemGroup::itemChange( + QGraphicsItem::GraphicsItemChange change, + const QVariant &value) +{ + if (change == QGraphicsItem::ItemSceneHasChanged) + { + if (m_parent_element + && m_parent_element->diagram() + && m_parent_element->diagram()->project()) + { + QETProject *project = m_parent_element->diagram()->project(); + if (!m_project_xref_connection) + m_project_xref_connection = connect( + project, + &QETProject::XRefPropertiesChanged, + this, + &ElementTextItemGroup::updateXref); + updateXref(); + } + } + return QGraphicsItemGroup::itemChange(change, value); +} + ElementTextItemGroup::~ElementTextItemGroup() {} @@ -772,13 +808,13 @@ void ElementTextItemGroup::updateXref() { QETProject *project = m_parent_element->diagram()->project(); - if(m_parent_element->linkType() == Element::Master && - !m_parent_element->linkedElements().isEmpty()) + if(m_parent_element->linkType() == Element::Master) { - XRefProperties xrp = project->defaultXRefProperties(m_parent_element->kindInformations()["type"].toString()); - - if(xrp.snapTo() == XRefProperties::Label) + + if(xrp.snapTo() == XRefProperties::Label && + (!m_parent_element->linkedElements().isEmpty() + || CrossRefItem::showAllConfiguredSlaves(m_parent_element, xrp))) { //At least one text owned by this group must be set with //textFrom -> element info and element info name -> label diff --git a/sources/qetgraphicsitem/elementtextitemgroup.h b/sources/qetgraphicsitem/elementtextitemgroup.h index da1d87e66..1e1e3c082 100644 --- a/sources/qetgraphicsitem/elementtextitemgroup.h +++ b/sources/qetgraphicsitem/elementtextitemgroup.h @@ -101,6 +101,8 @@ class ElementTextItemGroup : public QObject, public QGraphicsItemGroup void keyPressEvent(QKeyEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + QVariant itemChange(GraphicsItemChange change, + const QVariant &value) override; private: void updateXref(); @@ -120,6 +122,7 @@ class ElementTextItemGroup : public QObject, public QGraphicsItemGroup Element *m_parent_element = nullptr; QList m_update_slave_Xref_connection; QGraphicsTextItem *m_slave_Xref_item = nullptr; + QMetaObject::Connection m_project_xref_connection; QMetaObject::Connection m_XrefChanged_timer, m_linked_changed_timer; }; diff --git a/sources/qetgraphicsitem/masterelement.cpp b/sources/qetgraphicsitem/masterelement.cpp index 2c94f2627..e28e27563 100644 --- a/sources/qetgraphicsitem/masterelement.cpp +++ b/sources/qetgraphicsitem/masterelement.cpp @@ -167,6 +167,13 @@ QVariant MasterElement::itemChange(QGraphicsItem::GraphicsItemChange change, con m_Xref_item = new CrossRefItem(this); m_Xref_item->updateLabel(); } + // Same idea for a master whose contact comb must show every + // contact group it defines: the cross ref is expected even + // before any slave is linked to it. + else if (!m_Xref_item && mustShowXrefWithoutSlave()) + { + m_Xref_item = new CrossRefItem(this); + } } return Element::itemChange(change, value); } @@ -193,6 +200,26 @@ void MasterElement::xrefPropertiesChanged() aboutDeleteXref(); } +/** + @brief MasterElement::mustShowXrefWithoutSlave + @return true when the cross ref of this master has to be shown even + though no slave is linked to it yet: the user asks the contact comb to + display every contact group the master defines, the comb (contacts) + display is the one in use, and the cross ref is owned by the element + itself (snap to bottom). +*/ +bool MasterElement::mustShowXrefWithoutSlave() const +{ + if (!diagram() || !diagram()->project()) + return false; + + const XRefProperties xrp = diagram()->project()->defaultXRefProperties( + kindInformations()["type"].toString()); + + return xrp.snapTo() == XRefProperties::Bottom + && CrossRefItem::showAllConfiguredSlaves(this, xrp); +} + /** @brief MasterElement::aboutDeleteXref Check if Xref item must be displayed, if not, delete it. @@ -220,6 +247,11 @@ void MasterElement::aboutDeleteXref() return; } + // The contact comb shows the contact groups the master defines, linked + // or not: keep the item even when it draws nothing so far. + if (mustShowXrefWithoutSlave()) + return; + if (m_Xref_item->boundingRect().isNull()) { delete m_Xref_item; diff --git a/sources/qetgraphicsitem/masterelement.h b/sources/qetgraphicsitem/masterelement.h index 2892e68e1..638143387 100644 --- a/sources/qetgraphicsitem/masterelement.h +++ b/sources/qetgraphicsitem/masterelement.h @@ -60,6 +60,7 @@ class MasterElement : public Element private: void xrefPropertiesChanged(); void aboutDeleteXref (); + bool mustShowXrefWithoutSlave() const; void connectSlavePositionUpdates(Element *slave); void disconnectSlavePositionUpdates(Element *slave); diff --git a/sources/ui/xrefpropertieswidget.cpp b/sources/ui/xrefpropertieswidget.cpp index 97f4dadf8..c113eda45 100644 --- a/sources/ui/xrefpropertieswidget.cpp +++ b/sources/ui/xrefpropertieswidget.cpp @@ -38,6 +38,7 @@ XRefPropertiesWidget::XRefPropertiesWidget(QHash prope ui->setupUi(this); buildUi(); connect(ui->m_display_has_cross_rb, &QRadioButton::toggled, ui->m_cross_properties_gb, &QWidget::setEnabled); + connect(ui->m_display_has_contacts_rb, &QRadioButton::toggled, ui->m_show_all_slaves_cb, &QWidget::setEnabled); connect(ui->m_type_cb, qOverload(&QComboBox::currentIndexChanged), this, &XRefPropertiesWidget::typeChanged); connect(ui->m_snap_to_cb, qOverload(&QComboBox::currentIndexChanged), this, &XRefPropertiesWidget::enableOffsetSB); updateDisplay(); @@ -50,6 +51,7 @@ XRefPropertiesWidget::XRefPropertiesWidget(QHash prope XRefPropertiesWidget::~XRefPropertiesWidget() { disconnect(ui->m_display_has_cross_rb, &QRadioButton::toggled, ui->m_cross_properties_gb, &QWidget::setEnabled); + disconnect(ui->m_display_has_contacts_rb, &QRadioButton::toggled, ui->m_show_all_slaves_cb, &QWidget::setEnabled); disconnect(ui->m_type_cb, qOverload(&QComboBox::currentIndexChanged), this, &XRefPropertiesWidget::typeChanged); disconnect(ui->m_snap_to_cb, qOverload(&QComboBox::currentIndexChanged), this, &XRefPropertiesWidget::enableOffsetSB); delete ui; @@ -143,6 +145,7 @@ void XRefPropertiesWidget::saveProperties(int index) { else if(ui->m_xrefpos_cb->itemData(ui->m_xrefpos_cb->currentIndex()).toString() == "text_field") xrp.setXrefPos(Qt::AlignHCenter); xrp.setShowPowerContac(ui->m_show_power_cb->isChecked()); xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked()); + xrp.setShowAllConfiguredSlaves(ui->m_show_all_slaves_cb->isChecked()); xrp.setPrefix("power", ui->m_power_prefix_le->text()); xrp.setPrefix("delay", ui->m_delay_prefix_le->text()); xrp.setPrefix("switch", ui->m_switch_prefix_le->text()); @@ -200,10 +203,30 @@ void XRefPropertiesWidget::updateDisplay() else if(xrp.getXrefPos() == Qt::AlignHCenter) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("text_field")); ui->m_show_power_cb->setChecked(xrp.showPowerContact()); ui->m_show_terminal_name_cb->setChecked(xrp.showTerminalName()); + ui->m_show_all_slaves_cb->setChecked(xrp.showAllConfiguredSlaves()); + //The radio button only emits toggled() when it really changes: loading + //a type whose display did not change left the checkbox with the enabled + //state of the previously displayed type (it stayed clickable although + //the cross display was selected). Set the state explicitly here. + ui->m_show_all_slaves_cb->setEnabled( + ui->m_display_has_contacts_rb->isChecked()); ui->m_power_prefix_le-> setText(xrp.prefix("power")); ui->m_delay_prefix_le-> setText(xrp.prefix("delay")); ui->m_switch_prefix_le->setText(xrp.prefix("switch")); ui->m_cross_properties_gb->setDisabled(!ui->m_display_has_cross_rb->isChecked()); + + //The cross ref of a PLC master is always drawn as its IO table, and + //the slaves are referenced directly into that table: the contacts/ + //cross choice, the two display checkboxes and the cross options below + //have no effect at all for this type, so they are hidden instead of + //being offered for nothing. The positioning settings and the labels + //(the table really uses them) stay available. + const bool is_plc = type == QLatin1String("plc"); + ui->m_display_has_contacts_rb->setVisible(!is_plc); + ui->m_display_has_cross_rb->setVisible(!is_plc); + ui->m_show_terminal_name_cb->setVisible(!is_plc); + ui->m_show_all_slaves_cb->setVisible(!is_plc); + ui->m_cross_properties_gb->setVisible(!is_plc); } /** diff --git a/sources/ui/xrefpropertieswidget.ui b/sources/ui/xrefpropertieswidget.ui index a6bf89d39..e79070539 100644 --- a/sources/ui/xrefpropertieswidget.ui +++ b/sources/ui/xrefpropertieswidget.ui @@ -151,6 +151,16 @@ + + + + Afficher dans le peigne de contacts tous les contacts esclaves définis par le maître, même ceux qui ne sont pas encore reliés, dans l'ordre défini par le maître + + + Afficher tous les esclaves définis par le maître + + + @@ -327,6 +337,7 @@ m_slave_le m_show_power_cb m_show_terminal_name_cb + m_show_all_slaves_cb m_power_prefix_le m_delay_prefix_le m_switch_prefix_le