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_power_ctc = true;
m_show_terminal_name = true; m_show_terminal_name = true;
m_show_all_configured_slaves = false; m_show_all_configured_slaves = false;
m_stack_overlapping = true;
m_display = Cross; m_display = Cross;
m_snap_to = Bottom; m_snap_to = Bottom;
m_prefix_keys << "power" << "delay" << "switch"; 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 % "showpowerctc", m_show_power_ctc);
settings.setValue(prefix % "showterminalname", m_show_terminal_name); settings.setValue(prefix % "showterminalname", m_show_terminal_name);
settings.setValue(prefix % "showallconfiguredslaves", m_show_all_configured_slaves); settings.setValue(prefix % "showallconfiguredslaves", m_show_all_configured_slaves);
settings.setValue(prefix % "stackoverlapping", m_stack_overlapping);
QString display = m_display == Cross? "cross" : "contacts"; QString display = m_display == Cross? "cross" : "contacts";
settings.setValue(prefix % "displayhas", display); settings.setValue(prefix % "displayhas", display);
QString snap = m_snap_to == Bottom? "bottom" : "label"; 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_power_ctc = settings.value(prefix % "showpowerctc", true).toBool();
m_show_terminal_name = settings.value(prefix % "showterminalname", true).toBool(); m_show_terminal_name = settings.value(prefix % "showterminalname", true).toBool();
m_show_all_configured_slaves = settings.value(prefix % "showallconfiguredslaves", false).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(); QString display = settings.value(prefix % "displayhas", "cross").toString();
display == "cross"? m_display = Cross : m_display = Contacts; display == "cross"? m_display = Cross : m_display = Contacts;
QString snap = settings.value(prefix % "snapto", "label").toString(); 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("showpowerctc", m_show_power_ctc? "true" : "false");
xml_element.setAttribute("showterminalname", m_show_terminal_name? "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("showallconfiguredslaves", m_show_all_configured_slaves? "true" : "false");
xml_element.setAttribute("stackoverlapping", m_stack_overlapping? "true" : "false");
QString display = m_display == Cross? "cross" : "contacts"; QString display = m_display == Cross? "cross" : "contacts";
xml_element.setAttribute("displayhas", display); xml_element.setAttribute("displayhas", display);
QString snap = m_snap_to == Bottom? "bottom" : "label"; 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_power_ctc = xml_element.attribute("showpowerctc") == "true";
m_show_terminal_name = xml_element.attribute("showterminalname", "true") == "true"; m_show_terminal_name = xml_element.attribute("showterminalname", "true") == "true";
m_show_all_configured_slaves = xml_element.attribute("showallconfiguredslaves", "false") == "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"); QString display = xml_element.attribute("displayhas", "cross");
display == "cross"? m_display = Cross : m_display = Contacts; display == "cross"? m_display = Cross : m_display = Contacts;
QString snap = xml_element.attribute("snapto", "label"); 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 return (m_show_power_ctc == xrp.m_show_power_ctc
&& m_show_terminal_name == xrp.m_show_terminal_name && m_show_terminal_name == xrp.m_show_terminal_name
&& m_show_all_configured_slaves == xrp.m_show_all_configured_slaves && m_show_all_configured_slaves == xrp.m_show_all_configured_slaves
&& m_stack_overlapping == xrp.m_stack_overlapping
&& m_display == xrp.m_display && m_display == xrp.m_display
&& m_snap_to == xrp.m_snap_to && m_snap_to == xrp.m_snap_to
&& m_prefix == xrp.m_prefix && 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;} void setShowAllConfiguredSlaves (const bool a) {m_show_all_configured_slaves = a;}
bool showAllConfiguredSlaves () const {return m_show_all_configured_slaves;} 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;} void setDisplayHas (const DisplayHas dh) {m_display = dh;}
DisplayHas displayHas () const {return m_display;} DisplayHas displayHas () const {return m_display;}
@@ -92,6 +95,7 @@ class XRefProperties : public PropertiesInterface
bool m_show_power_ctc; bool m_show_power_ctc;
bool m_show_terminal_name; bool m_show_terminal_name;
bool m_show_all_configured_slaves; bool m_show_all_configured_slaves;
bool m_stack_overlapping;
DisplayHas m_display; DisplayHas m_display;
SnapTo m_snap_to; SnapTo m_snap_to;
Qt::AlignmentFlag m_xref_pos; Qt::AlignmentFlag m_xref_pos;
+70 -36
View File
@@ -268,6 +268,7 @@ void CrossRefItem::updateLabel()
m_shape_path = QPainterPath(); m_shape_path = QPainterPath();
prepareGeometryChange(); prepareGeometryChange();
m_bounding_rect = QRectF(); m_bounding_rect = QRectF();
m_text_rects.clear();
// Build geometry and m_hovered_contacts_map using a QImage-backed // Build geometry and m_hovered_contacts_map using a QImage-backed
// painter so font metrics match the screen painter in paint(). // painter so font metrics match the screen painter in paint().
@@ -343,9 +344,12 @@ void CrossRefItem::autoPos()
@brief CrossRefItem::stackAtBottom @brief CrossRefItem::stackAtBottom
Places every cross reference of this folio that snaps to the bottom. 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 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 when its texts would be drawn over the texts of another one, the one
other: several coils in one column get their crosses stacked in the of the higher element goes above the other: several coils in one
same order as the coils, instead of all on the same spot. 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() void CrossRefItem::stackAtBottom()
{ {
@@ -374,41 +378,56 @@ void CrossRefItem::stackAtBottom()
return a->m_element->uuid() < b->m_element->uuid(); return a->m_element->uuid() < b->m_element->uuid();
}); });
struct Placed { QRectF rect; QList<QRectF> texts; };
const qreal gap = 5; const qreal gap = 5;
QList<QRectF> placed; QList<Placed> placed;
for (CrossRefItem *xref : std::as_const(xrefs)) { for (CrossRefItem *xref : std::as_const(xrefs)) {
const qreal offset = xref->m_properties.offset(); const qreal offset = xref->m_properties.offset();
QGIUtility::centerToBottomDiagram(xref, xref->m_element, QGIUtility::centerToBottomDiagram(xref, xref->m_element,
offset <= 40 ? 5 : offset); 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. Placed self;
// A list of contacts has an empty margin in its bounding rect: self.rect = xref->sceneBoundingRect();
// only its content is tested, so side by side lists do not const QTransform to_scene = xref->sceneTransform();
// count as overlapping. for (const QRectF &text : std::as_const(xref->m_text_rects))
const bool list = xref->m_properties.displayHas() self.texts << to_scene.mapRect(text);
== XRefProperties::Contacts; const qreal bottom = self.rect.bottom();
QRectF rect = xref->sceneBoundingRect();
if (list) // Move up past every cross already placed whose texts it would
rect.adjust(list_margin_left, 0, -list_margin_right, 0); // draw over. Each move goes strictly up, so this ends.
const qreal bottom = rect.bottom();
for (bool moved = true; moved; ) { for (bool moved = true; moved; ) {
moved = false; moved = false;
for (const QRectF &other : std::as_const(placed)) for (const Placed &other : std::as_const(placed)) {
if (rect.left() < other.right() && other.left() < rect.right() const qreal dy = other.rect.top() - gap - self.rect.bottom();
&& rect.top() < other.bottom() + gap if (dy >= 0 || !textsOverlap(self.texts, other.texts))
&& other.top() - gap < rect.bottom()) { continue;
rect.moveBottom(other.top() - gap); self.rect.translate(0, dy);
moved = true; 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->setPos(xref->parentItem()->mapFromScene(
xref->scenePos() + QPointF(0, rect.bottom() - bottom))); xref->scenePos() + QPointF(0, self.rect.bottom() - bottom)));
placed << rect; 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 @brief CrossRefItem::sceneEvent
@param event @param event
@@ -1156,9 +1175,9 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in
painter.setFont(QETApp::diagramTextsFont(4)); painter.setFont(QETApp::diagramTextsFont(4));
QRectF bt(0, offset, 24, 10); QRectF bt(0, offset, 24, 10);
if (terminal_names.size() >= 1) 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) 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)); 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); QRectF text_rect = painter.boundingRect(QRectF(30, offset, 5, 10), Qt::AlignLeft | Qt::AlignVCenter, str);
if (!str.isEmpty()) 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); 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)); painter.setFont(QETApp::diagramTextsFont(4));
// Storage order set above: [0]=NC, [1]=NO, [2]=Common // Storage order set above: [0]=NC, [1]=NO, [2]=Common
if (terminal_names.size() >= 2) 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 Qt::AlignLeft|Qt::AlignTop, terminal_names[1]); // NO top-left
if (terminal_names.size() >= 3) 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 Qt::AlignRight|Qt::AlignTop, terminal_names[2]); // Common right
if (terminal_names.size() >= 1) 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 Qt::AlignLeft|Qt::AlignTop, terminal_names[0]); // NC bottom-left
painter.setFont(QETApp::diagramTextsFont(5)); painter.setFont(QETApp::diagramTextsFont(5));
} }
@@ -1325,7 +1344,7 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in
str); str);
if (!str.isEmpty()) if (!str.isEmpty())
{ {
painter.drawText(text_rect, drawText(painter, text_rect,
Qt::AlignLeft | Qt::AlignVCenter, Qt::AlignLeft | Qt::AlignVCenter,
str); str);
bounding_rect = bounding_rect.united(text_rect); bounding_rect = bounding_rect.united(text_rect);
@@ -1361,7 +1380,7 @@ QRectF CrossRefItem::drawContact(QPainter &painter, int flags, Element *elmt, in
str); str);
if (!str.isEmpty()) if (!str.isEmpty())
{ {
painter.drawText(text_rect, drawText(painter, text_rect,
Qt::AlignLeft | Qt::AlignVCenter, Qt::AlignLeft | Qt::AlignVCenter,
str); str);
bounding_rect = bounding_rect.united(text_rect); bounding_rect = bounding_rect.united(text_rect);
@@ -1454,7 +1473,7 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
QSize(middle_cross, 1)), QSize(middle_cross, 1)),
Qt::AlignLeft, Qt::AlignLeft,
str); str);
painter.drawText(bounding, Qt::AlignLeft, str); drawText(painter, bounding, Qt::AlignLeft, str);
if (m_update_map) { if (m_update_map) {
QString pos_str = elementPositionText(elmt, true); QString pos_str = elementPositionText(elmt, true);
@@ -1534,7 +1553,7 @@ void CrossRefItem::fillCrossRef(QPainter &painter)
QSize(middle_cross, 1)), QSize(middle_cross, 1)),
Qt::AlignRight, Qt::AlignRight,
str); str);
painter.drawText(bounding, Qt::AlignRight, str); drawText(painter, bounding, Qt::AlignRight, str);
if (m_update_map) { if (m_update_map) {
QString pos_str = elementPositionText(elmt, true); QString pos_str = elementPositionText(elmt, true);
@@ -1582,7 +1601,7 @@ void CrossRefItem::AddExtraInfo(QPainter &painter, const QString& type)
r, r,
Qt::TextWordWrap | Qt::AlignHCenter, Qt::TextWordWrap | Qt::AlignHCenter,
text); text);
painter.drawText(text_bounding, drawText(painter, text_bounding,
Qt::TextWordWrap | Qt::AlignHCenter, Qt::TextWordWrap | Qt::AlignHCenter,
text); 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 @brief CrossRefItem::NOElements
@return The linked elements of m_element which are open or switch contact. @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 init();
void setUpConnection(); void setUpConnection();
void stackAtBottom(); void stackAtBottom();
static bool textsOverlap(const QList<QRectF> &a,
const QList<QRectF> &b);
public: public:
enum { Type = UserType + 1009 }; enum { Type = UserType + 1009 };
@@ -138,6 +140,8 @@ class CrossRefItem : public QGraphicsObject
const QStringList &master_labels = QStringList()); const QStringList &master_labels = QStringList());
void fillCrossRef(QPainter &painter); void fillCrossRef(QPainter &painter);
void AddExtraInfo(QPainter &painter, const QString&); void AddExtraInfo(QPainter &painter, const QString&);
void drawText(QPainter &painter, const QRectF &rect, int flags,
const QString &text);
QList<Element *> NOElements() const; QList<Element *> NOElements() const;
QList<Element *> NCElements() const; QList<Element *> NCElements() const;
@@ -150,6 +154,7 @@ class CrossRefItem : public QGraphicsObject
int m_drawed_contacts; int m_drawed_contacts;
bool m_update_map = false; bool m_update_map = false;
QMultiMap <Element *, QRectF> m_hovered_contacts_map; QMultiMap <Element *, QRectF> m_hovered_contacts_map;
QList <QRectF> m_text_rects; //rects of the drawn texts (local coords)
Element *m_hovered_contact = nullptr; Element *m_hovered_contact = nullptr;
DynamicElementTextItem *m_text = nullptr; DynamicElementTextItem *m_text = nullptr;
ElementTextItemGroup *m_group = 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.setShowPowerContac(ui->m_show_power_cb->isChecked());
xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked()); xrp.setShowTerminalName(ui->m_show_terminal_name_cb->isChecked());
xrp.setShowAllConfiguredSlaves(ui->m_show_all_slaves_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("power", ui->m_power_prefix_le->text());
xrp.setPrefix("delay", ui->m_delay_prefix_le->text()); xrp.setPrefix("delay", ui->m_delay_prefix_le->text());
xrp.setPrefix("switch", ui->m_switch_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_snap_to_cb->setCurrentIndex(ui->m_snap_to_cb->findData("label"));
ui->m_offset_sb->setEnabled(false); 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")); 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")); 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_display_has_cross_rb->setVisible(!is_plc);
ui->m_show_terminal_name_cb->setVisible(!is_plc); ui->m_show_terminal_name_cb->setVisible(!is_plc);
ui->m_show_all_slaves_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); ui->m_cross_properties_gb->setVisible(!is_plc);
} }
@@ -261,4 +265,5 @@ void XRefPropertiesWidget::enableOffsetSB(int i){
ui->m_offset_sb->setEnabled(false); ui->m_offset_sb->setEnabled(false);
else else
ui->m_offset_sb->setEnabled(true); 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> </widget>
</item> </item>
</layout> </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>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_6"> <layout class="QHBoxLayout" name="horizontalLayout_6">
@@ -330,6 +340,7 @@
<tabstop>m_type_cb</tabstop> <tabstop>m_type_cb</tabstop>
<tabstop>m_snap_to_cb</tabstop> <tabstop>m_snap_to_cb</tabstop>
<tabstop>m_offset_sb</tabstop> <tabstop>m_offset_sb</tabstop>
<tabstop>m_stack_overlapping_cb</tabstop>
<tabstop>m_xrefpos_cb</tabstop> <tabstop>m_xrefpos_cb</tabstop>
<tabstop>m_display_has_contacts_rb</tabstop> <tabstop>m_display_has_contacts_rb</tabstop>
<tabstop>m_display_has_cross_rb</tabstop> <tabstop>m_display_has_cross_rb</tabstop>