Files
qelectrotech-source-mirror/tests/qttest/tst_terminalcableauto.cpp
T
Kellermorph 36b1cb08ff Tell the fields of a target apart by line breaks only
A target typed in by hand was split into its fields on " / ", which
broke as soon as a field itself contained a slash: an installation
named "ASP/01" gave the box two lines with half a name in each, and
nothing to tell a separator apart from the name. Only a line break now
breaks the text into fields (RealTerminal::cableTarget()), and the
Target cell shows the fields the very same way, one line each
(modelRealTerminalData::targetText()), so the cell and the box under
the cable always read the same text.

The Target cell is edited line by line to go with that: it takes a
multi line editor where Return writes a new line, Ctrl+Return hands
the cell back to the table and Escape throws the text away
(TerminalStripModelDelegate), and the rows of the table are as tall as
the lines their cell shows, so the fields below the first one are never
cut off -- a cell without a target keeps the row it has always had.

The text copied to the clipboard keeps one line per row, the fields of
such a cell joined with " / " again, or a spreadsheet would take them
for several rows.

tst_terminalcableauto reads the three fields of a target of the
drawing back one line each, checks a "/" typed inside a field stays
part of it -- one line holding it, slashes and all -- checks the
editor of the cell is the multi line one, hands its text back
untouched, gives the cell back on Ctrl+Return while Return keeps
writing a new line, and checks the rows grow with the lines the cell
holds and shrink back afterwards. tst_cabletarget paints a strip whose
cable ends on a field containing a slash: the box is no deeper than
with one plain field.
2026-10-20 19:08:16 +02:00

723 lines
29 KiB
C++

// SPDX-License-Identifier: GPL-2.0-or-later
/*
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 <http://www.gnu.org/licenses/>.
*/
#include "qetapptest.h"
#include "TerminalStrip/physicalterminal.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"
#include "qetgraphicsitem/element.h"
#include "qetgraphicsitem/terminal.h"
#include "qetinformation.h"
#include "qetproject.h"
#include <QAbstractItemView>
#include <QCoreApplication>
#include <QKeyEvent>
#include <QPlainTextEdit>
#include <QSignalSpy>
#include <QTableView>
#include <QTextCursor>
/**
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
wired to it shows that cable and its core, the cell is read only, and
nothing is written into the element -- so a cable renamed or removed
cannot leave a stale value behind. A terminal no cable is wired to
keeps the value typed in by hand, cell and all.
The Target column follows the same rule: it names where the cable
lands past this strip (Installation, Location and Label), the very
three fields the strip drawing writes in its box. Worked out from a
cable it is read only -- only the drawing decides it -- and with no
cable landing anywhere it is there to be typed in, just like the
Cable cell, the text then stored in the element and kept until a
cable works a target out again. A text typed in is written one line
per field, Installation, Location, Label, the way the column shows
them: only a line break breaks it into fields, so a "/" written
inside a field (a name like "ASP/01") is part of that field and the
box draws one line containing it.
fixtures/every_feature.qet has the strip X1 whose two terminals are
wired to conductors on the folio, and no cable at all.
*/
class tst_terminalcableauto : public QObject
{
Q_OBJECT
// The wired terminal of strip X1: its element carries a conductor.
static QSharedPointer<RealTerminal> wiredTerminal(QETProject &project)
{
const QVector<TerminalStrip *> strips = project.terminalStrip();
if (strips.isEmpty()) return {};
for (const QSharedPointer<RealTerminal> &real_t
: strips.first()->realTerminals())
{
Element *element = real_t ? real_t->element() : nullptr;
if (!element) continue;
for (Terminal *terminal : element->terminals())
if (terminal && !terminal->conductors().isEmpty())
return real_t;
}
return {};
}
static QList<Conductor *> conductorsOf(const QSharedPointer<RealTerminal> &real_t)
{
QList<Conductor *> conductors;
Element *element = real_t ? real_t->element() : nullptr;
if (!element) return conductors;
for (Terminal *terminal : element->terminals()) {
if (!terminal) continue;
for (Conductor *conductor : terminal->conductors()) {
if (conductor && !conductors.contains(conductor))
conductors.append(conductor);
}
}
return conductors;
}
static Conductor *conductorOf(const QSharedPointer<RealTerminal> &real_t)
{
const QList<Conductor *> conductors = conductorsOf(real_t);
return conductors.isEmpty() ? nullptr : conductors.first();
}
// The wire which leaves the strip: one of its ends belongs to a piece
// outside the strip, while the wire joining two terminals of this very
// strip never leaves it -- and never has a target to name.
static Conductor *outgoingConductor(const QSharedPointer<RealTerminal> &real_t)
{
TerminalStrip *strip = real_t ? real_t->parentStrip() : nullptr;
if (!strip) return nullptr;
QSet<QUuid> own;
for (const QSharedPointer<PhysicalTerminal> &physical
: strip->physicalTerminal())
{
if (!physical) continue;
for (const QSharedPointer<RealTerminal> &real : physical->realTerminals())
if (real && real->element())
own.insert(real->element()->uuid());
}
for (Conductor *conductor : conductorsOf(real_t))
{
if (!conductor) continue;
Element *ends[2] = {
conductor->terminal1 ? conductor->terminal1->parentElement() : nullptr,
conductor->terminal2 ? conductor->terminal2->parentElement() : nullptr };
for (Element *end : ends)
if (end && !own.contains(end->uuid()))
return conductor;
}
return nullptr;
}
// The cable the create dialog hands over: owned by the project, four
// 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,
const QString &designation = QStringLiteral("15W1"),
Conductor *on = nullptr)
{
if (!on) on = conductorOf(real_t);
Cable *cable = project.newCable();
cable->setDesignation(designation);
cable->setCoreCount(4);
cable->setCoreColors({QStringLiteral("bn"), QStringLiteral("bl"),
QStringLiteral("gn/ye"), QStringLiteral("gy")});
CableCore assignment;
assignment.core = core;
assignment.conductor = on->uuid();
cable->setCore(assignment);
return cable;
}
private slots:
void wiredCableIsWorkedOutAndReadsOnly()
{
const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet");
QVERIFY2(!fixture.isEmpty(), "fixture project not found");
QETProject project(fixture);
QCOMPARE(project.state(), QETProject::Ok);
const auto real_t = wiredTerminal(project);
QVERIFY2(!real_t.isNull(), "the fixture has a wired terminal");
Element *element = real_t->element();
//Without a cable, the cells show nothing and may be typed in
QVERIFY(!real_t->cableIsAutomatic());
TerminalStripModel model_before(project.terminalStrip().first());
QVERIFY(model_before.rowCount() > 0);
QVERIFY(model_before.flags(model_before.index(0, TerminalStripModel::Cable))
& Qt::ItemIsEditable);
wireCable(project, real_t);
//The terminal itself reads the cable and its core
QCOMPARE(real_t->cable(), QStringLiteral("15W1"));
QVERIFY(real_t->cableIsAutomatic());
QCOMPARE(real_t->cableWire(), QStringLiteral("bl"));
//The automatic value is never written into the element: nothing
//stays behind once the cable is renamed or removed
QVERIFY(element);
QVERIFY(element->elementData().m_informations
.value(RealTerminal::cableInfoKey()).toString().isEmpty());
//The model shows both cells and keeps them out of the edits
TerminalStripModel model(project.terminalStrip().first());
QCOMPARE(model.rowCount(), model_before.rowCount());
const QModelIndex cable_cell = model.index(0, TerminalStripModel::Cable);
const QModelIndex wire_cell = model.index(0, TerminalStripModel::CableWire);
QCOMPARE(model.data(cable_cell).toString(), QStringLiteral("15W1"));
QCOMPARE(model.data(wire_cell).toString(), QStringLiteral("bl"));
QVERIFY(!(model.flags(cable_cell) & Qt::ItemIsEditable));
QVERIFY(!(model.flags(wire_cell) & Qt::ItemIsEditable));
//The other cells go on being editable
QVERIFY(model.flags(model.index(0, TerminalStripModel::Label))
& Qt::ItemIsEditable);
}
void coreWithoutColorIsShownAsItsNumber()
{
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());
Cable *cable = project.newCable();
cable->setDesignation(QStringLiteral("15W1"));
cable->setCoreCount(4);
//A type file which gives no colours: the number of the core is shown
CableCore assignment;
assignment.core = 1;
assignment.conductor = conductorOf(real_t)->uuid();
cable->setCore(assignment);
QCOMPARE(real_t->cable(), QStringLiteral("15W1"));
QCOMPARE(real_t->cableWire(), QStringLiteral("2"));
QVERIFY(real_t->cableIsAutomatic());
}
void typedValueStaysWhenNoCableIsThere()
{
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());
Element *element = real_t->element();
QVERIFY(element);
//A cable designation typed in by hand in the editor
ElementData typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableInfoKey(),
QStringLiteral("K9"));
element->setElementData(typed);
QCOMPARE(real_t->cable(), QStringLiteral("K9"));
QVERIFY(!real_t->cableIsAutomatic());
//Wired to a cable: the cable is shown, the typed value is set aside
Cable *cable = wireCable(project, real_t);
QCOMPARE(real_t->cable(), QStringLiteral("15W1"));
QVERIFY(real_t->cableIsAutomatic());
QCOMPARE(real_t->cableWire(), QStringLiteral("bl"));
//Cable removed: the typed value is back, no stale 15W1 anywhere
cable->removeCore(1);
QCOMPARE(real_t->cable(), QStringLiteral("K9"));
QVERIFY(!real_t->cableIsAutomatic());
QVERIFY(element->elementData().m_informations
.value(RealTerminal::cableInfoKey())
.toString() == QStringLiteral("K9"));
TerminalStripModel model(project.terminalStrip().first());
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());
}
// The Target column names where the cable lands, past this strip: the
// three fields the strip drawing writes in the box under the cable,
// joined in one line. Worked out from a cable it is read only, there
// is nothing to name without one, and the cell empties again once the
// cable goes.
void targetColumnNamesWhereTheCableLands()
{
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());
Conductor *outgoing = outgoingConductor(real_t);
QVERIFY2(outgoing, "the fixture terminal has a wire leaving the strip");
Element *target = (outgoing->terminal1
&& outgoing->terminal1->parentElement() == real_t->element())
? (outgoing->terminal2 ? outgoing->terminal2->parentElement() : nullptr)
: (outgoing->terminal1 ? outgoing->terminal1->parentElement() : nullptr);
QVERIFY(target);
//The piece the cable lands on carries an Installation, a Location
//and a Label, the way an element of a real drawing does
ElementData named = target->elementData();
named.m_informations.addValue(QETInformation::ELMT_PLANT,
QStringLiteral("Plant 7"));
named.m_informations.addValue(QETInformation::ELMT_LOCATION,
QStringLiteral("Cabinet 7"));
named.m_informations.addValue(QETInformation::ELMT_LABEL,
QStringLiteral("-S7"));
target->setElementData(named);
//No cable yet : nothing to name, so the cell is there to be
//filled in by hand -- the very rule of the Cable cell
TerminalStripModel model_before(project.terminalStrip().first());
const QModelIndex before_cell = model_before.index(
0, TerminalStripModel::CableTarget);
QVERIFY(model_before.data(before_cell).toString().isEmpty());
QVERIFY(model_before.flags(before_cell) & Qt::ItemIsEditable);
Cable *cable = wireCable(project, real_t, 1, QStringLiteral("15W1"),
outgoing);
//Row zero is the terminal the cable is wired to
TerminalStripModel model(project.terminalStrip().first());
QCOMPARE(model.data(model.index(0, TerminalStripModel::Cable))
.toString(), QStringLiteral("15W1"));
//The three fields, one on its own line as the box draws them
const QModelIndex target_cell = model.index(0,
TerminalStripModel::CableTarget);
QCOMPARE(model.data(target_cell).toString(),
QStringLiteral("Plant 7\nCabinet 7\n-S7"));
QVERIFY(!(model.flags(target_cell) & Qt::ItemIsEditable));
QCOMPARE(model.headerData(TerminalStripModel::CableTarget,
Qt::Horizontal).toString(),
QStringLiteral("Target"));
//The cable taken away : the cell is empty again, nothing stale
//behind, and it takes an edit once more
QVERIFY(cable);
cable->removeCore(1);
TerminalStripModel reloaded(project.terminalStrip().first());
const QModelIndex reloaded_cell = reloaded.index(
0, TerminalStripModel::CableTarget);
QVERIFY(reloaded.data(reloaded_cell).toString().isEmpty());
QVERIFY(reloaded.flags(reloaded_cell) & Qt::ItemIsEditable);
}
// A target the drawing cannot work out is typed in by hand, exactly
// like a cable designation: the cell takes the edit, Apply stores the
// text in the element, a fresh model shows it again. A cable landing
// on a named piece then gives the read only target of the drawing and
// the text typed in by hand waits, untouched, until that cable goes.
void targetTypedByHandIsStoredAndKept()
{
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());
Element *element = real_t->element();
QVERIFY(element);
Conductor *outgoing = outgoingConductor(real_t);
QVERIFY2(outgoing, "the fixture terminal has a wire leaving the strip");
//The editor takes a target typed in by hand : no cable is wired,
//so there is nothing the drawing could name and the cell is open
TerminalStripEditor editor(&project);
editor.setCurrentStrip(project.terminalStrip().first());
QAbstractItemView *table = editor.findChild<QAbstractItemView *>(
QStringLiteral("m_table_widget"));
QVERIFY2(table && table->model(), "the editor has its table");
QAbstractItemModel *model = table->model();
const QModelIndex cell = model->index(0, TerminalStripModel::CableTarget);
QVERIFY(model->data(cell).toString().isEmpty());
QVERIFY(model->flags(cell) & Qt::ItemIsEditable);
const QString typed{QStringLiteral("Boiler room\nCabinet 3\n-K3")};
QVERIFY(model->setData(cell, typed, Qt::EditRole));
QCOMPARE(model->data(cell, Qt::EditRole).toString(), typed);
QCOMPARE(model->data(cell).toString(), typed);
//Apply writes it in the element, and nowhere else
editor.apply();
QCOMPARE(element->elementData().m_informations
.value(RealTerminal::cableTargetInfoKey()).toString(), typed);
//Three lines, each field on its own: the box draws them one under
//the other, without any separator
QCOMPARE(real_t->cableTarget(),
(QVector<QString>{QStringLiteral("Boiler room"),
QStringLiteral("Cabinet 3"),
QStringLiteral("-K3")}));
QVERIFY(!real_t->cableTargetIsAutomatic());
//A fresh model shows the text and still lets it be edited
TerminalStripModel model_after(project.terminalStrip().first());
const QModelIndex after_cell = model_after.index(
0, TerminalStripModel::CableTarget);
QCOMPARE(model_after.data(after_cell).toString(), typed);
QVERIFY(model_after.flags(after_cell) & Qt::ItemIsEditable);
//The piece the cable lands on is named now : once the cable is
//wired the drawing gives the target of its own, and that one is
//read only while the text typed in by hand stays in the element
Element *target = (outgoing->terminal1
&& outgoing->terminal1->parentElement() == real_t->element())
? (outgoing->terminal2 ? outgoing->terminal2->parentElement() : nullptr)
: (outgoing->terminal1 ? outgoing->terminal1->parentElement() : nullptr);
QVERIFY(target);
ElementData named = target->elementData();
named.m_informations.addValue(QETInformation::ELMT_PLANT,
QStringLiteral("Plant 9"));
named.m_informations.addValue(QETInformation::ELMT_LOCATION,
QStringLiteral("Cabinet 9"));
named.m_informations.addValue(QETInformation::ELMT_LABEL,
QStringLiteral("-S9"));
target->setElementData(named);
Cable *cable = wireCable(project, real_t, 1, QStringLiteral("15W1"),
outgoing);
QVERIFY(cable);
QVERIFY(real_t->cableTargetIsAutomatic());
QCOMPARE(real_t->cableTarget(),
(QVector<QString>{QStringLiteral("Plant 9"),
QStringLiteral("Cabinet 9"),
QStringLiteral("-S9")}));
QCOMPARE(element->elementData().m_informations
.value(RealTerminal::cableTargetInfoKey()).toString(), typed);
TerminalStripModel model_cable(project.terminalStrip().first());
const QModelIndex cable_cell = model_cable.index(
0, TerminalStripModel::CableTarget);
QCOMPARE(model_cable.data(cable_cell).toString(),
QStringLiteral("Plant 9\nCabinet 9\n-S9"));
QVERIFY(!(model_cable.flags(cable_cell) & Qt::ItemIsEditable));
//And the automatic target was never written in the element
QCOMPARE(element->elementData().m_informations
.value(RealTerminal::cableTargetInfoKey()).toString(), typed);
//The cable taken away : the text typed in by hand is back, cell
//and all, no stale Plant 9 anywhere
cable->removeCore(1);
QVERIFY(!real_t->cableTargetIsAutomatic());
QCOMPARE(real_t->cableTarget(),
(QVector<QString>{QStringLiteral("Boiler room"),
QStringLiteral("Cabinet 3"),
QStringLiteral("-K3")}));
TerminalStripModel model_back(project.terminalStrip().first());
const QModelIndex back_cell = model_back.index(
0, TerminalStripModel::CableTarget);
QCOMPARE(model_back.data(back_cell).toString(), typed);
QVERIFY(model_back.flags(back_cell) & Qt::ItemIsEditable);
}
// A text typed in by hand is written one line per field, and only a
// line break breaks it into fields: the box under the cable draws
// one line per field, exactly like a target worked out from the
// project, while a "/" written inside a field is part of it and
// gives one single line holding it.
void typedTargetGivesOneLinePerField()
{
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());
Element *element = real_t->element();
QVERIFY(element);
//One field only: one single line in the box
ElementData typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
QStringLiteral("Boiler room"));
element->setElementData(typed);
QCOMPARE(real_t->cableTarget(),
QVector<QString>{QStringLiteral("Boiler room")});
//Three fields, written one per line: three lines, and no
//separator anywhere in them
typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
QStringLiteral("Plant 9\nCabinet 9\n-S9"));
element->setElementData(typed);
QCOMPARE(real_t->cableTarget(),
(QVector<QString>{QStringLiteral("Plant 9"),
QStringLiteral("Cabinet 9"),
QStringLiteral("-S9")}));
//A typed text is never a target of the drawing
QVERIFY(!real_t->cableTargetIsAutomatic());
//The column shows the text back the way it was written
TerminalStripModel model(project.terminalStrip().first());
QCOMPARE(model.data(model.index(0, TerminalStripModel::CableTarget))
.toString(), QStringLiteral("Plant 9\nCabinet 9\n-S9"));
//A slash inside a field, and one typed between two words of a
//field kept in one line: both stay part of the field, the cell
//shows them untouched and the box holds one single line each
typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
QStringLiteral("Betrieb 1/2"));
element->setElementData(typed);
QCOMPARE(real_t->cableTarget(),
QVector<QString>{QStringLiteral("Betrieb 1/2")});
typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
QStringLiteral("Betrieb 1/2 / Keller"));
element->setElementData(typed);
QCOMPARE(real_t->cableTarget(),
QVector<QString>{QStringLiteral("Betrieb 1/2 / Keller")});
TerminalStripModel slashed(project.terminalStrip().first());
QCOMPARE(slashed.data(slashed.index(0, TerminalStripModel::CableTarget))
.toString(), QStringLiteral("Betrieb 1/2 / Keller"));
//The line breaks written by hand are the fields again, whatever
//the fields themselves contain
typed = element->elementData();
typed.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
QStringLiteral("Betrieb 1/2\nKeller\n-K4"));
element->setElementData(typed);
QCOMPARE(real_t->cableTarget(),
(QVector<QString>{QStringLiteral("Betrieb 1/2"),
QStringLiteral("Keller"),
QStringLiteral("-K4")}));
}
// The Target cell is edited line by line: its editor holds one line
// per field and hands the text back exactly as the cell holds it.
// Return writes a new line (the cell is multi line), Ctrl+Return
// gives the cell back without waiting for the focus to go somewhere
// else, and the row of the table is as tall as the lines the cell
// shows, so none of them is cut off.
void targetCellIsEditedLineByLine()
{
const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet");
QETProject project(fixture);
QCOMPARE(project.state(), QETProject::Ok);
TerminalStripEditor editor(&project);
editor.setCurrentStrip(project.terminalStrip().first());
QTableView *table = editor.findChild<QTableView *>(
QStringLiteral("m_table_widget"));
QVERIFY2(table && table->model(), "the editor has its table");
QAbstractItemModel *model = table->model();
QVERIFY(model->rowCount() > 1);
const QModelIndex cell = model->index(0, TerminalStripModel::CableTarget);
QVERIFY(model->flags(cell) & Qt::ItemIsEditable);
const QString typed{QStringLiteral("Betrieb 1/2\nKeller\n-K4")};
QVERIFY(model->setData(cell, typed, Qt::EditRole));
QCOMPARE(model->data(cell).toString(), typed);
//The editor of the table is the one taking a cell of several
//lines, and one of its own does the very same thing for the
//keys, without a view in between
QVERIFY(qobject_cast<TerminalStripModelDelegate *>(
table->itemDelegate()));
TerminalStripModelDelegate delegate;
QStyleOptionViewItem option;
option.rect = QRect(0, 0, qMax(
50, table->columnWidth(TerminalStripModel::CableTarget)), 400);
option.font = table->font();
option.fontMetrics = QFontMetrics(option.font);
QWidget *cell_editor = delegate.createEditor(table, option, cell);
QVERIFY(cell_editor);
auto *text_edit = qobject_cast<QPlainTextEdit *>(cell_editor);
QVERIFY2(text_edit, "the Target cell takes a multi line editor");
QCOMPARE(text_edit->objectName(),
QStringLiteral("terminal_cable_target"));
//The editor gives the lines back exactly as the cell holds
//them: nothing joined, nothing split again
delegate.setEditorData(cell_editor, cell);
QCOMPARE(text_edit->toPlainText(), typed);
delegate.setModelData(cell_editor, model, cell);
QCOMPARE(model->data(cell).toString(), typed);
//Return writes a new line in the cell, and hands nothing over
//yet: Ctrl+Return is the key giving the cell back
QSignalSpy committed(&delegate, &TerminalStripModelDelegate::commitData);
QSignalSpy closed(&delegate, &TerminalStripModelDelegate::closeEditor);
text_edit->moveCursor(QTextCursor::End);
QKeyEvent return_key{QEvent::KeyPress, Qt::Key_Return,
Qt::NoModifier};
QCoreApplication::sendEvent(cell_editor, &return_key);
QCOMPARE(text_edit->toPlainText(), typed + QLatin1Char('\n'));
QCOMPARE(committed.count(), 0);
QCOMPARE(closed.count(), 0);
QKeyEvent ctrl_return_key{QEvent::KeyPress, Qt::Key_Return,
Qt::ControlModifier};
QCoreApplication::sendEvent(cell_editor, &ctrl_return_key);
QCOMPARE(committed.count(), 1);
QCOMPARE(closed.count(), 1);
delete cell_editor;
}
// The row of the table follows the lines its Target cell holds: the
// cell is never cut off, however many lines it takes, the row grows
// to hold them all and shrinks back afterwards, while the rows
// around keep the height they have always had.
void targetRowGrowsWithTheLinesOfItsCell()
{
const QString fixture = QFINDTESTDATA("fixtures/every_feature.qet");
QETProject project(fixture);
QCOMPARE(project.state(), QETProject::Ok);
TerminalStripEditor editor(&project);
editor.setCurrentStrip(project.terminalStrip().first());
QTableView *table = editor.findChild<QTableView *>(
QStringLiteral("m_table_widget"));
QVERIFY2(table && table->model(), "the editor has its table");
QAbstractItemModel *model = table->model();
QVERIFY(model->rowCount() > 1);
const QModelIndex cell = model->index(0, TerminalStripModel::CableTarget);
QVERIFY(model->flags(cell) & Qt::ItemIsEditable);
QVERIFY2(model->data(cell).toString().isEmpty(),
"no target to start with, the row is the usual one");
const QModelIndex other_cell = model->index(
1, TerminalStripModel::CableTarget);
const int base_row = table->rowHeight(cell.row());
const int base_other_row = table->rowHeight(other_cell.row());
QVERIFY(base_row > 0);
//Three lines in the cell: the row holds them all, at the
//font of the table -- the row was already taller than that
//for what else the terminal draws, so it may stay as it is
const QString typed{QStringLiteral("Betrieb 1/2\nKeller\n-K4")};
QVERIFY(model->setData(cell, typed, Qt::EditRole));
QCOMPARE(model->data(cell).toString(), typed);
const auto heightOfLines = [table](int lines_) {
return lines_ * table->fontMetrics().height();
};
const int three_line_row = table->rowHeight(cell.row());
QVERIFY2(three_line_row >= qMax(base_row, heightOfLines(3)),
"the row must be as tall as both the lines the cell "
"shows and what it held before, or something is cut off");
QVERIFY2(table->rowHeight(other_cell.row()) >= base_other_row,
"the rows around are not pushed down");
//From here on the rows have been worked out once: what the
//other rows hold is the height they keep
const int other_row = table->rowHeight(other_cell.row());
//Many more lines than anything else in the row: the row grows
//to hold them, and the rows around stay where they are
const QString many{QStringLiteral(
"Plant 6\nCabinet 6\n-K6\nZone 6\nLevel 6\nEnd 6")};
QVERIFY(model->setData(cell, many, Qt::EditRole));
const int six_line_row = table->rowHeight(cell.row());
QVERIFY2(six_line_row >= heightOfLines(6),
"the row must be as tall as the lines the cell shows, "
"or the fields below the first one are cut off");
QVERIFY2(six_line_row > qMax(base_row, three_line_row),
"a cell which took six lines must take a taller row "
"than the one it had before");
QVERIFY2(table->rowHeight(other_cell.row()) == other_row,
"the rows around keep their own height");
//Back to one single line: the row follows again
QVERIFY(model->setData(cell, QStringLiteral("Keller"), Qt::EditRole));
QVERIFY2(table->rowHeight(cell.row()) < six_line_row,
"the row follows the cell back to a single line");
QVERIFY2(table->rowHeight(cell.row())
<= base_row + table->fontMetrics().height(),
"the row is back to the height it had before");
QVERIFY2(table->rowHeight(cell.row()) >= table->fontMetrics().height(),
"the single line left is still shown");
}
};
QET_APP_TEST_MAIN(tst_terminalcableauto)
#include "tst_terminalcableauto.moc"