Merge pull request #1311 from ispyisail/feature/xref-stack-option

Add a setting for stacking cross references at the folio bottom
This commit is contained in:
Laurent Trinques
2026-10-05 06:34:49 +02:00
committed by GitHub
6 changed files with 101 additions and 36 deletions
+6
View File
@@ -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
+4
View File
@@ -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;
+70 -36
View File
@@ -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<QRectF> texts; };
const qreal gap = 5;
QList<QRectF> placed;
QList<Placed> 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<QRectF> &a, const QList<QRectF> &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.
+5
View File
@@ -59,6 +59,8 @@ class CrossRefItem : public QGraphicsObject
void init();
void setUpConnection();
void stackAtBottom();
static bool textsOverlap(const QList<QRectF> &a,
const QList<QRectF> &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<Element *> NOElements() const;
QList<Element *> NCElements() const;
@@ -150,6 +154,7 @@ class CrossRefItem : public QGraphicsObject
int m_drawed_contacts;
bool m_update_map = false;
QMultiMap <Element *, QRectF> m_hovered_contacts_map;
QList <QRectF> m_text_rects; //rects of the drawn texts (local coords)
Element *m_hovered_contact = nullptr;
DynamicElementTextItem *m_text = nullptr;
ElementTextItemGroup *m_group = nullptr;
+5
View File
@@ -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());
}
+11
View File
@@ -89,6 +89,16 @@
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="m_stack_overlapping_cb">
<property name="toolTip">
<string>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.</string>
</property>
<property name="text">
<string>Empiler les références dont les textes se chevauchent</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
@@ -330,6 +340,7 @@
<tabstop>m_type_cb</tabstop>
<tabstop>m_snap_to_cb</tabstop>
<tabstop>m_offset_sb</tabstop>
<tabstop>m_stack_overlapping_cb</tabstop>
<tabstop>m_xrefpos_cb</tabstop>
<tabstop>m_display_has_contacts_rb</tabstop>
<tabstop>m_display_has_cross_rb</tabstop>