diff --git a/sources/editor/ui/elementpropertieseditorwidget.cpp b/sources/editor/ui/elementpropertieseditorwidget.cpp index 7a24f984f..6e16ffa1b 100644 --- a/sources/editor/ui/elementpropertieseditorwidget.cpp +++ b/sources/editor/ui/elementpropertieseditorwidget.cpp @@ -589,6 +589,29 @@ void ElementPropertiesEditorWidget::populateSlaveGroupsTable() contact_ct->setValue(group.contactCount); ui->m_slave_groups_table->setCellWidget(i, 2, contact_ct); + // When the contact count changes, keep the terminal count in step + // with it, otherwise the two drift apart (both are edited + // independently): the stored terminals-per-contact ratio is kept, + // or the contact type default (2, 3 for a switch) is used when the + // stored values don't divide evenly (inconsistent legacy data). + const int old_contacts = group.contactCount; + const int old_terminals = group.terminalCount; + connect(contact_ct, QOverload::of(&QSpinBox::valueChanged), + this, [this, i, old_contacts, old_terminals](int val) { + if (i < m_data.m_slave_contact_groups.size()) { + readSlaveGroupsFromTable(); + auto &group = m_data.m_slave_contact_groups[i]; + int per_pole = old_terminals / qMax(1, old_contacts); + if (per_pole < 1 || old_terminals % qMax(1, old_contacts) != 0) + per_pole = group.type == ElementData::SW ? 3 : 2; + else if (group.type == ElementData::SW && per_pole < 3) + per_pole = 3; //a switch needs common, NC and NO + group.contactCount = val; + group.terminalCount = val * per_pole; + populateSlaveGroupsTable(); + } + }); + // Terminal count auto *terminal_ct = new QSpinBox(ui->m_slave_groups_table); terminal_ct->setMinimum(1); 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..60b0f7ca9 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,99 @@ 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 (checked against the + //diagram). Changeover contacts are not handled here: + //their labels are mapped to the right contact half in + //drawContact(), per pole, so multi pole changeovers work + //too. Groups with several NO/NC poles keep the order the + //master defines: a 3 pole power contact already reads + //correctly that way. + if (poles == 1 && (option & NOC)) + std::reverse(labels.begin(), labels.end()); + + //The declared terminals are distributed over the declared + //poles. terminalCount and contactCount are edited + //independently in the element editor, so the list can be + //shorter than two entries per pole (three for a switch): + //every pole then gets its share of what exists, instead + //of a fixed 2/3 stride starving all but the first poles. + const int per_pole = labels.size() / poles; + const int extra = labels.size() % poles; + int begin = 0; + for (int pole = 0; pole < poles; ++pole) + { + const int count = per_pole + (pole < extra ? 1 : 0); + const QStringList pole_labels = labels.mid(begin, count); + begin += count; + bounding_rect = bounding_rect.united( + drawContact(painter, + option, + nullptr, + pole, + 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 +907,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"; + 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,11 +986,34 @@ 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, so the slot shows + //them instead of staying mute. master_labels contains exactly + //the terminals of this pole (the caller slices the declared + //terminal list over the declared poles), in the order a linked + //slave would receive them. + 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: every stored entry goes to its own position. + //Entries the master doesn't declare (terminal count lower + //than three) simply stay empty instead of landing on the + //wrong contact half like the raw stored order would. + terminal_names << master_labels.value(1) + << master_labels.value(2) + << master_labels.value(0); + } else { + terminal_names = master_labels; + } } if (is_power_ctc) { @@ -860,7 +1061,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,11 +1157,18 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in } } + //The hit rect is registered even when the position text is + //empty: a linked contact had (and keeps) its hover/click entry + //in that case too, only the drawing is skipped. Free slots + //(elmt == nullptr) have nothing to click and stay out of the map. 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()) + { + painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); + bounding_rect = bounding_rect.united(text_rect); + } - if (m_update_map) + if (m_update_map && elmt) m_hovered_contacts_map.insert(elmt, text_rect); ++m_drawed_contacts; @@ -1038,12 +1246,16 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in 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) + if (!str.isEmpty()) + { + painter.drawText(text_rect, + Qt::AlignLeft | Qt::AlignVCenter, + str); + bounding_rect = bounding_rect.united(text_rect); + } + //Hit rect kept even for an empty position text (as before), + //free slots are not clickable. + if (m_update_map && elmt) m_hovered_contacts_map.insert(elmt, text_rect); //a switch contact take place of two normal contact @@ -1067,15 +1279,19 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in //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 (m_update_map) + QRectF(30, offset, 5, 10), + Qt::AlignLeft | Qt::AlignVCenter, + str); + if (!str.isEmpty()) + { + painter.drawText(text_rect, + Qt::AlignLeft | Qt::AlignVCenter, + str); + bounding_rect = bounding_rect.united(text_rect); + } + //Hit rect kept even for an empty position text (as before), + //free slots are not clickable. + if (m_update_map && elmt) m_hovered_contacts_map.insert(elmt, text_rect); ++m_drawed_contacts; } 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 7082dfc8a..1316d992b 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -810,8 +810,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; @@ -1582,8 +1586,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..52f82551b 100644 --- a/sources/qetgraphicsitem/masterelement.cpp +++ b/sources/qetgraphicsitem/masterelement.cpp @@ -167,6 +167,14 @@ 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); + m_Xref_item->updateLabel(); + } } return Element::itemChange(change, value); } @@ -193,6 +201,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 +248,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