From a8f70fe0711c04bb530479a405173698ca536b74 Mon Sep 17 00:00:00 2001 From: DaTweety <129504390+DaTweety@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:41:28 +0200 Subject: [PATCH 1/5] Write qet-assistant.json after the settings dialog applies the paths GeneralConfigurationPage::applyConf() wrote qet-assistant.json before it applied the collection and title block paths, so the file kept the paths the dialog had just replaced. The qet MCP server hands the user and company collections from that file to every headless run it starts: a company collection chosen in the dialog stayed unknown there, and company:// paths failed to resolve until QElectroTech was started again. Write the file last. tst_assistantinfopaths picks a new company and user collection on the page, applies it and reads the file back; it runs in QStandardPaths test mode from main() on, since QETApp writes the file to the standard data folder as its editor opens, whatever --data-dir says. Co-Authored-By: Claude Opus 5.5 --- .../configpage/generalconfigurationpage.cpp | 9 +- tests/qttest/CMakeLists.txt | 4 + tests/qttest/tst_assistantinfopaths.cpp | 100 ++++++++++++++++++ 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 tests/qttest/tst_assistantinfopaths.cpp diff --git a/sources/ui/configpage/generalconfigurationpage.cpp b/sources/ui/configpage/generalconfigurationpage.cpp index 05688b006..6dd1916e8 100644 --- a/sources/ui/configpage/generalconfigurationpage.cpp +++ b/sources/ui/configpage/generalconfigurationpage.cpp @@ -395,9 +395,6 @@ void GeneralConfigurationPage::applyConf() if (!ui->m_live_assistant->isChecked()) LiveServer::instance().stop(); #endif } - //What an assistant reads about this QElectroTech follows the change - AssistantInfo::write(); - //GENERIC PANEL settings.setValue("genericpanel/folio",ui->m_use_folio_label->isChecked()); @@ -565,6 +562,12 @@ void GeneralConfigurationPage::applyConf() diagram->update(); } } + + //What an assistant reads about this QElectroTech follows the + //change. Written last: the collection and title block paths are + //applied above, and an assistant launching a headless run takes + //the user and company collections from this file. + AssistantInfo::write(); } /** diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 6dab05931..e81ce96fc 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -267,6 +267,10 @@ endfunction() # alone and with a group. qet_add_app_test(tst_rotateselectionimage tst_rotateselectionimage.cpp) +# Accepting the settings dialog writes qet-assistant.json with the +# collection paths just chosen, not the ones they replace. +qet_add_app_test(tst_assistantinfopaths tst_assistantinfopaths.cpp) + # Escape during a picture's resize, rotate or pivot drag puts it back, keeps # it selected and records no undo step (#1414); the key goes to the view. qet_add_app_test(tst_imagehandleescape tst_imagehandleescape.cpp) diff --git a/tests/qttest/tst_assistantinfopaths.cpp b/tests/qttest/tst_assistantinfopaths.cpp new file mode 100644 index 000000000..96e99dd67 --- /dev/null +++ b/tests/qttest/tst_assistantinfopaths.cpp @@ -0,0 +1,100 @@ +/* + 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 "scripting/assistantinfo.h" +#include "ui/configpage/generalconfigurationpage.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + qet-assistant.json names the user and company collections, and the qet + MCP server hands them to every headless run it starts. Accepting the + settings dialog wrote the file before the dialog applied the new + collection paths, so the file kept the old ones: a company collection + chosen in the dialog stayed unknown to the assistant, and company:// + paths failed in its runs, until QElectroTech was started again. +*/ +class tst_assistantinfopaths : public QObject +{ + Q_OBJECT + + static QString writtenPath(const QString &key) + { + QFile file(AssistantInfo::path()); + if (!file.open(QIODevice::ReadOnly)) + return QString(); + const QJsonObject folders = QJsonDocument::fromJson(file.readAll()) + .object().value(QStringLiteral("folders")).toObject(); + return QDir::cleanPath(folders.value(key).toString()); + } + + static void choose(GeneralConfigurationPage &page, const QString &box, const QString &path) + { + auto *combo = page.findChild(box); + QVERIFY(combo); + //The state the page itself leaves once a folder is picked; + //signals blocked, or index 1 opens the folder dialog + const QSignalBlocker blocker(combo); + combo->setCurrentIndex(1); + combo->setItemData(1, path, Qt::DisplayRole); + } + +private slots: + void companyCollectionChosenInTheDialogIsWritten() + { + QTemporaryDir company; + QVERIFY(company.isValid()); + + GeneralConfigurationPage page; + choose(page, QStringLiteral("m_company_elmt_path_cb"), company.path()); + page.applyConf(); + + QCOMPARE(writtenPath(QStringLiteral("elements_company")), + QDir::cleanPath(company.path())); + } + + void customCollectionChosenInTheDialogIsWritten() + { + QTemporaryDir custom; + QVERIFY(custom.isValid()); + + GeneralConfigurationPage page; + choose(page, QStringLiteral("m_custom_elmt_path_cb"), custom.path()); + page.applyConf(); + + QCOMPARE(writtenPath(QStringLiteral("elements_custom")), + QDir::cleanPath(custom.path())); + } +}; + +int main(int argc, char **argv) +{ + //AssistantInfo writes to the standard data folder, whatever + //--data-dir says, and QETApp writes it as its editor opens: test + //mode from the very start keeps the user's own file out of reach. + QStandardPaths::setTestModeEnabled(true); + return QetAppTest::run(argc, argv); +} + +#include "tst_assistantinfopaths.moc" From 34557928d1b0458c91d97e8a113f6ff3e66a88d2 Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 17 Oct 2026 20:21:35 +0200 Subject: [PATCH 2/5] Terminal strip: realistic drawing styles and separator plates Add two realistic drawing styles for the terminal strip plan, besides the existing schematic one: - Realistic (push-in) and Realistic (screw): the strip seen from above, at scale (real width of each terminal), like a cabinet mounting drawing: end stops, group marker carrier with the strip name, one row of wire entries per level, bridge channels with red bridges, marker tags with the terminal number, type symbol (fuse, LED, diode, sectional, ground...), green/yellow PE and terminal color. The style is chosen for each plan in the "Add terminal strip plan" dialog and saved with the item (attribute draw_style of terminal_strip_item). Items saved before keep the schematic style. Several plans of the same strip can use different styles. The values used by the realistic styles are in the layout (scale in px/mm, terminal length in mm, end stops, group marker) and can be set in the layout editor, whose style combo box only changes the preview. Separator plates: a new "Separator plate after" check box column in the terminal strip editor, stored in the terminal element informations (key terminal_separator). Drawn as a plate at scale in the realistic styles and as a thick line in the schematic style. New files: realisticterminalstripdrawer.{h,cpp} (realistic drawing) and terminalstripdrawerhelpers.h (helpers shared with the schematic drawer). --- .../properties/terminalstriplayoutpattern.cpp | 46 ++ .../properties/terminalstriplayoutpattern.h | 31 + .../realisticterminalstripdrawer.cpp | 564 ++++++++++++++++++ .../realisticterminalstripdrawer.h | 50 ++ .../GraphicsItem/terminalstripdrawer.cpp | 77 +++ .../GraphicsItem/terminalstripdrawer.h | 6 + .../GraphicsItem/terminalstripdrawerhelpers.h | 48 ++ .../GraphicsItem/terminalstripitem.cpp | 20 + .../GraphicsItem/terminalstripitem.h | 2 + .../GraphicsItem/trueterminalstrip.cpp | 5 + .../GraphicsItem/trueterminalstrip.h | 1 + sources/TerminalStrip/realterminal.cpp | 14 + sources/TerminalStrip/realterminal.h | 3 + .../ui/addterminalstripitemdialog.cpp | 13 + .../ui/addterminalstripitemdialog.h | 1 + .../ui/addterminalstripitemdialog.ui | 29 + sources/TerminalStrip/ui/modelTerminalData.h | 2 + .../TerminalStrip/ui/terminalstripeditor.cpp | 1 + .../ui/terminalstriplayouteditor.cpp | 34 ++ .../ui/terminalstriplayouteditor.ui | 82 +++ .../TerminalStrip/ui/terminalstripmodel.cpp | 18 +- sources/TerminalStrip/ui/terminalstripmodel.h | 1 + sources/xml/terminalstripitemxml.cpp | 11 + 23 files changed, 1057 insertions(+), 2 deletions(-) create mode 100644 sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp create mode 100644 sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h create mode 100644 sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp index e11ede4a3..0bbb726b5 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp @@ -171,6 +171,32 @@ namespace } } +/** + * @brief TerminalStripLayoutPattern::drawStyleToString + * @return the string used to save @a style in the project xml (terminal strip item) + */ +QString TerminalStripLayoutPattern::drawStyleToString(DrawStyle style) +{ + switch (style) { + case DrawStyle::RealisticPushIn : return QStringLiteral("realistic_push_in"); + case DrawStyle::RealisticScrew : return QStringLiteral("realistic_screw"); + default : return QStringLiteral("schematic"); + } +} + +/** + * @brief TerminalStripLayoutPattern::drawStyleFromString + * @return the style saved as @a string, or @a default_style if @a string is unknown or empty + * (item saved before the drawing styles : the schematic style is kept). + */ +TerminalStripLayoutPattern::DrawStyle TerminalStripLayoutPattern::drawStyleFromString(const QString &string, DrawStyle default_style) +{ + if (string == QLatin1String("schematic")) return DrawStyle::Schematic; + if (string == QLatin1String("realistic_push_in") || string == QLatin1String("realistic")) return DrawStyle::RealisticPushIn; + if (string == QLatin1String("realistic_screw")) return DrawStyle::RealisticScrew; + return default_style; +} + /** * @brief TerminalStripLayoutPattern::xmlTagName * @return the tag name of the xml element used by toXml / fromXml @@ -228,6 +254,11 @@ QDomElement TerminalStripLayoutPattern::toXml(QDomDocument &document) const element.setAttribute(QStringLiteral("bridge_point_y_offset_%1").arg(i), m_bridge_point_y_offset.at(i)); } + element.setAttribute(QStringLiteral("realistic_px_per_mm"), m_realistic_px_per_mm); + element.setAttribute(QStringLiteral("realistic_length_mm"), m_realistic_length_mm); + element.setAttribute(QStringLiteral("realistic_end_stops"), m_realistic_end_stops ? 1 : 0); + element.setAttribute(QStringLiteral("realistic_group_marker"), m_realistic_group_marker ? 1 : 0); + return element; } @@ -290,4 +321,19 @@ void TerminalStripLayoutPattern::fromXml(const QDomElement &layout_element) for (auto i = 0; i < m_bridge_point_y_offset.size(); ++i) { m_bridge_point_y_offset[i] = realFromXml(layout_element, QStringLiteral("bridge_point_y_offset_%1").arg(i), m_bridge_point_y_offset.at(i)); } + + const auto px_per_mm_ = realFromXml(layout_element, QStringLiteral("realistic_px_per_mm"), m_realistic_px_per_mm); + if (px_per_mm_ > 0) { + m_realistic_px_per_mm = px_per_mm_; + } + const auto length_mm_ = realFromXml(layout_element, QStringLiteral("realistic_length_mm"), m_realistic_length_mm); + if (length_mm_ > 0) { + m_realistic_length_mm = length_mm_; + } + if (layout_element.hasAttribute(QStringLiteral("realistic_end_stops"))) { + m_realistic_end_stops = layout_element.attribute(QStringLiteral("realistic_end_stops")).toInt() != 0; + } + if (layout_element.hasAttribute(QStringLiteral("realistic_group_marker"))) { + m_realistic_group_marker = layout_element.attribute(QStringLiteral("realistic_group_marker")).toInt() != 0; + } } diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h index b432783b2..4dc941601 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h @@ -44,6 +44,37 @@ class TerminalStripLayoutPattern public: TerminalStripLayoutPattern(); + /** + * @brief The DrawStyle enum + * How the terminals of a strip are drawn. + * The style is chosen for each terminal strip item, when the item is + * added to a folio (see TerminalStripItem::setDrawStyle), the layout + * only hold the values used by the styles. + * Schematic : the classic drawing (header, xrefs, symbols, connections, cables). + * RealisticPushIn : the strip seen from above, at scale, like the mounting + * drawing of a cabinet : end stops, group marker, push-in wire entries. + * RealisticScrew : same as RealisticPushIn, with screw clamps. + * The schematic style use most of the values of this class, + * the realistic styles use the values prefixed by m_realistic_. + */ + enum class DrawStyle { + Schematic = 0, + RealisticPushIn = 1, + RealisticScrew = 2 + }; + static QString drawStyleToString(DrawStyle style); + static DrawStyle drawStyleFromString(const QString &string, DrawStyle default_style); + + //Realistic style + //Scale of the drawing : number of pixels for one millimeter + qreal m_realistic_px_per_mm{4.0}; + //Length (mm) of a terminal along the wires, from the top to the bottom + qreal m_realistic_length_mm{60.0}; + //Draw an end stop at each end of the strip + bool m_realistic_end_stops{true}; + //Draw a group marker carrier with the name of the strip, after the first end stop + bool m_realistic_group_marker{true}; + //Header of terminal strip QRectF m_header_rect{0,30,50,146}; Qt::Orientation m_header_text_orientation{Qt::Horizontal}; diff --git a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp new file mode 100644 index 000000000..431b6c1e2 --- /dev/null +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp @@ -0,0 +1,564 @@ +/* + 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 "realisticterminalstripdrawer.h" +#include "terminalstripdrawerhelpers.h" + +#include +#include +#include +#include +#include + +#include + +namespace TerminalStripDrawer { +namespace Realistic { + +namespace { + + //Width (mm) of the accessories. Values of usual products, enough for a drawing. +constexpr qreal end_stop_width_mm{8.0}; +constexpr qreal group_marker_width_mm{11.0}; +constexpr qreal separator_width_mm{1.5}; + //Biggest radius of a wire entry (mm), for the wide terminals +constexpr qreal max_entry_radius_mm{1.9}; + + //Vertical layout of a terminal, in fraction of its length (top = 0, bottom = 1). + //The wire entries are in [entries_top, band_top] and [band_bottom, 1 - entries_top], + //the middle band (marker tag, bridge channels) is in [band_top, band_bottom]. +constexpr qreal entries_top{0.04}; +constexpr qreal band_top{0.40}; +constexpr qreal band_bottom{0.60}; +constexpr qreal channels_top{0.405}; +constexpr qreal channels_bottom{0.445}; + + //Colors of the accessories, close to the real products +const QColor end_stop_color{232, 228, 220}; +const QColor end_stop_grip_color{35, 35, 35}; +const QColor group_marker_color{168, 168, 168}; +const QColor group_marker_text_color{200, 0, 0}; +const QColor band_color{85, 85, 85}; +const QColor channel_color{55, 55, 55}; +const QColor bridge_color{200, 30, 30}; +const QColor separator_color{196, 196, 198}; + +/** + * @brief The PhysicalGeometry struct + * What is needed to draw a physical terminal. + */ +struct PhysicalGeometry +{ + qreal width_mm{0}; + bool separator_after{false}; //A separator plate is mounted after this terminal + QVector> reals; +}; + +/** + * @return the physical terminals of @a strip with their width, in the drawing order. + * The width of a physical terminal is the biggest width of its real terminals, + * or the reference width if no width is known. + */ +QVector geometry(const AbstractTerminalStripInterface *strip) +{ + QVector geometry_; + if (!strip) { + return geometry_; + } + + for (const auto &physical_t : strip->physicalTerminal()) + { + if (!physical_t) { + continue; + } + PhysicalGeometry g_; + for (const auto &real_t : physical_t->realTerminals()) + { + if (!real_t) { + continue; + } + g_.reals.append(real_t); + 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 + break; + } + } + if (g_.reals.isEmpty()) { + continue; + } + if (g_.width_mm <= 0) { + g_.width_mm = Helpers::referenceTerminalWidthMm(); + } + geometry_.append(g_); + } + return geometry_; +} + +qreal endStopWidthMm(const TerminalStripLayoutPattern &pattern) { + if (!pattern.m_realistic_end_stops) { + return 0; + } + return end_stop_width_mm; +} + +qreal groupMarkerWidthMm(const TerminalStripLayoutPattern &pattern) { + return pattern.m_realistic_group_marker ? group_marker_width_mm : 0; +} + +/** + * @brief fitFont + * Reduce the pixel size of the font of @a painter so that @a text fit in + * @a length (along the text) and @a thickness (across the text). + * The font is never enlarged. + */ +void fitFont(QPainter *painter, const QString &text, qreal length, qreal thickness) +{ + auto font_{painter->font()}; + qreal size_{font_.pixelSize() > 0 ? qreal(font_.pixelSize()) : font_.pointSizeF()}; + const QFontMetricsF metrics_{font_}; + qreal factor_{1}; + if (metrics_.horizontalAdvance(text) > length && length > 0) { + factor_ = std::min(factor_, length / metrics_.horizontalAdvance(text)); + } + if (metrics_.height() > thickness && thickness > 0) { + factor_ = std::min(factor_, thickness / metrics_.height()); + } + if (factor_ < 1) + { + size_ = std::max(2.0, size_ * factor_); + if (font_.pixelSize() > 0) { + font_.setPixelSize(std::max(1, static_cast(size_))); + } else { + font_.setPointSizeF(size_); + } + painter->setFont(font_); + } +} + +/** + * @brief drawVerticalText + * Draw @a text from the bottom to the top, centered in @a rect. + * The font is reduced if needed. + */ +void drawVerticalText(QPainter *painter, const QRectF &rect, const QString &text, const QColor &color) +{ + if (text.isEmpty() || rect.isEmpty()) { + return; + } + painter->save(); + fitFont(painter, text, rect.height() - 2, rect.width() - 1); + painter->setPen(color); + painter->translate(rect.left(), rect.bottom()); + painter->rotate(270); + painter->drawText(QRectF{0, 0, rect.height(), rect.width()}, + Qt::AlignCenter, text); + painter->restore(); +} + +/** + * @brief drawGrip + * Draw a black ribbed grip (end of an end stop). + */ +void drawGrip(QPainter *painter, const QRectF &rect) +{ + painter->save(); + painter->setBrush(end_stop_grip_color); + painter->drawRect(rect); + painter->setPen(QPen(QColor(150, 150, 150), 0)); + const qreal step_{std::max(1.5, rect.height() / 5)}; + for (qreal y = rect.top() + step_ ; y < rect.bottom() - 0.5 ; y += step_) { + painter->drawLine(QPointF{rect.left() + 1, y}, QPointF{rect.right() - 1, y}); + } + painter->restore(); +} + +/** + * @brief drawEndStop + * Draw an end stop seen from above : a light body with a black grip at each end, + * the fixing screw, the key slot and the locking part. + * @param rect : the whole end stop + */ +void drawEndStop(QPainter *painter, const QRectF &rect) +{ + painter->save(); + painter->setBrush(end_stop_color); + painter->drawRect(rect); + + const qreal w_{rect.width()}; + const qreal h_{rect.height()}; + const qreal cx_{rect.center().x()}; + const qreal grip_h_{h_ * 0.08}; + drawGrip(painter, QRectF{rect.left(), rect.top(), w_, grip_h_}); + drawGrip(painter, QRectF{rect.left(), rect.bottom() - grip_h_, w_, grip_h_}); + + //Fixing screw + const qreal screw_r_{std::min(w_ * 0.3, h_ * 0.05)}; + const QPointF screw_c_{cx_, rect.top() + h_ * 0.25}; + painter->setBrush(QColor(60, 60, 60)); + painter->drawEllipse(screw_c_, screw_r_, screw_r_); + painter->setBrush(QColor(20, 20, 20)); + painter->drawEllipse(screw_c_, screw_r_ * 0.65, screw_r_ * 0.65); + + //Key slot : a narrow slot with a wider middle + painter->setBrush(QColor(210, 205, 195)); + const qreal slot_w_{w_ * 0.22}; + painter->drawRoundedRect(QRectF{cx_ - slot_w_ / 2, rect.top() + h_ * 0.36, slot_w_, h_ * 0.26}, 1, 1); + painter->drawRect(QRectF{cx_ - slot_w_, rect.top() + h_ * 0.45, slot_w_ * 2, h_ * 0.06}); + + //Locking part + painter->setBrush(QColor(20, 20, 20)); + const qreal lock_w_{w_ * 0.36}; + painter->drawRect(QRectF{cx_ - lock_w_ / 2, rect.top() + h_ * 0.68, lock_w_, h_ * 0.07}); + painter->drawEllipse(QPointF{cx_, rect.top() + h_ * 0.79}, lock_w_ * 0.35, lock_w_ * 0.35); + + painter->restore(); +} + +/** + * @brief drawSeparator + * Draw a separator plate (partition plate) seen from above : + * a thin plate as long as the terminals, with rounded ends and a light edge. + */ +void drawSeparator(QPainter *painter, const QRectF &rect) +{ + painter->save(); + painter->setPen(QPen(separator_color.darker(170), 0.5)); + painter->setBrush(separator_color); + const qreal r_{std::min(rect.width() / 2, qreal(2.0))}; + painter->drawRoundedRect(rect, r_, r_); + //Light edge, the plate is a bit higher than the terminals + painter->setPen(QPen(separator_color.lighter(118), std::max(0.5, rect.width() * 0.25))); + painter->drawLine(QPointF{rect.left() + rect.width() * 0.35, rect.top() + r_}, + QPointF{rect.left() + rect.width() * 0.35, rect.bottom() - r_}); + painter->restore(); +} + +/** + * @brief drawGroupMarker + * Draw the group marker carrier with the name of the strip on a white label. + */ +void drawGroupMarker(QPainter *painter, const QRectF &rect, const QString &text) +{ + painter->save(); + painter->setBrush(group_marker_color); + painter->drawRect(rect); + + if (!text.isEmpty()) + { + //White label : as long as the text, centered on the carrier + const qreal label_w_{rect.width() * 0.6}; + fitFont(painter, text, rect.height() * 0.8, label_w_ - 2); + const qreal text_len_{QFontMetricsF{painter->font()}.horizontalAdvance(text)}; + const qreal label_h_{std::min(rect.height() * 0.85, text_len_ + label_w_ * 0.6)}; + const QRectF label_{rect.center().x() - label_w_ / 2, + rect.center().y() - label_h_ / 2, + label_w_, label_h_}; + painter->setBrush(Qt::white); + painter->drawRect(label_); + drawVerticalText(painter, label_, text, group_marker_text_color); + } + painter->restore(); +} + +/** + * @brief drawWireEntry + * Draw one wire entry of a terminal. + * Push-in : a black hole. + * Screw : the head of the clamping screw, with the hole of the wire next to it. + * @param center : center of the entry + * @param width : width of the terminal + * @param row_h : height available for the entry + * @param max_r : biggest radius of the entry (a wide terminal don't have a huge hole) + * @param outer : -1 if the end of the terminal is above the entry (top half), +1 if it is under (bottom half) + * @param screw : true for a screw clamp + */ +void drawWireEntry(QPainter *painter, const QPointF ¢er, qreal width, qreal row_h, + qreal max_r, qreal outer, const QColor &fill, bool screw) +{ + painter->save(); + if (!screw) + { + const qreal r_{std::min({width * 0.32, row_h * 0.3, max_r})}; + painter->setBrush(Qt::black); + painter->drawEllipse(center, r_, r_); + } + else + { + //Hole of the wire, toward the end of the terminal + const qreal hole_{std::min({width * 0.45, row_h * 0.22, max_r * 1.6})}; + const qreal hole_y_{outer < 0 ? center.y() - row_h * 0.38 : center.y() + row_h * 0.38 - hole_}; + painter->setBrush(Qt::black); + painter->drawRect(QRectF{center.x() - hole_ / 2, hole_y_, hole_, hole_}); + //Screw head, toward the middle of the terminal + const qreal r_{std::min({width * 0.36, row_h * 0.26, max_r * 1.1})}; + const QPointF c_{center.x(), center.y() - outer * row_h * 0.08}; + painter->setBrush(fill.darker(125)); + painter->drawEllipse(c_, r_ * 1.15, r_ * 1.15); + painter->setBrush(QColor(175, 175, 175)); + painter->drawEllipse(c_, r_, r_); + painter->setPen(QPen(Qt::black, std::max(0.6, r_ * 0.25))); + painter->drawLine(QPointF{c_.x() - r_ * 0.7, c_.y()}, QPointF{c_.x() + r_ * 0.7, c_.y()}); + } + painter->restore(); +} + +/** + * @brief drawEntriesHalf + * Draw the rows of wire entries of one half of a terminal (top or bottom). + * One row by level. Each row have a few ribs at its outer side and a line + * at its inner side, like the real terminals. + * @param outer : y of the outer side of the half (end of the terminal) + * @param inner : y of the inner side of the half (middle band). For the bottom half outer > inner. + * @param max_r : biggest radius of a wire entry + */ +void drawEntriesHalf(QPainter *painter, const QRectF &body, qreal outer, qreal inner, + int levels, qreal max_r, const QColor &fill, bool screw) +{ + const qreal row_h_{(inner - outer) / levels}; //Negative for the bottom half + const qreal abs_row_h_{std::abs(row_h_)}; + const QPen rib_pen_{fill.darker(170), 0}; + + for (int k = 0 ; k < levels ; ++k) + { + const qreal a_{outer + k * row_h_}; + //Ribs + painter->save(); + painter->setPen(rib_pen_); + for (const qreal f_ : {0.12, 0.22, 0.32}) { + const qreal y_{a_ + f_ * row_h_}; + painter->drawLine(QPointF{body.left(), y_}, QPointF{body.right(), y_}); + } + //Limit of the row + if (k > 0) { + painter->drawLine(QPointF{body.left(), a_}, QPointF{body.right(), a_}); + } + painter->restore(); + + drawWireEntry(painter, QPointF{body.center().x(), a_ + 0.67 * row_h_}, + body.width(), abs_row_h_, max_r, row_h_ > 0 ? -1 : 1, fill, screw); + } +} + +/** + * @brief The BridgePoint struct + * Where a bridge is plugged. + */ +struct BridgePoint +{ + QPointF point; + qreal width{0}; +}; + +} //End anonymous namespace + +QRectF boundingRect(const AbstractTerminalStripInterface *strip, + const TerminalStripLayoutPattern &pattern) +{ + qreal width_mm_{2 * endStopWidthMm(pattern) + groupMarkerWidthMm(pattern)}; + for (const auto &g_ : geometry(strip)) { + width_mm_ += g_.width_mm; + if (g_.separator_after) { + width_mm_ += separator_width_mm; + } + } + const qreal s_{pattern.m_realistic_px_per_mm}; + return QRectF{0, 0, width_mm_ * s_, pattern.m_realistic_length_mm * s_}; +} + +void paint(QPainter *painter, + const AbstractTerminalStripInterface &strip, + const TerminalStripLayoutPattern &pattern, + TerminalStripLayoutPattern::DrawStyle style, + bool preview_draw) +{ + const bool screw_{style == TerminalStripLayoutPattern::DrawStyle::RealisticScrew}; + const qreal s_{pattern.m_realistic_px_per_mm}; + const qreal length_{pattern.m_realistic_length_mm * s_}; + if (s_ <= 0 || length_ <= 0) { + return; + } + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setFont(pattern.font()); + painter->setPen(QPen(Qt::black, 0.5)); + + if (preview_draw) + { + painter->save(); + painter->setPen(Qt::blue); + painter->setBrush(Qt::NoBrush); + painter->drawRect(boundingRect(&strip, pattern)); + painter->restore(); + } + + qreal x_{0}; + + //The accessories are a bit shorter than the terminals, centered on them + const qreal end_stop_w_{endStopWidthMm(pattern) * s_}; + const qreal end_stop_h_{length_ * 0.88}; + const qreal end_stop_y_{(length_ - end_stop_h_) / 2}; + auto draw_end_stop = [&]() + { + if (end_stop_w_ <= 0) { + return; + } + const QRectF rect_{x_, end_stop_y_, end_stop_w_, end_stop_h_}; + drawEndStop(painter, rect_); + x_ += end_stop_w_; + }; + + draw_end_stop(); + + if (pattern.m_realistic_group_marker) + { + const qreal w_{group_marker_width_mm * s_}; + const qreal h_{length_ * 0.72}; + drawGroupMarker(painter, QRectF{x_, (length_ - h_) / 2, w_, h_}, strip.name()); + x_ += w_; + } + + QHash> bridges_; + + for (const auto &g_ : geometry(&strip)) + { + const qreal w_{g_.width_mm * s_}; + const QRectF body_{x_, 0, w_, length_}; + const auto first_{g_.reals.first()}; + const int levels_{static_cast(g_.reals.size())}; + + QColor fill_, stripe_; + Helpers::terminalColors(first_, fill_, stripe_); + if (!fill_.isValid()) { + fill_ = QColor(150, 150, 150); + } + + //Body + painter->save(); + painter->setPen(QPen(fill_.darker(220), 0.5)); + painter->setBrush(fill_); + painter->drawRect(body_); + if (stripe_.isValid()) + { + //Two-tone terminal (PE) : the stripe color on the left part, like the real ones + painter->setPen(Qt::NoPen); + painter->setBrush(stripe_); + painter->drawRect(QRectF{body_.left(), body_.top(), w_ * 0.6, length_}); + } + painter->restore(); + + //Wire entries : one row by level, at the top and at the bottom + const qreal max_r_{max_entry_radius_mm * s_}; + drawEntriesHalf(painter, body_, length_ * entries_top, length_ * band_top, levels_, max_r_, fill_, screw_); + drawEntriesHalf(painter, body_, length_ * (1 - entries_top), length_ * band_bottom, levels_, max_r_, fill_, screw_); + + //Middle band, with one bridge channel by level + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(band_color); + painter->drawRect(QRectF{x_, length_ * band_top, w_, length_ * (band_bottom - band_top)}); + painter->setBrush(channel_color); + const qreal channel_h_{length_ * (channels_bottom - channels_top) / levels_}; + for (int k = 0 ; k < levels_ ; ++k) { + painter->drawRect(QRectF{x_ + w_ * 0.15, length_ * channels_top + (k + 0.2) * channel_h_, + w_ * 0.7, channel_h_ * 0.6}); + } + painter->restore(); + + //Symbol of the type (fuse, led...), at the bottom of the band + const bool have_symbol_{first_->type() != ElementData::TTGeneric || first_->isLed()}; + const qreal tag_top_{length_ * 0.455}; + const qreal tag_bottom_{length_ * (have_symbol_ ? 0.535 : 0.59)}; + if (have_symbol_) + { + painter->save(); + painter->setPen(QPen(Qt::white, 0.5)); + Helpers::drawTypeSymbol(painter, QRectF{x_, length_ * 0.54, w_, length_ * 0.055}, + first_->type(), first_->isLed()); + painter->restore(); + } + + //Marker tag with the number(s) of the terminal + QStringList numbers_; + for (const auto &real_t : g_.reals) { + numbers_ << Helpers::terminalNumber(real_t->label()); + } + const bool sectional_{first_->type() == ElementData::TTSectional}; + const QRectF tag_{x_ + std::min(0.6 * s_, w_ * 0.12), tag_top_, + w_ - 2 * std::min(0.6 * s_, w_ * 0.12), tag_bottom_ - tag_top_}; + painter->save(); + painter->setPen(QPen(QColor(120, 120, 120), 0.3)); + painter->setBrush(sectional_ ? QColor(Qt::black) : QColor(Qt::white)); + painter->drawRect(tag_); + painter->restore(); + drawVerticalText(painter, tag_, numbers_.join(QStringLiteral(" / ")), + sectional_ ? QColor(Qt::white) : QColor(Qt::black)); + + //Bridge points : one channel by level + for (int k = 0 ; k < levels_ ; ++k) + { + const auto &real_t{g_.reals.at(k)}; + if (!real_t->isBridged()) { + continue; + } + QScopedPointer bridge_{real_t->bridge()}; + if (!bridge_) { + continue; + } + const qreal channel_h_{length_ * (channels_bottom - channels_top) / levels_}; + const qreal y_{length_ * channels_top + (k + 0.5) * channel_h_}; + bridges_[bridge_->uuid()].append(BridgePoint{QPointF{x_ + w_ / 2, y_}, w_}); + } + + x_ += w_; + + //Separator plate after the terminal + if (g_.separator_after) + { + const qreal sep_w_{separator_width_mm * s_}; + drawSeparator(painter, QRectF{x_, 0, sep_w_, length_}); + x_ += sep_w_; + } + } + + //Bridges : a red comb plugged in the channels + for (const auto &points_ : std::as_const(bridges_)) + { + if (points_.size() < 2) { + continue; + } + painter->save(); + const qreal thickness_{std::max(1.0, 0.9 * s_)}; + painter->setPen(QPen(bridge_color, thickness_, Qt::SolidLine, Qt::FlatCap)); + painter->drawLine(points_.first().point, points_.last().point); + painter->setPen(Qt::NoPen); + painter->setBrush(bridge_color.darker(140)); + for (const auto &p_ : points_) { + const qreal r_{std::min(p_.width * 0.18, thickness_ * 0.8)}; + painter->drawEllipse(p_.point, r_, r_); + } + painter->restore(); + } + + draw_end_stop(); + + painter->restore(); +} + +} //End namespace Realistic +} //End namespace TerminalStripDrawer diff --git a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h new file mode 100644 index 000000000..97afa0206 --- /dev/null +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h @@ -0,0 +1,50 @@ +/* + 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 . +*/ +#ifndef REALISTICTERMINALSTRIPDRAWER_H +#define REALISTICTERMINALSTRIPDRAWER_H + +#include "terminalstripdrawer.h" + +class QPainter; + +/** + * Drawing of a terminal strip at scale, seen from above, like the + * mounting drawing of a cabinet. Used by the styles + * RealisticPushIn and RealisticScrew of TerminalStripLayoutPattern. + * + * The strip is drawn from the left to the right : + * end stop, group marker carrier (with the name of the strip), terminals, end stop. + * 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. + */ +namespace TerminalStripDrawer +{ + namespace Realistic + { + QRectF boundingRect(const AbstractTerminalStripInterface *strip, + const TerminalStripLayoutPattern &pattern); + void paint(QPainter *painter, + const AbstractTerminalStripInterface &strip, + const TerminalStripLayoutPattern &pattern, + TerminalStripLayoutPattern::DrawStyle style, + bool preview_draw = false); + } +} + +#endif // REALISTICTERMINALSTRIPDRAWER_H diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index 789226ab7..cefecbdd2 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -16,6 +16,8 @@ along with QElectroTech. If not, see . */ #include "terminalstripdrawer.h" +#include "terminalstripdrawerhelpers.h" +#include "realisticterminalstripdrawer.h" #include #include @@ -834,12 +836,51 @@ QColor contrastColor(const QColor &background) } //End anonymous namespace +/* + * Helpers : give to the other drawing styles access to + * the functions of the anonymous namespace. + */ +qreal Helpers::referenceTerminalWidthMm() { + return reference_terminal_width_mm; +} + +void Helpers::terminalColors(const QSharedPointer &real_t, + QColor &fill, QColor &stripe) +{ + const auto colors_{::TerminalStripDrawer::terminalColors(real_t)}; + fill = colors_.fill; + stripe = colors_.stripe; +} + +QColor Helpers::contrastColor(const QColor &background) { + return ::TerminalStripDrawer::contrastColor(background); +} + +QString Helpers::terminalNumber(const QString &label) { + return ::TerminalStripDrawer::terminalNumber(label); +} + +void Helpers::drawTypeSymbol(QPainter *painter, const QRectF &box, + ElementData::TerminalType type, bool led) { + ::TerminalStripDrawer::drawTypeSymbol(painter, box, type, led); +} + /** * @brief TerminalStripDrawer::paint * @param painter */ void TerminalStripDrawer::paint(QPainter *painter) { + if (m_strip && m_pattern && m_draw_style != TerminalStripLayoutPattern::DrawStyle::Schematic) + { + //No xref in the realistic styles + 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); + return; + } + if (m_strip && m_pattern) { m_united_xref_text_rect = QRectF(); @@ -1195,6 +1236,25 @@ void TerminalStripDrawer::paint(QPainter *painter) painter->translate(terminal_rect.width(),0); x_offset += terminal_rect.width(); } + + //Separator plate after the physical terminal : a thick line + //on the full height of the terminal, after its last level + const bool separator_after_ = std::any_of(real_terminal_vector.cbegin(), real_terminal_vector.cend(), + [](const QSharedPointer &real_t) { + return real_t && real_t->hasSeparatorAfter(); + }); + if (separator_after_ && real_t_count > 0 && offset_ >= 0 && offset_ < 4) + { + const auto &rect_{m_pattern->m_terminal_rect.at(offset_)}; + painter->save(); + QPen separator_pen_{painter->pen()}; + separator_pen_.setColor(Qt::black); + separator_pen_.setWidthF(3); + separator_pen_.setCapStyle(Qt::FlatCap); + painter->setPen(separator_pen_); + painter->drawLine(QPointF{0, rect_.top() - 3}, QPointF{0, rect_.top() + rect_.height() + 3}); + painter->restore(); + } physical_index++; } painter->restore(); @@ -1225,6 +1285,10 @@ void TerminalStripDrawer::paint(QPainter *painter) QRectF TerminalStripDrawer::boundingRect() const { + if (m_pattern && m_draw_style != TerminalStripLayoutPattern::DrawStyle::Schematic) { + return Realistic::boundingRect(m_strip.data(), *m_pattern); + } + QRectF rect_{0, 0, width(), height()}; if (m_pattern) @@ -1292,6 +1356,19 @@ void TerminalStripDrawer::setPreviewDraw(bool draw) { m_preview_draw = draw; } +/** + * @brief TerminalStripDrawer::setDrawStyle + * Set how the terminals are drawn (schematic, realistic...). + * The values used by each style come from the layout. + */ +void TerminalStripDrawer::setDrawStyle(TerminalStripLayoutPattern::DrawStyle style) { + m_draw_style = style; +} + +TerminalStripLayoutPattern::DrawStyle TerminalStripDrawer::drawStyle() const { + return m_draw_style; +} + void TerminalStripDrawer::setMouseHoverPos(const QPointF &pos) { m_last_mouse_pos_in_xrefs_rect = m_united_xref_text_rect.contains(m_mouse_hover_pos); diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h index ca8917e30..04d92cec5 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h @@ -85,6 +85,8 @@ namespace TerminalStripDrawer //Second color of a two-tone color (like green/yellow), drawn as stripes over color() virtual QColor stripeColor() const { return QColor(); } virtual qreal widthMm() const { return 0; } + //A separator plate is mounted after the terminal (drawn by the realistic styles) + virtual bool hasSeparatorAfter() const { return false; } }; class AbstractPhysicalTerminalInterface @@ -121,6 +123,9 @@ namespace TerminalStripDrawer void setPreviewDraw(bool draw = true); + void setDrawStyle(TerminalStripLayoutPattern::DrawStyle style); + TerminalStripLayoutPattern::DrawStyle drawStyle() const; + void setMouseHoverPos(const QPointF &pos); bool mouseHoverXref() const; bool needUpdate(); @@ -135,6 +140,7 @@ namespace TerminalStripDrawer QSharedPointer m_strip; QSharedPointer m_pattern; bool m_preview_draw { false }; + TerminalStripLayoutPattern::DrawStyle m_draw_style { TerminalStripLayoutPattern::DrawStyle::Schematic }; QPointF m_mouse_hover_pos; QRectF m_united_xref_text_rect; bool m_last_mouse_pos_in_xrefs_rect{false}; diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h new file mode 100644 index 000000000..7cbafd5ea --- /dev/null +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h @@ -0,0 +1,48 @@ +/* + 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 . +*/ +#ifndef TERMINALSTRIPDRAWERHELPERS_H +#define TERMINALSTRIPDRAWERHELPERS_H + +#include "terminalstripdrawer.h" + +class QPainter; + +/** + * Functions shared by the drawing styles of a terminal strip + * (schematic style in terminalstripdrawer.cpp, realistic style in realisticterminalstripdrawer.cpp). + */ +namespace TerminalStripDrawer +{ + namespace Helpers + { + //Width (mm) of the reference terminal (2.5 mm2), used when the width of a terminal is unknown + qreal referenceTerminalWidthMm(); + //Fill color and stripe color (can be invalid) of a terminal + void terminalColors(const QSharedPointer &real_t, + QColor &fill, QColor &stripe); + //Black or white, the more readable on background + QColor contrastColor(const QColor &background); + //Number of a terminal : "1" for "XAUZ1:1" + QString terminalNumber(const QString &label); + //Symbol of the type of the terminal (fuse, diode, led...) scaled to fit in box + void drawTypeSymbol(QPainter *painter, const QRectF &box, + ElementData::TerminalType type, bool led); + } +} + +#endif // TERMINALSTRIPDRAWERHELPERS_H diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp index fc706fc1b..1867b8032 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp @@ -205,6 +205,26 @@ void TerminalStripItem::setLayout(QSharedPointer lay m_drawer.setLayout(layout); } +/** + * @brief TerminalStripItem::setDrawStyle + * Set how this item draw the terminals (schematic, realistic...). + * The style is chosen when the item is added to a folio and saved with the item. + * @param style + */ +void TerminalStripItem::setDrawStyle(TerminalStripLayoutPattern::DrawStyle style) +{ + if (style == m_drawer.drawStyle()) { + return; + } + prepareGeometryChange(); + m_drawer.setDrawStyle(style); + update(); +} + +TerminalStripLayoutPattern::DrawStyle TerminalStripItem::drawStyle() const { + return m_drawer.drawStyle(); +} + void TerminalStripItem::setDefaultLayout() { if (m_strip && m_strip->project()) { diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripitem.h b/sources/TerminalStrip/GraphicsItem/terminalstripitem.h index b5c29c67c..b4cd5d77f 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripitem.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripitem.h @@ -54,6 +54,8 @@ class TerminalStripItem : public QetGraphicsItem void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void refreshPending(); void setLayout(QSharedPointer layout); + void setDrawStyle(TerminalStripLayoutPattern::DrawStyle style); + TerminalStripLayoutPattern::DrawStyle drawStyle() const; /** * @brief The PdfRef struct diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp index f96fcc356..8b6e66462 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp @@ -168,6 +168,11 @@ namespace TerminalStripDrawer return m_real ? m_real->widthMm() : 0; } + bool TrueRealTerminal::hasSeparatorAfter() const + { + return m_real ? m_real->hasSeparatorAfter() : false; + } + TrueBridge::TrueBridge(QSharedPointer bridge) : m_bridge { bridge } {} diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h index 4438eb97f..807a29f4e 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h @@ -67,6 +67,7 @@ namespace TerminalStripDrawer QColor color() const override; QColor stripeColor() const override; qreal widthMm() const override; + bool hasSeparatorAfter() const override; private: QSharedPointer m_real; diff --git a/sources/TerminalStrip/realterminal.cpp b/sources/TerminalStrip/realterminal.cpp index 4ad4bcd54..afd308385 100644 --- a/sources/TerminalStrip/realterminal.cpp +++ b/sources/TerminalStrip/realterminal.cpp @@ -264,6 +264,20 @@ qreal RealTerminal::widthMm() const return 0; } +/** + * @brief RealTerminal::hasSeparatorAfter + * @return true if a separator plate (partition plate) is mounted + * after this terminal, between this terminal and the next one of the strip. + * Used to separate groups of terminals (voltages, functions...). + */ +bool RealTerminal::hasSeparatorAfter() const { + if (m_element) { + return QET::infoFlagIsTrue( + m_element->elementData().m_informations.value(separatorInfoKey()).toString()); + } + return false; +} + /** * @brief RealTerminal::conductor * @return diff --git a/sources/TerminalStrip/realterminal.h b/sources/TerminalStrip/realterminal.h index b14969464..5946e707e 100644 --- a/sources/TerminalStrip/realterminal.h +++ b/sources/TerminalStrip/realterminal.h @@ -71,6 +71,7 @@ class RealTerminal QColor stripeColor() const; qreal section() const; qreal widthMm() const; + bool hasSeparatorAfter() const; //Keys used to store the cable data in the element informations static QString cableInfoKey() { return QStringLiteral("terminal_cable"); } @@ -81,6 +82,8 @@ class RealTerminal static QString sectionInfoKey() { return QStringLiteral("terminal_section"); } //Key used to store the width (mm) of the terminal, drawn in the terminal strip static QString widthInfoKey() { return QStringLiteral("terminal_width"); } + //Key used to store if a separator plate is mounted after the terminal + static QString separatorInfoKey() { return QStringLiteral("terminal_separator"); } QString conductor() const; diff --git a/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp b/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp index fe4c77d8b..0c0eb8752 100644 --- a/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp +++ b/sources/TerminalStrip/ui/addterminalstripitemdialog.cpp @@ -24,6 +24,8 @@ #include "../../diagram.h" #include "../../qet.h" + +#include void AddTerminalStripItemDialog::openDialog(Diagram *diagram, QWidget *parent) { AddTerminalStripItemDialog d(diagram->project(), parent); @@ -33,6 +35,7 @@ void AddTerminalStripItemDialog::openDialog(Diagram *diagram, QWidget *parent) if (strip_) { auto item_ = new TerminalStripItem(strip_); + item_->setDrawStyle(static_cast(d.selectedDrawStyle())); diagram->addItem(item_); item_->setPos(50, 50); @@ -77,6 +80,16 @@ TerminalStrip *AddTerminalStripItemDialog::selectedTerminalStrip() const return nullptr; } +/** + * @brief AddTerminalStripItemDialog::selectedDrawStyle + * @return The drawing style chosen for the new item, + * as an int value of TerminalStripLayoutPattern::DrawStyle. + * The index of the combo box is the value of the enum. + */ +int AddTerminalStripItemDialog::selectedDrawStyle() const { + return std::clamp(ui->m_draw_style_cb->currentIndex(), 0, 2); +} + void AddTerminalStripItemDialog::fillComboBox() { if (m_project) diff --git a/sources/TerminalStrip/ui/addterminalstripitemdialog.h b/sources/TerminalStrip/ui/addterminalstripitemdialog.h index 02eb3bd26..0c1c1d36c 100644 --- a/sources/TerminalStrip/ui/addterminalstripitemdialog.h +++ b/sources/TerminalStrip/ui/addterminalstripitemdialog.h @@ -41,6 +41,7 @@ class AddTerminalStripItemDialog : public QDialog explicit AddTerminalStripItemDialog(QETProject *project, QWidget *parent = nullptr); ~AddTerminalStripItemDialog(); TerminalStrip *selectedTerminalStrip() const; + int selectedDrawStyle() const; void fillComboBox(); private: diff --git a/sources/TerminalStrip/ui/addterminalstripitemdialog.ui b/sources/TerminalStrip/ui/addterminalstripitemdialog.ui index f2300f856..f2ac6d61e 100644 --- a/sources/TerminalStrip/ui/addterminalstripitemdialog.ui +++ b/sources/TerminalStrip/ui/addterminalstripitemdialog.ui @@ -24,6 +24,35 @@ + + + + Drawing style : + + + + + + + How the terminals are drawn. The realistic styles draw the strip at scale, seen from above, with the real width of each terminal. + + + + Schematic + + + + + Realistic (push-in) + + + + + Realistic (screw) + + + + diff --git a/sources/TerminalStrip/ui/modelTerminalData.h b/sources/TerminalStrip/ui/modelTerminalData.h index 97ad958ca..7e6d9e2ac 100644 --- a/sources/TerminalStrip/ui/modelTerminalData.h +++ b/sources/TerminalStrip/ui/modelTerminalData.h @@ -38,6 +38,7 @@ struct modelRealTerminalData mrtd.conductor_ = real_t->conductor(); mrtd.led_ = real_t->isLed(); mrtd.shield_ = real_t->isShield(); + mrtd.separator_ = real_t->hasSeparatorAfter(); mrtd.color_ = real_t->color(); mrtd.stripe_color_ = real_t->stripeColor(); mrtd.section_ = real_t->section(); @@ -60,6 +61,7 @@ struct modelRealTerminalData QString conductor_; bool led_ = false; bool shield_ = false; + bool separator_ = false; //A separator plate is mounted after the terminal QColor color_; //Invalid = no color set by the user QColor stripe_color_; //Second color of a two-tone color, invalid if plain qreal section_ = 0; //In mm2, 0 = no section set by the user diff --git a/sources/TerminalStrip/ui/terminalstripeditor.cpp b/sources/TerminalStrip/ui/terminalstripeditor.cpp index ceafd9c33..a31fa46b8 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditor.cpp @@ -222,6 +222,7 @@ void TerminalStripEditor::apply() set_info(RealTerminal::colorInfoKey(), TerminalColor::toSpec(data_.color_, data_.stripe_color_)); set_info(RealTerminal::sectionInfoKey(), data_.section_ > 0 ? QString::number(data_.section_, 'g', 6) : QString()); set_info(RealTerminal::widthInfoKey(), data_.width_mm_ > 0 ? QString::number(data_.width_mm_, 'g', 6) : QString()); + set_info(RealTerminal::separatorInfoKey(), data_.separator_ ? QStringLiteral("true") : QString()); if (element->elementData() != current_data) m_project->undoStack()->push(new ChangeElementDataCommand(element, current_data)); diff --git a/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp b/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp index f67087017..64ab52a35 100644 --- a/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp +++ b/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp @@ -17,6 +17,8 @@ */ #include +#include + #include "terminalstriplayouteditor.h" #include "ui_terminalstriplayouteditor.h" #include "../GraphicsItem/properties/terminalstriplayoutpattern.h" @@ -33,6 +35,26 @@ TerminalStripLayoutEditor::TerminalStripLayoutEditor(QSharedPointerm_graphics_view->setBackgroundBrush(Qt::white); ui->m_graphics_view->scene()->addItem(&m_preview_strip_item); + + //Drawing style + //The style is chosen for each item when it is added to a folio, + //here it only change the preview, to see the values of each style. + connect(ui->m_draw_style_cb, &QComboBox::currentIndexChanged, this, [this](int index) { + m_preview_strip_item.prepareGeometryChange(); + m_preview_strip_item.m_drawer.setDrawStyle( + static_cast(std::clamp(index, 0, 2))); + m_preview_strip_item.update(); + updatePreview(); + }); + connect(ui->m_realistic_scale_sb, &QDoubleSpinBox::valueChanged, + this, &TerminalStripLayoutEditor::valueEdited); + connect(ui->m_realistic_length_sb, &QSpinBox::valueChanged, + this, &TerminalStripLayoutEditor::valueEdited); + connect(ui->m_realistic_end_stops_cb, &QCheckBox::toggled, + this, &TerminalStripLayoutEditor::valueEdited); + connect(ui->m_realistic_group_marker_cb, &QCheckBox::toggled, + this, &TerminalStripLayoutEditor::valueEdited); + updateUi(); } @@ -159,6 +181,12 @@ void TerminalStripLayoutEditor::valueEdited() m_layout.data()->m_cable_length = ui->m_cable_length_sb->value(); m_layout.data()->m_cable_end_length = ui->m_cable_end_length_sb->value(); + //Values of the realistic styles + m_layout.data()->m_realistic_px_per_mm = ui->m_realistic_scale_sb->value(); + m_layout.data()->m_realistic_length_mm = ui->m_realistic_length_sb->value(); + m_layout.data()->m_realistic_end_stops = ui->m_realistic_end_stops_cb->isChecked(); + m_layout.data()->m_realistic_group_marker = ui->m_realistic_group_marker_cb->isChecked(); + updateUi(); m_preview_strip_item.update(); } @@ -271,6 +299,12 @@ void TerminalStripLayoutEditor::updateUi() ui->m_cable_length_sb->setValue(data->m_cable_length); ui->m_cable_end_length_sb->setValue(data->m_cable_end_length); + //Values of the realistic styles + ui->m_realistic_scale_sb->setValue(data->m_realistic_px_per_mm); + ui->m_realistic_length_sb->setValue(qRound(data->m_realistic_length_mm)); + ui->m_realistic_end_stops_cb->setChecked(data->m_realistic_end_stops); + ui->m_realistic_group_marker_cb->setChecked(data->m_realistic_group_marker); + m_ui_updating = false; updatePreview(); } diff --git a/sources/TerminalStrip/ui/terminalstriplayouteditor.ui b/sources/TerminalStrip/ui/terminalstriplayouteditor.ui index 73b8c3ac9..d44f681c5 100644 --- a/sources/TerminalStrip/ui/terminalstriplayouteditor.ui +++ b/sources/TerminalStrip/ui/terminalstriplayouteditor.ui @@ -540,6 +540,88 @@ + + + + Preview style + + + + + + + Style of the preview only. The style of a terminal strip plan is chosen when it is added to a folio. + + + + Schematic + + + + + Realistic (push-in) + + + + + Realistic (screw) + + + + + + + + Scale (px/mm) / length (mm) + + + + + + + Number of pixels for one millimeter of terminal. + + + 1 + + + 0.5 + + + 50.0 + + + 0.5 + + + + + + + Length of a terminal along the wires, in millimeter. + + + 10 + + + 300 + + + + + + + End stops + + + + + + + Group marker + + + diff --git a/sources/TerminalStrip/ui/terminalstripmodel.cpp b/sources/TerminalStrip/ui/terminalstripmodel.cpp index 029647257..54e5ed8f1 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.cpp +++ b/sources/TerminalStrip/ui/terminalstripmodel.cpp @@ -55,8 +55,9 @@ const int SHIELD_CELL = 14; const int COLOR_CELL = 15; const int SECTION_CELL = 16; const int WIDTH_CELL = 17; +const int SEPARATOR_CELL = 18; -const int COLUMN_COUNT = 18; +const int COLUMN_COUNT = 19; static QVector UNMODIFIED_CELL_VECTOR(COLUMN_COUNT, false); @@ -130,6 +131,7 @@ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIn case 15 : return Color; case 16 : return Section; case 17 : return Width; + case 18 : return Separator; default : return Invalid; } } @@ -236,6 +238,11 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const { return mrtd.shield_ ? Qt::Checked : Qt::Unchecked; } + else if (role == Qt::CheckStateRole && + index.column() == SEPARATOR_CELL) + { + return mrtd.separator_ ? Qt::Checked : Qt::Unchecked; + } else if (role == Qt::BackgroundRole && index.column() < COLUMN_COUNT ) { if (m_modified_cell.contains(mrtd.element_) && @@ -327,6 +334,12 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value modified_ = true; modified_cell = SHIELD_CELL; } + else if (column_ == SEPARATOR_CELL) + { + mrtd.separator_ = value.toBool(); + modified_ = true; + modified_cell = SEPARATOR_CELL; + } else if (column_ == COLOR_CELL && role == Qt::EditRole) { @@ -417,6 +430,7 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation case COLOR_CELL: return tr("Color"); case SECTION_CELL: return tr("Section (mm²)"); case WIDTH_CELL: return tr("Width (mm)"); + case SEPARATOR_CELL: return tr("Separator plate after"); default : return QVariant(); } } @@ -434,7 +448,7 @@ Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const || c == CABLE_CELL || c == CABLE_WIRE_CELL || c == COLOR_CELL || c == SECTION_CELL || c == WIDTH_CELL) flags = flags | Qt::ItemIsEditable; - if (c == LED_CELL || c == SHIELD_CELL) { + if (c == LED_CELL || c == SHIELD_CELL || c == SEPARATOR_CELL) { flags = flags | Qt::ItemIsUserCheckable; } return flags; diff --git a/sources/TerminalStrip/ui/terminalstripmodel.h b/sources/TerminalStrip/ui/terminalstripmodel.h index 1a1f89fc5..8c2b48fb0 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.h +++ b/sources/TerminalStrip/ui/terminalstripmodel.h @@ -65,6 +65,7 @@ class TerminalStripModel : public QAbstractTableModel Color = 15, Section = 16, Width = 17, + Separator = 18, Invalid = 99 }; diff --git a/sources/xml/terminalstripitemxml.cpp b/sources/xml/terminalstripitemxml.cpp index 0b0290f51..0f9087d39 100644 --- a/sources/xml/terminalstripitemxml.cpp +++ b/sources/xml/terminalstripitemxml.cpp @@ -95,6 +95,12 @@ QDomElement TerminalStripItemXml::toXml(TerminalStripItem *item, QDomDocument &d dom_element.appendChild(QETXML::qGraphicsItemPosToXml(item, document)); + //Drawing style of the item, not saved for the default (schematic) style + if (item->drawStyle() != TerminalStripLayoutPattern::DrawStyle::Schematic) { + dom_element.setAttribute(QStringLiteral("draw_style"), + TerminalStripLayoutPattern::drawStyleToString(item->drawStyle())); + } + return dom_element; } else { return QDomElement(); @@ -134,6 +140,11 @@ bool TerminalStripItemXml::fromXml(TerminalStripItem *item, QETProject *project, bool b{QETXML::qGraphicsItemPosFromXml(item, xml_elmt.firstChildElement(QStringLiteral("pos")))}; + //Item saved before the drawing styles : no attribute, schematic style + item->setDrawStyle(TerminalStripLayoutPattern::drawStyleFromString( + xml_elmt.attribute(QStringLiteral("draw_style")), + TerminalStripLayoutPattern::DrawStyle::Schematic)); + return (a && b); } return false; From fed204eb788f529285c02a5e58cab1beda15ea58 Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 17 Oct 2026 20:41:25 +0200 Subject: [PATCH 3/5] 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. --- .../realisticterminalstripdrawer.cpp | 62 ++++++++++++++++++- .../realisticterminalstripdrawer.h | 9 ++- .../GraphicsItem/terminalstripdrawer.cpp | 33 +++++++++- .../GraphicsItem/terminalstripdrawer.h | 1 + .../GraphicsItem/terminalstripitem.cpp | 31 ++++++++++ .../GraphicsItem/terminalstripitem.h | 1 + 6 files changed, 133 insertions(+), 4 deletions(-) diff --git a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp index 431b6c1e2..37dfa10de 100644 --- a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp @@ -66,6 +66,11 @@ struct PhysicalGeometry qreal width_mm{0}; bool separator_after{false}; //A separator plate is mounted after this terminal QVector> 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 real_indexes; }; /** @@ -80,18 +85,24 @@ QVector 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 *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 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(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) { diff --git a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h index 97afa0206..440195eaf 100644 --- a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h @@ -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 *xref_rects = nullptr); } } diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index cefecbdd2..f348bee7c 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -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); diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h index 04d92cec5..b23072fd6 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h @@ -130,6 +130,7 @@ namespace TerminalStripDrawer bool mouseHoverXref() const; bool needUpdate(); hoverTerminal hoveredXref() const; + hoverTerminal xrefAt(const QPointF &pos) const; QVector xrefRects() const; private: diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp index 1867b8032..07139e595 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripitem.cpp @@ -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) diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripitem.h b/sources/TerminalStrip/GraphicsItem/terminalstripitem.h index b4cd5d77f..b126dbddc 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripitem.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripitem.h @@ -75,6 +75,7 @@ class TerminalStripItem : public QetGraphicsItem private: void setDefaultLayout(); + QString xrefToolTip(const TerminalStripDrawer::hoverTerminal &terminal) const; private: QPointer m_strip; From 3d2da9ed4e889d947fab5d32e3187b7d01cfec5b Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 17 Oct 2026 20:56:23 +0200 Subject: [PATCH 4/5] Terminal strip: specific drawings of fuse, disconnect and test terminals In the realistic styles, some types of terminal get the parts seen on the real products: - fuse terminal: black hinged fuse holder on the middle of the terminal, with its hinge pin, a window on the fuse, a ribbed grip and, for a fuse terminal with led, a red led - sectional (disconnect) terminal: orange knife with its hinge and grip - test terminal: two brass test sockets between the wire entries and the middle band (in the middle band for a terminal with levels) The marker tag and the type symbol are drawn on the lever. The lever is shorter with screw clamps and stays on the middle band for a terminal with levels. --- .../realisticterminalstripdrawer.cpp | 173 +++++++++++++++++- 1 file changed, 170 insertions(+), 3 deletions(-) diff --git a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp index 37dfa10de..148c49adc 100644 --- a/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -56,6 +57,9 @@ const QColor band_color{85, 85, 85}; const QColor channel_color{55, 55, 55}; const QColor bridge_color{200, 30, 30}; const QColor separator_color{196, 196, 198}; +const QColor fuse_holder_color{38, 38, 40}; +const QColor knife_color{240, 120, 0}; +const QColor led_color{220, 20, 20}; /** * @brief The PhysicalGeometry struct @@ -240,6 +244,148 @@ void drawEndStop(QPainter *painter, const QRectF &rect) painter->restore(); } +/** + * @brief leverZone + * Vertical zone (in fraction of the length of the terminal) of the lever of a + * fuse terminal or of the knife of a disconnect terminal, centered on the middle + * band. For a terminal with one level, the lever goes from a wire entry to the other + * (a bit shorter with screw clamps, whose screw heads are nearer the middle), + * for a terminal with levels it stay on the middle band, between the wire entries. + */ +QPair leverZone(int levels, bool screw) +{ + if (levels > 1) { + return qMakePair(qreal(0.395), qreal(0.605)); + } + return screw ? qMakePair(qreal(0.345), qreal(0.655)) + : qMakePair(qreal(0.315), qreal(0.685)); +} + +/** + * @brief drawFuseHolder + * Draw the hinged fuse holder of a fuse terminal, seen from above : a black lever + * on the middle of the terminal with its hinge pin, a window on the fuse, + * a ribbed grip at the other end and, for a fuse with led, the led. + * The marker tag and the symbol are drawn after, on the lever. + * @param x : left of the terminal + * @param w : width of the terminal + * @param length : length of the terminal + */ +void drawFuseHolder(QPainter *painter, qreal x, qreal w, qreal length, int levels, bool screw, bool led) +{ + const auto zone_{leverZone(levels, screw)}; + const qreal top_{length * zone_.first}; + const qreal bottom_{length * zone_.second}; + const qreal inset_{w * 0.08}; + const QRectF lever_{x + inset_, top_, w - 2 * inset_, bottom_ - top_}; + const qreal cx_{x + w / 2}; + + painter->save(); + painter->setPen(QPen(Qt::black, 0.5)); + painter->setBrush(fuse_holder_color); + painter->drawRoundedRect(lever_, w * 0.12, w * 0.12); + + //Hinge pin + const qreal pin_r_{w * 0.12}; + painter->setPen(QPen(QColor(90, 90, 90), 0.4)); + painter->setBrush(QColor(150, 150, 150)); + painter->drawEllipse(QPointF{cx_, top_ + pin_r_ * 2}, pin_r_, pin_r_); + + //Window on the fuse, under the hinge pin and above the marker tag, + //only when there is enough room + const qreal window_top_{std::max(length * 0.38, top_ + pin_r_ * 3.4)}; + const qreal window_bottom_{length * 0.445}; + if (levels == 1 && window_bottom_ - window_top_ > length * 0.03) + { + painter->setPen(QPen(QColor(110, 110, 110), 0.4)); + painter->setBrush(QColor(205, 205, 200)); + painter->drawRoundedRect(QRectF{cx_ - w * 0.25, window_top_, w * 0.5, window_bottom_ - window_top_}, 1, 1); + //The fuse in the window + painter->setPen(QPen(QColor(120, 90, 40), std::max(0.5, w * 0.08))); + const qreal margin_{(window_bottom_ - window_top_) * 0.1}; + painter->drawLine(QPointF{cx_, window_top_ + margin_}, QPointF{cx_, window_bottom_ - margin_}); + } + + //Ribbed grip at the other end + painter->setPen(QPen(QColor(110, 110, 110), 0.4)); + const qreal grip_top_{bottom_ - (bottom_ - top_) * 0.13}; + for (int i = 0 ; i < 3 ; ++i) { + const qreal y_{grip_top_ + i * (bottom_ - grip_top_) / 3}; + painter->drawLine(QPointF{lever_.left() + w * 0.12, y_}, QPointF{lever_.right() - w * 0.12, y_}); + } + + //Led : lit when the fuse is blown. Between the type symbol + //(bottom of the middle band) and the grip, whatever the length of the lever. + const qreal symbol_bottom_{length * 0.595}; + if (led && grip_top_ > symbol_bottom_) + { + const qreal led_r_{std::min({w * 0.13, length * 0.012, (grip_top_ - symbol_bottom_) * 0.45})}; + painter->setPen(QPen(led_color.darker(160), 0.4)); + painter->setBrush(led_color); + painter->drawEllipse(QPointF{cx_, (symbol_bottom_ + grip_top_) / 2}, led_r_, led_r_); + } + painter->restore(); +} + +/** + * @brief drawDisconnectKnife + * Draw the orange knife of a disconnect terminal, seen from above : + * a narrow lever on the middle of the terminal with its hinge and its grip. + */ +void drawDisconnectKnife(QPainter *painter, qreal x, qreal w, qreal length, int levels, bool screw) +{ + const auto zone_{leverZone(levels, screw)}; + const qreal top_{length * zone_.first}; + const qreal bottom_{length * zone_.second}; + const qreal cx_{x + w / 2}; + const qreal knife_w_{w * 0.56}; + + painter->save(); + painter->setPen(QPen(knife_color.darker(170), 0.5)); + painter->setBrush(knife_color); + painter->drawRoundedRect(QRectF{cx_ - knife_w_ / 2, top_, knife_w_, bottom_ - top_}, + knife_w_ * 0.3, knife_w_ * 0.3); + + //Hinge + const qreal pin_r_{knife_w_ * 0.22}; + painter->setBrush(knife_color.darker(150)); + painter->drawEllipse(QPointF{cx_, top_ + pin_r_ * 2}, pin_r_, pin_r_); + + //Grip + painter->setPen(QPen(knife_color.darker(190), 0.5)); + const qreal grip_top_{bottom_ - (bottom_ - top_) * 0.12}; + for (int i = 0 ; i < 3 ; ++i) { + const qreal y_{grip_top_ + i * (bottom_ - grip_top_) / 3}; + painter->drawLine(QPointF{cx_ - knife_w_ * 0.3, y_}, QPointF{cx_ + knife_w_ * 0.3, y_}); + } + painter->restore(); +} + +/** + * @brief drawTestSockets + * Draw the two test sockets of a test terminal : a brass ring with a black hole. + * For a terminal with one level they are between the wire entries and the middle band, + * for a terminal with levels (no room between the entries) they are in the middle band, + * in place of the bridge channels and of the type symbol. + */ +void drawTestSockets(QPainter *painter, qreal x, qreal w, qreal length, qreal s, int levels, bool screw, const QColor &fill) +{ + const qreal r_{std::min({w * 0.22, 1.2 * s, length * 0.018})}; + const qreal offset_{levels > 1 ? 0.425 : (screw ? 0.37 : 0.355)}; + painter->save(); + for (const qreal f_ : {offset_, 1 - offset_}) + { + const QPointF c_{x + w / 2, length * f_}; + painter->setPen(QPen(fill.darker(200), 0.4)); + painter->setBrush(QColor(200, 170, 60)); //Brass + painter->drawEllipse(c_, r_, r_); + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::black); + painter->drawEllipse(c_, r_ * 0.55, r_ * 0.55); + } + painter->restore(); +} + /** * @brief drawSeparator * Draw a separator plate (partition plate) seen from above : @@ -493,14 +639,35 @@ void paint(QPainter *painter, } painter->restore(); + //Specific parts of some types of terminal + const auto type_{first_->type()}; + switch (type_) + { + case ElementData::TTFuse: + drawFuseHolder(painter, x_, w_, length_, levels_, screw_, first_->isLed()); + break; + case ElementData::TTSectional: + drawDisconnectKnife(painter, x_, w_, length_, levels_, screw_); + break; + case ElementData::TTTestSocket: + drawTestSockets(painter, x_, w_, length_, s_, levels_, screw_, fill_); + break; + default: + break; + } + //Symbol of the type (fuse, led...), at the bottom of the band - const bool have_symbol_{first_->type() != ElementData::TTGeneric || first_->isLed()}; + //A test terminal with levels have its test sockets in place of the symbol + const bool have_symbol_{(first_->type() != ElementData::TTGeneric || first_->isLed()) + && !(type_ == ElementData::TTTestSocket && levels_ > 1)}; const qreal tag_top_{length_ * 0.455}; - const qreal tag_bottom_{length_ * (have_symbol_ ? 0.535 : 0.59)}; + const bool sockets_in_band_{type_ == ElementData::TTTestSocket && levels_ > 1}; + const qreal tag_bottom_{length_ * (have_symbol_ || sockets_in_band_ ? 0.535 : 0.59)}; if (have_symbol_) { painter->save(); - painter->setPen(QPen(Qt::white, 0.5)); + //Black on the orange knife, white on the dark band and on the fuse holder + painter->setPen(QPen(type_ == ElementData::TTSectional ? QColor(Qt::black) : QColor(Qt::white), 0.5)); Helpers::drawTypeSymbol(painter, QRectF{x_, length_ * 0.54, w_, length_ * 0.055}, first_->type(), first_->isLed()); painter->restore(); From 0b0c20fdb5829d8c87c7d35a27b8014ca3478804 Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 17 Oct 2026 21:32:12 +0200 Subject: [PATCH 5/5] Terminal strip: add the realistic drawer to the CMake sources --- cmake/qet_compilation_vars.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index cdfa8bd8d..b27a9c2dd 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -760,6 +760,12 @@ set(QET_SRC_FILES ${QET_DIR}/sources/TerminalStrip/GraphicsItem/terminalstripitem.h ${QET_DIR}/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp ${QET_DIR}/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h + ${QET_DIR}/sources/TerminalStrip/GraphicsItem/terminalstripdrawerhelpers.h ${QET_DIR}/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp ${QET_DIR}/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h ${QET_DIR}/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp