From 29a8f018f47a4916223f2bed1505f7411897fdf9 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Mon, 19 Oct 2026 21:43:27 +0200 Subject: [PATCH 1/2] Name where a cable goes at the end of a strip The strip graphic fanned the wires out again after the cable, which shows each core leaving the cable but says nothing about where it is going -- the far end was unnamed. Keep the bundle together at the end now: a short stub and a box with the Installation, Location and Label (BMK) of the piece the cable lands on, the same three fields the cable list computes for a target (cablelistrows.cpp), so the strip and the list can never disagree. The target is the first piece past the strip's own terminals the wires of the cable land on, in plan order; a report is no arrival (the cable continues on the linked sheet), several cores on one piece are one landing, and a cable which lands on nothing but the strip itself gets no box. The lines shrink to the end zone the Terminal plan settings give (Cable end length), so the box never grows past the strip the bounding rect reserved. RealTerminal::cableTarget() works the whole out: the cable is the one the wires drawn to the terminal carry, a name typed by hand is looked up among the cables of the project, and the box is drawn for the first wire of the cable group. tst_cabletarget checks the value rule (own strip no target, spare no target, no cable no target) and that painting with and without a target gives two different pictures. --- .../GraphicsItem/terminalstripdrawer.cpp | 59 +++- .../GraphicsItem/terminalstripdrawer.h | 6 + .../GraphicsItem/trueterminalstrip.cpp | 5 + .../GraphicsItem/trueterminalstrip.h | 1 + sources/TerminalStrip/realterminal.cpp | 146 +++++++++ sources/TerminalStrip/realterminal.h | 1 + tests/qttest/CMakeLists.txt | 5 + tests/qttest/tst_cabletarget.cpp | 307 ++++++++++++++++++ 8 files changed, 522 insertions(+), 8 deletions(-) create mode 100644 tests/qttest/tst_cabletarget.cpp diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index f348bee7c..282729d30 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -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 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 @@ -350,7 +354,6 @@ void drawCables(QPainter *painter, const QVector &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)) @@ -363,12 +366,51 @@ void drawCables(QPainter *painter, const QVector &cells, const Termin //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(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()}; + 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 +1079,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); diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h index 5588092fd..db080d234 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h @@ -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 cableTarget() const { return QVector(); } //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. diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp index d5248fc2e..8a0199ab3 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp @@ -153,6 +153,11 @@ namespace TerminalStripDrawer return m_real ? m_real->isShield() : false; } + QVector TrueRealTerminal::cableTarget() const + { + return m_real ? m_real->cableTarget() : QVector(); + } + QColor TrueRealTerminal::color() const { return m_real ? m_real->color() : QColor(); diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h index abde6dbd2..ac20d2bc8 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h @@ -64,6 +64,7 @@ namespace TerminalStripDrawer QString cable() const override; QString cableWire() const override; bool isShield() const override; + QVector cableTarget() const override; QColor color() const override; QColor stripeColor() const override; qreal widthMm() const override; diff --git a/sources/TerminalStrip/realterminal.cpp b/sources/TerminalStrip/realterminal.cpp index 3ec7ad462..787735cd7 100644 --- a/sources/TerminalStrip/realterminal.cpp +++ b/sources/TerminalStrip/realterminal.cpp @@ -22,7 +22,15 @@ #include "terminalcolor.h" #include "terminalcatalog.h" #include "../qetgraphicsitem/conductor.h" +#include "../qetgraphicsitem/element.h" +#include "../qetgraphicsitem/terminal.h" #include "../qetinformation.h" +#include "../qetproject.h" +#include "../cable/cable.h" +#include "../diagram.h" + +#include +#include /** * @brief RealTerminal @@ -322,6 +330,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 RealTerminal::cableTarget() const +{ + if (m_spare || m_element.isNull()) { + return QVector(); + } + TerminalStrip *strip_ = parentStrip(); + if (!strip_ || !strip_->project()) { + return QVector(); + } + 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(); + } + for (Cable *cable_ : project_->cables()) + { + if (cable_ && cable_->designation() == name_) { + cable_uuid_ = cable_->uuid(); + break; + } + } + if (cable_uuid_.isNull()) { + return QVector(); + } + } + + //The own side: this strip's terminals never are the target + QSet 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 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(); + } + + //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 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 diff --git a/sources/TerminalStrip/realterminal.h b/sources/TerminalStrip/realterminal.h index bf8d39bbd..d969871d7 100644 --- a/sources/TerminalStrip/realterminal.h +++ b/sources/TerminalStrip/realterminal.h @@ -74,6 +74,7 @@ class RealTerminal QString cable() const; QString cableWire() const; bool isShield() const; + QVector cableTarget() const; QColor color() const; QColor stripeColor() const; qreal section() const; diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 644cf4725..a2e6496aa 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -332,6 +332,11 @@ target_link_libraries(tst_textresizehandles PRIVATE Qt::Test Qt::Widgets) # rectangle's does; a pivot placed by hand stays. qet_add_app_test(tst_linepivotresize tst_linepivotresize.cpp) +# The strip graphic names where a cable goes at its far end -- Installation, +# Location and Label of the first piece its wires land on past the strip, in +# plan order, the same three fields the cable list computes for a target. +qet_add_app_test(tst_cabletarget tst_cabletarget.cpp) + # legacyelementtypes.h is header-only: the rewrite of element type names in # nomenclature queries saved before June 2022. add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp) diff --git a/tests/qttest/tst_cabletarget.cpp b/tests/qttest/tst_cabletarget.cpp new file mode 100644 index 000000000..b87385aca --- /dev/null +++ b/tests/qttest/tst_cabletarget.cpp @@ -0,0 +1,307 @@ +/* + 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 "qetapptest.h" + +#include "TerminalStrip/GraphicsItem/trueterminalstrip.h" +#include "TerminalStrip/physicalterminal.h" +#include "TerminalStrip/realterminal.h" +#include "TerminalStrip/terminalstrip.h" +#include "cable/cable.h" +#include "conductorproperties.h" +#include "diagram.h" +#include "properties/elementdata.h" +#include "qetgraphicsitem/conductor.h" +#include "qetgraphicsitem/element.h" +#include "qetinformation.h" +#include "qetproject.h" + +#include +#include +#include + +/** + The terminal strip graphic names where a cable goes: 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 computes for a target + (cablelistrows.cpp), so the strip and the list can never disagree. + The graphic draws them in a small box under the cable, the bundle + staying together (RealTerminal::cableTarget()). + + fixtures/every_feature.qet has the strip X1 (elements 891b70f2 and + 4861e268) and the contact fc9cc920 wired to 891b70f2 by the + conductor 8bd16d7a; the test gives the contact the Installation, + Location and Label it reads back. The conductor 584b452e joins the + two strip terminals to each other. No conductor of the fixture + belongs to a cable yet. +*/ +class tst_cabletarget : public QObject +{ + Q_OBJECT + + QETProject *m_project = nullptr; + + Element *element(const QString &uuid) const + { + for (Diagram *diagram_ : m_project->diagrams()) { + for (Element *element_ : diagram_->elements()) { + if (element_->uuid().toString() == uuid) { + return element_; + } + } + } + return nullptr; + } + + QSharedPointer terminalInStrip(const QString &element_uuid) const + { + const auto strips_ = m_project->terminalStrip(); + if (strips_.isEmpty()) { + return {}; + } + for (const auto &real_ : strips_.first()->realTerminals()) { + if (real_ && real_->element() + && real_->element()->uuid().toString() == element_uuid) { + return real_; + } + } + return {}; + } + + Conductor *conductor(const QString &uuid) const + { + for (Diagram *diagram_ : m_project->diagrams()) { + for (Conductor *conductor_ : diagram_->conductors()) { + if (conductor_ && conductor_->uuid().toString() == uuid) { + return conductor_; + } + } + } + return nullptr; + } + + /// Let the drawn wires of @a conductor_ belong to the cable @a cable_uuid. + void claimCable(Conductor *conductor_, const QUuid &cable_uuid) const + { + auto properties_ = conductor_->properties(); + properties_.m_cable_uuid = cable_uuid; + conductor_->setProperties(properties_); + } + + /** + Same setup for the value and the drawing test: the contact the + strip's wire lands on carries the three fields the test reads + back (the fixture's own block has none), the wire belongs to a + new cable, and the terminal carries that cable's name the way + the strip editor stores it -- without a name the strip draws no + cable at all. @return false when the fixture is not the one this + test was written for. + */ + bool wireCableToTheContact() + { + Element *contact_ = element( + QStringLiteral("{fc9cc920-5822-421a-916e-a17100acdcf6}")); + if (!contact_) { + return false; + } + ElementData contact_data_{contact_->elementData()}; + contact_data_.m_informations.addValue(QETInformation::ELMT_PLANT, + QStringLiteral("Plant 7")); + contact_data_.m_informations.addValue(QETInformation::ELMT_LOCATION, + QStringLiteral("Cabinet 7")); + contact_data_.m_informations.addValue(QETInformation::ELMT_LABEL, + QStringLiteral("-S7")); + contact_->setElementData(contact_data_); + + Cable *cable_ = m_project->newCable(); + Conductor *wired_ = conductor( + QStringLiteral("{8bd16d7a-18c4-4fa0-9495-8dab65fd6408}")); + if (!cable_ || !wired_) { + return false; + } + claimCable(wired_, cable_->uuid()); + + //The name of the cable on the terminal, the way the strip + //editor stores it: without it the strip draws no cable at all + Element *terminal_ = element( + QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}")); + if (!terminal_) { + return false; + } + ElementData terminal_data_{terminal_->elementData()}; + terminal_data_.m_informations.addValue(RealTerminal::cableInfoKey(), + QStringLiteral("X99")); + terminal_->setElementData(terminal_data_); + return true; + } + +private slots: + void init(); + void cleanup(); + + void noCableNoTarget(); + void targetIsTheOtherPiece(); + void ownStripIsNoTarget(); + void spareHasNoTarget(); + void paintsWithTargetBox(); +}; + +void tst_cabletarget::init() +{ + const QString fixture_ = QFINDTESTDATA("fixtures/every_feature.qet"); + QVERIFY(!fixture_.isEmpty()); + m_project = new QETProject(fixture_); + QCOMPARE(m_project->state(), QETProject::Ok); +} + +void tst_cabletarget::cleanup() +{ + delete m_project; + m_project = nullptr; +} + +/** + Nothing to name when no wire of the terminal belongs to a cable and + no cable of the project is drawn under a name the terminal carries. +*/ +void tst_cabletarget::noCableNoTarget() +{ + const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}")); + const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}")); + QVERIFY(a_); + QVERIFY(b_); + QVERIFY(a_->cable().isEmpty()); + QVERIFY(a_->cableTarget().isEmpty()); + QVERIFY(b_->cableTarget().isEmpty()); +} + +/** + The cable of the strip lands on the contact past it: the three + fields of that piece, in the order the cable list shows them + (Installation, Location, Label), and nothing of the strip itself. +*/ +void tst_cabletarget::targetIsTheOtherPiece() +{ + QVERIFY(wireCableToTheContact()); + + const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}")); + QVERIFY(a_); + QCOMPARE(a_->cableTarget(), + QVector({ + QStringLiteral("Plant 7"), + QStringLiteral("Cabinet 7"), + QStringLiteral("-S7"), + })); + + //The other strip terminal carries no claimed wire of its own + //(584b452e joins it to a_ and is not claimed) and no cable name, + //so it has no target to name + const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}")); + QVERIFY(b_); + QVERIFY(b_->cableTarget().isEmpty()); +} + +/** + A cable whose wires only ever join the terminals of the strip + itself lands on nothing past the strip: no target, no box. +*/ +void tst_cabletarget::ownStripIsNoTarget() +{ + Cable *cable_ = m_project->newCable(); + QVERIFY(cable_); + Conductor *inside_ = conductor( + QStringLiteral("{584b452e-a885-491a-98f1-fd348610b5e0}")); + QVERIFY(inside_); + claimCable(inside_, cable_->uuid()); + + const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}")); + const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}")); + QVERIFY(a_); + QVERIFY(b_); + QVERIFY(a_->cableTarget().isEmpty()); + QVERIFY(b_->cableTarget().isEmpty()); +} + +/** + A spare terminal stands on no folio and is wired to nothing: it has + no target to name either. +*/ +void tst_cabletarget::spareHasNoTarget() +{ + ElementData data_; + data_.m_informations.addValue(QStringLiteral("label"), + QStringLiteral("AAA")); + const auto spare_ = RealTerminal::createSpare(data_); + QVERIFY(spare_); + QVERIFY(spare_->isSpare()); + QVERIFY(spare_->cableTarget().isEmpty()); +} + +/** + The strip graphic draws the box: the same strip painted with + something to name at the end of the cable and painted with nothing + to name come out as two different pictures -- the box is the + difference. Painting it also runs the whole drawCables() path. +*/ +void tst_cabletarget::paintsWithTargetBox() +{ + QVERIFY(wireCableToTheContact()); + + const auto strips_ = m_project->terminalStrip(); + QVERIFY(!strips_.isEmpty()); + TerminalStripDrawer::TerminalStripDrawer drawer{ + QSharedPointer{ + new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}}, + QSharedPointer{new TerminalStripLayoutPattern}}; + + const QRectF bounds{drawer.boundingRect()}; + QVERIFY(!bounds.isEmpty()); + + const auto render = [&drawer, &bounds]() { + QImage image{bounds.size().toSize() + QSize{4, 4}, + QImage::Format_ARGB32_Premultiplied}; + image.fill(Qt::transparent); + QPainter painter{&image}; + painter.translate(-bounds.topLeft() + QPointF{2, 2}); + drawer.paint(&painter); + return image; + }; + + const QImage with_box{render()}; + + //Nothing left to name at the far end of the cable + Element *contact_ = element( + QStringLiteral("{fc9cc920-5822-421a-916e-a17100acdcf6}")); + QVERIFY(contact_); + ElementData contact_data_{contact_->elementData()}; + contact_data_.m_informations.addValue(QETInformation::ELMT_PLANT, QString()); + contact_data_.m_informations.addValue(QETInformation::ELMT_LOCATION, QString()); + contact_data_.m_informations.addValue(QETInformation::ELMT_LABEL, QString()); + contact_->setElementData(contact_data_); + + const QImage without_box{render()}; + QVERIFY(with_box != without_box); +} + +int main(int argc, char **argv) +{ + QStandardPaths::setTestModeEnabled(true); + return QetAppTest::run(argc, argv); +} + +#include "tst_cabletarget.moc" From b6312f1f78ec0ffaf640d8adc3831cdacd349285 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Mon, 19 Oct 2026 22:22:40 +0200 Subject: [PATCH 2/2] Keep the wire numbers out of the terminal drawing The number of a wire was written from its mark towards the terminal without any regard for the room left: a colour code like 'gnge' ran into the drawing of the terminal. The length of the wires setting moves the mark, but every project has to be tuned to its longest number by hand, and a short length leaves no room at all. Shrink the font while the number is longer than the room between the mark and the terminal, and clip away what still does not fit even at the smallest size, so a number never crosses into the terminal -- the wire length and the font stay the settings to make the drawing pleasant, they are just no longer able to break it. tst_cabletarget paints the strip with a long number and without any and checks the two pictures are the same above the bottom of the terminal: the number is drawn below it or not at all. --- .../GraphicsItem/terminalstripdrawer.cpp | 38 ++++++++-- tests/qttest/tst_cabletarget.cpp | 73 +++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index 282729d30..fc343ac7c 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -299,19 +299,45 @@ void drawCables(QPainter *painter, const QVector &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(); @@ -359,7 +385,7 @@ void drawCables(QPainter *painter, const QVector &cells, const Termin 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}); diff --git a/tests/qttest/tst_cabletarget.cpp b/tests/qttest/tst_cabletarget.cpp index b87385aca..1ca83a347 100644 --- a/tests/qttest/tst_cabletarget.cpp +++ b/tests/qttest/tst_cabletarget.cpp @@ -43,6 +43,10 @@ The graphic draws them in a small box under the cable, the bundle staying together (RealTerminal::cableTarget()). + Also here: the number of a wire must stay in the room left between + its mark and the terminal -- it shrinks, and what does not fit is + clipped, so it never runs into the drawing of the terminal. + fixtures/every_feature.qet has the strip X1 (elements 891b70f2 and 4861e268) and the contact fc9cc920 wired to 891b70f2 by the conductor 8bd16d7a; the test gives the contact the Installation, @@ -159,6 +163,7 @@ private slots: void ownStripIsNoTarget(); void spareHasNoTarget(); void paintsWithTargetBox(); + void wireLabelStaysOutOfTheTerminal(); }; void tst_cabletarget::init() @@ -298,6 +303,74 @@ void tst_cabletarget::paintsWithTargetBox() QVERIFY(with_box != without_box); } +/** + The number of a wire is written next to its mark, in the room left + between the mark and the terminal: it shrinks to fit there, and what + still does not fit is clipped away, so a long number never runs into + the drawing of the terminal. The two pictures -- one with a number + long enough to reach into the terminal, one without any -- must be + the same above the bottom of the terminal, and differ below it (the + number is drawn somewhere). +*/ +void tst_cabletarget::wireLabelStaysOutOfTheTerminal() +{ + QVERIFY(wireCableToTheContact()); + + auto pattern_ = QSharedPointer{new TerminalStripLayoutPattern}; + const auto strips_ = m_project->terminalStrip(); + QVERIFY(!strips_.isEmpty()); + TerminalStripDrawer::TerminalStripDrawer drawer{ + QSharedPointer{ + new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}}, + pattern_}; + + const QRectF bounds{drawer.boundingRect()}; + QVERIFY(!bounds.isEmpty()); + + const auto render = [&drawer, &bounds]() { + QImage image{bounds.size().toSize() + QSize{4, 4}, + QImage::Format_ARGB32_Premultiplied}; + image.fill(Qt::transparent); + QPainter painter{&image}; + painter.translate(-bounds.topLeft() + QPointF{2, 2}); + drawer.paint(&painter); + return image; + }; + + //A number much longer than the room left over the mark + Element *terminal_ = element( + QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}")); + QVERIFY(terminal_); + ElementData terminal_data_{terminal_->elementData()}; + terminal_data_.m_informations.addValue( + RealTerminal::cableWireInfoKey(), + QStringLiteral("gnge-gnge-gnge-gnge-gnge")); + terminal_->setElementData(terminal_data_); + const QImage with_label{render()}; + + //The same strip, the wire carries no number at all + terminal_data_.m_informations.addValue(RealTerminal::cableWireInfoKey(), + QString()); + terminal_->setElementData(terminal_data_); + const QImage without_label{render()}; + + //The number is drawn: the two pictures differ + QVERIFY(with_label != without_label); + + //The terminals of this strip all stand on the last level of the + //pattern, so their bottom is the same rectangle + const qreal terminal_bottom{pattern_->m_terminal_rect.at(3).bottom()}; + const auto rows_to_check{qMin(qRound(terminal_bottom - bounds.topLeft().y() + 2), + with_label.height())}; + for (auto y = 0 ; y < rows_to_check ; ++y) + { + for (auto x = 0 ; x < with_label.width() ; ++x) + { + QCOMPARE(with_label.pixel(x, y), without_label.pixel(x, y)); + } + } +} + int main(int argc, char **argv) { QStandardPaths::setTestModeEnabled(true);