From 30d29b933aaed9d8dc573f2f54f0e72f16eebf0f Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 4 Oct 2026 08:54:13 +1300 Subject: [PATCH] Stack cross-references snapped to the bottom of a folio instead of overlapping (#1178) With "snap to bottom", each master's cross was centred under it at the bottom of the folio on its own, so two coils in one column drew their crosses on the same spot and the references printed over each other. The crosses of a folio are now placed together: when two would overlap, the one of the higher element goes above the other, so a column of coils gets its crosses in the same order. Crosses that do not overlap stay where they were. Moving an element sideways now re-places them too, since it can move a cross into or out of another's column. Co-Authored-By: Claude Opus 5.5 --- sources/qetgraphicsitem/crossrefitem.cpp | 70 ++++++++++++++++++++++-- sources/qetgraphicsitem/crossrefitem.h | 1 + 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/sources/qetgraphicsitem/crossrefitem.cpp b/sources/qetgraphicsitem/crossrefitem.cpp index fd9267938..595fcdfad 100644 --- a/sources/qetgraphicsitem/crossrefitem.cpp +++ b/sources/qetgraphicsitem/crossrefitem.cpp @@ -117,6 +117,7 @@ void CrossRefItem::setUpConnection() set=true; else if(m_properties.snapTo() == XRefProperties::Bottom && !m_text && !m_group) //Snap to bottom of element and parent is the element itself { + m_update_connection << connect(m_element, &Element::xChanged, this, &CrossRefItem::autoPos); m_update_connection << connect(m_element, &Element::yChanged, this, &CrossRefItem::autoPos); m_update_connection << connect(m_element, &Element::rotationChanged, this, &CrossRefItem::autoPos); set=true; @@ -329,15 +330,74 @@ void CrossRefItem::autoPos() //We calculate the position according to the snapTo of the xrefproperties if (m_properties.snapTo() == XRefProperties::Bottom) - QGIUtility::centerToBottomDiagram(this, - m_element, - m_properties.offset() <= 40 - ? 5 - : m_properties.offset()); + stackAtBottom(); else QGIUtility::centerToParentBottom(this); } +/** + @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. +*/ +void CrossRefItem::stackAtBottom() +{ + Diagram *diagram = m_element->diagram(); + if (!diagram) return; + + QList xrefs; + for (QGraphicsItem *item : diagram->items()) { + if (item->type() != CrossRefItem::Type) continue; + auto xref = static_cast(item); + if (xref->m_properties.snapTo() == XRefProperties::Bottom + && !xref->m_text && !xref->m_group + && xref->m_element->elementData().m_master_type != ElementData::PLC) + xrefs << xref; + } + if (!xrefs.contains(this)) xrefs << this; + + // The lowest element first, so it keeps the bottom of the folio. + // Position, then uuid, so the order is the same on every load. + std::sort(xrefs.begin(), xrefs.end(), + [](const CrossRefItem *a, const CrossRefItem *b) { + const QPointF pa = a->m_element->sceneBoundingRect().center(); + const QPointF pb = b->m_element->sceneBoundingRect().center(); + if (pa.y() != pb.y()) return pa.y() > pb.y(); + if (pa.x() != pb.x()) return pa.x() < pb.x(); + return a->m_element->uuid() < b->m_element->uuid(); + }); + + const qreal gap = 5; + 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; + + // Move up past every cross already placed that it would overlap. + QRectF rect = xref->sceneBoundingRect(); + const qreal bottom = rect.bottom(); + 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; + } + } + if (rect.bottom() != bottom) + xref->setPos(xref->parentItem()->mapFromScene( + xref->scenePos() + QPointF(0, rect.bottom() - bottom))); + placed << rect; + } +} + /** @brief CrossRefItem::sceneEvent @param event diff --git a/sources/qetgraphicsitem/crossrefitem.h b/sources/qetgraphicsitem/crossrefitem.h index bff9c13e8..bb7068981 100644 --- a/sources/qetgraphicsitem/crossrefitem.h +++ b/sources/qetgraphicsitem/crossrefitem.h @@ -58,6 +58,7 @@ class CrossRefItem : public QGraphicsObject private: void init(); void setUpConnection(); + void stackAtBottom(); public: enum { Type = UserType + 1009 };