Terminal strip: clickable terminals in the realistic styles

The realistic styles don't write the xrefs, so each terminal becomes a
clickable zone, like the xref text of the schematic style:
- double-click on a terminal goes to its element on the folio
- the terminal under the mouse is outlined in blue, and a tool tip
  shows its label and its xref
- the zones are exported as clickable links in the PDF

A terminal with one level is clickable on its whole body; a terminal
with levels is clickable on the rows of wire entries of each level.
Only the terminals with an xref (element placed on a folio) are
clickable. The zones use the same physical / real indexes as the
schematic style, so the double-click and the PDF export need no
other change.
This commit is contained in:
Laurent Trinques
2026-10-17 20:41:25 +02:00
parent 34557928d1
commit fed204eb78
6 changed files with 133 additions and 4 deletions
@@ -66,6 +66,11 @@ struct PhysicalGeometry
qreal width_mm{0};
bool separator_after{false}; //A separator plate is mounted after this terminal
QVector<QSharedPointer<AbstractRealTerminalInterface>> reals;
//Index of the physical terminal in the strip, and of each real terminal
//in the physical terminal : the same indexes than the schematic style,
//used to find the element of a clicked terminal.
int physical_index{-1};
QVector<int> real_indexes;
};
/**
@@ -80,18 +85,24 @@ QVector<PhysicalGeometry> geometry(const AbstractTerminalStripInterface *strip)
return geometry_;
}
int physical_index_{-1};
for (const auto &physical_t : strip->physicalTerminal())
{
++physical_index_;
if (!physical_t) {
continue;
}
PhysicalGeometry g_;
g_.physical_index = physical_index_;
int real_index_{-1};
for (const auto &real_t : physical_t->realTerminals())
{
++real_index_;
if (!real_t) {
continue;
}
g_.reals.append(real_t);
g_.real_indexes.append(real_index_);
g_.width_mm = std::max(g_.width_mm, real_t->widthMm());
g_.separator_after = g_.separator_after || real_t->hasSeparatorAfter();
if (g_.reals.size() == 4) { //Same limit than the schematic style
@@ -384,7 +395,9 @@ void paint(QPainter *painter,
const AbstractTerminalStripInterface &strip,
const TerminalStripLayoutPattern &pattern,
TerminalStripLayoutPattern::DrawStyle style,
bool preview_draw)
bool preview_draw,
const QPointF &hover_pos,
QVector<XrefRect> *xref_rects)
{
const bool screw_{style == TerminalStripLayoutPattern::DrawStyle::RealisticScrew};
const qreal s_{pattern.m_realistic_px_per_mm};
@@ -509,6 +522,53 @@ void paint(QPainter *painter,
drawVerticalText(painter, tag_, numbers_.join(QStringLiteral(" / ")),
sectional_ ? QColor(Qt::white) : QColor(Qt::black));
//Clickable zones : the whole terminal for a terminal with one level,
//the rows of wire entries of the level (top and bottom) for a terminal with levels.
//Only the terminals with an xref (element placed on a folio) are clickable.
QVector<XrefRect> terminal_rects_;
for (int k = 0 ; k < levels_ ; ++k)
{
if (g_.reals.at(k)->xref().isEmpty()) {
continue;
}
const int physical_{g_.physical_index};
const int real_{g_.real_indexes.at(k)};
if (levels_ == 1) {
terminal_rects_.append({body_, physical_, real_});
} else {
const qreal top_h_{length_ * (band_top - entries_top) / levels_};
terminal_rects_.append({QRectF{x_, length_ * entries_top + k * top_h_, w_, top_h_}, physical_, real_});
terminal_rects_.append({QRectF{x_, length_ * (1 - entries_top) - (k + 1) * top_h_, w_, top_h_}, physical_, real_});
}
}
if (xref_rects) {
*xref_rects << terminal_rects_;
}
//Highlight the terminal under the mouse, like the xref of the schematic style
if (!hover_pos.isNull())
{
int hovered_real_{-1};
for (const auto &r_ : std::as_const(terminal_rects_)) {
if (r_.rect.contains(hover_pos)) {
hovered_real_ = r_.real;
break;
}
}
if (hovered_real_ >= 0)
{
painter->save();
painter->setPen(QPen(QColor(0, 90, 255), std::max<qreal>(1.0, 0.4 * s_)));
painter->setBrush(Qt::NoBrush);
for (const auto &r_ : std::as_const(terminal_rects_)) {
if (r_.real == hovered_real_) {
painter->drawRect(r_.rect.adjusted(0.5, 0.5, -0.5, -0.5));
}
}
painter->restore();
}
}
//Bridge points : one channel by level
for (int k = 0 ; k < levels_ ; ++k)
{
@@ -32,6 +32,11 @@ class QPainter;
* The width of each terminal is its real width (mm) multiplied by the scale of the layout.
* A terminal with several levels is drawn with one row of wire entries by level,
* at the top and at the bottom.
*
* paint() fill @a xref_rects with the zone of each terminal which have an xref,
* with the same indexes than the schematic style : the item use them to go to the
* element on double click, to show the xref in a tool tip and to make clickable
* links in an exported PDF. The terminal under @a hover_pos is outlined in blue.
*/
namespace TerminalStripDrawer
{
@@ -43,7 +48,9 @@ namespace TerminalStripDrawer
const AbstractTerminalStripInterface &strip,
const TerminalStripLayoutPattern &pattern,
TerminalStripLayoutPattern::DrawStyle style,
bool preview_draw = false);
bool preview_draw = false,
const QPointF &hover_pos = QPointF(),
QVector<XrefRect> *xref_rects = nullptr);
}
}
@@ -873,11 +873,24 @@ void TerminalStripDrawer::paint(QPainter *painter)
{
if (m_strip && m_pattern && m_draw_style != TerminalStripLayoutPattern::DrawStyle::Schematic)
{
//No xref in the realistic styles
//The realistic styles don't write the xrefs, but each terminal
//is a clickable zone, like the xref text of the schematic style.
m_united_xref_text_rect = QRectF();
m_xref_rects.clear();
m_hovered_xref = hoverTerminal{};
Realistic::paint(painter, *m_strip, *m_pattern, m_draw_style, m_preview_draw);
Realistic::paint(painter, *m_strip, *m_pattern, m_draw_style, m_preview_draw,
m_mouse_hover_pos, &m_xref_rects);
for (const auto &xref_rect : std::as_const(m_xref_rects))
{
m_united_xref_text_rect = m_united_xref_text_rect.isNull() ?
xref_rect.rect :
m_united_xref_text_rect.united(xref_rect.rect);
if (m_hovered_xref.physical < 0 && !m_mouse_hover_pos.isNull()
&& xref_rect.rect.contains(m_mouse_hover_pos)) {
m_hovered_xref.physical = xref_rect.physical;
m_hovered_xref.real = xref_rect.real;
}
}
return;
}
@@ -1369,6 +1382,22 @@ TerminalStripLayoutPattern::DrawStyle TerminalStripDrawer::drawStyle() const {
return m_draw_style;
}
/**
* @brief TerminalStripDrawer::xrefAt
* @param pos : position in the coordinates of the item
* @return the physical and real index of the terminal whose xref zone
* (drawn by the last call of paint()) contains @a pos, or -1 if none.
*/
hoverTerminal TerminalStripDrawer::xrefAt(const QPointF &pos) const
{
for (const auto &xref_rect : m_xref_rects) {
if (xref_rect.rect.contains(pos)) {
return hoverTerminal{xref_rect.physical, xref_rect.real};
}
}
return hoverTerminal{};
}
void TerminalStripDrawer::setMouseHoverPos(const QPointF &pos)
{
m_last_mouse_pos_in_xrefs_rect = m_united_xref_text_rect.contains(m_mouse_hover_pos);
@@ -130,6 +130,7 @@ namespace TerminalStripDrawer
bool mouseHoverXref() const;
bool needUpdate();
hoverTerminal hoveredXref() const;
hoverTerminal xrefAt(const QPointF &pos) const;
QVector<XrefRect> xrefRects() const;
private:
@@ -142,12 +142,43 @@ void TerminalStripItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
if (m_drawer.needUpdate()) {
update();
}
//The realistic styles don't write the xref : show it in a tool tip
if (m_drawer.drawStyle() != TerminalStripLayoutPattern::DrawStyle::Schematic) {
setToolTip(xrefToolTip(m_drawer.xrefAt(hoverMousePos())));
}
}
void TerminalStripItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
QetGraphicsItem::hoverLeaveEvent(event);
m_drawer.setMouseHoverPos(QPointF{});
setToolTip(QString());
//Remove the highlight of the xref (or of the terminal) under the mouse
update();
}
/**
* @brief TerminalStripItem::xrefToolTip
* @param terminal : physical and real index of a terminal of the strip
* @return the text of the tool tip of @a terminal : its label and its xref,
* or an empty string if @a terminal is invalid.
*/
QString TerminalStripItem::xrefToolTip(const TerminalStripDrawer::hoverTerminal &terminal) const
{
if (!m_strip || terminal.physical < 0 || terminal.real < 0) {
return QString();
}
const auto physical_terminal{m_strip->physicalTerminal(terminal.physical)};
if (physical_terminal.isNull()) {
return QString();
}
const auto real_terminal{physical_terminal->realTerminal(terminal.real)};
if (real_terminal.isNull() || real_terminal->Xref().isEmpty()) {
return QString();
}
return tr("%1 : %2\nDouble-click to go to the element")
.arg(real_terminal->label(), real_terminal->Xref());
}
void TerminalStripItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
@@ -75,6 +75,7 @@ class TerminalStripItem : public QetGraphicsItem
private:
void setDefaultLayout();
QString xrefToolTip(const TerminalStripDrawer::hoverTerminal &terminal) const;
private:
QPointer<TerminalStrip> m_strip;