Tell the fields of a target apart by line breaks only

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

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

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

tst_terminalcableauto reads the three fields of a target of the
drawing back one line each, checks a "/" typed inside a field stays
part of it -- one line holding it, slashes and all -- checks the
editor of the cell is the multi line one, hands its text back
untouched, gives the cell back on Ctrl+Return while Return keeps
writing a new line, and checks the rows grow with the lines the cell
holds and shrink back afterwards. tst_cabletarget paints a strip whose
cable ends on a field containing a slash: the box is no deeper than
with one plain field.
This commit is contained in:
Kellermorph
2026-10-20 19:08:16 +02:00
parent 5baf5bec60
commit 36b1cb08ff
7 changed files with 342 additions and 46 deletions
+11 -7
View File
@@ -413,8 +413,10 @@ bool RealTerminal::isShield() const {
* 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 the way the fields are shown in the column,
* "Installation / Location / Label", and gives one line per field.
* 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
@@ -430,13 +432,15 @@ QVector<QString> RealTerminal::cableTarget() const
.value(cableTargetInfoKey()).toString()};
if (!typed_.isEmpty())
{
//Written the way the fields are shown in the column,
//"Installation / Location / Label": the box draws one line
//per field, without the separators, like it does for a
//target worked out from the project
//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_
: typed_.split(QStringLiteral(" / "), Qt::SkipEmptyParts)) {
: text_.split(QLatin1Char('\n'), Qt::SkipEmptyParts)) {
lines_ << field_;
}
return lines_;
+7 -5
View File
@@ -27,10 +27,12 @@
struct modelRealTerminalData
{
///The Installation, Location and Label (BMK) of the piece the cable
///lands on, joined in one line : what the strip draws in the box
///under its cable (RealTerminal::cableTarget), in the same order.
///A target typed in by hand is one single line, and nothing is
///shown when the cable lands on nothing worth naming and no text
///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_)
{
@@ -41,7 +43,7 @@ struct modelRealTerminalData
fields_ << field_;
}
}
return fields_.join(QStringLiteral(" / "));
return fields_.join(QLatin1Char('\n'));
}
static modelRealTerminalData data(const QSharedPointer<RealTerminal> &real_t)
@@ -253,6 +253,14 @@ void TerminalStripEditor::setCurrentStrip(TerminalStrip *strip_)
//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);
}
@@ -880,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'));
}
@@ -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
@@ -517,8 +520,9 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation
orientation == Qt::Horizontal &&
section == CABLE_TARGET_CELL)
{
//Same three fields as the box drawn under the cable
return tr("Installation, Location and Label (BMK) of the piece the cable lands on, past this strip");
//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();
@@ -1128,10 +1132,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() &&
@@ -1214,6 +1267,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);
}
@@ -1254,6 +1318,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);
}
@@ -141,6 +141,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;