From bab2707fff07a6e09840504b0a66e1bdb5f82b13 Mon Sep 17 00:00:00 2001 From: Kellermorph Date: Mon, 19 Oct 2026 19:09:50 +0200 Subject: [PATCH] Terminal strip editor: the cable cells follow the cables The window stays open while the drawing is wired, so the table takes up what the cables say on its own: each cable of the project is watched, its change reloads the table after CableManager has worked the labels out again, and a cable taken away empties the cells right away. A reload never costs the user his own work: cell edits not applied yet are kept, and a cell being typed in is left alone. A cell worked out from a cable refuses to be written through setData(), so the apply button, the paste and the deletion of texts leave it alone too. --- .../TerminalStrip/ui/terminalstripeditor.cpp | 84 +++++++++++++++++ .../TerminalStrip/ui/terminalstripeditor.h | 7 ++ .../TerminalStrip/ui/terminalstripmodel.cpp | 8 ++ tests/qttest/tst_terminalcableauto.cpp | 90 ++++++++++++++++--- 4 files changed, 179 insertions(+), 10 deletions(-) diff --git a/sources/TerminalStrip/ui/terminalstripeditor.cpp b/sources/TerminalStrip/ui/terminalstripeditor.cpp index 56016fb0b..c5ea3f760 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditor.cpp @@ -35,6 +35,7 @@ #include "../terminalcatalog.h" #include "../realterminal.h" #include "../../qetinformation.h" +#include "../../cable/cable.h" #include #include @@ -95,6 +96,10 @@ TerminalStripEditor::TerminalStripEditor(QETProject *project, QWidget *parent) : } } }); + + //The cable cells are worked out from the cables of the project, + //so the table follows them while the user wires the drawing + watchCableChanges(); } /** @@ -107,9 +112,88 @@ TerminalStripEditor::~TerminalStripEditor() { void TerminalStripEditor::setProject(QETProject *project) { m_project = project; + watchCableChanges(); setCurrentStrip(nullptr); } +/** + * @brief TerminalStripEditor::watchCableChanges + * Follow the cables of the project: the cable cells are worked out from + * them, so a cable renamed, given cores or taken away changes what the + * cells say, and the window stays open while the user wires the drawing. + * The project the editor followed before is let go first, since the + * window may be shown on another project. + */ +void TerminalStripEditor::watchCableChanges() +{ + if (m_watched_project) { + //Only the connections made here go to the project + m_watched_project->disconnect(nullptr, this); + for (Cable *cable : m_watched_project->cables()) { + cable->disconnect(nullptr, this); + } + } + m_watched_project = m_project; + if (!m_project) { + return; + } + + connect(m_project, &QETProject::cableAdded, this, [this](Cable *cable) { + watchCable(cable); + }); + connect(m_project, &QETProject::cableRemoved, this, [this](Cable *cable) { + //The project stopped feeding its labels already, so the + //cells show what is left: the typed in value, or nothing + if (cable) { + cable->disconnect(nullptr, this); + } + reloadOnCableChange(); + }); + for (Cable *cable : m_project->cables()) { + watchCable(cable); + } +} + +/** + * @brief TerminalStripEditor::watchCable + * Reload the table when @a cable changes. The project runs its own + * connection first (made when the cable entered it), so the labels of + * the conductors are worked out again before this reload reads them. + * @param cable + */ +void TerminalStripEditor::watchCable(Cable *cable) +{ + if (!cable) { + return; + } + cable->disconnect(nullptr, this); + connect(cable, &Cable::changed, this, &TerminalStripEditor::reloadOnCableChange); +} + +/** + * @brief TerminalStripEditor::reloadOnCableChange + * Take up a change of the cables: reload so the cable cells say what + * they now must, never at the price of the user's own work -- cell + * edits not applied yet, or a cell being typed in, are kept and catch + * up with the next reload of any kind. + */ +void TerminalStripEditor::reloadOnCableChange() +{ + if (!m_current_strip || !m_model) { + return; + } + if (!m_model->modifiedmodelRealTerminalData().isEmpty()) { + return; + } + //A cell being typed in keeps its editor: the reload would close + //it under the fingers of the user (the edit itself is picked up + //by the next reload once it has been made) + if (ui->m_table_widget->isAncestorOf(QApplication::focusWidget())) { + return; + } + reload(); +} + /** * @brief TerminalStripEditor::setCurrentStrip * Set the current terminal strip edited to \p strip_ diff --git a/sources/TerminalStrip/ui/terminalstripeditor.h b/sources/TerminalStrip/ui/terminalstripeditor.h index 4a38d0e34..8ba7a878e 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.h +++ b/sources/TerminalStrip/ui/terminalstripeditor.h @@ -27,6 +27,7 @@ namespace Ui { } class QETProject; +class Cable; class TerminalStrip; /** @@ -58,6 +59,9 @@ class TerminalStripEditor : public QWidget void spanMultiLevelTerminals(); void selectionChanged(); QSize setUpBridgeCellWidth(); + void watchCableChanges(); + void watchCable(Cable *cable); + void reloadOnCableChange(); TerminalStripModel::Column isSingleColumnSelected() const; QPair> singleColumnData() const; @@ -79,6 +83,9 @@ class TerminalStripEditor : public QWidget private: Ui::TerminalStripEditor *ui; QPointer m_project; + //The project whose cables this editor follows, which is not + //always m_project yet when watchCableChanges() runs again + QPointer m_watched_project; QPointer m_current_strip; TerminalStripModel *m_model {nullptr}; }; diff --git a/sources/TerminalStrip/ui/terminalstripmodel.cpp b/sources/TerminalStrip/ui/terminalstripmodel.cpp index 6ecadcd61..6c4274bae 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.cpp +++ b/sources/TerminalStrip/ui/terminalstripmodel.cpp @@ -302,6 +302,14 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value int modified_cell = -1; auto column_ = index.column(); + //A cell worked out from the cables of the project is read only: + //the element never holds that value, so writing one in would only + //show something the next reload takes away again + if ((column_ == CABLE_CELL || column_ == CABLE_WIRE_CELL) + && mrtd.cable_auto_) { + return false; + } + if (column_ == LEVEL_CELL && role == Qt::EditRole) { diff --git a/tests/qttest/tst_terminalcableauto.cpp b/tests/qttest/tst_terminalcableauto.cpp index c82a4c8cd..802068b28 100644 --- a/tests/qttest/tst_terminalcableauto.cpp +++ b/tests/qttest/tst_terminalcableauto.cpp @@ -19,6 +19,7 @@ #include "qetapptest.h" #include "TerminalStrip/realterminal.h" #include "TerminalStrip/terminalstrip.h" +#include "TerminalStrip/ui/terminalstripeditor.h" #include "TerminalStrip/ui/terminalstripmodel.h" #include "cable/cable.h" #include "qetgraphicsitem/conductor.h" @@ -26,6 +27,8 @@ #include "qetgraphicsitem/terminal.h" #include "qetproject.h" +#include + /** The Cable and Color / wire number columns of the terminal strip editor are worked out from the cables of the project: a terminal with a cable @@ -58,32 +61,44 @@ class tst_terminalcableauto : public QObject return {}; } - static Conductor *conductorOf(const QSharedPointer &real_t) + static QList conductorsOf(const QSharedPointer &real_t) { - Element *element = real_t->element(); - if (!element) return nullptr; + QList conductors; + Element *element = real_t ? real_t->element() : nullptr; + if (!element) return conductors; for (Terminal *terminal : element->terminals()) { if (!terminal) continue; - const QList conductors = terminal->conductors(); - if (!conductors.isEmpty()) return conductors.first(); + for (Conductor *conductor : terminal->conductors()) { + if (conductor && !conductors.contains(conductor)) + conductors.append(conductor); + } } - return nullptr; + return conductors; + } + + static Conductor *conductorOf(const QSharedPointer &real_t) + { + const QList conductors = conductorsOf(real_t); + return conductors.isEmpty() ? nullptr : conductors.first(); } // The cable the create dialog hands over: owned by the project, four - // cores, core 1 (blue) wired to the terminal's conductor. + // cores, one core wired to the terminal's conductor (or to @p on). static Cable *wireCable(QETProject &project, const QSharedPointer &real_t, - int core = 1) + int core = 1, + const QString &designation = QStringLiteral("15W1"), + Conductor *on = nullptr) { + if (!on) on = conductorOf(real_t); Cable *cable = project.newCable(); - cable->setDesignation(QStringLiteral("15W1")); + cable->setDesignation(designation); cable->setCoreCount(4); cable->setCoreColors({QStringLiteral("bn"), QStringLiteral("bl"), QStringLiteral("gn/ye"), QStringLiteral("gy")}); CableCore assignment; assignment.core = core; - assignment.conductor = conductorOf(real_t)->uuid(); + assignment.conductor = on->uuid(); cable->setCore(assignment); return cable; } @@ -194,6 +209,61 @@ private slots: QVERIFY(model.flags(model.index(0, TerminalStripModel::Cable)) & Qt::ItemIsEditable); } + + // The window stays open while the user wires the drawing: the cells + // follow a cable wired, renamed or taken away without any reload by + // hand. + void openEditorFollowsCableChanges() + { + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + const auto real_t = wiredTerminal(project); + QVERIFY(!real_t.isNull()); + + TerminalStripEditor editor(&project); + editor.setCurrentStrip(project.terminalStrip().first()); + const QAbstractItemView *table = editor.findChild( + QStringLiteral("m_table_widget")); + QVERIFY2(table && table->model(), "the editor has its table"); + const QAbstractItemModel *model = table->model(); + const QModelIndex cable_cell = model->index(0, TerminalStripModel::Cable); + QVERIFY(model->data(cable_cell).toString().isEmpty()); + + //A cable wired to the terminal fills the cell in + Cable *cable = wireCable(project, real_t); + QCOMPARE(model->data(cable_cell).toString(), QStringLiteral("15W1")); + + //Renaming it follows at once, the drawing is not stale behind + cable->setDesignation(QStringLiteral("16W1")); + QCOMPARE(model->data(cable_cell).toString(), QStringLiteral("16W1")); + + //Taking it away empties the cell: nothing stale stays behind + QVERIFY(project.removeCable(cable)); + QVERIFY(model->data(cable_cell).toString().isEmpty()); + } + + // A terminal wired to two cables shows them both, comma separated. + void twoCablesOnOneTerminalAreBothShown() + { + const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet"); + QETProject project(fixture); + QCOMPARE(project.state(), QETProject::Ok); + + const auto real_t = wiredTerminal(project); + QVERIFY(!real_t.isNull()); + const QList conductors = conductorsOf(real_t); + QVERIFY2(conductors.size() >= 2, + "the fixture terminal carries two wires"); + + wireCable(project, real_t, 1, QStringLiteral("15W1"), conductors.at(0)); + wireCable(project, real_t, 1, QStringLiteral("16W1"), conductors.at(1)); + + QCOMPARE(real_t->cable(), QStringLiteral("15W1, 16W1")); + QCOMPARE(real_t->cableWire(), QStringLiteral("bl, bl")); + QVERIFY(real_t->cableIsAutomatic()); + } }; QET_APP_TEST_MAIN(tst_terminalcableauto)