/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. QElectroTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with QElectroTech. If not, see . */ #include "terminalstripdrawer.h" #include #include #include namespace TerminalStripDrawer { /** * @brief TerminalStripDrawer::TerminalStripDrawer * @param strip * @param pattern */ TerminalStripDrawer::TerminalStripDrawer(QSharedPointer strip, QSharedPointer layout) : m_strip { strip }, m_pattern { layout } {} void TerminalStripDrawer::setStrip(QSharedPointer strip) { m_strip = strip; } namespace { /* * The symbols are described in a box of 20 x 14 units centered on (0,0). * The current flow from the top to the bottom, like in the terminal strip. * drawTypeSymbol() scale this box to fit the real available area. */ constexpr qreal symbol_width{20}; constexpr qreal symbol_height{14}; void drawFuseSymbol(QPainter *painter) { painter->drawLine(QPointF{0, -7}, QPointF{0, 7}); painter->drawRect(QRectF{-3, -5, 6, 10}); } void drawSectionalSymbol(QPainter *painter) { painter->drawLine(QPointF{0, -7}, QPointF{0, 7}); painter->drawLine(QPointF{-4, 2}, QPointF{4, -2}); } void drawDiodeSymbol(QPainter *painter) { const qreal t{symbol_height / 3}; const QPointF triangle[3] { {-t, -t}, {t, -t}, {0, t} }; painter->drawLine(QPointF{0, -7}, QPointF{0, -t}); painter->drawPolygon(triangle, 3); painter->drawLine(QPointF{-t, t}, QPointF{t, t}); painter->drawLine(QPointF{0, t}, QPointF{0, 7}); } void drawLedSymbol(QPainter *painter) { drawDiodeSymbol(painter); const qreal t{symbol_height / 3}; //The two rays painter->drawLine(QPointF{t + 0.5, -1}, QPointF{t + 3.5, -4}); painter->drawLine(QPointF{t + 0.5, 2}, QPointF{t + 3.5, -1}); } void drawGroundSymbol(QPainter *painter) { painter->drawLine(QPointF{0, -7}, QPointF{0, -2}); painter->drawLine(QPointF{-5, -2}, QPointF{5, -2}); painter->drawLine(QPointF{-3, 1}, QPointF{3, 1}); painter->drawLine(QPointF{-1, 4}, QPointF{1, 4}); } /** * @brief drawScaled * Call @a draw with the painter moved by @a dx and scaled by @a factor. * The width of the pen is kept constant. */ template void drawScaled(QPainter *painter, qreal dx, qreal factor, Func draw) { painter->save(); painter->translate(dx, 0); painter->scale(factor, factor); auto pen_{painter->pen()}; pen_.setWidthF(pen_.widthF() / factor); painter->setPen(pen_); draw(painter); painter->restore(); } /** * @brief drawTypeSymbol * Draw the symbol of a terminal type in @a box. The symbol is scaled * to fit in @a box, so it never overflow on the neighbouring terminal. * The painter must have a pen set, the brush is ignored. * @param painter * @param box : area available for the symbol, in the painter coordinates * @param type : type of the terminal, a generic terminal have no symbol * @param led : if true a led symbol is drawn. If the terminal have * also a type (fuse...), the type symbol and the led are drawn side by side. */ void drawTypeSymbol(QPainter *painter, const QRectF &box, ElementData::TerminalType type, bool led) { const bool have_type{type != ElementData::TTGeneric}; if (!led && !have_type) { return; } const qreal scale_{qMin(box.width() / symbol_width, box.height() / symbol_height)}; if (scale_ <= 0) { return; } painter->save(); painter->setBrush(Qt::NoBrush); painter->translate(box.center()); painter->scale(scale_, scale_); auto pen_{painter->pen()}; pen_.setWidthF(pen_.widthF() / scale_); painter->setPen(pen_); auto draw_type = [type](QPainter *p) { switch (type) { case ElementData::TTFuse : drawFuseSymbol(p); break; case ElementData::TTSectional : drawSectionalSymbol(p); break; case ElementData::TTDiode : drawDiodeSymbol(p); break; case ElementData::TTGround : drawGroundSymbol(p); break; default: break; } }; if (led && have_type) { //Not enough place for two full size symbols, draw them smaller side by side drawScaled(painter, -5, 0.7, draw_type); drawScaled(painter, 2.5, 0.7, drawLedSymbol); } else if (led) { drawLedSymbol(painter); } else { draw_type(painter); } painter->restore(); } /** * @brief drawConnections * Draw a connection above and a connection under @a terminal_rect, in the middle * of the rect. A connection is a short line ended by a little circle. * @param painter * @param terminal_rect * @param length : the total length of a connection, circle included */ void drawConnections(QPainter *painter, const QRectF &terminal_rect, qreal length) { const qreal radius{qMin(length / 4, 2)}; const qreal x{terminal_rect.width() / 2}; const qreal top{terminal_rect.top()}; const qreal bottom{terminal_rect.top() + terminal_rect.height()}; painter->drawLine(QPointF{x, top}, QPointF{x, top - length + radius * 2}); painter->drawEllipse(QPointF{x, top - length + radius}, radius, radius); painter->drawLine(QPointF{x, bottom}, QPointF{x, bottom + length - radius * 2}); painter->drawEllipse(QPointF{x, bottom + length - radius}, radius, radius); } } //End anonymous namespace /** * @brief TerminalStripDrawer::paint * @param painter */ void TerminalStripDrawer::paint(QPainter *painter) { if (m_strip && m_pattern) { m_united_xref_text_rect = QRectF(); //To draw text, QPainter need a Qrect. Instead of create an instance //for each text, we re-use the same instance of QRect. QRect text_rect; painter->save(); auto pen_{painter->pen()}; pen_.setColor(Qt::black); pen_.setWidth(1); auto brush_ = painter->brush(); brush_.setColor(Qt::white); painter->setFont(m_pattern->font()); painter->setPen(pen_); painter->setBrush(brush_); if (m_preview_draw) { painter->save(); painter->setPen(Qt::blue); painter->drawRect(boundingRect()); painter->drawLine(QPointF{boundingRect().left(), boundingRect().center().y()}, QPointF{boundingRect().right(), boundingRect().center().y()}); painter->restore(); } //Draw header painter->drawRect(m_pattern->m_header_rect); //Draw the header text painter->save(); if (m_pattern->m_header_text_orientation == Qt::Horizontal) { text_rect.setRect(0,m_pattern->m_header_rect.y(),m_pattern->m_header_rect.width(),m_pattern->m_header_rect.height()); } else { painter->translate(m_pattern->m_header_rect.bottomLeft()); painter->rotate(270); text_rect.setRect(0,0,m_pattern->m_header_rect.height(),m_pattern->m_header_rect.width()); } const auto text_{m_strip->installation() + " " + m_strip->location() + " " + m_strip->name()}; painter->drawText(text_rect, text_, m_pattern->headerTextOption()); painter->restore(); //Move painter pos to next drawing painter->translate(m_pattern->m_header_rect.width(),0); qreal x_offset{m_pattern->m_header_rect.width()}; //Draw spacer painter->drawRect(m_pattern->m_spacer_rect); //Move painter pos to next drawing painter->translate(m_pattern->m_spacer_rect.width(),0); x_offset += m_pattern->m_spacer_rect.width(); //Draw terminals const auto terminals_text_orientation{m_pattern->m_terminals_text_orientation}; const auto terminals_text_option{m_pattern->terminalsTextOption()}; const auto terminals_text_height{m_pattern->m_terminals_text_height}; const auto terminals_text_y{m_pattern->m_terminals_text_y}; QRectF terminal_rect; const auto xref_text_orientation{m_pattern->m_xref_text_orientation}; const auto xref_text_option{m_pattern->xrefTextOption()}; const auto xref_text_height{m_pattern->m_xref_text_height}; const auto xref_text_y{m_pattern->m_xref_text_y}; QRectF xref_rect; QHash> bridges_anchor_points; m_hovered_xref = hoverTerminal{}; int physical_index = 0; //Loop over physical terminals for (const auto &physical_t : m_strip->physicalTerminal()) { //Get the good offset according to how many level have the current physical terminal const QVector> real_terminal_vector{physical_t->realTerminals()}; const auto real_t_count{real_terminal_vector.size()}; const auto offset_{4 - real_t_count}; //Loop over real terminals for (auto i=0 ; i= 4) { break; } terminal_rect = m_pattern->m_terminal_rect[index_]; //Draw terminal rect painter->drawRect(terminal_rect); //Draw the symbol of the terminal type (fuse, ground, led...) if (m_pattern->m_type_symbol_height > 0 && real_terminal_vector[i]) { drawTypeSymbol(painter, QRectF{0, m_pattern->m_type_symbol_y, terminal_rect.width(), m_pattern->m_type_symbol_height}, real_terminal_vector[i]->type(), real_terminal_vector[i]->isLed()); } //Draw the connections, above and under the terminal if (m_pattern->m_connection_length > 0) { drawConnections(painter, terminal_rect, m_pattern->m_connection_length); } //Draw a stronger line if the current terminal have level //and the current level is the first if (real_t_count > 1 && i == 0) { painter->save(); pen_ = painter->pen(); pen_.setWidth(4); pen_.setCapStyle(Qt::FlatCap); painter->setPen(pen_); const auto p1 { terminal_rect.topLeft() }; //We can't use terminal_rect.bottomLeft for p2 because //the returned value deviate from the true value //(see Qt documentation about QRect) const QPointF p2 { p1.x(), p1.y() + terminal_rect.height() }; painter->drawLine(p1, p2); painter->restore(); } if(m_preview_draw) { painter->save(); painter->setPen(Qt::yellow); painter->drawLine(QPointF{terminal_rect.x(), terminal_rect.y() + terminal_rect.height()/2}, QPointF{terminal_rect.width(), terminal_rect.y() + terminal_rect.height()/2}); painter->restore(); } //Draw text painter->save(); text_rect.setRect(0, terminals_text_y, terminal_rect.width(), terminals_text_height); if (terminals_text_orientation == Qt::Vertical) { painter->translate(text_rect.bottomLeft()); painter->rotate(270); text_rect.setRect(0, 0, text_rect.height(), text_rect.width()); } const auto shared_real_terminal{real_terminal_vector[i]}; painter->drawText(text_rect, shared_real_terminal ? shared_real_terminal->label() : QLatin1String(), terminals_text_option); if (m_preview_draw) { painter->setPen(Qt::blue); painter->drawRect(text_rect); } painter->restore(); //Draw xref xref_rect.setRect(0, xref_text_y, terminal_rect.width(), xref_text_height); painter->save(); if (xref_text_orientation == Qt::Vertical) { painter->translate(xref_rect.bottomLeft()); painter->rotate(270); xref_rect.setRect(0, 0, xref_rect.height(), xref_rect.width()); } QTransform transform; transform.translate(x_offset, 0); if (xref_text_orientation == Qt::Vertical) { transform.translate(0, xref_text_y + xref_text_height); transform.rotate(270); } auto xref_string = shared_real_terminal->xref(); const auto mapped_xref_text_rect = transform.mapRect(painter->boundingRect(xref_rect, xref_string, xref_text_option)); if (m_united_xref_text_rect.isNull()) { m_united_xref_text_rect = mapped_xref_text_rect; } else { m_united_xref_text_rect = m_united_xref_text_rect.united(mapped_xref_text_rect); } //if mouse hover the xref text, draw it in blue to advise user the xref is clickable. if (!m_mouse_hover_pos.isNull() && mapped_xref_text_rect.contains(m_mouse_hover_pos)) { painter->setPen(Qt::blue); m_hovered_xref.physical = physical_index; m_hovered_xref.real = i; } painter->drawText(xref_rect, xref_string, xref_text_option); if (m_preview_draw) { painter->setPen(Qt::blue); painter->drawRect(xref_rect); } painter->restore(); //Add bridge anchor if (shared_real_terminal->isBridged()) { painter->save(); if (QScopedPointer bridge_ { shared_real_terminal->bridge() }) { const auto x_anchor{terminal_rect.width()/2}; const auto y_anchor {m_pattern->m_bridge_point_y_offset[index_]}; const auto radius_anchor{m_pattern->m_bridge_point_d/2}; painter->setBrush(Qt::SolidPattern); painter->drawEllipse(QPointF(x_anchor, y_anchor), radius_anchor, radius_anchor); auto anchor_points{bridges_anchor_points.value(bridge_->uuid())}; anchor_points.append(QPointF(x_offset + x_anchor, y_anchor)); bridges_anchor_points.insert(bridge_->uuid(), anchor_points); } painter->restore(); } //Move painter pos to next drawing painter->translate(terminal_rect.width(),0); x_offset += terminal_rect.width(); } physical_index++; } painter->restore(); //Draw the bridges for (const auto &points_ : std::as_const(bridges_anchor_points)) { painter->save(); auto pen_{painter->pen()}; pen_.setWidth(2); painter->setPen(pen_); painter->drawPolyline(QPolygonF(points_)); painter->restore(); } } } QRectF TerminalStripDrawer::boundingRect() const { QRectF rect_{0, 0, width(), height()}; //The connections are drawn above and under the terminals if (m_pattern && m_pattern->m_connection_length > 0) { const auto length_{m_pattern->m_connection_length}; qreal top_{0}; for (const auto &terminal_rect : std::as_const(m_pattern->m_terminal_rect)) { top_ = std::min(top_, terminal_rect.top() - length_); } rect_.setBottom(rect_.bottom() + length_); rect_.setTop(top_); } return rect_; } void TerminalStripDrawer::setLayout(QSharedPointer layout) { m_pattern = layout; } bool TerminalStripDrawer::haveLayout() const { return !m_pattern.isNull(); } void TerminalStripDrawer::setPreviewDraw(bool draw) { m_preview_draw = draw; } void TerminalStripDrawer::setMouseHoverPos(const QPointF &pos) { m_last_mouse_pos_in_xrefs_rect = m_united_xref_text_rect.contains(m_mouse_hover_pos); m_mouse_hover_pos = pos; } /** * @brief TerminalStripDrawer::mouseHoverXref * @return True if the mouse position (given through the function setMouseHoverPos) * hover the rect of a xref. */ bool TerminalStripDrawer::mouseHoverXref() const { return m_united_xref_text_rect.contains(m_mouse_hover_pos); } bool TerminalStripDrawer::needUpdate() { if (mouseHoverXref()) { return true; } else if (m_last_mouse_pos_in_xrefs_rect) { return true; } return false; } /** * @brief TerminalStripDrawer::hoveredXref * @return the current terminal hovered by the mouse * in the xref bounding rectangle */ hoverTerminal TerminalStripDrawer::hoveredXref() const { return m_hovered_xref; } qreal TerminalStripDrawer::height() const { if (m_pattern) { auto height_{m_pattern->m_header_rect.y() + m_pattern->m_header_rect.height()}; height_ = std::max(height_, m_pattern->m_spacer_rect.y() + m_pattern->m_spacer_rect.height()); for (const auto &rect : m_pattern->m_terminal_rect) { height_ = std::max(height_, rect.y() + rect.height()); } return height_; } return 0; } qreal TerminalStripDrawer::width() const { if (m_pattern) { qreal width_{m_pattern->m_header_rect.width() + m_pattern->m_spacer_rect.width()}; if (m_strip) { //Loop over physical terminals for (const auto &physical_t : m_strip->physicalTerminal()) { //Get the good offset according to how many level have the current physical terminal const QVector> real_terminal_vector{physical_t->realTerminals()}; const auto real_t_count{real_terminal_vector.size()}; const auto offset_{4 - real_t_count}; //Loop over real terminals for (auto i=0 ; i= 4) { break; } width_ += m_pattern->m_terminal_rect[index_].width(); } } } return width_; } return 0; } } //End namespace TerminalStripDrawer