From e89c69719a30f169431dcfc8643e77c453404b86 Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Fri, 9 Oct 2026 09:18:40 +0200 Subject: [PATCH] Improve terminal_strip --- .../GraphicsItem/demoterminalstrip.cpp | 30 ++- .../properties/terminalstriplayoutpattern.h | 8 + .../GraphicsItem/terminalstripdrawer.cpp | 248 +++++++++++++++++- .../GraphicsItem/terminalstripdrawer.h | 4 + .../GraphicsItem/trueterminalstrip.cpp | 15 ++ .../GraphicsItem/trueterminalstrip.h | 3 + sources/TerminalStrip/realterminal.cpp | 24 +- sources/TerminalStrip/realterminal.h | 7 + sources/TerminalStrip/ui/modelTerminalData.h | 2 + .../TerminalStrip/ui/terminalstripeditor.cpp | 241 +++++++++++++++++ .../TerminalStrip/ui/terminalstripeditor.h | 7 + .../TerminalStrip/ui/terminalstripeditor.ui | 39 ++- .../ui/terminalstriplayouteditor.cpp | 6 + .../ui/terminalstriplayouteditor.ui | 85 ++++++ .../TerminalStrip/ui/terminalstripmodel.cpp | 41 ++- sources/TerminalStrip/ui/terminalstripmodel.h | 1 + 16 files changed, 729 insertions(+), 32 deletions(-) diff --git a/sources/TerminalStrip/GraphicsItem/demoterminalstrip.cpp b/sources/TerminalStrip/GraphicsItem/demoterminalstrip.cpp index cdfc7ad02..4e910975c 100644 --- a/sources/TerminalStrip/GraphicsItem/demoterminalstrip.cpp +++ b/sources/TerminalStrip/GraphicsItem/demoterminalstrip.cpp @@ -39,12 +39,16 @@ namespace TerminalStripDrawer { public: DemoRealTerminal(const QString &label, const QString &xref, const QUuid &bridge, - ElementData::TerminalType type = ElementData::TTGeneric, bool led = false) : + ElementData::TerminalType type = ElementData::TTGeneric, bool led = false, + const QString &cable = QString(), const QString &wire = QString(), bool shield = false) : m_label { label }, m_xref{ xref }, m_bridge { bridge }, m_type { type }, - m_led { led } + m_led { led }, + m_cable { cable }, + m_wire { wire }, + m_shield { shield } {} QString label() const override { @@ -71,11 +75,25 @@ namespace TerminalStripDrawer return m_led; } + QString cable() const override { + return m_cable; + } + + QString cableWire() const override { + return m_wire; + } + + bool isShield() const override { + return m_shield; + } + private: QString m_label, m_xref; QUuid m_bridge; ElementData::TerminalType m_type; bool m_led; + QString m_cable, m_wire; + bool m_shield; }; class DemoPhysicalTerminal : public AbstractPhysicalTerminalInterface @@ -122,19 +140,19 @@ namespace TerminalStripDrawer real_terminals_vector << QSharedPointer { new DemoRealTerminal( QStringLiteral("24vdc"), QStringLiteral("1_A1"), - lvl_1, ElementData::TTFuse, false)}; + lvl_1, ElementData::TTFuse, false, QStringLiteral("9W2"), QStringLiteral("1"), false)}; real_terminals_vector << QSharedPointer { new DemoRealTerminal( QStringLiteral("0vdc"), QStringLiteral("1_A2"), - lvl_2, ElementData::TTSectional, false)}; + lvl_2, ElementData::TTSectional, false, QStringLiteral("9W2"), QStringLiteral("2"), false)}; real_terminals_vector << QSharedPointer { new DemoRealTerminal( QStringLiteral("signal"), QStringLiteral("1_A3"), - lvl_3, ElementData::TTDiode, false)}; + lvl_3, ElementData::TTDiode, false, QStringLiteral("9W2"), QStringLiteral("3"), false)}; real_terminals_vector << QSharedPointer { new DemoRealTerminal( QStringLiteral("teach"), QStringLiteral("1_A4"), - lvl_4, ElementData::TTGround, false)}; + lvl_4, ElementData::TTGround, false, QStringLiteral("9W2"), QStringLiteral(""), true)}; m_physical_terminal << QSharedPointer { new DemoPhysicalTerminal {real_terminals_vector}}; diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h index 35a81417c..ee5e4e8ee 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h @@ -89,6 +89,14 @@ class TerminalStripLayoutPattern //Length of the connection drawn above and under each terminal, //in the middle of the terminal. 0 disable the connections. qreal m_connection_length{10}; + //Cable drawn under the terminals which have a cable (hose). + //Length of the wires between the terminals and the cable + //0 disable the drawing of the cables. + qreal m_cable_wire_length{40}; + //Length of the cable + qreal m_cable_length{60}; + //Length of the wires after the cable + qreal m_cable_end_length{40}; qreal m_bridge_point_d{5}; QVector m_bridge_point_y_offset{50,70,90,110}; diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index 92a162e54..be933ab63 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -18,6 +18,7 @@ #include "terminalstripdrawer.h" #include +#include #include #include @@ -168,13 +169,15 @@ void drawTypeSymbol(QPainter *painter, const QRectF &box, ElementData::TerminalT /** * @brief drawConnections - * Draw a connection above and a connection under @a terminal_rect, in the middle - * of the rect. A connection is a short line ended by a little circle. + * Draw a connection above and, if @a draw_bottom is true, a connection under + * @a terminal_rect, in the middle of the rect. + * A connection is a short line ended by a little circle. * @param painter * @param terminal_rect * @param length : the total length of a connection, circle included + * @param draw_bottom : false if the cable is drawn under the terminal */ -void drawConnections(QPainter *painter, const QRectF &terminal_rect, qreal length) +void drawConnections(QPainter *painter, const QRectF &terminal_rect, qreal length, bool draw_bottom) { const qreal radius{qMin(length / 4, 2)}; const qreal x{terminal_rect.width() / 2}; @@ -184,8 +187,163 @@ void drawConnections(QPainter *painter, const QRectF &terminal_rect, qreal lengt painter->drawLine(QPointF{x, top}, QPointF{x, top - length + radius * 2}); painter->drawEllipse(QPointF{x, top - length + radius}, radius, radius); - painter->drawLine(QPointF{x, bottom}, QPointF{x, bottom + length - radius * 2}); - painter->drawEllipse(QPointF{x, bottom + length - radius}, radius, radius); + if (draw_bottom) + { + painter->drawLine(QPointF{x, bottom}, QPointF{x, bottom + length - radius * 2}); + painter->drawEllipse(QPointF{x, bottom + length - radius}, radius, radius); + } +} + +/** + * @brief The CableCell struct + * What is needed to draw the cable under a terminal. + */ +struct CableCell +{ + qreal x{0}; ///< x of the middle of the terminal, in the strip coordinates + qreal bottom{0}; ///< y of the bottom of the terminal + QString cable; ///< name of the cable (hose) + QString wire; ///< color / number of the wire + bool shield{false}; ///< true if the terminal is the shield of the cable +}; + +/** + * @brief drawCables + * Draw the cables under the terminals. Consecutive terminals who have the same cable name + * share the same cable: the wires go down from the terminals, join in the cable, + * and leave the cable with the same wire marks. The shield is linked to a dashed ellipse + * drawn around the cable. + * @param painter : the pen and the font must be set + * @param cells : one cell per terminal, in the drawing order + * @param pattern + */ +void drawCables(QPainter *painter, const QVector &cells, const TerminalStripLayoutPattern &pattern) +{ + const qreal wire_length{pattern.m_cable_wire_length}; + if (wire_length <= 0 || cells.isEmpty()) { + return; + } + + const qreal cable_length{qMax(0, pattern.m_cable_length)}; + const qreal end_length{qMax(0, pattern.m_cable_end_length)}; + const qreal radius{2}; + const QFontMetricsF font_metrics{painter->font()}; + const qreal text_height{font_metrics.height()}; + + //Draw a mark (small oblique line) on a wire and the number of the wire next to it. + //The text is written from the bottom to the top, at the left of the wire. + const auto draw_wire_mark = [&](qreal x, qreal mark_y, const QString &text, bool text_above) + { + painter->drawLine(QPointF{x - 3, mark_y + 3}, QPointF{x + 3, mark_y - 3}); + if (text.isEmpty()) { + return; + } + + const qreal text_width{font_metrics.horizontalAdvance(text)}; + painter->save(); + painter->translate(x - 1, text_above ? mark_y - 4 : mark_y + 4 + text_width); + painter->rotate(270); + painter->drawText(QRectF{0, -text_height, text_width + 2, text_height}, + Qt::AlignLeft | Qt::AlignVCenter, + text); + painter->restore(); + }; + + auto first{0}; + while (first < cells.size()) + { + if (cells.at(first).cable.isEmpty()) { + ++first; + continue; + } + + //Find the last terminal of the cable + auto last{first}; + while (last + 1 < cells.size() && + cells.at(last + 1).cable == cells.at(first).cable) { + ++last; + } + + QVector wires, shields; + qreal max_bottom{cells.at(first).bottom}; + for (auto i = first ; i <= last ; ++i) + { + max_bottom = qMax(max_bottom, cells.at(i).bottom); + if (cells.at(i).shield) { + shields << cells.at(i); + } else { + wires << cells.at(i); + } + } + //A cable with only a shield is drawn like a cable with only one wire + if (wires.isEmpty()) { + wires = shields; + shields.clear(); + } + + const qreal min_x{wires.first().x}; + const qreal max_x{wires.last().x}; + const qreal cable_x{(min_x + max_x) / 2}; + const qreal bar_1_y{max_bottom + wire_length}; + const qreal bar_2_y{bar_1_y + cable_length}; + const qreal end_y{bar_2_y + end_length}; + + //Wires between the terminals and the cable + for (const auto &wire : std::as_const(wires)) + { + painter->drawLine(QPointF{wire.x, wire.bottom}, QPointF{wire.x, bar_1_y}); + draw_wire_mark(wire.x, qMax(wire.bottom + 4, bar_1_y - 10), wire.wire, true); + } + painter->drawLine(QPointF{min_x, bar_1_y}, QPointF{max_x, bar_1_y}); + + //The cable + painter->drawLine(QPointF{cable_x, bar_1_y}, QPointF{cable_x, bar_2_y}); + + //Wires after the cable + painter->drawLine(QPointF{min_x, bar_2_y}, QPointF{max_x, bar_2_y}); + for (const auto &wire : std::as_const(wires)) + { + painter->drawLine(QPointF{wire.x, bar_2_y}, QPointF{wire.x, end_y}); + draw_wire_mark(wire.x, qMin(bar_2_y + 10, end_y), wire.wire, false); + painter->drawEllipse(QPointF{wire.x, end_y + radius}, radius, radius); + } + + //The shield, linked to an ellipse around the cable + const qreal ellipse_ry{6}; + const qreal ellipse_rx{qMax((max_x - min_x) / 2 + 4, 12)}; + const qreal ellipse_y{shields.isEmpty() ? bar_2_y : qMax(bar_1_y + ellipse_ry, bar_2_y - 12)}; + + if (!shields.isEmpty()) + { + for (const auto &shield : std::as_const(shields)) + { + painter->drawLine(QPointF{shield.x, shield.bottom}, QPointF{shield.x, ellipse_y}); + const qreal edge_x{shield.x > cable_x ? cable_x + ellipse_rx : cable_x - ellipse_rx}; + painter->drawLine(QPointF{shield.x, ellipse_y}, QPointF{edge_x, ellipse_y}); + } + + painter->save(); + auto dashed_pen{painter->pen()}; + dashed_pen.setStyle(Qt::DashLine); + painter->setPen(dashed_pen); + painter->drawEllipse(QPointF{cable_x, ellipse_y}, ellipse_rx, ellipse_ry); + painter->restore(); + } + + //Name of the cable, at the left of the cable, above the ellipse + const qreal name_width{font_metrics.horizontalAdvance(cells.at(first).cable)}; + const qreal name_top{bar_1_y}; + const qreal name_bottom{shields.isEmpty() ? bar_2_y : ellipse_y - ellipse_ry}; + painter->save(); + painter->translate(cable_x - 2, (name_top + name_bottom) / 2 + name_width / 2); + painter->rotate(270); + painter->drawText(QRectF{0, -text_height, name_width + 2, text_height}, + Qt::AlignLeft | Qt::AlignVCenter, + cells.at(first).cable); + painter->restore(); + + first = last + 1; + } } } //End anonymous namespace @@ -271,6 +429,7 @@ void TerminalStripDrawer::paint(QPainter *painter) QRectF xref_rect; QHash> bridges_anchor_points; + QVector cable_cells; m_hovered_xref = hoverTerminal{}; int physical_index = 0; @@ -291,6 +450,19 @@ void TerminalStripDrawer::paint(QPainter *painter) } terminal_rect = m_pattern->m_terminal_rect[index_]; + + //Cable (hose) of this terminal + CableCell cable_cell; + if (real_terminal_vector[i]) + { + cable_cell.x = x_offset + terminal_rect.width()/2; + cable_cell.bottom = terminal_rect.y() + terminal_rect.height(); + cable_cell.cable = real_terminal_vector[i]->cable(); + cable_cell.wire = real_terminal_vector[i]->cableWire(); + cable_cell.shield = real_terminal_vector[i]->isShield(); + } + const bool have_cable{m_pattern->m_cable_wire_length > 0 && !cable_cell.cable.isEmpty()}; + cable_cells.append(cable_cell); //Draw terminal rect painter->drawRect(terminal_rect); @@ -306,7 +478,8 @@ void TerminalStripDrawer::paint(QPainter *painter) //Draw the connections, above and under the terminal if (m_pattern->m_connection_length > 0) { - drawConnections(painter, terminal_rect, m_pattern->m_connection_length); + //The cable replace the connection under the terminal + drawConnections(painter, terminal_rect, m_pattern->m_connection_length, !have_cable); } //Draw a stronger line if the current terminal have level //and the current level is the first @@ -442,6 +615,17 @@ void TerminalStripDrawer::paint(QPainter *painter) painter->drawPolyline(QPolygonF(points_)); painter->restore(); } + + //Draw the cables + painter->save(); + auto cable_pen{painter->pen()}; + cable_pen.setColor(Qt::black); + cable_pen.setWidth(1); + painter->setPen(cable_pen); + painter->setFont(m_pattern->font()); + painter->setBrush(Qt::NoBrush); + drawCables(painter, cable_cells, *m_pattern); + painter->restore(); } } @@ -449,16 +633,52 @@ QRectF TerminalStripDrawer::boundingRect() const { QRectF rect_{0, 0, width(), height()}; - //The connections are drawn above and under the terminals - if (m_pattern && m_pattern->m_connection_length > 0) + if (m_pattern) { - const auto length_{m_pattern->m_connection_length}; - qreal top_{0}; - for (const auto &terminal_rect : std::as_const(m_pattern->m_terminal_rect)) { - top_ = std::min(top_, terminal_rect.top() - length_); + qreal extra_bottom{0}; + + //The connections are drawn above and under the terminals + if (m_pattern->m_connection_length > 0) + { + const auto length_{m_pattern->m_connection_length}; + qreal top_{0}; + for (const auto &terminal_rect : std::as_const(m_pattern->m_terminal_rect)) { + top_ = std::min(top_, terminal_rect.top() - length_); + } + rect_.setTop(top_); + extra_bottom = length_; } - rect_.setBottom(rect_.bottom() + length_); - rect_.setTop(top_); + + //The cables are drawn under the terminals + if (m_strip && m_pattern->m_cable_wire_length > 0) + { + bool have_cable{false}; + for (const auto &physical_t : m_strip->physicalTerminal()) + { + for (const auto &real_t : physical_t->realTerminals()) + { + if (real_t && !real_t->cable().isEmpty()) { + have_cable = true; + break; + } + } + if (have_cable) { + break; + } + } + + if (have_cable) + { + //4 is the diameter of the circle at the end of the wires + extra_bottom = std::max(extra_bottom, + m_pattern->m_cable_wire_length + + std::max(0, m_pattern->m_cable_length) + + std::max(0, m_pattern->m_cable_end_length) + + 4); + } + } + + rect_.setBottom(rect_.bottom() + extra_bottom); } return rect_; diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h index bec643415..3c7c313f8 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h @@ -61,6 +61,10 @@ namespace TerminalStripDrawer //They are not pure virtual, so an implementation can ignore them. virtual ElementData::TerminalType type() const { return ElementData::TTGeneric; } virtual bool isLed() const { return false; } + //Cable (hose) connected to the terminal, drawn under the strip. + virtual QString cable() const { return QString(); } + virtual QString cableWire() const { return QString(); } + virtual bool isShield() const { return false; } }; class AbstractPhysicalTerminalInterface diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp index 23a2b492f..4ad3605bf 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp @@ -138,6 +138,21 @@ namespace TerminalStripDrawer return m_real ? m_real->isLed() : false; } + QString TrueRealTerminal::cable() const + { + return m_real ? m_real->cable() : QString(); + } + + QString TrueRealTerminal::cableWire() const + { + return m_real ? m_real->cableWire() : QString(); + } + + bool TrueRealTerminal::isShield() const + { + return m_real ? m_real->isShield() : false; + } + TrueBridge::TrueBridge(QSharedPointer bridge) : m_bridge { bridge } {} diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h index 37238f1b7..232246e85 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h @@ -61,6 +61,9 @@ namespace TerminalStripDrawer QString xref() const override; ElementData::TerminalType type() const override; bool isLed() const override; + QString cable() const override; + QString cableWire() const override; + bool isShield() const override; private: QSharedPointer m_real; diff --git a/sources/TerminalStrip/realterminal.cpp b/sources/TerminalStrip/realterminal.cpp index 0de2ebf36..e3f4089ee 100644 --- a/sources/TerminalStrip/realterminal.cpp +++ b/sources/TerminalStrip/realterminal.cpp @@ -157,20 +157,40 @@ QString RealTerminal::Xref() const /** * @brief RealTerminal::cable - * @return + * @return The name of the cable (hose) connected to this terminal. + * The value is stored in the informations of the element. */ QString RealTerminal::cable() const { + if (m_element) { + return m_element->elementData().m_informations.value(cableInfoKey()).toString(); + } return QString(); } /** * @brief RealTerminal::cableWire - * @return + * @return The color / number of the wire of the cable, connected to this terminal. */ QString RealTerminal::cableWire() const { + if (m_element) { + return m_element->elementData().m_informations.value(cableWireInfoKey()).toString(); + } return QString(); } +/** + * @brief RealTerminal::isShield + * @return true if this terminal is the shield of the cable + * and not one of its wires. + */ +bool RealTerminal::isShield() const { + if (m_element) { + return QET::infoFlagIsTrue( + m_element->elementData().m_informations.value(shieldInfoKey()).toString()); + } + return false; +} + /** * @brief RealTerminal::conductor * @return diff --git a/sources/TerminalStrip/realterminal.h b/sources/TerminalStrip/realterminal.h index 3b6c97d10..cf34c34ef 100644 --- a/sources/TerminalStrip/realterminal.h +++ b/sources/TerminalStrip/realterminal.h @@ -65,6 +65,13 @@ class RealTerminal QString Xref() const; QString cable() const; QString cableWire() const; + bool isShield() const; + + //Keys used to store the cable data in the element informations + static QString cableInfoKey() { return QStringLiteral("terminal_cable"); } + static QString cableWireInfoKey() { return QStringLiteral("terminal_cable_wire"); } + static QString shieldInfoKey() { return QStringLiteral("terminal_shield"); } + QString conductor() const; ElementData::TerminalType type() const; diff --git a/sources/TerminalStrip/ui/modelTerminalData.h b/sources/TerminalStrip/ui/modelTerminalData.h index 996ac819b..1ac7f65f2 100644 --- a/sources/TerminalStrip/ui/modelTerminalData.h +++ b/sources/TerminalStrip/ui/modelTerminalData.h @@ -36,6 +36,7 @@ struct modelRealTerminalData mrtd.cable_wire = real_t->cableWire(); mrtd.conductor_ = real_t->conductor(); mrtd.led_ = real_t->isLed(); + mrtd.shield_ = real_t->isShield(); mrtd.type_ = real_t->type(); mrtd.function_ = real_t->function(); mrtd.element_ = real_t->element(); @@ -53,6 +54,7 @@ struct modelRealTerminalData QString cable_wire; QString conductor_; bool led_ = false; + bool shield_ = false; bool bridged_ = false; ElementData::TerminalType type_ = ElementData::TerminalType::TTGeneric; diff --git a/sources/TerminalStrip/ui/terminalstripeditor.cpp b/sources/TerminalStrip/ui/terminalstripeditor.cpp index 64052f971..9a56339b0 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditor.cpp @@ -32,6 +32,13 @@ #include "../physicalterminal.h" #include "../terminalstripbridge.h" +#include +#include +#include + +#include +#include + /** * @brief TerminalStripEditor::TerminalStripEditor * @param project : Project to manage the terminal strip @@ -46,6 +53,9 @@ TerminalStripEditor::TerminalStripEditor(QETProject *project, QWidget *parent) : ui->m_table_widget->setItemDelegate(new TerminalStripModelDelegate{this}); + //Copy, paste and delete of the cells with the keyboard + ui->m_table_widget->installEventFilter(this); + //Setup the bridge color ui->m_bridge_color_cb->setColors(TerminalStripBridge::bridgeColor().toList()); @@ -198,6 +208,15 @@ void TerminalStripEditor::apply() current_data.setTerminalFunction(data_.function_); current_data.setTerminalLED(data_.led_); current_data.m_informations.addValue(QStringLiteral("label"), data_.label_); + //The cable data are only stored when they are used + const auto set_info = [¤t_data](const QString &key, const QString &value) { + if (!value.isEmpty() || current_data.m_informations.contains(key)) { + current_data.m_informations.addValue(key, value); + } + }; + set_info(RealTerminal::cableInfoKey(), data_.cable_); + set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire); + set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString()); if (element->elementData() != current_data) m_project->undoStack()->push(new ChangeElementDataCommand(element, current_data)); @@ -265,6 +284,8 @@ void TerminalStripEditor::selectionChanged() ui->m_type_cb ->setDisabled(true); ui->m_function_cb ->setDisabled(true); ui->m_led_cb ->setDisabled(true); + ui->m_cable_le ->setDisabled(true); + ui->m_cable_apply_pb ->setDisabled(true); ui->m_bridge_terminals_pb ->setDisabled(true); ui->m_unbridge_terminals_pb->setDisabled(true); @@ -280,10 +301,14 @@ void TerminalStripEditor::selectionChanged() ui->m_type_cb ->setDisabled(true); ui->m_function_cb ->setDisabled(true); ui->m_led_cb ->setDisabled(true); + ui->m_cable_le ->setDisabled(true); + ui->m_cable_apply_pb->setDisabled(true); } else { ui->m_type_cb ->setEnabled(true); ui->m_function_cb ->setEnabled(true); ui->m_led_cb ->setEnabled(true); + ui->m_cable_le ->setEnabled(true); + ui->m_cable_apply_pb->setEnabled(true); } const auto model_physical_terminal_vector = m_model->modelPhysicalTerminalDataForIndex(index_list); @@ -601,6 +626,222 @@ void TerminalStripEditor::on_m_led_cb_activated(int index) } } +namespace { +/** + * @return true if the cell at @a index contain a free text + * (label, cable or wire of the cable), so a text can be pasted in it. + */ +bool cellAcceptText(const QModelIndex &index) +{ + const auto column_ = TerminalStripModel::columnTypeForIndex(index); + return column_ == TerminalStripModel::Label + || column_ == TerminalStripModel::Cable + || column_ == TerminalStripModel::CableWire; +} +} + +/** + * @brief TerminalStripEditor::eventFilter + * Manage the shortcuts copy, paste and delete in the table + * @param watched + * @param event + * @return + */ +bool TerminalStripEditor::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == ui->m_table_widget && + event->type() == QEvent::KeyPress && + m_model) + { + const auto key_event = static_cast(event); + if (key_event->matches(QKeySequence::Copy)) { + copySelectionToClipboard(); + return true; + } + if (key_event->matches(QKeySequence::Paste)) { + pasteFromClipboard(); + return true; + } + if (key_event->matches(QKeySequence::Delete)) { + clearSelectedTexts(); + return true; + } + } + + return QWidget::eventFilter(watched, event); +} + +/** + * @brief TerminalStripEditor::copySelectionToClipboard + * Copy the selected cells to the clipboard, as a text where the columns + * are separated by a tabulation and the rows by a new line + * (the format used by the spreadsheets). + */ +void TerminalStripEditor::copySelectionToClipboard() +{ + if (!m_model || !ui->m_table_widget->selectionModel()) { + return; + } + + const auto selection = ui->m_table_widget->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) { + return; + } + + int top{std::numeric_limits::max()}, left{std::numeric_limits::max()}, bottom{-1}, right{-1}; + QHash texts; + for (const auto &index : selection) + { + top = std::min(top, index.row()); + left = std::min(left, index.column()); + bottom = std::max(bottom, index.row()); + right = std::max(right, index.column()); + texts.insert((qint64(index.row()) << 32) | qint64(index.column()), + index.data(Qt::DisplayRole).toString()); + } + + QStringList lines; + for (auto row = top ; row <= bottom ; ++row) + { + QStringList cells; + for (auto column = left ; column <= right ; ++column) { + cells << texts.value((qint64(row) << 32) | qint64(column)); + } + lines << cells.join(QLatin1Char('\t')); + } + + QApplication::clipboard()->setText(lines.join(QLatin1Char('\n'))); +} + +/** + * @brief TerminalStripEditor::pasteFromClipboard + * Paste the text of the clipboard in the table. + * - If the text is only one value and several cells are selected, + * the value is set to every selected cell. + * - Otherwise the text is read like a table (tabulation between the columns + * and new line between the rows) and written from the top left selected cell. + * Only the label, cable and cable wire cells are modified. + */ +void TerminalStripEditor::pasteFromClipboard() +{ + if (!m_model || !ui->m_table_widget->selectionModel()) { + return; + } + + auto text = QApplication::clipboard()->text(); + if (text.isEmpty()) { + return; + } + text.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); + text.replace(QLatin1Char('\r'), QLatin1Char('\n')); + + auto lines = text.split(QLatin1Char('\n')); + //A spreadsheet end the last row with a new line + if (lines.size() > 1 && lines.last().isEmpty()) { + lines.removeLast(); + } + + const auto selection = ui->m_table_widget->selectionModel()->selectedIndexes(); + + //One value, several cells : set the value to all of them + if (lines.size() == 1 && + !lines.first().contains(QLatin1Char('\t')) && + selection.size() > 1) + { + for (const auto &index : selection) + { + if (cellAcceptText(index)) { + m_model->setData(index, lines.first(), Qt::EditRole); + } + } + return; + } + + //Else write the text like a table, from the top left selected cell + int first_row{-1}, first_column{-1}; + if (selection.isEmpty()) + { + const auto current_ = ui->m_table_widget->currentIndex(); + if (!current_.isValid()) { + return; + } + first_row = current_.row(); + first_column = current_.column(); + } + else + { + first_row = std::numeric_limits::max(); + first_column = std::numeric_limits::max(); + for (const auto &index : selection) { + first_row = std::min(first_row, index.row()); + first_column = std::min(first_column, index.column()); + } + } + + for (auto i = 0 ; i < lines.size() ; ++i) + { + const auto row = first_row + i; + if (row >= m_model->rowCount()) { + break; + } + + const auto cells = lines.at(i).split(QLatin1Char('\t')); + for (auto j = 0 ; j < cells.size() ; ++j) + { + const auto column = first_column + j; + if (column >= m_model->columnCount()) { + break; + } + + const auto index = m_model->index(row, column); + if (cellAcceptText(index)) { + m_model->setData(index, cells.at(j), Qt::EditRole); + } + } + } +} + +/** + * @brief TerminalStripEditor::clearSelectedTexts + * Erase the text of the selected cells who accept a text. + */ +void TerminalStripEditor::clearSelectedTexts() +{ + if (!m_model || !ui->m_table_widget->selectionModel()) { + return; + } + + const auto selection = ui->m_table_widget->selectionModel()->selectedIndexes(); + for (const auto &index : selection) + { + if (cellAcceptText(index)) { + m_model->setData(index, QString(), Qt::EditRole); + } + } +} + +/** + * @brief TerminalStripEditor::on_m_cable_apply_pb_clicked + * Set the cable name written in the line edit + * to every selected terminal. + */ +void TerminalStripEditor::on_m_cable_apply_pb_clicked() +{ + if (m_model) + { + const auto index_list = ui->m_table_widget->selectionModel()->selectedIndexes(); + const auto cable_name = ui->m_cable_le->text(); + + for (auto model_index : index_list) + { + const auto cable_index = m_model->index(model_index.row(), TerminalStripModel::Cable, model_index.parent()); + if (cable_index.isValid()) { + m_model->setData(cable_index, cable_name, Qt::EditRole); + } + } + } +} + /** * @brief TerminalStripEditor::on_m_bridge_terminals_pb_clicked */ diff --git a/sources/TerminalStrip/ui/terminalstripeditor.h b/sources/TerminalStrip/ui/terminalstripeditor.h index b20d05dca..ea9e46d35 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.h +++ b/sources/TerminalStrip/ui/terminalstripeditor.h @@ -46,7 +46,13 @@ class TerminalStripEditor : public QWidget void reload(); void apply(); + protected: + bool eventFilter(QObject *watched, QEvent *event) override; + private: + void copySelectionToClipboard(); + void pasteFromClipboard(); + void clearSelectedTexts(); void clear(); void spanMultiLevelTerminals(); void selectionChanged(); @@ -62,6 +68,7 @@ class TerminalStripEditor : public QWidget void on_m_type_cb_activated(int index); void on_m_function_cb_activated(int index); void on_m_led_cb_activated(int index); + void on_m_cable_apply_pb_clicked(); void on_m_bridge_terminals_pb_clicked(); void on_m_unbridge_terminals_pb_clicked(); void on_m_bridge_color_cb_activated(const QColor &col); diff --git a/sources/TerminalStrip/ui/terminalstripeditor.ui b/sources/TerminalStrip/ui/terminalstripeditor.ui index 820c91aaf..35fbb9f5d 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.ui +++ b/sources/TerminalStrip/ui/terminalstripeditor.ui @@ -67,7 +67,7 @@ - + @@ -90,7 +90,7 @@ - + Bridge color : @@ -139,14 +139,41 @@ - + + + + Cable : + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Name of the cable of the selected terminals + + + + + + + Set the cable name to all the selected terminals + + + Apply the cable to the selection + + + + Qt::Horizontal - + Qt::Vertical @@ -233,14 +260,14 @@ - + Bridging the terminals - + Unbridge the terminals diff --git a/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp b/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp index bf1a897a5..f67087017 100644 --- a/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp +++ b/sources/TerminalStrip/ui/terminalstriplayouteditor.cpp @@ -155,6 +155,9 @@ void TerminalStripLayoutEditor::valueEdited() m_layout.data()->m_type_symbol_y = ui->m_type_symbol_y_sb->value(); m_layout.data()->m_type_symbol_height = ui->m_type_symbol_height_sb->value(); m_layout.data()->m_connection_length = ui->m_connection_length_sb->value(); + m_layout.data()->m_cable_wire_length = ui->m_cable_wire_length_sb->value(); + 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(); updateUi(); m_preview_strip_item.update(); @@ -264,6 +267,9 @@ void TerminalStripLayoutEditor::updateUi() ui->m_type_symbol_y_sb->setValue(data->m_type_symbol_y); ui->m_type_symbol_height_sb->setValue(data->m_type_symbol_height); ui->m_connection_length_sb->setValue(data->m_connection_length); + ui->m_cable_wire_length_sb->setValue(data->m_cable_wire_length); + ui->m_cable_length_sb->setValue(data->m_cable_length); + ui->m_cable_end_length_sb->setValue(data->m_cable_end_length); m_ui_updating = false; updatePreview(); diff --git a/sources/TerminalStrip/ui/terminalstriplayouteditor.ui b/sources/TerminalStrip/ui/terminalstriplayouteditor.ui index 15d784f40..73b8c3ac9 100644 --- a/sources/TerminalStrip/ui/terminalstriplayouteditor.ui +++ b/sources/TerminalStrip/ui/terminalstriplayouteditor.ui @@ -503,6 +503,43 @@ + + + + Cable: wires / cable / end + + + + + + + Length of the wires between the terminals and the cable. 0 disable the drawing of the cables. + + + 1000 + + + + + + + Length of the cable. + + + 1000 + + + + + + + Length of the wires after the cable. + + + 1000 + + + @@ -1186,6 +1223,54 @@ + + m_cable_wire_length_sb + valueChanged(int) + TerminalStripLayoutEditor + valueEdited() + + + 700 + 460 + + + 480 + 311 + + + + + m_cable_length_sb + valueChanged(int) + TerminalStripLayoutEditor + valueEdited() + + + 700 + 460 + + + 480 + 311 + + + + + m_cable_end_length_sb + valueChanged(int) + TerminalStripLayoutEditor + valueEdited() + + + 700 + 460 + + + 480 + 311 + + + valueEdited() diff --git a/sources/TerminalStrip/ui/terminalstripmodel.cpp b/sources/TerminalStrip/ui/terminalstripmodel.cpp index bb1f8119a..c6e3fac46 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.cpp +++ b/sources/TerminalStrip/ui/terminalstripmodel.cpp @@ -46,10 +46,11 @@ const int CABLE_WIRE_CELL = 10; const int TYPE_CELL = 11; const int FUNCTION_CELL = 12; const int LED_CELL = 13; +const int SHIELD_CELL = 14; -const int COLUMN_COUNT = 14; +const int COLUMN_COUNT = 15; -static QVector UNMODIFIED_CELL_VECTOR{false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +static QVector UNMODIFIED_CELL_VECTOR{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; /** * @brief TerminalStripModel::levelForColumn @@ -93,6 +94,7 @@ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIn case 11 : return Type; case 12 : return Function; case 13 : return Led; + case 14 : return Shield; default : return Invalid; } } @@ -177,6 +179,8 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const { switch (index.column()) { case LABEL_CELL : return mrtd.label_; + case CABLE_CELL : return mrtd.cable_; + case CABLE_WIRE_CELL : return mrtd.cable_wire; default: return QVariant(); } @@ -186,6 +190,11 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const { return mrtd.led_ ? Qt::Checked : Qt::Unchecked; } + else if (role == Qt::CheckStateRole && + index.column() == SHIELD_CELL) + { + return mrtd.shield_ ? Qt::Checked : Qt::Unchecked; + } else if (role == Qt::BackgroundRole && index.column() < COLUMN_COUNT ) { if (m_modified_cell.contains(mrtd.element_) && @@ -248,6 +257,28 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value modified_ = true; modified_cell = LABEL_CELL; } + else if (column_ == CABLE_CELL && + role == Qt::EditRole && + mrtd.cable_ != value.toString()) + { + mrtd.cable_ = value.toString(); + modified_ = true; + modified_cell = CABLE_CELL; + } + else if (column_ == CABLE_WIRE_CELL && + role == Qt::EditRole && + mrtd.cable_wire != value.toString()) + { + mrtd.cable_wire = value.toString(); + modified_ = true; + modified_cell = CABLE_WIRE_CELL; + } + else if (column_ == SHIELD_CELL) + { + mrtd.shield_ = value.toBool(); + modified_ = true; + modified_cell = SHIELD_CELL; + } //Set the modification to the terminal data if (modified_) @@ -294,6 +325,7 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation case TYPE_CELL: return tr("Type"); case FUNCTION_CELL : return tr("Function"); case LED_CELL: return tr("led"); + case SHIELD_CELL: return tr("Shield"); default : return QVariant(); } } @@ -307,9 +339,10 @@ Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; auto c = index.column(); - if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL) + if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL + || c == CABLE_CELL || c == CABLE_WIRE_CELL) flags = flags | Qt::ItemIsEditable; - if (c == LED_CELL) { + if (c == LED_CELL || c == SHIELD_CELL) { flags = flags | Qt::ItemIsUserCheckable; } return flags; diff --git a/sources/TerminalStrip/ui/terminalstripmodel.h b/sources/TerminalStrip/ui/terminalstripmodel.h index a3b0a696d..2de3bec73 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.h +++ b/sources/TerminalStrip/ui/terminalstripmodel.h @@ -61,6 +61,7 @@ class TerminalStripModel : public QAbstractTableModel Type = 11, Function = 12, Led = 13, + Shield = 14, Invalid = 99 };