Merge M10+M9a: name the cable target, keep wire numbers out

# Conflicts:
#	sources/TerminalStrip/realterminal.cpp
#	tests/qttest/CMakeLists.txt
This commit is contained in:
Kellermorph
2026-10-20 08:07:02 +02:00
8 changed files with 625 additions and 15 deletions
@@ -270,14 +270,18 @@ struct CableCell
QString cable; ///< name of the cable (hose)
QString wire; ///< color / number of the wire
bool shield{false}; ///< true if the terminal is the shield of the cable
QSharedPointer<AbstractRealTerminalInterface> terminal; ///< to ask where the cable goes
};
/**
* @brief drawCables
* Draw the cables under the terminals. Consecutive terminals who have the same cable name
* share the same cable: the wires go down from the terminals, join in the cable,
* and leave the cable with the same wire marks. The shield is linked to a dashed ellipse
* drawn around the cable.
* share the same cable: the wires go down from the terminals and join in the cable.
* The cable ends together -- a short stub, then a box naming where the cable goes
* (Installation, Location and Label of the piece it lands on). It does not fan out
* again after the cable: the wires drawn there suggested each core went its own way
* towards the target, which said nothing about where that target is.
* The shield is linked to a dashed ellipse drawn around the cable.
* @param painter : the pen and the font must be set
* @param cells : one cell per terminal, in the drawing order
* @param pattern
@@ -295,19 +299,45 @@ void drawCables(QPainter *painter, const QVector<CableCell> &cells, const Termin
const qreal text_height{font_metrics.height()};
//Draw a mark (small oblique line) on a wire and the number of the wire next to it.
//The text is written from the bottom to the top, at the left of the wire.
const auto draw_wire_mark = [&](qreal x, qreal mark_y, const QString &text, bool text_above)
//The text is written from the bottom to the top, at the left of the wire, starting
//above the mark and running towards the terminal: the font shrinks while the text
//is longer than the room left between the mark and the terminal, and what still
//does not fit is clipped away, so a long number never runs into the drawing of
//the terminal. The room left is @a terminal_bottom the bottom of that terminal.
const auto draw_wire_mark = [&](qreal x, qreal mark_y, qreal terminal_bottom, const QString &text)
{
painter->drawLine(QPointF{x - 3, mark_y + 3}, QPointF{x + 3, mark_y - 3});
if (text.isEmpty()) {
return;
}
const qreal text_width{font_metrics.horizontalAdvance(text)};
//Room left between the mark and the terminal
const qreal room{mark_y - 4 - (terminal_bottom + 2)};
if (room <= 0) {
return;
}
QFont text_font{painter->font()};
if (text_font.pixelSize() <= 0 && text_font.pointSizeF() > 0) {
text_font.setPixelSize(qMax(4, qRound(text_font.pointSizeF() * 96.0 / 72.0)));
}
while (QFontMetricsF{text_font}.horizontalAdvance(text) > room &&
text_font.pixelSize() > 4) {
text_font.setPixelSize(text_font.pixelSize() - 1);
}
const QFontMetricsF fitted_metrics{text_font};
const qreal fitted_width{fitted_metrics.horizontalAdvance(text)};
const qreal fitted_height{fitted_metrics.height()};
painter->save();
painter->translate(x - 1, text_above ? mark_y - 4 : mark_y + 4 + text_width);
painter->setFont(text_font);
//The clip keeps the text under the terminal even when a number is
//so long that the smallest font does not fit it any more
painter->setClipRect(QRectF{x - 1 - fitted_height, terminal_bottom + 2,
fitted_height + 1, room});
painter->translate(x - 1, mark_y - 4);
painter->rotate(270);
painter->drawText(QRectF{0, -text_height, text_width + 2, text_height},
painter->drawText(QRectF{0, -fitted_height, fitted_width + 2, fitted_height},
Qt::AlignLeft | Qt::AlignVCenter,
text);
painter->restore();
@@ -350,25 +380,63 @@ void drawCables(QPainter *painter, const QVector<CableCell> &cells, const Termin
const qreal cable_x{(min_x + max_x) / 2};
const qreal bar_1_y{max_bottom + wire_length};
const qreal bar_2_y{bar_1_y + cable_length};
const qreal end_y{bar_2_y + end_length};
//Wires between the terminals and the cable
for (const auto &wire : std::as_const(wires))
{
painter->drawLine(QPointF{wire.x, wire.bottom}, QPointF{wire.x, bar_1_y});
draw_wire_mark(wire.x, qMax(wire.bottom + 4, bar_1_y - 10), wire.wire, true);
draw_wire_mark(wire.x, qMax(wire.bottom + 4, bar_1_y - 10), wire.bottom, wire.wire);
}
painter->drawLine(QPointF{min_x, bar_1_y}, QPointF{max_x, bar_1_y});
//The cable
painter->drawLine(QPointF{cable_x, bar_1_y}, QPointF{cable_x, bar_2_y});
//Wires after the cable
//The bundle stays together at the end: the cross bar, a short
//stub, and a box naming where the cable goes -- the Installation,
//Location and Label (BMK) of the piece it lands on, the same
//three fields the cable list shows. The lines shrink to the end
//zone the settings leave, so the box never grows past the strip.
painter->drawLine(QPointF{min_x, bar_2_y}, QPointF{max_x, bar_2_y});
for (const auto &wire : std::as_const(wires))
const qreal stub{qMin<qreal>(6, end_length)};
painter->drawLine(QPointF{cable_x, bar_2_y}, QPointF{cable_x, bar_2_y + stub});
const auto target_lines{wires.first().terminal ? wires.first().terminal->cableTarget()
: QVector<QString>()};
if (!target_lines.isEmpty() && end_length - stub > 4)
{
painter->drawLine(QPointF{wire.x, bar_2_y}, QPointF{wire.x, end_y});
draw_wire_mark(wire.x, qMin(bar_2_y + 10, end_y), wire.wire, false);
QFont box_font{painter->font()};
if (box_font.pixelSize() <= 0 && box_font.pointSizeF() > 0) {
box_font.setPixelSize(qMax(4, qRound(box_font.pointSizeF() * 96.0 / 72.0)));
}
const qreal room{end_length - stub};
const auto fits_the_end_zone = [&target_lines, room](const QFont &font_) {
return QFontMetricsF{font_}.height() * target_lines.size() + 4 <= room;
};
while (!fits_the_end_zone(box_font) && box_font.pixelSize() > 4) {
box_font.setPixelSize(box_font.pixelSize() - 1);
}
const QFontMetricsF box_metrics{box_font};
qreal box_width{6};
for (const QString &line_ : target_lines) {
box_width = qMax(box_width, box_metrics.horizontalAdvance(line_) + 6);
}
const qreal box_height{box_metrics.height() * target_lines.size() + 4};
const QRectF box{cable_x - box_width / 2, bar_2_y + stub, box_width, box_height};
painter->save();
painter->setFont(box_font);
painter->drawRect(box);
for (auto i = 0 ; i < target_lines.size() ; ++i)
{
painter->drawText(QRectF{box.left() + 1,
box.top() + 2 + i * box_metrics.height(),
box.width() - 2, box_metrics.height()},
Qt::AlignCenter,
target_lines.at(i));
}
painter->restore();
}
//The shield, linked to an ellipse around the cable
@@ -1037,6 +1105,7 @@ void TerminalStripDrawer::paint(QPainter *painter)
cable_cell.cable = real_terminal_vector[i]->cable();
cable_cell.wire = real_terminal_vector[i]->cableWire();
cable_cell.shield = real_terminal_vector[i]->isShield();
cable_cell.terminal = real_terminal_vector[i];
}
const bool have_cable{m_pattern->m_cable_wire_length > 0 && !cable_cell.cable.isEmpty()};
cable_cells.append(cable_cell);
@@ -78,6 +78,12 @@ namespace TerminalStripDrawer
virtual QString cable() const { return QString(); }
virtual QString cableWire() const { return QString(); }
virtual bool isShield() const { return false; }
//Installation, Location and Label (BMK) of the piece the cable
//goes to at its far end -- the same three fields the cable list
//shows for a target -- drawn in a box under the cable. Empty
//when the terminal has no cable, or when the cable lands on
//nothing past the strip's own terminals.
virtual QVector<QString> cableTarget() const { return QVector<QString>(); }
//Color and width (mm) of the terminal, only used to draw the terminal.
//An invalid color means "no override" : the default color of the type is used.
//A width <= 0 means "unknown" : the default width of the layout is used.
@@ -153,6 +153,11 @@ namespace TerminalStripDrawer
return m_real ? m_real->isShield() : false;
}
QVector<QString> TrueRealTerminal::cableTarget() const
{
return m_real ? m_real->cableTarget() : QVector<QString>();
}
QColor TrueRealTerminal::color() const
{
return m_real ? m_real->color() : QColor();
@@ -64,6 +64,7 @@ namespace TerminalStripDrawer
QString cable() const override;
QString cableWire() const override;
bool isShield() const override;
QVector<QString> cableTarget() const override;
QColor color() const override;
QColor stripeColor() const override;
qreal widthMm() const override;
+144 -1
View File
@@ -22,11 +22,16 @@
#include "terminalcolor.h"
#include "terminalcatalog.h"
#include "../qetgraphicsitem/conductor.h"
#include "../qetinformation.h"
#include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/terminal.h"
#include "../qetinformation.h"
#include "../diagram.h"
#include "../cable/cable.h"
#include "../cable/cablemanager.h"
#include "../qetproject.h"
#include <QHash>
#include <QSet>
namespace
{
@@ -400,6 +405,144 @@ bool RealTerminal::isShield() const {
return false;
}
/**
* @brief RealTerminal::cableTarget
* Where the cable of this terminal goes at its far end: the
* Installation, Location and Label (BMK) of the first piece the
* wires of the cable land on, past the strip's own terminals, in
* plan order -- the same three fields the cable list shows for a
* target, in the same order. An empty field is left out; a terminal
* with no cable, or a cable which lands on nothing else, gives no
* lines at all.
*
* The cable is the one the wires drawn to this terminal carry; a
* cable name typed by hand, with no wire behind it, is looked up by
* its name among the cables of the project.
* @return the lines to write in the box under the cable
*/
QVector<QString> RealTerminal::cableTarget() const
{
if (m_spare || m_element.isNull()) {
return QVector<QString>();
}
TerminalStrip *strip_ = parentStrip();
if (!strip_ || !strip_->project()) {
return QVector<QString>();
}
QETProject *project_{strip_->project()};
//The cable the wires drawn to this terminal are part of
QUuid cable_uuid_;
for (Terminal *terminal_ : m_element->terminals())
{
if (!terminal_) {
continue;
}
for (Conductor *conductor_ : terminal_->conductors())
{
if (conductor_ && !conductor_->properties().m_cable_uuid.isNull()) {
cable_uuid_ = conductor_->properties().m_cable_uuid;
break;
}
}
if (!cable_uuid_.isNull()) {
break;
}
}
if (cable_uuid_.isNull())
{
//A cable name typed by hand, no wire behind it: the first
//cable of the project drawn under that name
const auto name_{cable()};
if (name_.isEmpty()) {
return QVector<QString>();
}
for (Cable *cable_ : project_->cables())
{
if (cable_ && cable_->designation() == name_) {
cable_uuid_ = cable_->uuid();
break;
}
}
if (cable_uuid_.isNull()) {
return QVector<QString>();
}
}
//The own side: this strip's terminals never are the target
QSet<QUuid> own_;
for (const auto &physical_ : strip_->physicalTerminal())
{
if (!physical_) {
continue;
}
for (const auto &real_ : physical_->realTerminals()) {
if (real_ && real_->element()) {
own_.insert(real_->element()->uuid());
}
}
}
//Where the wires of the cable land: one piece whatever the
//number of its cores, never a report (the cable continues on
//the sheet the report points to) and never the strip's own
//terminals. Same rule as the cable list (cablelistrows.cpp).
QHash<QUuid, Element *> landed_;
for (Diagram *diagram_ : project_->diagrams())
{
for (Conductor *conductor_ : diagram_->conductors())
{
if (!conductor_ || conductor_->properties().m_cable_uuid != cable_uuid_) {
continue;
}
Terminal *ends_[2] = {conductor_->terminal1, conductor_->terminal2};
for (Terminal *terminal_ : ends_)
{
if (!terminal_) {
continue;
}
Element *element_ = terminal_->parentElement();
if (!element_ || (element_->linkType() & Element::AllReport)) {
continue;
}
if (own_.contains(element_->uuid())) {
continue;
}
landed_.insert(element_->uuid(), element_);
}
}
}
if (landed_.isEmpty()) {
return QVector<QString>();
}
//The first piece in plan order is the target, as the cable
//list chooses it
Element *target_{nullptr};
for (Element *element_ : std::as_const(landed_)) {
if (!target_ || comparPos(element_, target_)) {
target_ = element_;
}
}
const DiagramContext informations_{target_->elementInformations()};
const auto installation_{informations_.value(QETInformation::ELMT_PLANT).toString()};
const auto location_{informations_.value(QETInformation::ELMT_LOCATION).toString()};
const auto label_{informations_.value(QETInformation::ELMT_LABEL).toString()};
QVector<QString> lines_;
if (!installation_.isEmpty()) {
lines_ << installation_;
}
if (!location_.isEmpty()) {
lines_ << location_;
}
if (!label_.isEmpty()) {
lines_ << label_;
}
return lines_;
}
/**
* @brief RealTerminal::color
* @return The color set by the user for this terminal, drawn in the
+1
View File
@@ -75,6 +75,7 @@ class RealTerminal
QString cableWire() const;
bool cableIsAutomatic() const;
bool isShield() const;
QVector<QString> cableTarget() const;
QColor color() const;
QColor stripeColor() const;
qreal section() const;