mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-23 23:44:13 +02:00
Merge pull request #1577 from Kellermorph/integration/terminal-manager-upstream
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m12s
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m12s
Terminal strip editor: automatic cable entry, plan order, target box, Target column and speed-ups
This commit is contained in:
@@ -318,6 +318,11 @@ qet_add_app_test(tst_assistantinfopaths tst_assistantinfopaths.cpp)
|
||||
# %T in a report or cross reference formula is the target folio's title.
|
||||
qet_add_app_test(tst_reportfoliotitle tst_reportfoliotitle.cpp)
|
||||
|
||||
# The cable cells of a terminal strip are worked out from the cables of
|
||||
# the project: wired shows the cable and its core read only, unwired keeps
|
||||
# what was typed in, and nothing stale is written into the element.
|
||||
qet_add_app_test(tst_terminalcableauto tst_terminalcableauto.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)
|
||||
@@ -364,6 +369,17 @@ target_link_libraries(tst_textresizehandles PRIVATE Qt::Test Qt::Widgets)
|
||||
# rectangle's does; a pivot placed by hand stays.
|
||||
qet_add_app_test(tst_linepivotresize tst_linepivotresize.cpp)
|
||||
|
||||
# The terminal strip sorts into the plan's own order -- folio by folio, then
|
||||
# left to right -- instead of by label; the auto import and
|
||||
# SortTerminalStripCommand share one comparator for it, so a strip the import
|
||||
# creates cannot come out in another order than the sort it then pushes.
|
||||
qet_add_app_test(tst_terminalstriporder tst_terminalstriporder.cpp)
|
||||
|
||||
# The strip graphic names where a cable goes at its far end -- Installation,
|
||||
# Location and Label of the first piece its wires land on past the strip, in
|
||||
# plan order, the same three fields the cable list computes for a target.
|
||||
qet_add_app_test(tst_cabletarget tst_cabletarget.cpp)
|
||||
|
||||
# legacyelementtypes.h is header-only: the rewrite of element type names in
|
||||
# nomenclature queries saved before June 2022.
|
||||
add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp)
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
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/GraphicsItem/realisticterminalstripdrawer.h"
|
||||
#include "TerminalStrip/GraphicsItem/trueterminalstrip.h"
|
||||
#include "TerminalStrip/physicalterminal.h"
|
||||
#include "TerminalStrip/realterminal.h"
|
||||
#include "TerminalStrip/terminalstrip.h"
|
||||
#include "cable/cable.h"
|
||||
#include "conductorproperties.h"
|
||||
#include "diagram.h"
|
||||
#include "properties/elementdata.h"
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetinformation.h"
|
||||
#include "qetproject.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QStandardPaths>
|
||||
|
||||
/**
|
||||
The terminal strip graphic names where a cable goes: Installation,
|
||||
Location and Label (BMK) of the first piece the wires of the cable
|
||||
land on, past the strip's own terminals, in plan order -- the same
|
||||
three fields the cable list computes for a target
|
||||
(cablelistrows.cpp), so the strip and the list can never disagree.
|
||||
The graphic draws them in a small box under the cable, the bundle
|
||||
staying together (RealTerminal::cableTarget()).
|
||||
|
||||
Also here: the number of a wire must stay in the room left between
|
||||
its mark and the terminal -- it shrinks, and what does not fit is
|
||||
clipped, so it never runs into the drawing of the terminal.
|
||||
|
||||
Also here: the box at the end of a cable is written at the font of
|
||||
the drawing -- the end zone the settings leave is what gives it the
|
||||
room, so three lines never end up squeezed into a tiny text. A
|
||||
target typed in by hand is written one line per field, Installation,
|
||||
Location, Label, the way the column shows them, and only a line
|
||||
break breaks it into fields: a "/" written inside a field is part of
|
||||
it, the box draws one line holding it. Two cables of one strip end
|
||||
close to each other, and their boxes are wider than the room between
|
||||
them: the box standing in the way of the other is set lower, its
|
||||
stub drawn longer, and the strip reserves the room for it too.
|
||||
|
||||
Both drawing styles are covered: the schematic one and the
|
||||
realistic one (the strip seen from above), which paint the same
|
||||
cable through Helpers::drawCables().
|
||||
|
||||
fixtures/every_feature.qet has the strip X1 (elements 891b70f2 and
|
||||
4861e268) and the contact fc9cc920 wired to 891b70f2 by the
|
||||
conductor 8bd16d7a; the test gives the contact the Installation,
|
||||
Location and Label it reads back. The conductor 584b452e joins the
|
||||
two strip terminals to each other. No conductor of the fixture
|
||||
belongs to a cable yet.
|
||||
*/
|
||||
class tst_cabletarget : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QETProject *m_project = nullptr;
|
||||
|
||||
Element *element(const QString &uuid) const
|
||||
{
|
||||
for (Diagram *diagram_ : m_project->diagrams()) {
|
||||
for (Element *element_ : diagram_->elements()) {
|
||||
if (element_->uuid().toString() == uuid) {
|
||||
return element_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QSharedPointer<RealTerminal> terminalInStrip(const QString &element_uuid) const
|
||||
{
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
if (strips_.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
for (const auto &real_ : strips_.first()->realTerminals()) {
|
||||
if (real_ && real_->element()
|
||||
&& real_->element()->uuid().toString() == element_uuid) {
|
||||
return real_;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Conductor *conductor(const QString &uuid) const
|
||||
{
|
||||
for (Diagram *diagram_ : m_project->diagrams()) {
|
||||
for (Conductor *conductor_ : diagram_->conductors()) {
|
||||
if (conductor_ && conductor_->uuid().toString() == uuid) {
|
||||
return conductor_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Let the drawn wires of @a conductor_ belong to the cable @a cable_uuid.
|
||||
void claimCable(Conductor *conductor_, const QUuid &cable_uuid) const
|
||||
{
|
||||
auto properties_ = conductor_->properties();
|
||||
properties_.m_cable_uuid = cable_uuid;
|
||||
conductor_->setProperties(properties_);
|
||||
}
|
||||
|
||||
/**
|
||||
Same setup for the value and the drawing test: the contact the
|
||||
strip's wire lands on carries the three fields the test reads
|
||||
back (the fixture's own block has none), the wire belongs to a
|
||||
new cable, and the terminal carries that cable's name the way
|
||||
the strip editor stores it -- without a name the strip draws no
|
||||
cable at all. @return false when the fixture is not the one this
|
||||
test was written for.
|
||||
*/
|
||||
bool wireCableToTheContact()
|
||||
{
|
||||
Element *contact_ = element(
|
||||
QStringLiteral("{fc9cc920-5822-421a-916e-a17100acdcf6}"));
|
||||
if (!contact_) {
|
||||
return false;
|
||||
}
|
||||
ElementData contact_data_{contact_->elementData()};
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_PLANT,
|
||||
QStringLiteral("Plant 7"));
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LOCATION,
|
||||
QStringLiteral("Cabinet 7"));
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LABEL,
|
||||
QStringLiteral("-S7"));
|
||||
contact_->setElementData(contact_data_);
|
||||
|
||||
Cable *cable_ = m_project->newCable();
|
||||
Conductor *wired_ = conductor(
|
||||
QStringLiteral("{8bd16d7a-18c4-4fa0-9495-8dab65fd6408}"));
|
||||
if (!cable_ || !wired_) {
|
||||
return false;
|
||||
}
|
||||
claimCable(wired_, cable_->uuid());
|
||||
|
||||
//The name of the cable on the terminal, the way the strip
|
||||
//editor stores it: without it the strip draws no cable at all
|
||||
Element *terminal_ = element(
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
if (!terminal_) {
|
||||
return false;
|
||||
}
|
||||
ElementData terminal_data_{terminal_->elementData()};
|
||||
terminal_data_.m_informations.addValue(RealTerminal::cableInfoKey(),
|
||||
QStringLiteral("X99"));
|
||||
terminal_->setElementData(terminal_data_);
|
||||
return true;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void noCableNoTarget();
|
||||
void targetIsTheOtherPiece();
|
||||
void ownStripIsNoTarget();
|
||||
void spareHasNoTarget();
|
||||
void paintsWithTargetBox();
|
||||
void wireLabelStaysOutOfTheTerminal();
|
||||
void targetBoxKeepsTheDrawingFont();
|
||||
void typedTargetIsDrawnInItsOwnLines();
|
||||
void boxesOfTwoCablesAreSetApart();
|
||||
void realisticStyleDrawsTheCable();
|
||||
};
|
||||
|
||||
void tst_cabletarget::init()
|
||||
{
|
||||
const QString fixture_ = QFINDTESTDATA("fixtures/every_feature.qet");
|
||||
QVERIFY(!fixture_.isEmpty());
|
||||
m_project = new QETProject(fixture_);
|
||||
QCOMPARE(m_project->state(), QETProject::Ok);
|
||||
}
|
||||
|
||||
void tst_cabletarget::cleanup()
|
||||
{
|
||||
delete m_project;
|
||||
m_project = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
Nothing to name when no wire of the terminal belongs to a cable and
|
||||
no cable of the project is drawn under a name the terminal carries.
|
||||
*/
|
||||
void tst_cabletarget::noCableNoTarget()
|
||||
{
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(b_);
|
||||
QVERIFY(a_->cable().isEmpty());
|
||||
QVERIFY(a_->cableTarget().isEmpty());
|
||||
QVERIFY(b_->cableTarget().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
The cable of the strip lands on the contact past it: the three
|
||||
fields of that piece, in the order the cable list shows them
|
||||
(Installation, Location, Label), and nothing of the strip itself.
|
||||
*/
|
||||
void tst_cabletarget::targetIsTheOtherPiece()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
QVERIFY(a_);
|
||||
QCOMPARE(a_->cableTarget(),
|
||||
QVector<QString>({
|
||||
QStringLiteral("Plant 7"),
|
||||
QStringLiteral("Cabinet 7"),
|
||||
QStringLiteral("-S7"),
|
||||
}));
|
||||
|
||||
//The other strip terminal carries no claimed wire of its own
|
||||
//(584b452e joins it to a_ and is not claimed) and no cable name,
|
||||
//so it has no target to name
|
||||
const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(b_);
|
||||
QVERIFY(b_->cableTarget().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
A cable whose wires only ever join the terminals of the strip
|
||||
itself lands on nothing past the strip: no target, no box.
|
||||
*/
|
||||
void tst_cabletarget::ownStripIsNoTarget()
|
||||
{
|
||||
Cable *cable_ = m_project->newCable();
|
||||
QVERIFY(cable_);
|
||||
Conductor *inside_ = conductor(
|
||||
QStringLiteral("{584b452e-a885-491a-98f1-fd348610b5e0}"));
|
||||
QVERIFY(inside_);
|
||||
claimCable(inside_, cable_->uuid());
|
||||
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(b_);
|
||||
QVERIFY(a_->cableTarget().isEmpty());
|
||||
QVERIFY(b_->cableTarget().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
A spare terminal stands on no folio and is wired to nothing: it has
|
||||
no target to name either.
|
||||
*/
|
||||
void tst_cabletarget::spareHasNoTarget()
|
||||
{
|
||||
ElementData data_;
|
||||
data_.m_informations.addValue(QStringLiteral("label"),
|
||||
QStringLiteral("AAA"));
|
||||
const auto spare_ = RealTerminal::createSpare(data_);
|
||||
QVERIFY(spare_);
|
||||
QVERIFY(spare_->isSpare());
|
||||
QVERIFY(spare_->cableTarget().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
The strip graphic draws the box: the same strip painted with
|
||||
something to name at the end of the cable and painted with nothing
|
||||
to name come out as two different pictures -- the box is the
|
||||
difference. Painting it also runs the whole drawCables() path.
|
||||
*/
|
||||
void tst_cabletarget::paintsWithTargetBox()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
TerminalStripDrawer::TerminalStripDrawer drawer{
|
||||
QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}},
|
||||
QSharedPointer<TerminalStripLayoutPattern>{new TerminalStripLayoutPattern}};
|
||||
|
||||
const QRectF bounds{drawer.boundingRect()};
|
||||
QVERIFY(!bounds.isEmpty());
|
||||
|
||||
const auto render = [&drawer, &bounds]() {
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
drawer.paint(&painter);
|
||||
return image;
|
||||
};
|
||||
|
||||
const QImage with_box{render()};
|
||||
|
||||
//Nothing left to name at the far end of the cable
|
||||
Element *contact_ = element(
|
||||
QStringLiteral("{fc9cc920-5822-421a-916e-a17100acdcf6}"));
|
||||
QVERIFY(contact_);
|
||||
ElementData contact_data_{contact_->elementData()};
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_PLANT, QString());
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LOCATION, QString());
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LABEL, QString());
|
||||
contact_->setElementData(contact_data_);
|
||||
|
||||
const QImage without_box{render()};
|
||||
QVERIFY(with_box != without_box);
|
||||
}
|
||||
|
||||
/**
|
||||
The number of a wire is written next to its mark, in the room left
|
||||
between the mark and the terminal: it shrinks to fit there, and what
|
||||
still does not fit is clipped away, so a long number never runs into
|
||||
the drawing of the terminal. The two pictures -- one with a number
|
||||
long enough to reach into the terminal, one without any -- must be
|
||||
the same above the bottom of the terminal, and differ below it (the
|
||||
number is drawn somewhere).
|
||||
*/
|
||||
void tst_cabletarget::wireLabelStaysOutOfTheTerminal()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
auto pattern_ = QSharedPointer<TerminalStripLayoutPattern>{new TerminalStripLayoutPattern};
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
TerminalStripDrawer::TerminalStripDrawer drawer{
|
||||
QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}},
|
||||
pattern_};
|
||||
|
||||
const QRectF bounds{drawer.boundingRect()};
|
||||
QVERIFY(!bounds.isEmpty());
|
||||
|
||||
const auto render = [&drawer, &bounds]() {
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
drawer.paint(&painter);
|
||||
return image;
|
||||
};
|
||||
|
||||
//A number much longer than the room left over the mark
|
||||
Element *terminal_ = element(
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
QVERIFY(terminal_);
|
||||
ElementData terminal_data_{terminal_->elementData()};
|
||||
terminal_data_.m_informations.addValue(
|
||||
RealTerminal::cableWireInfoKey(),
|
||||
QStringLiteral("gnge-gnge-gnge-gnge-gnge"));
|
||||
terminal_->setElementData(terminal_data_);
|
||||
const QImage with_label{render()};
|
||||
|
||||
//The same strip, the wire carries no number at all
|
||||
terminal_data_.m_informations.addValue(RealTerminal::cableWireInfoKey(),
|
||||
QString());
|
||||
terminal_->setElementData(terminal_data_);
|
||||
const QImage without_label{render()};
|
||||
|
||||
//The number is drawn: the two pictures differ
|
||||
QVERIFY(with_label != without_label);
|
||||
|
||||
//The terminals of this strip all stand on the last level of the
|
||||
//pattern, so their bottom is the same rectangle
|
||||
const qreal terminal_bottom{pattern_->m_terminal_rect.at(3).bottom()};
|
||||
const auto rows_to_check{qMin(qRound(terminal_bottom - bounds.topLeft().y() + 2),
|
||||
with_label.height())};
|
||||
for (auto y = 0 ; y < rows_to_check ; ++y)
|
||||
{
|
||||
for (auto x = 0 ; x < with_label.width() ; ++x)
|
||||
{
|
||||
QCOMPARE(with_label.pixel(x, y), without_label.pixel(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The box at the end of a cable keeps the font of the drawing: with
|
||||
its three lines (Installation, Location and Label) and the end zone
|
||||
the settings leave, the text is still written at the font of the
|
||||
strip instead of being squeezed into a tiny one. The box is the
|
||||
lowest thing the strip draws, so its bottom line is the lowest
|
||||
painted row, and it must land where three lines of that font need
|
||||
it to.
|
||||
*/
|
||||
void tst_cabletarget::targetBoxKeepsTheDrawingFont()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
auto pattern_ = QSharedPointer<TerminalStripLayoutPattern>{new TerminalStripLayoutPattern};
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
TerminalStripDrawer::TerminalStripDrawer drawer{
|
||||
QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}},
|
||||
pattern_};
|
||||
|
||||
const QRectF bounds{drawer.boundingRect()};
|
||||
QVERIFY(!bounds.isEmpty());
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
drawer.paint(&painter);
|
||||
painter.end();
|
||||
|
||||
qreal lowest{-1};
|
||||
for (auto y = 0 ; y < image.height() ; ++y)
|
||||
{
|
||||
for (auto x = 0 ; x < image.width() ; ++x)
|
||||
{
|
||||
if (image.pixel(x, y) != qRgba(0, 0, 0, 0)) {
|
||||
lowest = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
QVERIFY(lowest >= 0);
|
||||
|
||||
//The end zone the default layout leaves is what gives the box its
|
||||
//room: three lines of the default font must fit in it, or the text
|
||||
//of a target is squeezed down again by the drawing itself
|
||||
const QFontMetricsF metrics{pattern_->font()};
|
||||
const qreal room{pattern_->m_cable_end_length
|
||||
- qMin<qreal>(6, pattern_->m_cable_end_length)};
|
||||
QVERIFY2(metrics.height() * 3 + 4 <= room,
|
||||
"the default end zone leaves no room for three lines of the "
|
||||
"default font, a target would be squeezed again");
|
||||
|
||||
//Where the bottom line of the box lands when it holds the three
|
||||
//lines of its text at the font of the drawing
|
||||
const qreal terminals_bottom{pattern_->m_terminal_rect.at(3).bottom()};
|
||||
const qreal bar_2{terminals_bottom + pattern_->m_cable_wire_length
|
||||
+ pattern_->m_cable_length};
|
||||
const qreal box_bottom{bar_2 + qMin<qreal>(6, pattern_->m_cable_end_length)
|
||||
+ metrics.height() * 3 + 4};
|
||||
|
||||
//The strip draws nothing under the box, so the lowest painted row
|
||||
//is its bottom line -- within a pixel, the outline of a rectangle
|
||||
//is painted on its edge
|
||||
const qreal row{box_bottom - bounds.top() + 2};
|
||||
QVERIFY(lowest + 2 >= row);
|
||||
}
|
||||
|
||||
/**
|
||||
The realistic style draws the cable too. The strip seen from above
|
||||
reserves the zone under it when one of its terminals carries a
|
||||
cable, and the wires, the cable and the box naming where it goes
|
||||
are painted in it -- exactly what the schematic style draws, since
|
||||
both styles now share the same Helpers::drawCables().
|
||||
*/
|
||||
void tst_cabletarget::realisticStyleDrawsTheCable()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
const auto strip_ = QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}};
|
||||
TerminalStripLayoutPattern pattern_;
|
||||
|
||||
const auto bounds_of = [&strip_, &pattern_]() {
|
||||
return TerminalStripDrawer::Realistic::boundingRect(strip_.data(),
|
||||
pattern_);
|
||||
};
|
||||
const auto render = [&strip_, &pattern_](const QRectF &bounds) {
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
TerminalStripDrawer::Realistic::paint(
|
||||
&painter, *strip_, pattern_,
|
||||
TerminalStripLayoutPattern::DrawStyle::RealisticPushIn);
|
||||
return image;
|
||||
};
|
||||
|
||||
const QRectF with_bounds{bounds_of()};
|
||||
const QImage with_cable{render(with_bounds)};
|
||||
|
||||
//The body of the strip ends there : everything painted under it
|
||||
//belongs to the cable
|
||||
const qreal body_bottom{
|
||||
pattern_.m_realistic_length_mm * pattern_.m_realistic_px_per_mm};
|
||||
QVERIFY(with_bounds.height() > body_bottom);
|
||||
|
||||
int painted_{0};
|
||||
for (auto y = qRound(body_bottom) + 4 ; y + 4 < with_cable.height() ; ++y)
|
||||
{
|
||||
for (auto x = 0 ; x + 4 < with_cable.width() ; ++x)
|
||||
{
|
||||
if (with_cable.pixel(x, y) != qRgba(0, 0, 0, 0)) {
|
||||
++painted_;
|
||||
}
|
||||
}
|
||||
}
|
||||
//The wires and the cable are drawn in that zone
|
||||
QVERIFY(painted_ > 0);
|
||||
|
||||
//The same strip, its terminal carries no cable name any more
|
||||
Element *terminal_ = element(
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
QVERIFY(terminal_);
|
||||
ElementData terminal_data_{terminal_->elementData()};
|
||||
terminal_data_.m_informations.addValue(RealTerminal::cableInfoKey(),
|
||||
QString());
|
||||
terminal_->setElementData(terminal_data_);
|
||||
|
||||
//No cable to let out : the strip does not reserve the zone
|
||||
const QRectF without_bounds{bounds_of()};
|
||||
QVERIFY(without_bounds.height() < with_bounds.height());
|
||||
}
|
||||
|
||||
/**
|
||||
A target 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, with no separator in it. The picture of a strip
|
||||
whose cable ends on three fields is therefore that much deeper than
|
||||
the picture of the same strip with one single field -- two lines of
|
||||
the font of the drawing. A "/" written inside a field, an
|
||||
installation named "Betrieb 1/2", keeps its line: the box is no
|
||||
deeper than with one plain field.
|
||||
*/
|
||||
void tst_cabletarget::typedTargetIsDrawnInItsOwnLines()
|
||||
{
|
||||
//The cable lands on a piece which names nothing, so there is no
|
||||
//target of the drawing and the box takes what is typed in
|
||||
QVERIFY(wireCableToTheContact());
|
||||
Element *contact_ = element(
|
||||
QStringLiteral("{fc9cc920-5822-421a-916e-a17100acdcf6}"));
|
||||
QVERIFY(contact_);
|
||||
ElementData contact_data_{contact_->elementData()};
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_PLANT, QString());
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LOCATION, QString());
|
||||
contact_data_.m_informations.addValue(QETInformation::ELMT_LABEL, QString());
|
||||
contact_->setElementData(contact_data_);
|
||||
|
||||
const auto a_ = terminalInStrip(
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(a_->cableTarget().isEmpty());
|
||||
Element *terminal_ = a_->element();
|
||||
QVERIFY(terminal_);
|
||||
|
||||
auto pattern_ = QSharedPointer<TerminalStripLayoutPattern>{new TerminalStripLayoutPattern};
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
TerminalStripDrawer::TerminalStripDrawer drawer{
|
||||
QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}},
|
||||
pattern_};
|
||||
|
||||
const QRectF bounds{drawer.boundingRect()};
|
||||
QVERIFY(!bounds.isEmpty());
|
||||
|
||||
const auto render = [&drawer, &bounds]() {
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
drawer.paint(&painter);
|
||||
painter.end();
|
||||
return image;
|
||||
};
|
||||
const auto lowest_row = [](const QImage &image_) {
|
||||
qreal lowest{-1};
|
||||
for (auto y = 0 ; y < image_.height() ; ++y)
|
||||
{
|
||||
for (auto x = 0 ; x < image_.width() ; ++x)
|
||||
{
|
||||
if (image_.pixel(x, y) != qRgba(0, 0, 0, 0)) {
|
||||
lowest = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return lowest;
|
||||
};
|
||||
const auto type_in = [terminal_](const QString &text_) {
|
||||
ElementData data_{terminal_->elementData()};
|
||||
data_.m_informations.addValue(RealTerminal::cableTargetInfoKey(), text_);
|
||||
terminal_->setElementData(data_);
|
||||
};
|
||||
|
||||
//One field: the box holds one single line
|
||||
type_in(QStringLiteral("Boiler room"));
|
||||
const qreal one_line{lowest_row(render())};
|
||||
QVERIFY(one_line >= 0);
|
||||
|
||||
//Three fields: the box is two lines deeper, at the font of the
|
||||
//drawing -- the fields are written one under the other, the cell
|
||||
//holding them only tells them apart by their line breaks
|
||||
type_in(QStringLiteral("Boiler room\nCabinet 3\n-K3"));
|
||||
QCOMPARE(a_->cableTarget(), (QVector<QString>{QStringLiteral("Boiler room"),
|
||||
QStringLiteral("Cabinet 3"),
|
||||
QStringLiteral("-K3")}));
|
||||
const qreal three_lines{lowest_row(render())};
|
||||
const QFontMetricsF metrics{pattern_->font()};
|
||||
QVERIFY2(three_lines - one_line > 1.5 * metrics.height(),
|
||||
"three fields must give a box two lines deeper than one field");
|
||||
|
||||
//A slash written inside a field is part of it: the text gives
|
||||
//one single line, the box is back to the depth of one field and
|
||||
//holds the slash where it was typed
|
||||
type_in(QStringLiteral("Betrieb 1/2 / Keller"));
|
||||
QCOMPARE(a_->cableTarget(),
|
||||
QVector<QString>{QStringLiteral("Betrieb 1/2 / Keller")});
|
||||
const qreal one_slashed_field{lowest_row(render())};
|
||||
QVERIFY2(qAbs(one_slashed_field - one_line) < metrics.height() / 2,
|
||||
"a slash inside a field must not split it into lines");
|
||||
}
|
||||
|
||||
/**
|
||||
Two cables of one strip end close to each other, and their boxes are
|
||||
wider than the room between the two cables: the box standing in the
|
||||
way of the other is set lower than it, its stub drawn longer -- the
|
||||
two boxes are never written the one over the other, and the text is
|
||||
never cut down to fit. The strip reserves the room for the lower box
|
||||
too, or the drawing would be cut off there.
|
||||
*/
|
||||
void tst_cabletarget::boxesOfTwoCablesAreSetApart()
|
||||
{
|
||||
QVERIFY(wireCableToTheContact());
|
||||
|
||||
//The second cable of the strip : its wires only join the strip's
|
||||
//own terminals, so it names nothing and takes what is typed in
|
||||
Cable *cable_ = m_project->newCable();
|
||||
QVERIFY(cable_);
|
||||
Conductor *inside_ = conductor(
|
||||
QStringLiteral("{584b452e-a885-491a-98f1-fd348610b5e0}"));
|
||||
QVERIFY(inside_);
|
||||
claimCable(inside_, cable_->uuid());
|
||||
|
||||
const auto b_ = terminalInStrip(
|
||||
QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(b_);
|
||||
Element *second_ = b_->element();
|
||||
QVERIFY(second_);
|
||||
|
||||
auto pattern_ = QSharedPointer<TerminalStripLayoutPattern>{new TerminalStripLayoutPattern};
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
QVERIFY(!strips_.isEmpty());
|
||||
TerminalStripDrawer::TerminalStripDrawer drawer{
|
||||
QSharedPointer<TerminalStripDrawer::TrueTerminalStrip>{
|
||||
new TerminalStripDrawer::TrueTerminalStrip{strips_.first()}},
|
||||
pattern_};
|
||||
|
||||
const auto render = [&drawer](const QRectF &bounds) {
|
||||
QImage image{bounds.size().toSize() + QSize{4, 4},
|
||||
QImage::Format_ARGB32_Premultiplied};
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter{&image};
|
||||
painter.translate(-bounds.topLeft() + QPointF{2, 2});
|
||||
drawer.paint(&painter);
|
||||
painter.end();
|
||||
return image;
|
||||
};
|
||||
const auto lowest_row = [](const QImage &image_) {
|
||||
qreal lowest{-1};
|
||||
for (auto y = 0 ; y < image_.height() ; ++y)
|
||||
{
|
||||
for (auto x = 0 ; x < image_.width() ; ++x)
|
||||
{
|
||||
if (image_.pixel(x, y) != qRgba(0, 0, 0, 0)) {
|
||||
lowest = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return lowest;
|
||||
};
|
||||
|
||||
//One cable with a target : one box, in the end zone
|
||||
const QRectF one_bounds{drawer.boundingRect()};
|
||||
const qreal one_row{lowest_row(render(one_bounds))};
|
||||
QVERIFY(one_row >= 0);
|
||||
|
||||
//The second cable gets a target of its own : its box is wider
|
||||
//than the distance between the two cables and would be written
|
||||
//over the first one
|
||||
ElementData second_data_{second_->elementData()};
|
||||
second_data_.m_informations.addValue(RealTerminal::cableInfoKey(),
|
||||
QStringLiteral("X98"));
|
||||
second_data_.m_informations.addValue(RealTerminal::cableTargetInfoKey(),
|
||||
QStringLiteral("Plant 9\nCabinet 9\n-S9"));
|
||||
second_->setElementData(second_data_);
|
||||
QCOMPARE(b_->cableTarget(),
|
||||
(QVector<QString>{QStringLiteral("Plant 9"),
|
||||
QStringLiteral("Cabinet 9"),
|
||||
QStringLiteral("-S9")}));
|
||||
|
||||
const QRectF two_bounds{drawer.boundingRect()};
|
||||
const qreal two_row{lowest_row(render(two_bounds))};
|
||||
|
||||
//The box in the way is set lower : the strip draws a whole box
|
||||
//deeper than it did with one single box -- the second box stands
|
||||
//under the first one, one box height plus the gap between them
|
||||
const QFontMetricsF metrics{pattern_->font()};
|
||||
const qreal box_height{metrics.height() * 3 + 4};
|
||||
QVERIFY2(two_row - one_row > box_height,
|
||||
"a box reaching into another one must be set lower, not "
|
||||
"written over it");
|
||||
|
||||
//And the strip is deep enough for it, or the lower box would be
|
||||
//cut off by the drawing area
|
||||
QVERIFY2(two_bounds.height() > one_bounds.height(),
|
||||
"the strip reserves no room for the box it sets lower");
|
||||
QVERIFY2(two_bounds.bottom() - one_bounds.bottom() > 0.5 * box_height,
|
||||
"the room reserved for the lower box is too small");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
return QetAppTest::run<tst_cabletarget>(argc, argv);
|
||||
}
|
||||
|
||||
#include "tst_cabletarget.moc"
|
||||
@@ -0,0 +1,722 @@
|
||||
// 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"
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
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/UndoCommand/sortterminalstripcommand.h"
|
||||
#include "TerminalStrip/physicalterminal.h"
|
||||
#include "TerminalStrip/realterminal.h"
|
||||
#include "TerminalStrip/terminalstrip.h"
|
||||
#include "diagram.h"
|
||||
#include "properties/elementdata.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetproject.h"
|
||||
#include "utils/qetutils.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
/**
|
||||
The terminal strip sorts into the plan's own order -- folio by folio,
|
||||
then left to right -- instead of by label, so "X1:N" and "X1:PE"
|
||||
stand where they are drawn rather than at the end of the strip. The
|
||||
auto import and SortTerminalStripCommand share the one comparator
|
||||
(SortTerminalStripCommand::planOrderLessThan), so the order the
|
||||
import works out cannot be rewritten by the sort it then pushes for
|
||||
the strip it just created.
|
||||
|
||||
fixtures/every_feature.qet has the strip X1 with element 891b70f2
|
||||
(x=300) left of 4861e268 (x=360) on the first folio, and 4861e268
|
||||
holds an empty label where 891b70f2 has a non-empty one: a label
|
||||
sort would put 4861e268 first, which is the very swap this test
|
||||
pins down.
|
||||
*/
|
||||
class tst_terminalstriporder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QETProject *m_project = nullptr;
|
||||
|
||||
Element *element(const QString &uuid) const
|
||||
{
|
||||
for (Diagram *d_ : m_project->diagrams()) {
|
||||
for (Element *e_ : d_->elements()) {
|
||||
if (e_->uuid().toString() == uuid) {
|
||||
return e_;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TerminalStrip *strip() const
|
||||
{
|
||||
const auto strips_ = m_project->terminalStrip();
|
||||
return strips_.isEmpty() ? nullptr : strips_.first();
|
||||
}
|
||||
|
||||
QSharedPointer<RealTerminal> terminalInStrip(const QString &element_uuid) const
|
||||
{
|
||||
auto *strip_ = strip();
|
||||
if (!strip_) {
|
||||
return {};
|
||||
}
|
||||
for (const auto &real_ : strip_->realTerminals()) {
|
||||
if (real_ && real_->element()
|
||||
&& real_->element()->uuid().toString() == element_uuid) {
|
||||
return real_;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// The element uuids of the strip's physical positions, in strip order.
|
||||
QStringList physicalOrder() const
|
||||
{
|
||||
QStringList order_;
|
||||
auto *strip_ = strip();
|
||||
if (!strip_) {
|
||||
return order_;
|
||||
}
|
||||
for (const auto &physical_ : strip_->physicalTerminal()) {
|
||||
if (physical_ && physical_->realTerminalCount()
|
||||
&& physical_->realTerminals().first()->element()) {
|
||||
order_ << physical_->realTerminals().first()->element()->uuid().toString();
|
||||
}
|
||||
}
|
||||
return order_;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void init();
|
||||
void cleanup();
|
||||
|
||||
void positionBeatsLabel();
|
||||
void folioComesBeforePosition();
|
||||
void spareWithoutFolioGoesLast();
|
||||
void sortKeepsThePlanOrder();
|
||||
void sortFollowsMovedElements();
|
||||
};
|
||||
|
||||
void tst_terminalstriporder::init()
|
||||
{
|
||||
const QString fixture_ = QFINDTESTDATA("fixtures/every_feature.qet");
|
||||
QVERIFY(!fixture_.isEmpty());
|
||||
m_project = new QETProject(fixture_);
|
||||
QCOMPARE(m_project->state(), QETProject::Ok);
|
||||
}
|
||||
|
||||
void tst_terminalstriporder::cleanup()
|
||||
{
|
||||
delete m_project;
|
||||
m_project = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
Two terminals of one folio, 891b70f2 left of 4861e268: the position
|
||||
decides, and the label says the opposite (4861e268's is empty), so a
|
||||
comparator by label would order them the wrong way round.
|
||||
*/
|
||||
void tst_terminalstriporder::positionBeatsLabel()
|
||||
{
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(b_);
|
||||
QCOMPARE(a_->element()->pos().x() < b_->element()->pos().x(), true);
|
||||
|
||||
QVERIFY(SortTerminalStripCommand::planOrderLessThan(a_, b_));
|
||||
QVERIFY(!SortTerminalStripCommand::planOrderLessThan(b_, a_));
|
||||
|
||||
//The labels would sort the other way round (b's is empty), which
|
||||
//is exactly the order the plan must NOT follow
|
||||
QVERIFY(QETUtils::naturalLessThan(b_->label(), a_->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
The page comes before the position ("page by page, then left to
|
||||
right"): a terminal moved to the second folio sorts after every
|
||||
terminal of the first one, even when it stands further left than
|
||||
them. The element is put back on its own folio before the test
|
||||
ends, so the project is left as it was.
|
||||
*/
|
||||
void tst_terminalstriporder::folioComesBeforePosition()
|
||||
{
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
const auto b_ = terminalInStrip(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(b_);
|
||||
Element *moved_ = a_->element();
|
||||
QVERIFY(moved_);
|
||||
const QList<Diagram *> folios_ = m_project->diagrams();
|
||||
QVERIFY(folios_.size() > 1);
|
||||
Diagram *own_folio_ = moved_->diagram();
|
||||
Diagram *later_folio_ = folios_.at(1);
|
||||
QVERIFY(later_folio_ != own_folio_);
|
||||
const QPointF original_pos_ = moved_->pos();
|
||||
|
||||
//Put a on the second folio, left of where b stands on the first
|
||||
own_folio_->removeItem(moved_);
|
||||
later_folio_->addItem(moved_);
|
||||
moved_->setPos(QPointF(50, 600));
|
||||
QVERIFY(moved_->diagram() == later_folio_);
|
||||
|
||||
const bool folio_later_ = later_folio_->folioIndex() > own_folio_->folioIndex();
|
||||
const bool a_left_of_b_ = moved_->pos().x() < b_->element()->pos().x();
|
||||
const bool b_first_ = SortTerminalStripCommand::planOrderLessThan(b_, a_);
|
||||
const bool a_first_ = SortTerminalStripCommand::planOrderLessThan(a_, b_);
|
||||
|
||||
//Back to where it was, so the conductors drawn to it and the
|
||||
//project's cleanup see the folio they were loaded from
|
||||
later_folio_->removeItem(moved_);
|
||||
own_folio_->addItem(moved_);
|
||||
moved_->setPos(original_pos_);
|
||||
QVERIFY(moved_->diagram() == own_folio_);
|
||||
|
||||
QVERIFY(folio_later_);
|
||||
QVERIFY(a_left_of_b_); //the position alone would put a first
|
||||
QVERIFY(b_first_); //the page decides: b (first folio) first
|
||||
QVERIFY(!a_first_);
|
||||
}
|
||||
|
||||
/**
|
||||
A spare terminal stands on no folio: it has no place in the plan
|
||||
order and goes last, whatever its label would say.
|
||||
*/
|
||||
void tst_terminalstriporder::spareWithoutFolioGoesLast()
|
||||
{
|
||||
ElementData data_;
|
||||
data_.m_informations.addValue(QStringLiteral("label"),
|
||||
QStringLiteral("AAA"));
|
||||
const auto spare_ = RealTerminal::createSpare(data_);
|
||||
QVERIFY(spare_);
|
||||
QVERIFY(spare_->isSpare());
|
||||
|
||||
const auto a_ = terminalInStrip(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
QVERIFY(a_);
|
||||
//"AAA" would sort first by label alone
|
||||
QVERIFY(QETUtils::naturalLessThan(spare_->label(), a_->label()));
|
||||
|
||||
QVERIFY(SortTerminalStripCommand::planOrderLessThan(a_, spare_));
|
||||
QVERIFY(!SortTerminalStripCommand::planOrderLessThan(spare_, a_));
|
||||
}
|
||||
|
||||
/**
|
||||
The sort command, run on the fixture as it stands, keeps the strip
|
||||
the way the plan is drawn: 891b70f2 (x=300) before 4861e268
|
||||
(x=360). The old label sort swapped them, since 4861e268's label is
|
||||
the empty one.
|
||||
*/
|
||||
void tst_terminalstriporder::sortKeepsThePlanOrder()
|
||||
{
|
||||
auto *strip_ = strip();
|
||||
QVERIFY(strip_);
|
||||
QCOMPARE(strip_->physicalTerminalCount(), 2);
|
||||
|
||||
SortTerminalStripCommand command_(strip_);
|
||||
command_.redo();
|
||||
|
||||
QCOMPARE(physicalOrder(),
|
||||
QStringList({
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"),
|
||||
QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
The order follows the plan, not the strip's history: once the two
|
||||
elements are swapped on the folio, sorting puts the strip the other
|
||||
way round too.
|
||||
*/
|
||||
void tst_terminalstriporder::sortFollowsMovedElements()
|
||||
{
|
||||
auto *strip_ = strip();
|
||||
QVERIFY(strip_);
|
||||
Element *a_ = element(QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"));
|
||||
Element *b_ = element(QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"));
|
||||
QVERIFY(a_);
|
||||
QVERIFY(b_);
|
||||
|
||||
b_->setPos(100, 400);
|
||||
a_->setPos(700, 400);
|
||||
|
||||
SortTerminalStripCommand command_(strip_);
|
||||
command_.redo();
|
||||
|
||||
QCOMPARE(physicalOrder(),
|
||||
QStringList({
|
||||
QStringLiteral("{4861e268-d344-413e-a86f-034ea5ada16c}"),
|
||||
QStringLiteral("{891b70f2-2822-41c3-9f94-28efb448b5c4}"),
|
||||
}));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
return QetAppTest::run<tst_terminalstriporder>(argc, argv);
|
||||
}
|
||||
|
||||
#include "tst_terminalstriporder.moc"
|
||||
Reference in New Issue
Block a user