From b8eb5c90a1f919f800419fdf0d0d6e228f70393a Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 5 Oct 2026 17:24:11 +1300 Subject: [PATCH] Cross references at the folio bottom: stack only when texts overlap, add a checkbox (#1287 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stacking added in #1287 tested the crosses' bounding rects, so crosses whose frames or empty margins touched were raised although no text was hidden (the list display case found in review). The texts each cross really draws are now recorded while its geometry is built, and a cross is raised only when one of its texts would be drawn over a text of another one. A new "Empiler les références dont les textes se chevauchent" checkbox in the cross reference properties, per type, turns the stacking off; the crosses of that type are then only centred, as before #1287. It is saved in the project (xref attribute "stackoverlapping") and in the settings, and is on when absent. Co-Authored-By: Claude Opus 5.5 --- sources/properties/xrefproperties.cpp | 6 ++ sources/properties/xrefproperties.h | 4 + sources/qetgraphicsitem/crossrefitem.cpp | 106 +++++++++++++++-------- sources/qetgraphicsitem/crossrefitem.h | 5 ++ sources/ui/xrefpropertieswidget.cpp | 5 ++ sources/ui/xrefpropertieswidget.ui | 11 +++ 6 files changed, 101 insertions(+), 36 deletions(-) diff --git a/sources/properties/xrefproperties.cpp b/sources/properties/xrefproperties.cpp index 22fe44cea..e6ed7cd53 100644 --- a/sources/properties/xrefproperties.cpp +++ b/sources/properties/xrefproperties.cpp @@ -61,6 +61,7 @@ XRefProperties::XRefProperties() m_show_power_ctc = true; m_show_terminal_name = true; m_show_all_configured_slaves = false; + m_stack_overlapping = true; m_display = Cross; m_snap_to = Bottom; m_prefix_keys << "power" << "delay" << "switch"; @@ -83,6 +84,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); + settings.setValue(prefix % "stackoverlapping", m_stack_overlapping); QString display = m_display == Cross? "cross" : "contacts"; settings.setValue(prefix % "displayhas", display); QString snap = m_snap_to == Bottom? "bottom" : "label"; @@ -117,6 +119,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(); + m_stack_overlapping = settings.value(prefix % "stackoverlapping", true).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(); @@ -148,6 +151,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"); + xml_element.setAttribute("stackoverlapping", m_stack_overlapping? "true" : "false"); QString display = m_display == Cross? "cross" : "contacts"; xml_element.setAttribute("displayhas", display); QString snap = m_snap_to == Bottom? "bottom" : "label"; @@ -181,6 +185,7 @@ 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"; + m_stack_overlapping = xml_element.attribute("stackoverlapping", "true") == "true"; QString display = xml_element.attribute("displayhas", "cross"); display == "cross"? m_display = Cross : m_display = Contacts; QString snap = xml_element.attribute("snapto", "label"); @@ -228,6 +233,7 @@ 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_stack_overlapping == xrp.m_stack_overlapping && 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 e2faa015e..08dfeca02 100644 --- a/sources/properties/xrefproperties.h +++ b/sources/properties/xrefproperties.h @@ -63,6 +63,9 @@ class XRefProperties : public PropertiesInterface void setShowAllConfiguredSlaves (const bool a) {m_show_all_configured_slaves = a;} bool showAllConfiguredSlaves () const {return m_show_all_configured_slaves;} + void setStackOverlapping (const bool a) {m_stack_overlapping = a;} + bool stackOverlapping () const {return m_stack_overlapping;} + void setDisplayHas (const DisplayHas dh) {m_display = dh;} DisplayHas displayHas () const {return m_display;} @@ -92,6 +95,7 @@ class XRefProperties : public PropertiesInterface bool m_show_power_ctc; bool m_show_terminal_name; bool m_show_all_configured_slaves; + bool m_stack_overlapping; 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 fb710a968..33b17c463 100644 --- a/sources/qetgraphicsitem/crossrefitem.cpp +++ b/sources/qetgraphicsitem/crossrefitem.cpp @@ -268,6 +268,7 @@ void CrossRefItem::updateLabel() m_shape_path = QPainterPath(); prepareGeometryChange(); m_bounding_rect = QRectF(); + m_text_rects.clear(); // Build geometry and m_hovered_contacts_map using a QImage-backed // painter so font metrics match the screen painter in paint(). @@ -343,9 +344,12 @@ void CrossRefItem::autoPos() @brief CrossRefItem::stackAtBottom Places every cross reference of this folio that snaps to the bottom. Each one is centred under its element at the bottom of the folio, and - when two would overlap, the one of the higher element goes above the - other: several coils in one column get their crosses stacked in the - same order as the coils, instead of all on the same spot. + when its texts would be drawn over the texts of another one, the one + of the higher element goes above the other: several coils in one + column get their crosses stacked in the same order as the coils, + instead of all on the same spot. Crosses that only touch, or overlap + without any text over text, stay where they are. + The types whose properties do not ask for stacking are only centred. */ void CrossRefItem::stackAtBottom() { @@ -374,41 +378,56 @@ void CrossRefItem::stackAtBottom() return a->m_element->uuid() < b->m_element->uuid(); }); + struct Placed { QRectF rect; QList texts; }; const qreal gap = 5; - QList placed; + QList placed; for (CrossRefItem *xref : std::as_const(xrefs)) { const qreal offset = xref->m_properties.offset(); QGIUtility::centerToBottomDiagram(xref, xref->m_element, offset <= 40 ? 5 : offset); - if (xref->boundingRect().isEmpty()) continue; + if (xref->boundingRect().isEmpty() + || !xref->m_properties.stackOverlapping()) + continue; - // Move up past every cross already placed that it would overlap. - // A list of contacts has an empty margin in its bounding rect: - // only its content is tested, so side by side lists do not - // count as overlapping. - const bool list = xref->m_properties.displayHas() - == XRefProperties::Contacts; - QRectF rect = xref->sceneBoundingRect(); - if (list) - rect.adjust(list_margin_left, 0, -list_margin_right, 0); - const qreal bottom = rect.bottom(); + Placed self; + self.rect = xref->sceneBoundingRect(); + const QTransform to_scene = xref->sceneTransform(); + for (const QRectF &text : std::as_const(xref->m_text_rects)) + self.texts << to_scene.mapRect(text); + const qreal bottom = self.rect.bottom(); + + // Move up past every cross already placed whose texts it would + // draw over. Each move goes strictly up, so this ends. for (bool moved = true; moved; ) { moved = false; - for (const QRectF &other : std::as_const(placed)) - if (rect.left() < other.right() && other.left() < rect.right() - && rect.top() < other.bottom() + gap - && other.top() - gap < rect.bottom()) { - rect.moveBottom(other.top() - gap); - moved = true; - } + for (const Placed &other : std::as_const(placed)) { + const qreal dy = other.rect.top() - gap - self.rect.bottom(); + if (dy >= 0 || !textsOverlap(self.texts, other.texts)) + continue; + self.rect.translate(0, dy); + for (QRectF &text : self.texts) text.translate(0, dy); + moved = true; + } } - if (rect.bottom() != bottom) + if (self.rect.bottom() != bottom) xref->setPos(xref->parentItem()->mapFromScene( - xref->scenePos() + QPointF(0, rect.bottom() - bottom))); - placed << rect; + xref->scenePos() + QPointF(0, self.rect.bottom() - bottom))); + placed << self; } } +/** + @brief CrossRefItem::textsOverlap + @return true if one of the rects of a intersects one of the rects of b +*/ +bool CrossRefItem::textsOverlap(const QList &a, const QList &b) +{ + for (const QRectF &ra : a) + for (const QRectF &rb : b) + if (ra.intersects(rb)) return true; + return false; +} + /** @brief CrossRefItem::sceneEvent @param event @@ -1156,9 +1175,9 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in painter.setFont(QETApp::diagramTextsFont(4)); QRectF bt(0, offset, 24, 10); if (terminal_names.size() >= 1) - painter.drawText(bt, Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); + drawText(painter, bt, Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); if (terminal_names.size() >= 2) - painter.drawText(bt, Qt::AlignRight|Qt::AlignTop, terminal_names[1]); + drawText(painter, bt, Qt::AlignRight|Qt::AlignTop, terminal_names[1]); painter.setFont(QETApp::diagramTextsFont(5)); } @@ -1241,7 +1260,7 @@ 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); if (!str.isEmpty()) { - painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); + drawText(painter, text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); bounding_rect = bounding_rect.united(text_rect); } @@ -1286,13 +1305,13 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in painter.setFont(QETApp::diagramTextsFont(4)); // Storage order set above: [0]=NC, [1]=NO, [2]=Common if (terminal_names.size() >= 2) - painter.drawText(QRectF(0, offset, 8, 8), + drawText(painter, QRectF(0, offset, 8, 8), Qt::AlignLeft|Qt::AlignTop, terminal_names[1]); // NO top-left if (terminal_names.size() >= 3) - painter.drawText(QRectF(16, offset+4, 8, 6), + drawText(painter, QRectF(16, offset+4, 8, 6), Qt::AlignRight|Qt::AlignTop, terminal_names[2]); // Common right if (terminal_names.size() >= 1) - painter.drawText(QRectF(0, offset+9, 8, 6), + drawText(painter, QRectF(0, offset+9, 8, 6), Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); // NC bottom-left painter.setFont(QETApp::diagramTextsFont(5)); } @@ -1325,7 +1344,7 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in str); if (!str.isEmpty()) { - painter.drawText(text_rect, + drawText(painter, text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); bounding_rect = bounding_rect.united(text_rect); @@ -1361,7 +1380,7 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in str); if (!str.isEmpty()) { - painter.drawText(text_rect, + drawText(painter, text_rect, Qt::AlignLeft | Qt::AlignVCenter, str); bounding_rect = bounding_rect.united(text_rect); @@ -1454,7 +1473,7 @@ void CrossRefItem::fillCrossRef(QPainter &painter) QSize(middle_cross, 1)), Qt::AlignLeft, str); - painter.drawText(bounding, Qt::AlignLeft, str); + drawText(painter, bounding, Qt::AlignLeft, str); if (m_update_map) { QString pos_str = elementPositionText(elmt, true); @@ -1534,7 +1553,7 @@ void CrossRefItem::fillCrossRef(QPainter &painter) QSize(middle_cross, 1)), Qt::AlignRight, str); - painter.drawText(bounding, Qt::AlignRight, str); + drawText(painter, bounding, Qt::AlignRight, str); if (m_update_map) { QString pos_str = elementPositionText(elmt, true); @@ -1582,7 +1601,7 @@ void CrossRefItem::AddExtraInfo(QPainter &painter, const QString& type) r, Qt::TextWordWrap | Qt::AlignHCenter, text); - painter.drawText(text_bounding, + drawText(painter, text_bounding, Qt::TextWordWrap | Qt::AlignHCenter, text); @@ -1598,6 +1617,21 @@ void CrossRefItem::AddExtraInfo(QPainter &painter, const QString& type) } } +/** + @brief CrossRefItem::drawText + Draws text in rect with painter, like QPainter::drawText. While the + geometry is built (m_update_map), also stores the rect really covered + by the text, so stackAtBottom() can tell texts drawn over each other. +*/ +void CrossRefItem::drawText(QPainter &painter, const QRectF &rect, + int flags, const QString &text) +{ + QRectF drawn; + painter.drawText(rect, flags, text, &drawn); + if (m_update_map && !text.isEmpty()) + m_text_rects << painter.transform().mapRect(drawn); +} + /** @brief CrossRefItem::NOElements @return The linked elements of m_element which are open or switch contact. diff --git a/sources/qetgraphicsitem/crossrefitem.h b/sources/qetgraphicsitem/crossrefitem.h index bb7068981..a16167d83 100644 --- a/sources/qetgraphicsitem/crossrefitem.h +++ b/sources/qetgraphicsitem/crossrefitem.h @@ -59,6 +59,8 @@ class CrossRefItem : public QGraphicsObject void init(); void setUpConnection(); void stackAtBottom(); + static bool textsOverlap(const QList &a, + const QList &b); public: enum { Type = UserType + 1009 }; @@ -138,6 +140,8 @@ class CrossRefItem : public QGraphicsObject const QStringList &master_labels = QStringList()); void fillCrossRef(QPainter &painter); void AddExtraInfo(QPainter &painter, const QString&); + void drawText(QPainter &painter, const QRectF &rect, int flags, + const QString &text); QList NOElements() const; QList NCElements() const; @@ -150,6 +154,7 @@ class CrossRefItem : public QGraphicsObject int m_drawed_contacts; bool m_update_map = false; QMultiMap m_hovered_contacts_map; + QList m_text_rects; //rects of the drawn texts (local coords) Element *m_hovered_contact = nullptr; DynamicElementTextItem *m_text = nullptr; ElementTextItemGroup *m_group = nullptr; diff --git a/sources/ui/xrefpropertieswidget.cpp b/sources/ui/xrefpropertieswidget.cpp index d08c8d5c7..911804491 100644 --- a/sources/ui/xrefpropertieswidget.cpp +++ b/sources/ui/xrefpropertieswidget.cpp @@ -146,6 +146,7 @@ void XRefPropertiesWidget::saveProperties(int index) { 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.setStackOverlapping(ui->m_stack_overlapping_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()); @@ -201,6 +202,8 @@ void XRefPropertiesWidget::updateDisplay() ui->m_snap_to_cb->setCurrentIndex(ui->m_snap_to_cb->findData("label")); ui->m_offset_sb->setEnabled(false); } + ui->m_stack_overlapping_cb->setChecked(xrp.stackOverlapping()); + ui->m_stack_overlapping_cb->setEnabled(ui->m_offset_sb->isEnabled()); if(xrp.getXrefPos() == Qt::AlignTop) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("top")); else if(xrp.getXrefPos() == Qt::AlignLeft) ui->m_xrefpos_cb->setCurrentIndex(ui->m_xrefpos_cb->findData("left")); @@ -233,6 +236,7 @@ void XRefPropertiesWidget::updateDisplay() 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_stack_overlapping_cb->setVisible(!is_plc); ui->m_cross_properties_gb->setVisible(!is_plc); } @@ -261,4 +265,5 @@ void XRefPropertiesWidget::enableOffsetSB(int i){ ui->m_offset_sb->setEnabled(false); else ui->m_offset_sb->setEnabled(true); + ui->m_stack_overlapping_cb->setEnabled(ui->m_offset_sb->isEnabled()); } diff --git a/sources/ui/xrefpropertieswidget.ui b/sources/ui/xrefpropertieswidget.ui index e79070539..0d33c7a27 100644 --- a/sources/ui/xrefpropertieswidget.ui +++ b/sources/ui/xrefpropertieswidget.ui @@ -89,6 +89,16 @@ + + + + + Quand les textes de deux références croisées se chevauchent, celle de l'élément le plus haut est placée au-dessus de l'autre. + + + Empiler les références dont les textes se chevauchent + + @@ -330,6 +340,7 @@ m_type_cb m_snap_to_cb m_offset_sb + m_stack_overlapping_cb m_xrefpos_cb m_display_has_contacts_rb m_display_has_cross_rb