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.
This commit is contained in:
Kellermorph
2026-10-19 19:09:50 +02:00
parent 94a929c8c0
commit bab2707fff
4 changed files with 179 additions and 10 deletions
@@ -35,6 +35,7 @@
#include "../terminalcatalog.h"
#include "../realterminal.h"
#include "../../qetinformation.h"
#include "../../cable/cable.h"
#include <QApplication>
#include <QCheckBox>
@@ -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_
@@ -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<TerminalStripModel::Column, QVector<modelRealTerminalData>> singleColumnData() const;
@@ -79,6 +83,9 @@ class TerminalStripEditor : public QWidget
private:
Ui::TerminalStripEditor *ui;
QPointer<QETProject> m_project;
//The project whose cables this editor follows, which is not
//always m_project yet when watchCableChanges() runs again
QPointer<QETProject> m_watched_project;
QPointer<TerminalStrip> m_current_strip;
TerminalStripModel *m_model {nullptr};
};
@@ -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)
{
+80 -10
View File
@@ -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 <QAbstractItemView>
/**
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<RealTerminal> &real_t)
static QList<Conductor *> conductorsOf(const QSharedPointer<RealTerminal> &real_t)
{
Element *element = real_t->element();
if (!element) return nullptr;
QList<Conductor *> conductors;
Element *element = real_t ? real_t->element() : nullptr;
if (!element) return conductors;
for (Terminal *terminal : element->terminals()) {
if (!terminal) continue;
const QList<Conductor *> 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<RealTerminal> &real_t)
{
const QList<Conductor *> 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<RealTerminal> &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<QAbstractItemView *>(
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<Conductor *> 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)