mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-23 15:34:12 +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:
@@ -127,8 +127,11 @@ class TerminalStripLayoutPattern
|
||||
qreal m_cable_wire_length{40};
|
||||
//Length of the cable
|
||||
qreal m_cable_length{60};
|
||||
//Length of the wires after the cable
|
||||
qreal m_cable_end_length{40};
|
||||
//Length of the wires after the cable. It leaves the room the box
|
||||
//naming where the cable goes needs: three lines (Installation,
|
||||
//Location, Label) at the default font of 15 px, so the box does
|
||||
//not have to shrink its text down.
|
||||
qreal m_cable_end_length{80};
|
||||
qreal m_bridge_point_d{5};
|
||||
QVector<qreal> m_bridge_point_y_offset{50,70,90,110};
|
||||
|
||||
|
||||
@@ -522,6 +522,77 @@ struct BridgePoint
|
||||
qreal width{0};
|
||||
};
|
||||
|
||||
//True when at least one terminal of the strip carries a cable : the strip
|
||||
//then needs the zone drawn under it to let the cables out, like the
|
||||
//schematic style does.
|
||||
bool haveCable(const AbstractTerminalStripInterface *strip)
|
||||
{
|
||||
if (!strip) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &physical_t : strip->physicalTerminal())
|
||||
{
|
||||
for (const auto &real_t : physical_t->realTerminals())
|
||||
{
|
||||
if (real_t && !real_t->cable().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief cableCells
|
||||
* The cells the cables are drawn from : one per terminal of the strip
|
||||
* carrying a cable, spread over the width of its body, all of them
|
||||
* leaving the strip at its bottom edge. The paint and the depth the
|
||||
* strip reserves under it both work on this list, so a box set lower
|
||||
* than the others is never cut off by the drawing area.
|
||||
*/
|
||||
QVector<Helpers::CableCell> cableCells(const AbstractTerminalStripInterface &strip,
|
||||
const TerminalStripLayoutPattern &pattern)
|
||||
{
|
||||
const qreal s_{pattern.m_realistic_px_per_mm};
|
||||
const qreal length_{pattern.m_realistic_length_mm * s_};
|
||||
|
||||
//Left end stop and strip name : the terminals start after them
|
||||
qreal x_{endStopWidthMm(pattern) * s_};
|
||||
if (pattern.m_realistic_group_marker) {
|
||||
x_ += group_marker_width_mm * s_;
|
||||
}
|
||||
|
||||
QVector<Helpers::CableCell> cells;
|
||||
for (const auto &g_ : geometry(&strip))
|
||||
{
|
||||
const qreal w_{g_.width_mm * s_};
|
||||
const int levels_{static_cast<int>(g_.reals.size())};
|
||||
|
||||
//One wire exit by level, spread over the width of the body
|
||||
for (int k = 0 ; k < levels_ ; ++k)
|
||||
{
|
||||
const auto &real_t{g_.reals.at(k)};
|
||||
if (!real_t || real_t->cable().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
Helpers::CableCell cell_;
|
||||
cell_.x = x_ + w_ * (k + 0.5) / levels_;
|
||||
cell_.bottom = length_;
|
||||
cell_.cable = real_t->cable();
|
||||
cell_.wire = real_t->cableWire();
|
||||
cell_.shield = real_t->isShield();
|
||||
cell_.terminal = real_t;
|
||||
cells.append(cell_);
|
||||
}
|
||||
|
||||
x_ += w_;
|
||||
if (g_.separator_after) {
|
||||
x_ += separator_width_mm * s_;
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
} //End anonymous namespace
|
||||
|
||||
QRectF boundingRect(const AbstractTerminalStripInterface *strip,
|
||||
@@ -535,7 +606,25 @@ QRectF boundingRect(const AbstractTerminalStripInterface *strip,
|
||||
}
|
||||
}
|
||||
const qreal s_{pattern.m_realistic_px_per_mm};
|
||||
return QRectF{0, 0, width_mm_ * s_, pattern.m_realistic_length_mm * s_};
|
||||
const qreal length_{pattern.m_realistic_length_mm * s_};
|
||||
|
||||
//The cables are drawn under the strip, in the zone the schematic
|
||||
//style reserves for them too
|
||||
qreal extra_bottom_{0};
|
||||
if (pattern.m_cable_wire_length > 0 && haveCable(strip))
|
||||
{
|
||||
//4 is the diameter of the circle at the end of the wires.
|
||||
//A box standing in the way of another one is drawn lower than
|
||||
//the end zone, and the room for it is reserved too (same rule,
|
||||
//same cells as the drawing) or it is cut off
|
||||
extra_bottom_ = pattern.m_cable_wire_length
|
||||
+ std::max<qreal>(0, pattern.m_cable_length)
|
||||
+ std::max<qreal>(0, pattern.m_cable_end_length)
|
||||
+ 4
|
||||
+ Helpers::cableBoxesExtraDepth(cableCells(*strip, pattern),
|
||||
pattern);
|
||||
}
|
||||
return QRectF{0, 0, width_mm_ * s_, length_ + extra_bottom_};
|
||||
}
|
||||
|
||||
void paint(QPainter *painter,
|
||||
@@ -595,6 +684,11 @@ void paint(QPainter *painter,
|
||||
}
|
||||
|
||||
QHash<QUuid, QVector<BridgePoint>> bridges_;
|
||||
//The cells the cables are drawn from, worked out once for the whole
|
||||
//strip: the depth the strip reserves under it is built on the same
|
||||
//list (boundingRect()), so a box set lower than another one is
|
||||
//never cut off
|
||||
QVector<Helpers::CableCell> cable_cells_{cableCells(strip, pattern)};
|
||||
|
||||
for (const auto &g_ : geometry(&strip))
|
||||
{
|
||||
@@ -793,6 +887,18 @@ void paint(QPainter *painter,
|
||||
|
||||
draw_end_stop();
|
||||
|
||||
//The cables under the strip : the wires of a cable join it, and the
|
||||
//cable ends together in the box naming where it goes -- drawn exactly
|
||||
//like the schematic style draws them, so both styles tell the same thing
|
||||
if (!cable_cells_.isEmpty())
|
||||
{
|
||||
painter->save();
|
||||
painter->setFont(pattern.font());
|
||||
painter->setPen(QPen(Qt::black, 0.5));
|
||||
Helpers::drawCables(painter, cable_cells_, pattern);
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
|
||||
namespace TerminalStripDrawer {
|
||||
|
||||
//The cells of a cable drawing live with the helpers, so both drawing
|
||||
//styles build the same thing and pass it to Helpers::drawCables()
|
||||
using Helpers::CableCell;
|
||||
|
||||
/**
|
||||
* @brief TerminalStripDrawer::TerminalStripDrawer
|
||||
* @param strip
|
||||
@@ -260,24 +264,176 @@ void drawConnections(QPainter *painter, const QRectF &terminal_rect, qreal lengt
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The CableCell struct
|
||||
* What is needed to draw the cable under a terminal.
|
||||
*/
|
||||
struct CableCell
|
||||
One box at the end of a cable : which terminals the cable has and
|
||||
where the box goes. The boxes of a strip are all measured before any
|
||||
of them is drawn, so a box reaching into another one can be set
|
||||
lower than it instead of being written over it.
|
||||
*/
|
||||
struct TargetBox
|
||||
{
|
||||
qreal x{0}; ///< x of the middle of the terminal, in the strip coordinates
|
||||
qreal bottom{0}; ///< y of the bottom of the terminal
|
||||
QString cable; ///< name of the cable (hose)
|
||||
QString wire; ///< color / number of the wire
|
||||
bool shield{false}; ///< true if the terminal is the shield of the cable
|
||||
int first{-1}; ///<First terminal of the cable, in the cells
|
||||
QVector<QString> lines; ///<One line per field, empty when there is no box
|
||||
qreal bar_2_y{0}; ///<Cross bar the cable ends on
|
||||
qreal cable_x{0}; ///<Middle of the cable the box hangs on
|
||||
qreal min_x{0}; ///<Cross bar the box spans
|
||||
qreal max_x{0};
|
||||
qreal top{0}; ///<Top of the box, the end of the stub
|
||||
qreal width{0};
|
||||
qreal height{0};
|
||||
|
||||
qreal bottom() const { return top + height; }
|
||||
|
||||
///True when the two boxes would stand the one over the other, the
|
||||
///way two texts written on the same line reach into each other
|
||||
///when they are wider than the room between them
|
||||
bool reachesInto(const TargetBox &other) const {
|
||||
return cable_x - width / 2 < other.cable_x + other.width / 2
|
||||
&& cable_x + width / 2 > other.cable_x - other.width / 2
|
||||
&& top < other.bottom() && bottom() > other.top;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief boxFont
|
||||
* The font of a box naming where a cable goes : the font of the drawing,
|
||||
* shrunk only while the lines of the target do not fit the height left
|
||||
* for the box. The width never shrinks anything -- a line too long for
|
||||
* the cross bar gives a wider box, it never gives a smaller text.
|
||||
* @param room : the height left for the box
|
||||
* @return the font the lines are written with
|
||||
*/
|
||||
QFont boxFont(const QFont &font, const QVector<QString> &lines, qreal room)
|
||||
{
|
||||
QFont box_font{font};
|
||||
if (box_font.pixelSize() <= 0 && box_font.pointSizeF() > 0) {
|
||||
box_font.setPixelSize(qMax(4, qRound(box_font.pointSizeF() * 96.0 / 72.0)));
|
||||
}
|
||||
const auto fits_the_height = [&lines, room](const QFont &font_) {
|
||||
return QFontMetricsF{font_}.height() * lines.size() + 4 <= room;
|
||||
};
|
||||
while (!fits_the_height(box_font) && box_font.pixelSize() > 4) {
|
||||
box_font.setPixelSize(box_font.pixelSize() - 1);
|
||||
}
|
||||
return box_font;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief targetBoxes
|
||||
* Where the boxes naming where the cables go stand, and how big they
|
||||
* are. Every box is measured first -- the lines of the target at the
|
||||
* font of the drawing, a width of the widest line or of the cross bar
|
||||
* the cable ends on, whichever is wider -- and they are then placed
|
||||
* from the left to the right : a box which would reach into one
|
||||
* already placed is set just low enough to stand clear of it, its stub
|
||||
* drawn longer. Two cables of one strip therefore never write their
|
||||
* boxes the one over the other, and no text is ever cut down to fit.
|
||||
* @return one entry per cable which has something to name at its end
|
||||
*/
|
||||
QVector<TargetBox> targetBoxes(const QVector<CableCell> &cells,
|
||||
const TerminalStripLayoutPattern &pattern,
|
||||
const QFont &font)
|
||||
{
|
||||
QVector<TargetBox> boxes;
|
||||
const qreal wire_length{pattern.m_cable_wire_length};
|
||||
if (wire_length <= 0) {
|
||||
return boxes;
|
||||
}
|
||||
const qreal cable_length{qMax<qreal>(0, pattern.m_cable_length)};
|
||||
const qreal end_length{qMax<qreal>(0, pattern.m_cable_end_length)};
|
||||
const qreal stub{qMin<qreal>(6, end_length)};
|
||||
if (end_length - stub <= 4) {
|
||||
return boxes;
|
||||
}
|
||||
constexpr qreal box_gap{2};
|
||||
|
||||
auto first{0};
|
||||
while (first < cells.size())
|
||||
{
|
||||
if (cells.at(first).cable.isEmpty()) {
|
||||
++first;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Find the last terminal of the cable, the same groups the
|
||||
//drawing makes
|
||||
auto last{first};
|
||||
while (last + 1 < cells.size() &&
|
||||
cells.at(last + 1).cable == cells.at(first).cable) {
|
||||
++last;
|
||||
}
|
||||
|
||||
QVector<int> wires, shields;
|
||||
qreal max_bottom{cells.at(first).bottom};
|
||||
for (auto i = first ; i <= last ; ++i)
|
||||
{
|
||||
max_bottom = qMax(max_bottom, cells.at(i).bottom);
|
||||
if (cells.at(i).shield) {
|
||||
shields << i;
|
||||
} else {
|
||||
wires << i;
|
||||
}
|
||||
}
|
||||
//A cable with only a shield is drawn like a cable with only one wire
|
||||
if (wires.isEmpty()) {
|
||||
wires = shields;
|
||||
}
|
||||
|
||||
const auto &first_wire{cells.at(wires.first())};
|
||||
TargetBox box;
|
||||
box.first = first;
|
||||
box.bar_2_y = max_bottom + wire_length + cable_length;
|
||||
box.min_x = first_wire.x;
|
||||
box.max_x = cells.at(wires.last()).x;
|
||||
box.cable_x = (box.min_x + box.max_x) / 2;
|
||||
box.lines = first_wire.terminal ? first_wire.terminal->cableTarget()
|
||||
: QVector<QString>();
|
||||
|
||||
if (!box.lines.isEmpty())
|
||||
{
|
||||
const QFontMetricsF metrics{boxFont(font, box.lines, end_length - stub)};
|
||||
box.width = qMax<qreal>(box.max_x - box.min_x, 16);
|
||||
for (const QString &line_ : std::as_const(box.lines)) {
|
||||
box.width = qMax(box.width, metrics.horizontalAdvance(line_) + 6);
|
||||
}
|
||||
box.height = metrics.height() * box.lines.size() + 4;
|
||||
box.top = box.bar_2_y + stub;
|
||||
boxes << box;
|
||||
}
|
||||
|
||||
first = last + 1;
|
||||
}
|
||||
|
||||
//Place them : a box reaching into one already placed goes just
|
||||
//under it, and the boxes set aside settle down again with it
|
||||
for (auto i = 0 ; i < boxes.size() ; ++i)
|
||||
{
|
||||
bool settled{false};
|
||||
for (auto guard = 0 ; !settled && guard < boxes.size() + 2 ; ++guard)
|
||||
{
|
||||
settled = true;
|
||||
for (auto j = 0 ; j < i ; ++j)
|
||||
{
|
||||
if (boxes.at(i).reachesInto(boxes.at(j))
|
||||
&& boxes.at(i).top < boxes.at(j).bottom() + box_gap)
|
||||
{
|
||||
boxes[i].top = boxes.at(j).bottom() + box_gap;
|
||||
settled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return boxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief drawCables
|
||||
* Draw the cables under the terminals. Consecutive terminals who have the same cable name
|
||||
* share the same cable: the wires go down from the terminals, join in the cable,
|
||||
* and leave the cable with the same wire marks. The shield is linked to a dashed ellipse
|
||||
* drawn around the cable.
|
||||
* share the same cable: the wires go down from the terminals and join in the cable.
|
||||
* The cable ends together -- a short stub, then a box naming where the cable goes
|
||||
* (Installation, Location and Label of the piece it lands on). It does not fan out
|
||||
* again after the cable: the wires drawn there suggested each core went its own way
|
||||
* towards the target, which said nothing about where that target is.
|
||||
* The shield is linked to a dashed ellipse drawn around the cable.
|
||||
* @param painter : the pen and the font must be set
|
||||
* @param cells : one cell per terminal, in the drawing order
|
||||
* @param pattern
|
||||
@@ -295,24 +451,59 @@ void drawCables(QPainter *painter, const QVector<CableCell> &cells, const Termin
|
||||
const qreal text_height{font_metrics.height()};
|
||||
|
||||
//Draw a mark (small oblique line) on a wire and the number of the wire next to it.
|
||||
//The text is written from the bottom to the top, at the left of the wire.
|
||||
const auto draw_wire_mark = [&](qreal x, qreal mark_y, const QString &text, bool text_above)
|
||||
//The text is written from the bottom to the top, at the left of the wire, starting
|
||||
//above the mark and running towards the terminal: the font shrinks while the text
|
||||
//is longer than the room left between the mark and the terminal, and what still
|
||||
//does not fit is clipped away, so a long number never runs into the drawing of
|
||||
//the terminal. The room left is @a terminal_bottom the bottom of that terminal.
|
||||
const auto draw_wire_mark = [&](qreal x, qreal mark_y, qreal terminal_bottom, const QString &text)
|
||||
{
|
||||
painter->drawLine(QPointF{x - 3, mark_y + 3}, QPointF{x + 3, mark_y - 3});
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qreal text_width{font_metrics.horizontalAdvance(text)};
|
||||
//Room left between the mark and the terminal
|
||||
const qreal room{mark_y - 4 - (terminal_bottom + 2)};
|
||||
if (room <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFont text_font{painter->font()};
|
||||
if (text_font.pixelSize() <= 0 && text_font.pointSizeF() > 0) {
|
||||
text_font.setPixelSize(qMax(4, qRound(text_font.pointSizeF() * 96.0 / 72.0)));
|
||||
}
|
||||
while (QFontMetricsF{text_font}.horizontalAdvance(text) > room &&
|
||||
text_font.pixelSize() > 4) {
|
||||
text_font.setPixelSize(text_font.pixelSize() - 1);
|
||||
}
|
||||
|
||||
const QFontMetricsF fitted_metrics{text_font};
|
||||
const qreal fitted_width{fitted_metrics.horizontalAdvance(text)};
|
||||
const qreal fitted_height{fitted_metrics.height()};
|
||||
painter->save();
|
||||
painter->translate(x - 1, text_above ? mark_y - 4 : mark_y + 4 + text_width);
|
||||
painter->setFont(text_font);
|
||||
//The clip keeps the text under the terminal even when a number is
|
||||
//so long that the smallest font does not fit it any more
|
||||
painter->setClipRect(QRectF{x - 1 - fitted_height, terminal_bottom + 2,
|
||||
fitted_height + 1, room});
|
||||
painter->translate(x - 1, mark_y - 4);
|
||||
painter->rotate(270);
|
||||
painter->drawText(QRectF{0, -text_height, text_width + 2, text_height},
|
||||
painter->drawText(QRectF{0, -fitted_height, fitted_width + 2, fitted_height},
|
||||
Qt::AlignLeft | Qt::AlignVCenter,
|
||||
text);
|
||||
painter->restore();
|
||||
};
|
||||
|
||||
//Where every box stands, worked out for the whole strip before
|
||||
//any of them is drawn: a box reaching into another one is set
|
||||
//lower than it, never written over it
|
||||
QHash<int, TargetBox> box_of;
|
||||
const auto boxes{targetBoxes(cells, pattern, painter->font())};
|
||||
for (const TargetBox &box_ : boxes) {
|
||||
box_of.insert(box_.first, box_);
|
||||
}
|
||||
|
||||
auto first{0};
|
||||
while (first < cells.size())
|
||||
{
|
||||
@@ -350,25 +541,61 @@ void drawCables(QPainter *painter, const QVector<CableCell> &cells, const Termin
|
||||
const qreal cable_x{(min_x + max_x) / 2};
|
||||
const qreal bar_1_y{max_bottom + wire_length};
|
||||
const qreal bar_2_y{bar_1_y + cable_length};
|
||||
const qreal end_y{bar_2_y + end_length};
|
||||
|
||||
//Wires between the terminals and the cable
|
||||
for (const auto &wire : std::as_const(wires))
|
||||
{
|
||||
painter->drawLine(QPointF{wire.x, wire.bottom}, QPointF{wire.x, bar_1_y});
|
||||
draw_wire_mark(wire.x, qMax(wire.bottom + 4, bar_1_y - 10), wire.wire, true);
|
||||
draw_wire_mark(wire.x, qMax(wire.bottom + 4, bar_1_y - 10), wire.bottom, wire.wire);
|
||||
}
|
||||
painter->drawLine(QPointF{min_x, bar_1_y}, QPointF{max_x, bar_1_y});
|
||||
|
||||
//The cable
|
||||
painter->drawLine(QPointF{cable_x, bar_1_y}, QPointF{cable_x, bar_2_y});
|
||||
|
||||
//Wires after the cable
|
||||
//The bundle stays together at the end: the cross bar, a short
|
||||
//stub, and a box naming where the cable goes -- the Installation,
|
||||
//Location and Label (BMK) of the piece it lands on, the same
|
||||
//three fields the cable list shows. The box spans the cross bar
|
||||
//and never shrinks its text down to fit: when its lines are
|
||||
//longer than the cross bar, the box is the one which grows. A
|
||||
//box standing in the way of another one is set lower instead,
|
||||
//its stub drawn longer, so no box is ever written over another.
|
||||
painter->drawLine(QPointF{min_x, bar_2_y}, QPointF{max_x, bar_2_y});
|
||||
for (const auto &wire : std::as_const(wires))
|
||||
const qreal stub{qMin<qreal>(6, end_length)};
|
||||
const TargetBox box{box_of.value(first)};
|
||||
if (box.lines.isEmpty())
|
||||
{
|
||||
painter->drawLine(QPointF{wire.x, bar_2_y}, QPointF{wire.x, end_y});
|
||||
draw_wire_mark(wire.x, qMin(bar_2_y + 10, end_y), wire.wire, false);
|
||||
painter->drawLine(QPointF{cable_x, bar_2_y},
|
||||
QPointF{cable_x, bar_2_y + stub});
|
||||
}
|
||||
else
|
||||
{
|
||||
//The stub goes down to the box, which stands lower than the
|
||||
//end zone when another box of this strip was in its way
|
||||
painter->drawLine(QPointF{cable_x, bar_2_y},
|
||||
QPointF{cable_x, box.top});
|
||||
|
||||
//The font of the drawing, shrunk only while the lines do not
|
||||
//fit the height the settings leave for the box
|
||||
const QFont box_font{boxFont(painter->font(), box.lines,
|
||||
end_length - stub)};
|
||||
const QFontMetricsF box_metrics{box_font};
|
||||
const QRectF box_rect{cable_x - box.width / 2, box.top,
|
||||
box.width, box.height};
|
||||
|
||||
painter->save();
|
||||
painter->setFont(box_font);
|
||||
painter->drawRect(box_rect);
|
||||
for (auto i = 0 ; i < box.lines.size() ; ++i)
|
||||
{
|
||||
painter->drawText(QRectF{box_rect.left() + 3,
|
||||
box_rect.top() + 2 + i * box_metrics.height(),
|
||||
box_rect.width() - 6, box_metrics.height()},
|
||||
Qt::AlignCenter,
|
||||
box.lines.at(i));
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
//The shield, linked to an ellipse around the cable
|
||||
@@ -439,6 +666,53 @@ QRectF terminalRect(const TerminalStripLayoutPattern &pattern,
|
||||
return rect_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief cableCells
|
||||
* The cells the cables are drawn from : one per real terminal of the
|
||||
* strip, in the order they stand. The paint loop and the depth the
|
||||
* strip reserves under its terminals both work on this list, so a box
|
||||
* set lower than the others is never cut off by the drawing area.
|
||||
* @param strip
|
||||
* @param pattern
|
||||
* @return the cells to hand over to Helpers::drawCables()
|
||||
*/
|
||||
QVector<CableCell> cableCells(const AbstractTerminalStripInterface &strip,
|
||||
const TerminalStripLayoutPattern &pattern)
|
||||
{
|
||||
QVector<CableCell> cells;
|
||||
qreal x_offset{pattern.m_header_rect.width() + pattern.m_spacer_rect.width()};
|
||||
for (const auto &physical_t : strip.physicalTerminal())
|
||||
{
|
||||
const QVector<QSharedPointer<AbstractRealTerminalInterface>> real_terminal_vector{physical_t->realTerminals()};
|
||||
const auto real_t_count{real_terminal_vector.size()};
|
||||
const auto offset_{4 - real_t_count};
|
||||
|
||||
for (auto i=0 ; i<real_t_count ; ++i)
|
||||
{
|
||||
const auto index_ = offset_ + i;
|
||||
if (index_ >= 4) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto terminal_rect{terminalRect(pattern, index_,
|
||||
real_terminal_vector[i])};
|
||||
CableCell cell;
|
||||
cell.x = x_offset + terminal_rect.width()/2;
|
||||
cell.bottom = terminal_rect.y() + terminal_rect.height();
|
||||
if (real_terminal_vector[i])
|
||||
{
|
||||
cell.cable = real_terminal_vector[i]->cable();
|
||||
cell.wire = real_terminal_vector[i]->cableWire();
|
||||
cell.shield = real_terminal_vector[i]->isShield();
|
||||
cell.terminal = real_terminal_vector[i];
|
||||
}
|
||||
cells.append(cell);
|
||||
x_offset += terminal_rect.width();
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The TerminalColors struct
|
||||
* Colors used to fill the rectangle of a terminal.
|
||||
@@ -865,6 +1139,27 @@ void Helpers::drawTypeSymbol(QPainter *painter, const QRectF &box,
|
||||
::TerminalStripDrawer::drawTypeSymbol(painter, box, type, led);
|
||||
}
|
||||
|
||||
void Helpers::drawCables(QPainter *painter, const QVector<CableCell> &cells,
|
||||
const TerminalStripLayoutPattern &pattern) {
|
||||
::TerminalStripDrawer::drawCables(painter, cells, pattern);
|
||||
}
|
||||
|
||||
qreal Helpers::cableBoxesExtraDepth(const QVector<CableCell> &cells,
|
||||
const TerminalStripLayoutPattern &pattern)
|
||||
{
|
||||
const qreal end_length{qMax<qreal>(0, pattern.m_cable_end_length)};
|
||||
qreal extra{0};
|
||||
for (const auto &box_ : ::TerminalStripDrawer::targetBoxes(cells, pattern,
|
||||
pattern.font()))
|
||||
{
|
||||
//How much deeper than the end zone the strip reserves this box
|
||||
//stands -- the zone is the room of the box, a box set lower
|
||||
//than the others needs room the strip must reserve too
|
||||
extra = qMax(extra, box_.bottom() - (box_.bar_2_y + end_length));
|
||||
}
|
||||
return qMax<qreal>(0, extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripDrawer::paint
|
||||
* @param painter
|
||||
@@ -970,7 +1265,12 @@ void TerminalStripDrawer::paint(QPainter *painter)
|
||||
QRectF xref_rect;
|
||||
|
||||
QHash<QUuid, QVector<QPointF>> bridges_anchor_points;
|
||||
QVector<CableCell> cable_cells;
|
||||
//The cells the cables are drawn from, worked out once for the
|
||||
//whole strip: the depth the strip reserves under its terminals
|
||||
//is built on the same list (boundingRect()), so a box set lower
|
||||
//than the others is never cut off
|
||||
QVector<CableCell> cable_cells{cableCells(*m_strip, *m_pattern)};
|
||||
auto cell_index{0};
|
||||
|
||||
m_hovered_xref = hoverTerminal{};
|
||||
|
||||
@@ -1028,18 +1328,13 @@ void TerminalStripDrawer::paint(QPainter *painter)
|
||||
|
||||
terminal_rect = terminalRect(*m_pattern, index_, real_terminal_vector[i]);
|
||||
|
||||
//Cable (hose) of this terminal
|
||||
CableCell cable_cell;
|
||||
if (real_terminal_vector[i])
|
||||
{
|
||||
cable_cell.x = x_offset + terminal_rect.width()/2;
|
||||
cable_cell.bottom = terminal_rect.y() + terminal_rect.height();
|
||||
cable_cell.cable = real_terminal_vector[i]->cable();
|
||||
cable_cell.wire = real_terminal_vector[i]->cableWire();
|
||||
cable_cell.shield = real_terminal_vector[i]->isShield();
|
||||
}
|
||||
//Cable (hose) of this terminal, and where this
|
||||
//terminal stands: the very cell the depth of the strip
|
||||
//was worked out from, so the drawing and the room it
|
||||
//is given can never disagree
|
||||
const CableCell &cable_cell{cable_cells.at(cell_index++)};
|
||||
x_offset = cable_cell.x - terminal_rect.width()/2;
|
||||
const bool have_cable{m_pattern->m_cable_wire_length > 0 && !cable_cell.cable.isEmpty()};
|
||||
cable_cells.append(cable_cell);
|
||||
//Draw terminal rect, filled with the color of the terminal (if any)
|
||||
const auto colors_{terminalColors(real_terminal_vector[i])};
|
||||
const auto fill_color_{colors_.fill};
|
||||
@@ -1291,7 +1586,7 @@ void TerminalStripDrawer::paint(QPainter *painter)
|
||||
painter->setPen(cable_pen);
|
||||
painter->setFont(m_pattern->font());
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
drawCables(painter, cable_cells, *m_pattern);
|
||||
Helpers::drawCables(painter, cable_cells, *m_pattern);
|
||||
painter->restore();
|
||||
}
|
||||
}
|
||||
@@ -1340,12 +1635,18 @@ QRectF TerminalStripDrawer::boundingRect() const
|
||||
|
||||
if (have_cable)
|
||||
{
|
||||
//4 is the diameter of the circle at the end of the wires
|
||||
//4 is the diameter of the circle at the end of the wires.
|
||||
//A box standing in the way of another one is drawn lower
|
||||
//than the end zone, and the room for it is reserved too
|
||||
//(same rule, same cells as the drawing) or it is cut off
|
||||
extra_bottom = std::max(extra_bottom,
|
||||
m_pattern->m_cable_wire_length
|
||||
+ std::max<qreal>(0, m_pattern->m_cable_length)
|
||||
+ std::max<qreal>(0, m_pattern->m_cable_end_length)
|
||||
+ 4);
|
||||
+ 4
|
||||
+ Helpers::cableBoxesExtraDepth(
|
||||
cableCells(*m_strip, *m_pattern),
|
||||
*m_pattern));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ namespace TerminalStripDrawer
|
||||
virtual QString cable() const { return QString(); }
|
||||
virtual QString cableWire() const { return QString(); }
|
||||
virtual bool isShield() const { return false; }
|
||||
//Installation, Location and Label (BMK) of the piece the cable
|
||||
//goes to at its far end -- the same three fields the cable list
|
||||
//shows for a target -- drawn in a box under the cable. Empty
|
||||
//when the terminal has no cable, or when the cable lands on
|
||||
//nothing past the strip's own terminals.
|
||||
virtual QVector<QString> cableTarget() const { return QVector<QString>(); }
|
||||
//Color and width (mm) of the terminal, only used to draw the terminal.
|
||||
//An invalid color means "no override" : the default color of the type is used.
|
||||
//A width <= 0 means "unknown" : the default width of the layout is used.
|
||||
|
||||
@@ -42,6 +42,29 @@ namespace TerminalStripDrawer
|
||||
//Symbol of the type of the terminal (fuse, diode, led...) scaled to fit in box
|
||||
void drawTypeSymbol(QPainter *painter, const QRectF &box,
|
||||
ElementData::TerminalType type, bool led);
|
||||
//What is needed to draw the cable under a terminal
|
||||
struct CableCell
|
||||
{
|
||||
qreal x{0}; ///< x of the middle of the terminal, in the strip coordinates
|
||||
qreal bottom{0}; ///< y of the bottom of the terminal
|
||||
QString cable; ///< name of the cable (hose)
|
||||
QString wire; ///< color / number of the wire
|
||||
bool shield{false}; ///< true if the terminal is the shield of the cable
|
||||
QSharedPointer<AbstractRealTerminalInterface> terminal; ///< to ask where the cable goes
|
||||
};
|
||||
//Draw the cables under the terminals, whichever style draws them :
|
||||
//consecutive terminals with the same cable share one cable, which ends
|
||||
//together in a short stub and a box naming where it goes (see drawCables()
|
||||
//in terminalstripdrawer.cpp for the whole drawing)
|
||||
void drawCables(QPainter *painter, const QVector<CableCell> &cells,
|
||||
const TerminalStripLayoutPattern &pattern);
|
||||
//How much deeper than the end zone (m_cable_end_length) the boxes of
|
||||
//the cables need: a box reaching into another one is set lower than
|
||||
//the others, and the strip must reserve the room for it or the lower
|
||||
//box is cut off. The same rule the drawing uses, worked out of the
|
||||
//same cells, so the two can never disagree.
|
||||
qreal cableBoxesExtraDepth(const QVector<CableCell> &cells,
|
||||
const TerminalStripLayoutPattern &pattern);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,11 @@ namespace TerminalStripDrawer
|
||||
return m_real ? m_real->isShield() : false;
|
||||
}
|
||||
|
||||
QVector<QString> TrueRealTerminal::cableTarget() const
|
||||
{
|
||||
return m_real ? m_real->cableTarget() : QVector<QString>();
|
||||
}
|
||||
|
||||
QColor TrueRealTerminal::color() const
|
||||
{
|
||||
return m_real ? m_real->color() : QColor();
|
||||
|
||||
@@ -64,6 +64,7 @@ namespace TerminalStripDrawer
|
||||
QString cable() const override;
|
||||
QString cableWire() const override;
|
||||
bool isShield() const override;
|
||||
QVector<QString> cableTarget() const override;
|
||||
QColor color() const override;
|
||||
QColor stripeColor() const override;
|
||||
qreal widthMm() const override;
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#include "../terminalstrip.h"
|
||||
#include "../physicalterminal.h"
|
||||
#include "../realterminal.h"
|
||||
#include "../../diagram.h"
|
||||
#include "../../positionorder.h"
|
||||
#include "../../qetgraphicsitem/element.h"
|
||||
#include "../../utils/qetutils.h"
|
||||
|
||||
SortTerminalStripCommand::SortTerminalStripCommand(TerminalStrip *strip, QUndoCommand *parent) :
|
||||
@@ -47,16 +50,73 @@ void SortTerminalStripCommand::redo()
|
||||
|
||||
void SortTerminalStripCommand::sort()
|
||||
{
|
||||
auto label_of = [](const QSharedPointer<PhysicalTerminal> &t) -> QString
|
||||
//The plan position of a physical terminal is the position of the
|
||||
//level that sits on a folio; a terminal whose levels are all
|
||||
//spares has no position and is ordered by label only.
|
||||
auto position_of = [](const QSharedPointer<PhysicalTerminal> &t_) -> QSharedPointer<RealTerminal>
|
||||
{
|
||||
return t->realTerminalCount() ? t->realTerminals().constLast()->label()
|
||||
: QString();
|
||||
if (!t_) {
|
||||
return {};
|
||||
}
|
||||
for (const auto &real_ : t_->realTerminals()) {
|
||||
if (real_ && real_->element()) {
|
||||
return real_;
|
||||
}
|
||||
}
|
||||
return t_->realTerminalCount() ? t_->realTerminals().constFirst()
|
||||
: QSharedPointer<RealTerminal>();
|
||||
};
|
||||
|
||||
std::stable_sort(m_new_order.begin(), m_new_order.end(),
|
||||
[&label_of](const QSharedPointer<PhysicalTerminal> &arg1,
|
||||
[&position_of](const QSharedPointer<PhysicalTerminal> &arg1,
|
||||
const QSharedPointer<PhysicalTerminal> &arg2)
|
||||
{
|
||||
return QETUtils::naturalLessThan(label_of(arg1), label_of(arg2));
|
||||
return planOrderLessThan(position_of(arg1), position_of(arg2));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@brief SortTerminalStripCommand::planOrderLessThan
|
||||
Order two real terminals the way the plan is drawn : folio by folio,
|
||||
then left to right. Terminals with no folio position (spares) come
|
||||
last, and the label only breaks ties, so the result stays stable and
|
||||
predictable whatever the labels look like ("X1:N" and "X1:PE" stand
|
||||
where they are drawn, not at the end).
|
||||
Both the auto import and sort() use this, so the two orders of the
|
||||
same strip can never fight each other.
|
||||
@a a_ terminal to compare
|
||||
@a b_ other terminal to compare
|
||||
@return true if @a a_ comes before @a b_
|
||||
*/
|
||||
bool SortTerminalStripCommand::planOrderLessThan(
|
||||
const QSharedPointer<RealTerminal> &a_,
|
||||
const QSharedPointer<RealTerminal> &b_)
|
||||
{
|
||||
if (!a_ || !b_) {
|
||||
return a_ && !b_; //a missing terminal goes last
|
||||
}
|
||||
|
||||
const Element *ea = a_->element();
|
||||
const Element *eb = b_->element();
|
||||
auto folio_of = [](const Element *e_) -> int
|
||||
{
|
||||
return (e_ && e_->diagram()) ? e_->diagram()->folioIndex() : -1;
|
||||
};
|
||||
|
||||
const int fa = folio_of(ea);
|
||||
const int fb = folio_of(eb);
|
||||
if (fa != fb) {
|
||||
//A terminal with no folio has no place in the plan order
|
||||
if (fa < 0) {
|
||||
return false;
|
||||
}
|
||||
if (fb < 0) {
|
||||
return true;
|
||||
}
|
||||
return fa < fb;
|
||||
}
|
||||
if (fa >= 0 && ea->pos() != eb->pos()) {
|
||||
return PositionOrder::roundedXThenY(ea->pos(), eb->pos());
|
||||
}
|
||||
return QETUtils::naturalLessThan(a_->label(), b_->label());
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QPointer>
|
||||
#include <QSharedPointer>
|
||||
#include <QVector>
|
||||
|
||||
class TerminalStrip;
|
||||
class PhysicalTerminal;
|
||||
class RealTerminal;
|
||||
|
||||
/**
|
||||
* @brief The SortTerminalStripCommand class
|
||||
@@ -38,6 +40,25 @@ class SortTerminalStripCommand : public QUndoCommand
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
/**
|
||||
* @brief planOrderLessThan
|
||||
* Order two real terminals the way the plan is drawn : folio by
|
||||
* folio, then left to right (top to bottom only breaks ties
|
||||
* within a column). A spare has no folio and comes last, by
|
||||
* label, and the label breaks every remaining tie (two
|
||||
* terminals at the very same spot), which keeps the result
|
||||
* stable.
|
||||
*
|
||||
* The auto import sorts with it too, so the order it works out
|
||||
* cannot be rewritten by the SortTerminalStripCommand it then
|
||||
* pushes for the strip it just created.
|
||||
* @param a_ : first terminal
|
||||
* @param b_ : second terminal
|
||||
* @return true if @a a_ comes before @a b_
|
||||
*/
|
||||
static bool planOrderLessThan(const QSharedPointer<RealTerminal> &a_,
|
||||
const QSharedPointer<RealTerminal> &b_);
|
||||
|
||||
private:
|
||||
void sort();
|
||||
|
||||
|
||||
@@ -22,7 +22,56 @@
|
||||
#include "terminalcolor.h"
|
||||
#include "terminalcatalog.h"
|
||||
#include "../qetgraphicsitem/conductor.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/terminal.h"
|
||||
#include "../qetinformation.h"
|
||||
#include "../diagram.h"
|
||||
#include "../cable/cable.h"
|
||||
#include "../cable/cablemanager.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* The cable cores wired to one terminal element, worked out from
|
||||
* the cables of the project (PR #1438). A core knows its
|
||||
* conductor, and CableManager::refreshLabels() writes the cable
|
||||
* and the core into the properties of that conductor, so reading
|
||||
* the conductors of the element is enough.
|
||||
* Each pair is (cable designation, core colour or number).
|
||||
*/
|
||||
QList<QPair<QString, QString>> wiredCores(Element *element)
|
||||
{
|
||||
QList<QPair<QString, QString>> cores;
|
||||
if (!element || !element->diagram()) {
|
||||
return cores;
|
||||
}
|
||||
const auto project = element->diagram()->project();
|
||||
for (const auto terminal : element->terminals())
|
||||
{
|
||||
for (const auto conductor : terminal->conductors())
|
||||
{
|
||||
const auto properties = conductor->ownProperties();
|
||||
const auto cable = CableManager::cableByUuid(project, properties.m_cable_uuid);
|
||||
if (!cable || cable->designation().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
auto wire = cable->colorOfCore(properties.m_cable_slot);
|
||||
if (wire.isEmpty() && properties.m_cable_slot >= 0) {
|
||||
wire = QString::number(properties.m_cable_slot + 1);
|
||||
}
|
||||
const QPair<QString, QString> core(cable->designation(), wire);
|
||||
if (!cores.contains(core)) {
|
||||
cores.append(core);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal
|
||||
@@ -291,18 +340,52 @@ QString RealTerminal::Xref() const
|
||||
* @return The name of the cable (hose) connected to this terminal.
|
||||
* The value is stored in the informations of the element.
|
||||
*/
|
||||
QString RealTerminal::cable() const {
|
||||
QString RealTerminal::cable() const
|
||||
{
|
||||
const auto cores = wiredCores(m_element.data());
|
||||
if (!cores.isEmpty())
|
||||
{
|
||||
QStringList cables;
|
||||
for (const auto &core : cores) {
|
||||
if (!cables.contains(core.first)) {
|
||||
cables.append(core.first);
|
||||
}
|
||||
}
|
||||
return cables.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
if (hasData()) {
|
||||
return terminalData().m_informations.value(cableInfoKey()).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::cableIsAutomatic
|
||||
* @return true when the cable and the core of this terminal come from
|
||||
* a cable of the project wired to it, false when they are the values
|
||||
* typed in by the user in the terminal strip editor.
|
||||
*/
|
||||
bool RealTerminal::cableIsAutomatic() const {
|
||||
return !wiredCores(m_element.data()).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::cableWire
|
||||
* @return The color / number of the wire of the cable, connected to this terminal.
|
||||
*/
|
||||
QString RealTerminal::cableWire() const {
|
||||
QString RealTerminal::cableWire() const
|
||||
{
|
||||
const auto cores = wiredCores(m_element.data());
|
||||
if (!cores.isEmpty())
|
||||
{
|
||||
QStringList wires;
|
||||
for (const auto &core : cores) {
|
||||
wires.append(core.second);
|
||||
}
|
||||
return wires.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
if (hasData()) {
|
||||
return terminalData().m_informations.value(cableWireInfoKey()).toString();
|
||||
}
|
||||
@@ -322,6 +405,199 @@ bool RealTerminal::isShield() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::cableTarget
|
||||
* Where the cable of this terminal goes at its far end: the lines to
|
||||
* write in the box under the cable. What the project itself says when
|
||||
* a cable lands on a piece (see automaticCableTarget()), and the text
|
||||
* typed in by hand when none does -- the Target cell of the terminal
|
||||
* strip editor is read only in the first case, so a target the drawing
|
||||
* cannot work out is still worth naming by hand. A text typed in is
|
||||
* written one line per field, Installation, Location and Label (BMK),
|
||||
* the way the cell shows them: only a line break breaks it into fields.
|
||||
* A "/" written inside a field (a name like "ASP/01") is part of that
|
||||
* field and never breaks anything.
|
||||
* @return the lines to write in the box under the cable
|
||||
*/
|
||||
QVector<QString> RealTerminal::cableTarget() const
|
||||
{
|
||||
const auto automatic_{automaticCableTarget()};
|
||||
if (!automatic_.isEmpty()) {
|
||||
return automatic_;
|
||||
}
|
||||
|
||||
if (hasData())
|
||||
{
|
||||
const auto typed_{terminalData().m_informations
|
||||
.value(cableTargetInfoKey()).toString()};
|
||||
if (!typed_.isEmpty())
|
||||
{
|
||||
//One line by field, and only a line break breaks the text
|
||||
//into fields : a "/" written inside a field stays where it is
|
||||
QString text_{typed_};
|
||||
text_.replace(QStringLiteral("\r\n"), QStringLiteral("\n"));
|
||||
text_.replace(QLatin1Char('\r'), QLatin1Char('\n'));
|
||||
|
||||
QVector<QString> lines_;
|
||||
for (const QString &field_
|
||||
: text_.split(QLatin1Char('\n'), Qt::SkipEmptyParts)) {
|
||||
lines_ << field_;
|
||||
}
|
||||
return lines_;
|
||||
}
|
||||
}
|
||||
return QVector<QString>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::cableTargetIsAutomatic
|
||||
* @return true when the target of this terminal is worked out from the
|
||||
* project -- one of its cables lands on a piece worth naming -- and
|
||||
* false when only a text typed in by hand is there to show.
|
||||
*/
|
||||
bool RealTerminal::cableTargetIsAutomatic() const
|
||||
{
|
||||
return !automaticCableTarget().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::automaticCableTarget
|
||||
* Where the cable of this terminal goes at its far end: the
|
||||
* 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 shows for a
|
||||
* target, in the same order. An empty field is left out; a terminal
|
||||
* with no cable, or a cable which lands on nothing else, gives no
|
||||
* lines at all.
|
||||
*
|
||||
* The cable is the one the wires drawn to this terminal carry; a
|
||||
* cable name typed by hand, with no wire behind it, is looked up by
|
||||
* its name among the cables of the project.
|
||||
* @return the lines to write in the box under the cable
|
||||
*/
|
||||
QVector<QString> RealTerminal::automaticCableTarget() const
|
||||
{
|
||||
if (m_spare || m_element.isNull()) {
|
||||
return QVector<QString>();
|
||||
}
|
||||
TerminalStrip *strip_ = parentStrip();
|
||||
if (!strip_ || !strip_->project()) {
|
||||
return QVector<QString>();
|
||||
}
|
||||
QETProject *project_{strip_->project()};
|
||||
|
||||
//The cable the wires drawn to this terminal are part of
|
||||
QUuid cable_uuid_;
|
||||
for (Terminal *terminal_ : m_element->terminals())
|
||||
{
|
||||
if (!terminal_) {
|
||||
continue;
|
||||
}
|
||||
for (Conductor *conductor_ : terminal_->conductors())
|
||||
{
|
||||
if (conductor_ && !conductor_->properties().m_cable_uuid.isNull()) {
|
||||
cable_uuid_ = conductor_->properties().m_cable_uuid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cable_uuid_.isNull()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cable_uuid_.isNull())
|
||||
{
|
||||
//A cable name typed by hand, no wire behind it: the first
|
||||
//cable of the project drawn under that name
|
||||
const auto name_{cable()};
|
||||
if (name_.isEmpty()) {
|
||||
return QVector<QString>();
|
||||
}
|
||||
for (Cable *cable_ : project_->cables())
|
||||
{
|
||||
if (cable_ && cable_->designation() == name_) {
|
||||
cable_uuid_ = cable_->uuid();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cable_uuid_.isNull()) {
|
||||
return QVector<QString>();
|
||||
}
|
||||
}
|
||||
|
||||
//The own side: this strip's terminals never are the target
|
||||
QSet<QUuid> own_;
|
||||
for (const auto &physical_ : strip_->physicalTerminal())
|
||||
{
|
||||
if (!physical_) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &real_ : physical_->realTerminals()) {
|
||||
if (real_ && real_->element()) {
|
||||
own_.insert(real_->element()->uuid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Where the wires of the cable land: one piece whatever the
|
||||
//number of its cores, never a report (the cable continues on
|
||||
//the sheet the report points to) and never the strip's own
|
||||
//terminals. Same rule as the cable list (cablelistrows.cpp).
|
||||
QHash<QUuid, Element *> landed_;
|
||||
for (Diagram *diagram_ : project_->diagrams())
|
||||
{
|
||||
for (Conductor *conductor_ : diagram_->conductors())
|
||||
{
|
||||
if (!conductor_ || conductor_->properties().m_cable_uuid != cable_uuid_) {
|
||||
continue;
|
||||
}
|
||||
Terminal *ends_[2] = {conductor_->terminal1, conductor_->terminal2};
|
||||
for (Terminal *terminal_ : ends_)
|
||||
{
|
||||
if (!terminal_) {
|
||||
continue;
|
||||
}
|
||||
Element *element_ = terminal_->parentElement();
|
||||
if (!element_ || (element_->linkType() & Element::AllReport)) {
|
||||
continue;
|
||||
}
|
||||
if (own_.contains(element_->uuid())) {
|
||||
continue;
|
||||
}
|
||||
landed_.insert(element_->uuid(), element_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (landed_.isEmpty()) {
|
||||
return QVector<QString>();
|
||||
}
|
||||
|
||||
//The first piece in plan order is the target, as the cable
|
||||
//list chooses it
|
||||
Element *target_{nullptr};
|
||||
for (Element *element_ : std::as_const(landed_)) {
|
||||
if (!target_ || comparPos(element_, target_)) {
|
||||
target_ = element_;
|
||||
}
|
||||
}
|
||||
|
||||
const DiagramContext informations_{target_->elementInformations()};
|
||||
const auto installation_{informations_.value(QETInformation::ELMT_PLANT).toString()};
|
||||
const auto location_{informations_.value(QETInformation::ELMT_LOCATION).toString()};
|
||||
const auto label_{informations_.value(QETInformation::ELMT_LABEL).toString()};
|
||||
|
||||
QVector<QString> lines_;
|
||||
if (!installation_.isEmpty()) {
|
||||
lines_ << installation_;
|
||||
}
|
||||
if (!location_.isEmpty()) {
|
||||
lines_ << location_;
|
||||
}
|
||||
if (!label_.isEmpty()) {
|
||||
lines_ << label_;
|
||||
}
|
||||
return lines_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RealTerminal::color
|
||||
* @return The color set by the user for this terminal, drawn in the
|
||||
|
||||
@@ -55,6 +55,11 @@ class RealTerminal
|
||||
|
||||
void setPhysicalTerminal(const QSharedPointer<PhysicalTerminal> &phy_t);
|
||||
|
||||
///Where the wires of a cable land, worked out from the project:
|
||||
///empty when no cable lands on a piece worth naming (cableTarget()
|
||||
///then falls back on the text typed in by hand).
|
||||
QVector<QString> automaticCableTarget() const;
|
||||
|
||||
public:
|
||||
~RealTerminal();
|
||||
static QSharedPointer<RealTerminal> createSpare(const ElementData &data, const QUuid &uuid = QUuid());
|
||||
@@ -73,7 +78,12 @@ class RealTerminal
|
||||
QString Xref() const;
|
||||
QString cable() const;
|
||||
QString cableWire() const;
|
||||
bool cableIsAutomatic() const;
|
||||
bool isShield() const;
|
||||
QVector<QString> cableTarget() const;
|
||||
///True when cableTarget() is worked out from the project (read only),
|
||||
///false when it is the text typed in by hand (which may be edited)
|
||||
bool cableTargetIsAutomatic() const;
|
||||
QColor color() const;
|
||||
QColor stripeColor() const;
|
||||
qreal section() const;
|
||||
@@ -84,6 +94,8 @@ class RealTerminal
|
||||
//Keys used to store the cable data in the element informations
|
||||
static QString cableInfoKey() { return QStringLiteral("terminal_cable"); }
|
||||
static QString cableWireInfoKey() { return QStringLiteral("terminal_cable_wire"); }
|
||||
//Target typed in by hand, only read when the cables give none
|
||||
static QString cableTargetInfoKey() { return QStringLiteral("terminal_cable_target"); }
|
||||
static QString shieldInfoKey() { return QStringLiteral("terminal_shield"); }
|
||||
//Keys used to store the color and the section (mm2) of the terminal
|
||||
static QString colorInfoKey() { return QStringLiteral("terminal_color"); }
|
||||
|
||||
@@ -118,6 +118,10 @@ class TerminalStrip : public QObject
|
||||
void unBridge(const QVector<QSharedPointer<RealTerminal>> &real_terminals);
|
||||
bool canUnBridge(const QVector <QSharedPointer<RealTerminal>> &real_terminals) const;
|
||||
QSharedPointer<TerminalStripBridge> isBridged(const QSharedPointer<RealTerminal> real_terminal) const;
|
||||
///True when at least one bridge joins terminals of this strip :
|
||||
///the bridge cells of the terminal strip editor are the only thing
|
||||
///drawn from them (see TerminalStripModel::bridgePixmapFor)
|
||||
bool hasBridges() const { return !m_bridge.isEmpty(); }
|
||||
QSharedPointer<TerminalStripBridge> bridgeFor (const QVector<QSharedPointer<RealTerminal>> &terminal_vector) const;
|
||||
|
||||
QSharedPointer<RealTerminal> previousTerminalInLevel(const QSharedPointer<RealTerminal> &real_terminal) const;
|
||||
|
||||
@@ -128,7 +128,9 @@ namespace
|
||||
* whose label prefix or plant and location no longer match its strip.
|
||||
*
|
||||
* In both cases the order the user gave to a strip is never changed,
|
||||
* only a strip created by the run itself is sorted.
|
||||
* only a strip created by the run itself is sorted -- the way the plan
|
||||
* is drawn : folio by folio, then left to right (see
|
||||
* SortTerminalStripCommand::planOrderLessThan).
|
||||
*/
|
||||
TerminalStripAutoImport::ImportResult TerminalStripAutoImport::importFreeTerminals(
|
||||
QETProject *project, bool move_placed)
|
||||
@@ -187,16 +189,21 @@ TerminalStripAutoImport::ImportResult TerminalStripAutoImport::importFreeTermina
|
||||
|
||||
for (auto it_ = groups_.begin(); it_ != groups_.end(); ++it_)
|
||||
{
|
||||
auto by_label_ = [](const QSharedPointer<RealTerminal> &a_,
|
||||
const QSharedPointer<RealTerminal> &b_) {
|
||||
return QETUtils::naturalLessThan(a_->label(), b_->label());
|
||||
//The plan order : folio by folio, then left to right, so the
|
||||
//strip comes out the way the plan is drawn instead of label
|
||||
//by label ("N" and "PE" stay where they are on the sheet).
|
||||
//SortTerminalStripCommand, pushed below for a strip this run
|
||||
//creates, sorts with the same rule and cannot undo it.
|
||||
auto by_plan_ = [](const QSharedPointer<RealTerminal> &a_,
|
||||
const QSharedPointer<RealTerminal> &b_) {
|
||||
return SortTerminalStripCommand::planOrderLessThan(a_, b_);
|
||||
};
|
||||
std::sort(it_.value().free_terminals.begin(),
|
||||
it_.value().free_terminals.end(), by_label_);
|
||||
it_.value().free_terminals.end(), by_plan_);
|
||||
std::sort(it_.value().placed_terminals.begin(),
|
||||
it_.value().placed_terminals.end(),
|
||||
[&by_label_](const auto &a_, const auto &b_) {
|
||||
return by_label_(a_.first, b_.first);
|
||||
[&by_plan_](const auto &a_, const auto &b_) {
|
||||
return by_plan_(a_.first, b_.first);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,32 @@
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include "../../qetgraphicsitem/element.h"
|
||||
#include "../realterminal.h"
|
||||
|
||||
struct modelRealTerminalData
|
||||
{
|
||||
///The Installation, Location and Label (BMK) of the piece the cable
|
||||
///lands on, one line by field : what the strip draws in the box
|
||||
///under its cable (RealTerminal::cableTarget) and what the Target
|
||||
///cell shows, both read line by line. A "/" written inside a field
|
||||
///is part of that field, never a separator.
|
||||
///A target typed in by hand is kept as it was written, and nothing
|
||||
///is shown when the cable lands on nothing worth naming and no text
|
||||
///was ever written in.
|
||||
static QString targetText(const QVector<QString> &target_)
|
||||
{
|
||||
QStringList fields_;
|
||||
for (const QString &field_ : target_)
|
||||
{
|
||||
if (!field_.isEmpty()) {
|
||||
fields_ << field_;
|
||||
}
|
||||
}
|
||||
return fields_.join(QLatin1Char('\n'));
|
||||
}
|
||||
|
||||
static modelRealTerminalData data(const QSharedPointer<RealTerminal> &real_t)
|
||||
{
|
||||
modelRealTerminalData mrtd;
|
||||
@@ -35,6 +56,9 @@ struct modelRealTerminalData
|
||||
mrtd.Xref_ = real_t->Xref();
|
||||
mrtd.cable_ = real_t->cable();
|
||||
mrtd.cable_wire = real_t->cableWire();
|
||||
mrtd.cable_target_ = targetText(real_t->cableTarget());
|
||||
mrtd.cable_target_auto_ = real_t->cableTargetIsAutomatic();
|
||||
mrtd.cable_auto_ = real_t->cableIsAutomatic();
|
||||
mrtd.conductor_ = real_t->conductor();
|
||||
mrtd.led_ = real_t->isLed();
|
||||
mrtd.shield_ = real_t->isShield();
|
||||
@@ -60,6 +84,12 @@ struct modelRealTerminalData
|
||||
QString Xref_;
|
||||
QString cable_;
|
||||
QString cable_wire;
|
||||
//Where the cable lands, past this strip : Installation / Location / Label
|
||||
QString cable_target_;
|
||||
//True when cable_target_ comes from the project (read only),
|
||||
//false when it is empty or was typed in by hand (editable)
|
||||
bool cable_target_auto_ = false;
|
||||
bool cable_auto_ = false; //True when cable_ and cable_wire come from the cables of the project
|
||||
QString conductor_;
|
||||
bool led_ = false;
|
||||
bool shield_ = false;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "../terminalcatalog.h"
|
||||
#include "../realterminal.h"
|
||||
#include "../../qetinformation.h"
|
||||
#include "../../cable/cable.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
@@ -95,6 +96,10 @@ TerminalStripEditor::TerminalStripEditor(QETProject *project, QWidget *parent) :
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//The cable cells are worked out from the cables of the project,
|
||||
//so the table follows them while the user wires the drawing
|
||||
watchCableChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,9 +112,88 @@ TerminalStripEditor::~TerminalStripEditor() {
|
||||
void TerminalStripEditor::setProject(QETProject *project)
|
||||
{
|
||||
m_project = project;
|
||||
watchCableChanges();
|
||||
setCurrentStrip(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripEditor::watchCableChanges
|
||||
* Follow the cables of the project: the cable cells are worked out from
|
||||
* them, so a cable renamed, given cores or taken away changes what the
|
||||
* cells say, and the window stays open while the user wires the drawing.
|
||||
* The project the editor followed before is let go first, since the
|
||||
* window may be shown on another project.
|
||||
*/
|
||||
void TerminalStripEditor::watchCableChanges()
|
||||
{
|
||||
if (m_watched_project) {
|
||||
//Only the connections made here go to the project
|
||||
m_watched_project->disconnect(nullptr, this);
|
||||
for (Cable *cable : m_watched_project->cables()) {
|
||||
cable->disconnect(nullptr, this);
|
||||
}
|
||||
}
|
||||
m_watched_project = m_project;
|
||||
if (!m_project) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_project, &QETProject::cableAdded, this, [this](Cable *cable) {
|
||||
watchCable(cable);
|
||||
});
|
||||
connect(m_project, &QETProject::cableRemoved, this, [this](Cable *cable) {
|
||||
//The project stopped feeding its labels already, so the
|
||||
//cells show what is left: the typed in value, or nothing
|
||||
if (cable) {
|
||||
cable->disconnect(nullptr, this);
|
||||
}
|
||||
reloadOnCableChange();
|
||||
});
|
||||
for (Cable *cable : m_project->cables()) {
|
||||
watchCable(cable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripEditor::watchCable
|
||||
* Reload the table when @a cable changes. The project runs its own
|
||||
* connection first (made when the cable entered it), so the labels of
|
||||
* the conductors are worked out again before this reload reads them.
|
||||
* @param cable
|
||||
*/
|
||||
void TerminalStripEditor::watchCable(Cable *cable)
|
||||
{
|
||||
if (!cable) {
|
||||
return;
|
||||
}
|
||||
cable->disconnect(nullptr, this);
|
||||
connect(cable, &Cable::changed, this, &TerminalStripEditor::reloadOnCableChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripEditor::reloadOnCableChange
|
||||
* Take up a change of the cables: reload so the cable cells say what
|
||||
* they now must, never at the price of the user's own work -- cell
|
||||
* edits not applied yet, or a cell being typed in, are kept and catch
|
||||
* up with the next reload of any kind.
|
||||
*/
|
||||
void TerminalStripEditor::reloadOnCableChange()
|
||||
{
|
||||
if (!m_current_strip || !m_model) {
|
||||
return;
|
||||
}
|
||||
if (!m_model->modifiedmodelRealTerminalData().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
//A cell being typed in keeps its editor: the reload would close
|
||||
//it under the fingers of the user (the edit itself is picked up
|
||||
//by the next reload once it has been made)
|
||||
if (ui->m_table_widget->isAncestorOf(QApplication::focusWidget())) {
|
||||
return;
|
||||
}
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripEditor::setCurrentStrip
|
||||
* Set the current terminal strip edited to \p strip_
|
||||
@@ -165,6 +249,18 @@ void TerminalStripEditor::setCurrentStrip(TerminalStrip *strip_)
|
||||
{
|
||||
m_model = new TerminalStripModel{strip_, this};
|
||||
ui->m_table_widget->setModel(m_model);
|
||||
//The Target column is worked out from the drawing: it takes
|
||||
//the width of its own text, never a cut off Installation
|
||||
ui->m_table_widget->horizontalHeader()->setSectionResizeMode(
|
||||
TerminalStripModel::CableTarget, QHeaderView::ResizeToContents);
|
||||
//That column holds one line by field: its rows must be tall
|
||||
//enough for as many lines as a cell shows, while a cell of
|
||||
//one line keeps the height the rows have always had
|
||||
auto v_header_ = ui->m_table_widget->verticalHeader();
|
||||
v_header_->setMinimumSectionSize(qMax(
|
||||
v_header_->defaultSectionSize(),
|
||||
ui->m_table_widget->rowHeight(0)));
|
||||
v_header_->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_model->buildBridgePixmap(setUpBridgeCellWidth());
|
||||
connect(ui->m_table_widget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &TerminalStripEditor::selectionChanged);
|
||||
}
|
||||
@@ -245,8 +341,19 @@ void TerminalStripEditor::apply()
|
||||
current_data.m_informations.addValue(key, value);
|
||||
}
|
||||
};
|
||||
set_info(RealTerminal::cableInfoKey(), data_.cable_);
|
||||
set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire);
|
||||
//A cable worked out from the cables of the project is
|
||||
//never written in the element: it would stay there,
|
||||
//out of date, once the cable is renamed or removed
|
||||
if (!data_.cable_auto_) {
|
||||
set_info(RealTerminal::cableInfoKey(), data_.cable_);
|
||||
set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire);
|
||||
}
|
||||
//The same for the target: a target worked out from the
|
||||
//project is never written in the element, only the one
|
||||
//typed in by hand is stored
|
||||
if (!data_.cable_target_auto_) {
|
||||
set_info(RealTerminal::cableTargetInfoKey(), data_.cable_target_);
|
||||
}
|
||||
set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString());
|
||||
//The color and the section are stored with the dot as decimal separator, whatever the locale
|
||||
set_info(RealTerminal::colorInfoKey(), TerminalColor::toSpec(data_.color_, data_.stripe_color_));
|
||||
@@ -701,7 +808,8 @@ void TerminalStripEditor::on_m_led_cb_activated(int index)
|
||||
namespace {
|
||||
/**
|
||||
* @return true if the cell at @a index contain a free text
|
||||
* (label, cable, wire of the cable, color, section or width), so a text can be pasted in it.
|
||||
* (label, cable, wire of the cable, target of the cable, color, section
|
||||
* or width), so a text can be pasted in it.
|
||||
*/
|
||||
bool cellAcceptText(const QModelIndex &index)
|
||||
{
|
||||
@@ -709,6 +817,7 @@ bool cellAcceptText(const QModelIndex &index)
|
||||
return column_ == TerminalStripModel::Label
|
||||
|| column_ == TerminalStripModel::Cable
|
||||
|| column_ == TerminalStripModel::CableWire
|
||||
|| column_ == TerminalStripModel::CableTarget
|
||||
|| column_ == TerminalStripModel::Color
|
||||
|| column_ == TerminalStripModel::Section
|
||||
|| column_ == TerminalStripModel::Width;
|
||||
@@ -779,8 +888,15 @@ void TerminalStripEditor::copySelectionToClipboard()
|
||||
for (auto row = top ; row <= bottom ; ++row)
|
||||
{
|
||||
QStringList cells;
|
||||
for (auto column = left ; column <= right ; ++column) {
|
||||
cells << texts.value((qint64(row) << 32) | qint64(column));
|
||||
for (auto column = left ; column <= right ; ++column)
|
||||
{
|
||||
//A cell may hold several lines (the Target cell holds one
|
||||
//line by field): the text of the clipboard keeps one line by
|
||||
//row, the fields of that cell are joined back the way a
|
||||
//spreadsheet reads them
|
||||
QString cell_{texts.value((qint64(row) << 32) | qint64(column))};
|
||||
cell_.replace(QLatin1Char('\n'), QStringLiteral(" / "));
|
||||
cells << cell_;
|
||||
}
|
||||
lines << cells.join(QLatin1Char('\t'));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Ui {
|
||||
}
|
||||
|
||||
class QETProject;
|
||||
class Cable;
|
||||
class TerminalStrip;
|
||||
|
||||
/**
|
||||
@@ -58,6 +59,9 @@ class TerminalStripEditor : public QWidget
|
||||
void spanMultiLevelTerminals();
|
||||
void selectionChanged();
|
||||
QSize setUpBridgeCellWidth();
|
||||
void watchCableChanges();
|
||||
void watchCable(Cable *cable);
|
||||
void reloadOnCableChange();
|
||||
TerminalStripModel::Column isSingleColumnSelected() const;
|
||||
QPair<TerminalStripModel::Column, QVector<modelRealTerminalData>> singleColumnData() const;
|
||||
|
||||
@@ -79,6 +83,9 @@ class TerminalStripEditor : public QWidget
|
||||
private:
|
||||
Ui::TerminalStripEditor *ui;
|
||||
QPointer<QETProject> m_project;
|
||||
//The project whose cables this editor follows, which is not
|
||||
//always m_project yet when watchCableChanges() runs again
|
||||
QPointer<QETProject> m_watched_project;
|
||||
QPointer<TerminalStrip> m_current_strip;
|
||||
TerminalStripModel *m_model {nullptr};
|
||||
};
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#include <QSpinBox>
|
||||
#include <QPainter>
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QFrame>
|
||||
|
||||
/**
|
||||
* Some const int who describe what a column contain
|
||||
@@ -48,16 +51,17 @@ const int CONDUCTOR_CELL = 7;
|
||||
const int XREF_CELL = 8;
|
||||
const int CABLE_CELL = 9;
|
||||
const int CABLE_WIRE_CELL = 10;
|
||||
const int TYPE_CELL = 11;
|
||||
const int FUNCTION_CELL = 12;
|
||||
const int LED_CELL = 13;
|
||||
const int SHIELD_CELL = 14;
|
||||
const int COLOR_CELL = 15;
|
||||
const int SECTION_CELL = 16;
|
||||
const int WIDTH_CELL = 17;
|
||||
const int SEPARATOR_CELL = 18;
|
||||
const int CABLE_TARGET_CELL = 11;
|
||||
const int TYPE_CELL = 12;
|
||||
const int FUNCTION_CELL = 13;
|
||||
const int LED_CELL = 14;
|
||||
const int SHIELD_CELL = 15;
|
||||
const int COLOR_CELL = 16;
|
||||
const int SECTION_CELL = 17;
|
||||
const int WIDTH_CELL = 18;
|
||||
const int SEPARATOR_CELL = 19;
|
||||
|
||||
const int COLUMN_COUNT = 19;
|
||||
const int COLUMN_COUNT = 20;
|
||||
|
||||
static QVector<bool> UNMODIFIED_CELL_VECTOR(COLUMN_COUNT, false);
|
||||
|
||||
@@ -132,14 +136,15 @@ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIn
|
||||
case 8 : return XRef;
|
||||
case 9 : return Cable;
|
||||
case 10 : return CableWire;
|
||||
case 11 : return Type;
|
||||
case 12 : return Function;
|
||||
case 13 : return Led;
|
||||
case 14 : return Shield;
|
||||
case 15 : return Color;
|
||||
case 16 : return Section;
|
||||
case 17 : return Width;
|
||||
case 18 : return Separator;
|
||||
case 11 : return CableTarget;
|
||||
case 12 : return Type;
|
||||
case 13 : return Function;
|
||||
case 14 : return Led;
|
||||
case 15 : return Shield;
|
||||
case 16 : return Color;
|
||||
case 17 : return Section;
|
||||
case 18 : return Width;
|
||||
case 19 : return Separator;
|
||||
default : return Invalid;
|
||||
}
|
||||
}
|
||||
@@ -182,12 +187,7 @@ int TerminalStripModel::rowCount(const QModelIndex &parent) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto count = 0;
|
||||
for (const auto &mptd : m_physical_data) {
|
||||
count += mptd.real_data.size();
|
||||
}
|
||||
|
||||
return count;
|
||||
return m_row_count;
|
||||
}
|
||||
|
||||
int TerminalStripModel::columnCount(const QModelIndex &parent) const
|
||||
@@ -215,6 +215,7 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
|
||||
case XREF_CELL : return mrtd.spare_ ? tr("Spare") : mrtd.Xref_;
|
||||
case CABLE_CELL : return mrtd.cable_;
|
||||
case CABLE_WIRE_CELL : return mrtd.cable_wire;
|
||||
case CABLE_TARGET_CELL : return mrtd.cable_target_;
|
||||
case TYPE_CELL : return ElementData::translatedTerminalType(mrtd.type_);
|
||||
case FUNCTION_CELL : return ElementData::translatedTerminalFunction(mrtd.function_);
|
||||
case COLOR_CELL : return TerminalColor::displayName(mrtd.color_, mrtd.stripe_color_);
|
||||
@@ -235,6 +236,7 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
|
||||
case LABEL_CELL : return mrtd.label_;
|
||||
case CABLE_CELL : return mrtd.cable_;
|
||||
case CABLE_WIRE_CELL : return mrtd.cable_wire;
|
||||
case CABLE_TARGET_CELL : return mrtd.cable_target_;
|
||||
case COLOR_CELL : return TerminalColor::toSpec(mrtd.color_, mrtd.stripe_color_);
|
||||
case SECTION_CELL : return mrtd.section_ > 0 ? QLocale().toString(mrtd.section_, 'g', 6) : QString();
|
||||
case WIDTH_CELL : return mrtd.width_mm_ > 0 ? QLocale().toString(mrtd.width_mm_, 'g', 6) : QString();
|
||||
@@ -267,7 +269,8 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
return QBrush(Qt::gray); //"Spare" : not a reference to a folio
|
||||
}
|
||||
else if (role == Qt::BackgroundRole && index.column() < COLUMN_COUNT )
|
||||
else if (role == Qt::BackgroundRole && index.column() < COLUMN_COUNT
|
||||
&& !m_modified_cell.isEmpty())
|
||||
{
|
||||
const auto key_ = modifiedCellKey(mrtd);
|
||||
if (m_modified_cell.contains(key_) &&
|
||||
@@ -302,6 +305,18 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value
|
||||
int modified_cell = -1;
|
||||
auto column_ = index.column();
|
||||
|
||||
//A cell worked out from the cables of the project is read only:
|
||||
//the element never holds that value, so writing one in would only
|
||||
//show something the next reload takes away again
|
||||
if ((column_ == CABLE_CELL || column_ == CABLE_WIRE_CELL)
|
||||
&& mrtd.cable_auto_) {
|
||||
return false;
|
||||
}
|
||||
if (column_ == CABLE_TARGET_CELL
|
||||
&& mrtd.cable_target_auto_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (column_ == LEVEL_CELL &&
|
||||
role == Qt::EditRole)
|
||||
{
|
||||
@@ -353,6 +368,14 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value
|
||||
modified_ = true;
|
||||
modified_cell = CABLE_WIRE_CELL;
|
||||
}
|
||||
else if (column_ == CABLE_TARGET_CELL &&
|
||||
role == Qt::EditRole &&
|
||||
mrtd.cable_target_ != value.toString())
|
||||
{
|
||||
mrtd.cable_target_ = value.toString();
|
||||
modified_ = true;
|
||||
modified_cell = CABLE_TARGET_CELL;
|
||||
}
|
||||
else if (column_ == SHIELD_CELL)
|
||||
{
|
||||
mrtd.shield_ = value.toBool();
|
||||
@@ -476,6 +499,7 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation
|
||||
case XREF_CELL: return tr("Cross-reference");
|
||||
case CABLE_CELL: return tr("Cable");
|
||||
case CABLE_WIRE_CELL: return tr("Color / wire number of the cable");
|
||||
case CABLE_TARGET_CELL: return tr("Target");
|
||||
case TYPE_CELL: return tr("Type");
|
||||
case FUNCTION_CELL : return tr("Function");
|
||||
case LED_CELL: return tr("led");
|
||||
@@ -488,6 +512,14 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (role == Qt::ToolTipRole &&
|
||||
orientation == Qt::Horizontal &&
|
||||
section == CABLE_TARGET_CELL)
|
||||
{
|
||||
//Same three fields as the box drawn under the cable, one line
|
||||
//by field: only a line break separates them
|
||||
return tr("Installation, Location and Label (BMK) of the piece the cable lands on, past this strip, one line per field");
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
@@ -497,8 +529,18 @@ Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
|
||||
auto c = index.column();
|
||||
//The cable cells are only edited by hand when no cable
|
||||
//of the project is wired to the terminal
|
||||
if ((c == CABLE_CELL || c == CABLE_WIRE_CELL)
|
||||
&& !dataAtRow(index.row()).cable_auto_)
|
||||
flags = flags | Qt::ItemIsEditable;
|
||||
//The target the project works out is read only: only the drawing
|
||||
//decides it. No target to show, or one typed in by hand, may be
|
||||
//written -- the same rule as the cable cells.
|
||||
if (c == CABLE_TARGET_CELL
|
||||
&& !dataAtRow(index.row()).cable_target_auto_)
|
||||
flags = flags | Qt::ItemIsEditable;
|
||||
if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL
|
||||
|| c == CABLE_CELL || c == CABLE_WIRE_CELL
|
||||
|| c == COLOR_CELL || c == SECTION_CELL || c == WIDTH_CELL)
|
||||
flags = flags | Qt::ItemIsEditable;
|
||||
if (c == LED_CELL || c == SHIELD_CELL || c == SEPARATOR_CELL) {
|
||||
@@ -589,7 +631,7 @@ QVector<modelRealTerminalData> TerminalStripModel::modelRealTerminalDataForIndex
|
||||
for (auto i : set_) {
|
||||
const auto rtd_ = realDataAtIndex(i);
|
||||
if (rtd_.level_ > -1) {
|
||||
vector_.append(realDataAtIndex(i));
|
||||
vector_.append(rtd_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,6 +739,8 @@ void TerminalStripModel::reload()
|
||||
|
||||
void TerminalStripModel::fillPhysicalTerminalData()
|
||||
{
|
||||
m_row_index.clear();
|
||||
|
||||
//Get all physical terminal
|
||||
if (m_terminal_strip)
|
||||
{
|
||||
@@ -706,41 +750,32 @@ void TerminalStripModel::fillPhysicalTerminalData()
|
||||
mptd.pos_ = phy_t->pos();
|
||||
mptd.uuid_ = phy_t->uuid();
|
||||
|
||||
const auto physical_index = static_cast<int>(m_physical_data.size());
|
||||
for (const auto &real_t : phy_t->realTerminals())
|
||||
{
|
||||
if (!real_t.isNull())
|
||||
{
|
||||
mptd.real_data.append(modelRealTerminalData::data(real_t));
|
||||
m_row_index.append({physical_index,
|
||||
static_cast<int>(mptd.real_data.size()) - 1});
|
||||
}
|
||||
}
|
||||
|
||||
m_physical_data.append(mptd);
|
||||
}
|
||||
}
|
||||
|
||||
m_row_count = m_row_index.size();
|
||||
}
|
||||
|
||||
modelRealTerminalData TerminalStripModel::dataAtRow(int row) const
|
||||
{
|
||||
if (row > rowCount(QModelIndex())) {
|
||||
if (row < 0 || row >= m_row_count) {
|
||||
return modelRealTerminalData();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto current_row = 0;
|
||||
for (const auto &physical_data : std::as_const(m_physical_data))
|
||||
{
|
||||
for (const auto &real_data : physical_data.real_data)
|
||||
{
|
||||
if (current_row == row) {
|
||||
return real_data;
|
||||
} else {
|
||||
++current_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modelRealTerminalData();
|
||||
const auto &place_ = m_row_index.at(row);
|
||||
return m_physical_data.at(place_.physical_).real_data.at(place_.real_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,32 +786,12 @@ modelRealTerminalData TerminalStripModel::dataAtRow(int row) const
|
||||
*/
|
||||
void TerminalStripModel::replaceDataAtRow(modelRealTerminalData data, int row)
|
||||
{
|
||||
if (row > rowCount(QModelIndex())) {
|
||||
if (row < 0 || row >= m_row_count) {
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto current_row = 0;
|
||||
auto current_physical = 0;
|
||||
|
||||
for (const auto &physical_data : std::as_const(m_physical_data))
|
||||
{
|
||||
auto current_real = 0;
|
||||
for (int i=0 ; i<physical_data.real_data.count() ; ++i)
|
||||
{
|
||||
if (current_row == row) {
|
||||
auto physical_data = m_physical_data.at(current_physical);
|
||||
physical_data.real_data.replace(current_real, data);
|
||||
m_physical_data.replace(current_physical, physical_data);
|
||||
return;
|
||||
} else {
|
||||
++current_real;
|
||||
++current_row;
|
||||
}
|
||||
}
|
||||
++current_physical;
|
||||
}
|
||||
}
|
||||
const auto &place_ = m_row_index.at(row);
|
||||
m_physical_data[place_.physical_].real_data.replace(place_.real_, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -794,26 +809,17 @@ modelPhysicalTerminalData TerminalStripModel::physicalDataAtIndex(int index) con
|
||||
return modelPhysicalTerminalData();
|
||||
}
|
||||
|
||||
int current_checked_index = -1;
|
||||
int current_phy = -1;
|
||||
bool match_ = false;
|
||||
|
||||
for (const auto &ptd_ : std::as_const(m_physical_data))
|
||||
{
|
||||
current_checked_index += ptd_.real_data.size();
|
||||
++current_phy;
|
||||
|
||||
if (current_checked_index >= index) {
|
||||
match_ = true;
|
||||
break;
|
||||
}
|
||||
//A row always belongs to the physical terminal it is a level of,
|
||||
//m_row_index says which one (negative index: the first physical
|
||||
//terminal, as the walk this replaces used to give)
|
||||
if (index < 0) {
|
||||
return m_physical_data.first();
|
||||
}
|
||||
|
||||
if (match_) {
|
||||
return m_physical_data.at(current_phy);
|
||||
} else {
|
||||
if (index >= m_row_count) {
|
||||
return modelPhysicalTerminalData();
|
||||
}
|
||||
|
||||
return m_physical_data.at(m_row_index.at(index).physical_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -823,23 +829,34 @@ modelPhysicalTerminalData TerminalStripModel::physicalDataAtIndex(int index) con
|
||||
*/
|
||||
modelRealTerminalData TerminalStripModel::realDataAtIndex(int index) const
|
||||
{
|
||||
if (m_physical_data.isEmpty()) {
|
||||
if (index < 0 || index >= m_row_count) {
|
||||
return modelRealTerminalData();
|
||||
}
|
||||
|
||||
int current_checked_index = -1;
|
||||
const auto &place_ = m_row_index.at(index);
|
||||
return m_physical_data.at(place_.physical_).real_data.at(place_.real_);
|
||||
}
|
||||
|
||||
for (const auto & ptd_ : std::as_const(m_physical_data))
|
||||
/**
|
||||
* @brief bridgeWalkStep
|
||||
* The three fields the neighbour walk of bridgePixmapFor() reads: level,
|
||||
* bridge and identity. The whole row data would answer as well, but filling
|
||||
* it reads the cables of the project (RealTerminal::cableTarget() and
|
||||
* friends) -- work done for every step of a walk which the view runs for
|
||||
* every bridge cell it paints and measures.
|
||||
* @param real_t : the terminal to look at, may be null (no previous one)
|
||||
* @return the data the walk needs, everything else left as it defaults
|
||||
*/
|
||||
static modelRealTerminalData bridgeWalkStep(const QSharedPointer<RealTerminal> &real_t)
|
||||
{
|
||||
modelRealTerminalData mrtd;
|
||||
if (!real_t.isNull())
|
||||
{
|
||||
for (const auto & rtd_ : std::as_const(ptd_.real_data)) {
|
||||
++current_checked_index;
|
||||
if (current_checked_index == index) {
|
||||
return rtd_;
|
||||
}
|
||||
}
|
||||
mrtd.level_ = real_t->level();
|
||||
mrtd.bridged_ = real_t->isBridged();
|
||||
mrtd.real_terminal = real_t.toWeakRef();
|
||||
}
|
||||
|
||||
return modelRealTerminalData();
|
||||
return mrtd;
|
||||
}
|
||||
|
||||
QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
@@ -853,6 +870,15 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
//Without a bridge on this strip there is nothing to draw in the
|
||||
//bridge cells: the check at the end of this function ends on an
|
||||
//empty pixmap, and every step of the walk below to reach it used
|
||||
//to fill the row data of a terminal (which reads the cables of the
|
||||
//whole project). Skip the walk when there is no bridge at all.
|
||||
if (!m_terminal_strip->hasBridges()) {
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
auto mrtd = modelRealTerminalDataForIndex(index);
|
||||
|
||||
//Terminal level correspond to the column level of index
|
||||
@@ -891,22 +917,30 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
//Terminal level ins't in the same column level of index
|
||||
//Check if we need to draw a none bridge pixmap
|
||||
|
||||
//Check previous
|
||||
auto phy_t = mrtd.real_terminal.toStrongRef()->physicalTerminal();
|
||||
auto current_real_terminal = mrtd;
|
||||
auto current_phy_uuid = phy_t->uuid();
|
||||
//Check previous. The rows of the table hold the terminals of the
|
||||
//strip in strip order, so the neighbour of a row is the row next
|
||||
//to it: asking the strip for its neighbour walked to the head of
|
||||
//the strip every time, for every bridge cell the view paints and
|
||||
//measures (and on every layout of the table).
|
||||
auto current_row = index.row();
|
||||
auto current_t = mrtd.real_terminal.toStrongRef();
|
||||
auto current_phy_uuid = current_t->physicalTerminal()->uuid();
|
||||
bool already_jumped_to_previous = false;
|
||||
modelRealTerminalData previous_data;
|
||||
|
||||
do {
|
||||
current_real_terminal = modelRealTerminalData::data(m_terminal_strip->previousRealTerminal(current_real_terminal.real_terminal));
|
||||
--current_row;
|
||||
const auto previous_t = current_row >= 0
|
||||
? dataAtRow(current_row).real_terminal.toStrongRef()
|
||||
: QSharedPointer<RealTerminal>();
|
||||
const auto current_real_terminal = bridgeWalkStep(previous_t);
|
||||
|
||||
if (current_real_terminal.level_ == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
//We are in the same physical terminal as previous loop
|
||||
if (current_phy_uuid == current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid())
|
||||
if (current_phy_uuid == previous_t->physicalTerminal()->uuid())
|
||||
{
|
||||
if (current_real_terminal.bridged_ &&
|
||||
current_real_terminal.level_ == level_column) {
|
||||
@@ -918,7 +952,7 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
break;
|
||||
} else {
|
||||
already_jumped_to_previous = true;
|
||||
current_phy_uuid = current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid();
|
||||
current_phy_uuid = previous_t->physicalTerminal()->uuid();
|
||||
if (current_real_terminal.bridged_ &&
|
||||
current_real_terminal.level_ == level_column) {
|
||||
previous_data = current_real_terminal;
|
||||
@@ -928,20 +962,24 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
} while(true);
|
||||
|
||||
//Check next
|
||||
current_real_terminal = mrtd;
|
||||
current_phy_uuid = phy_t->uuid();
|
||||
current_row = index.row();
|
||||
current_phy_uuid = current_t->physicalTerminal()->uuid();
|
||||
bool already_jumped_to_next = false;
|
||||
modelRealTerminalData next_data;
|
||||
|
||||
do {
|
||||
current_real_terminal = modelRealTerminalData::data(m_terminal_strip->nextRealTerminal(current_real_terminal.real_terminal));
|
||||
++current_row;
|
||||
const auto next_t = current_row < m_row_count
|
||||
? dataAtRow(current_row).real_terminal.toStrongRef()
|
||||
: QSharedPointer<RealTerminal>();
|
||||
const auto current_real_terminal = bridgeWalkStep(next_t);
|
||||
|
||||
if (current_real_terminal.level_ == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
//We are in the same physical terminal as previous loop
|
||||
if (current_phy_uuid == current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid())
|
||||
if (current_phy_uuid == next_t->physicalTerminal()->uuid())
|
||||
{
|
||||
if (current_real_terminal.bridged_ &&
|
||||
current_real_terminal.level_ == level_column) {
|
||||
@@ -953,7 +991,7 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const
|
||||
break;
|
||||
} else {
|
||||
already_jumped_to_next = true;
|
||||
current_phy_uuid = current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid();
|
||||
current_phy_uuid = next_t->physicalTerminal()->uuid();
|
||||
if (current_real_terminal.bridged_ &&
|
||||
current_real_terminal.level_ == level_column) {
|
||||
next_data = current_real_terminal;
|
||||
@@ -1086,10 +1124,59 @@ QWidget *TerminalStripModelDelegate::createEditor(QWidget *parent, const QStyleO
|
||||
|
||||
return qcb;
|
||||
}
|
||||
if (index.column() == CABLE_TARGET_CELL) {
|
||||
//The Target cell holds one line by field, so it takes a multi
|
||||
//line editor : Return writes a new line (the cell gives its
|
||||
//text back when the focus goes somewhere else), Ctrl+Return
|
||||
//hands it over at once, Escape throws the text away
|
||||
auto te = new QPlainTextEdit(parent);
|
||||
te->setObjectName(QStringLiteral("terminal_cable_target"));
|
||||
te->setFrameStyle(QFrame::NoFrame);
|
||||
te->setTabChangesFocus(true);
|
||||
te->setToolTip(tr("One line per field: Installation, Location, Label (BMK).\n"
|
||||
"Ctrl+Return gives the cell back, Escape throws the text away."));
|
||||
te->installEventFilter(const_cast<TerminalStripModelDelegate *>(this));
|
||||
|
||||
return te;
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::createEditor(parent, option, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief TerminalStripModelDelegate::eventFilter
|
||||
* The filter taken on by the Target cell editor: only the key which is
|
||||
* handled here is the one giving the text back to the table. The filter
|
||||
* of the base class is left aside for that cell on purpose, it is written
|
||||
* for one line editors and would take Return away from the text.
|
||||
* @param object the editor of the cell
|
||||
* @param event the event sent to it
|
||||
* @return true when the event is taken by this delegate
|
||||
*/
|
||||
bool TerminalStripModelDelegate::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if (object && object->objectName() == QLatin1String("terminal_cable_target"))
|
||||
{
|
||||
if (event && event->type() == QEvent::KeyPress)
|
||||
{
|
||||
const auto key_event = static_cast<QKeyEvent *>(event);
|
||||
if ((key_event->key() == Qt::Key_Return ||
|
||||
key_event->key() == Qt::Key_Enter) &&
|
||||
key_event->modifiers().testFlag(Qt::ControlModifier))
|
||||
{
|
||||
auto editor_ = qobject_cast<QWidget *>(object);
|
||||
emit commitData(editor_);
|
||||
emit closeEditor(editor_, QAbstractItemDelegate::NoHint);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void TerminalStripModelDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
if (index.isValid() &&
|
||||
@@ -1172,6 +1259,17 @@ void TerminalStripModelDelegate::setEditorData(QWidget *editor, const QModelInde
|
||||
return;
|
||||
}
|
||||
|
||||
if (index.isValid() &&
|
||||
editor->objectName() == QLatin1String("terminal_cable_target"))
|
||||
{
|
||||
if (auto te = dynamic_cast<QPlainTextEdit *>(editor)) {
|
||||
//The cell shows one line by field: the editor gets them
|
||||
//back exactly as they are, nothing joined, nothing split
|
||||
te->setPlainText(index.data(Qt::EditRole).toString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QStyledItemDelegate::setEditorData(editor, index);
|
||||
}
|
||||
|
||||
@@ -1212,6 +1310,14 @@ void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemMode
|
||||
model->setData(index, qcb->currentData(), Qt::EditRole);
|
||||
}
|
||||
}
|
||||
else if (editor->objectName() == QLatin1String("terminal_cable_target"))
|
||||
{
|
||||
if (auto te = dynamic_cast<QPlainTextEdit *>(editor)) {
|
||||
//The lines are handed over as they were written, so a
|
||||
//" / " inside a field is part of it and never a separator
|
||||
model->setData(index, te->toPlainText(), Qt::EditRole);
|
||||
}
|
||||
}
|
||||
else {
|
||||
QStyledItemDelegate::setModelData(editor, model, index);
|
||||
}
|
||||
|
||||
@@ -58,14 +58,15 @@ class TerminalStripModel : public QAbstractTableModel
|
||||
XRef = 8,
|
||||
Cable = 9,
|
||||
CableWire = 10,
|
||||
Type = 11,
|
||||
Function = 12,
|
||||
Led = 13,
|
||||
Shield = 14,
|
||||
Color = 15,
|
||||
Section = 16,
|
||||
Width = 17,
|
||||
Separator = 18,
|
||||
CableTarget = 11,
|
||||
Type = 12,
|
||||
Function = 13,
|
||||
Led = 14,
|
||||
Shield = 15,
|
||||
Color = 16,
|
||||
Section = 17,
|
||||
Width = 18,
|
||||
Separator = 19,
|
||||
Invalid = 99
|
||||
};
|
||||
|
||||
@@ -106,10 +107,24 @@ class TerminalStripModel : public QAbstractTableModel
|
||||
QPixmap bridgePixmapFor(const QModelIndex &index) const;
|
||||
|
||||
private:
|
||||
//Where the data of one row of the table sit in m_physical_data.
|
||||
//The view asks for every cell of every row, over and over (paint,
|
||||
//flags, row height), so looking a row up must be a lookup and not
|
||||
//a walk through the whole strip each time.
|
||||
struct RowPlace
|
||||
{
|
||||
int physical_; //index in m_physical_data
|
||||
int real_; //index in the real_data of that physical terminal
|
||||
};
|
||||
|
||||
QPointer<TerminalStrip> m_terminal_strip;
|
||||
//Modified cells of each real terminal (spare terminals have no element)
|
||||
QHash<const RealTerminal *, QVector<bool>> m_modified_cell;
|
||||
QVector<modelPhysicalTerminalData> m_physical_data;
|
||||
//One RowPlace by row, filled with m_physical_data (see fillPhysicalTerminalData)
|
||||
QVector<RowPlace> m_row_index;
|
||||
//Number of rows held in m_row_index, so rowCount() needs no walk
|
||||
int m_row_count = 0;
|
||||
struct BridgePixmap
|
||||
{
|
||||
QPixmap top_,
|
||||
@@ -140,6 +155,7 @@ class TerminalStripModelDelegate : public QStyledItemDelegate
|
||||
QWidget *editor,
|
||||
QAbstractItemModel *model,
|
||||
const QModelIndex &index) const override;
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
|
||||
@@ -3314,8 +3314,9 @@ bool QetScriptApi::bridgeTerminals(int stripIndex, const QVariantList &realTermi
|
||||
|
||||
/**
|
||||
@brief QetScriptApi::sortTerminalStrip
|
||||
Reorder a strip's physical positions into the canonical order the
|
||||
editor's own "sort" button computes, through SortTerminalStripCommand.
|
||||
Reorder a strip's physical positions into the order the plan is
|
||||
drawn in -- folio by folio, then left to right -- the same
|
||||
SortTerminalStripCommand the editor's own "sort" button pushes.
|
||||
*/
|
||||
bool QetScriptApi::sortTerminalStrip(int stripIndex)
|
||||
{
|
||||
|
||||
@@ -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