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