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 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..148c49adc --- /dev/null +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.cpp @@ -0,0 +1,791 @@ +/* + 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 + +#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}; +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 + * 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; + //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; +}; + +/** + * @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_; + } + + 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 + 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 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 : + * 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 QPointF &hover_pos, + QVector *xref_rects) +{ + 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(); + + //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 + //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 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(); + //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(); + } + + //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)); + + //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) + { + 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..440195eaf --- /dev/null +++ b/sources/TerminalStrip/GraphicsItem/realisticterminalstripdrawer.h @@ -0,0 +1,57 @@ +/* + 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. + * + * 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 +{ + 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, + const QPointF &hover_pos = QPointF(), + QVector *xref_rects = nullptr); + } +} + +#endif // REALISTICTERMINALSTRIPDRAWER_H diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index 789226ab7..f348bee7c 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,64 @@ 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) + { + //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, + 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; + } + if (m_strip && m_pattern) { m_united_xref_text_rect = QRectF(); @@ -1195,6 +1249,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 +1298,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 +1369,35 @@ 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; +} + +/** + * @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 ca8917e30..b23072fd6 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,10 +123,14 @@ 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(); hoverTerminal hoveredXref() const; + hoverTerminal xrefAt(const QPointF &pos) const; QVector xrefRects() const; private: @@ -135,6 +141,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..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) @@ -205,6 +236,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..b126dbddc 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 @@ -73,6 +75,7 @@ class TerminalStripItem : public QetGraphicsItem private: void setDefaultLayout(); + QString xrefToolTip(const TerminalStripDrawer::hoverTerminal &terminal) const; private: QPointer m_strip; 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/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/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; diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index fbb8cc809..a54290356 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"