mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-11 14:44:15 +02:00
bf9b34398d
Cables are drawn line objects (toolbar button next to the auto break/reconnect button, click-move-click, right click cancels), not components. The type comes from a CSV catalog following the material list pattern (designation, cores, core colours, editable through the entry dialog with "Nouvelle entrée" / "Modifier l'entrée"). Each core is bound to the wire its colour label stands on, and the cable reference is generated per core and written into the Conductor entry via Conductor::setCableReference(label, cable uuid, slot). The conductor entry is where it has to live because the terminal strip plan (Klemmenplan) consumes it in the next step. Numbering: "Cables" rule under Programmeinstellungen/Neues Projekt/Nummerierung auto (mirrored in project properties), literal "W" fallback without a rule, French gate dialog when no rule exists, Nummerierung menu with the renumber dialog (preferred axis, per-folio counter, rule and counter state written back, hand-typed names ask, whole-project scope). Also included: label block and under-line texts with per-text font and alignment plus the material-list cell margins, free-core placement and right-click removal, claim questions for wires of other cables, multi-selection drag as one undo step, type change with confirmation, cross-references for type "Cable" (%f-%l%c, own font, clickable links in exported PDFs), report propagation of the cable definition, Listes menu (table of contents, material list, terminal strip manager, terminal generator plugin, cable list), cable list with freely selectable columns (installation/localisation of cable, start and end, Blatt start/end, length, used cores...), CSV export dialog with column pick and preview in the Projekt menu, script API (addTable kind "cable_list", exportCableList) and a unit test for the type catalog.
1408 lines
44 KiB
C++
1408 lines
44 KiB
C++
/*
|
|
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 "cablecreatedialog.h"
|
|
|
|
#include "editcablecommand.h"
|
|
#include "../diagram.h"
|
|
#include "../qetgraphicsitem/conductor.h"
|
|
#include "../qetproject.h"
|
|
|
|
#include <QAbstractItemView>
|
|
#include <QBrush>
|
|
#include <QCheckBox>
|
|
#include <QCoreApplication>
|
|
#include <QDialogButtonBox>
|
|
#include <QFormLayout>
|
|
#include <QGridLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QItemSelectionModel>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
#include <QModelIndex>
|
|
#include <QPushButton>
|
|
#include <QSpinBox>
|
|
#include <QStandardItem>
|
|
#include <QStandardItemModel>
|
|
#include <QTableView>
|
|
#include <QTabWidget>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <utility>
|
|
|
|
/**
|
|
@brief CableFilterProxy::CableFilterProxy
|
|
@param parent
|
|
*/
|
|
CableFilterProxy::CableFilterProxy(QObject *parent) :
|
|
QSortFilterProxyModel(parent)
|
|
{
|
|
}
|
|
|
|
/**
|
|
@brief CableFilterProxy::setTokens
|
|
@param tokens every word the user typed, all of which have to be found
|
|
*/
|
|
void CableFilterProxy::setTokens(const QStringList &tokens)
|
|
{
|
|
if (m_tokens == tokens) return;
|
|
m_tokens = tokens;
|
|
beginFilterChange();
|
|
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
|
}
|
|
|
|
/**
|
|
@brief CableFilterProxy::setHideUnsuitable
|
|
@param hide true to keep the rows which are of no use out of sight,
|
|
false to show them -- struck through while a line was just drawn,
|
|
plain while a type is being changed, where the loss was explained
|
|
instead of being refused
|
|
*/
|
|
void CableFilterProxy::setHideUnsuitable(bool hide)
|
|
{
|
|
if (m_hide_unsuitable == hide) return;
|
|
m_hide_unsuitable = hide;
|
|
beginFilterChange();
|
|
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
|
}
|
|
|
|
/**
|
|
@brief CableFilterProxy::filterAcceptsRow
|
|
@param source_row
|
|
@param source_parent
|
|
@return
|
|
*/
|
|
bool CableFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
|
{
|
|
const QModelIndex first = sourceModel()->index(source_row, 0, source_parent);
|
|
|
|
if (m_hide_unsuitable && first.data(UnsuitableRole).toBool()) {
|
|
return false;
|
|
}
|
|
if (m_tokens.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
const int columns = sourceModel()->columnCount();
|
|
for (const QString &token : m_tokens)
|
|
{
|
|
bool found = false;
|
|
for (int column = 0; column < columns; ++column)
|
|
{
|
|
const QString value = sourceModel()->index(source_row, column, source_parent)
|
|
.data(Qt::DisplayRole).toString();
|
|
if (value.contains(token, Qt::CaseInsensitive))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::CableCreateDialog
|
|
@param diagram the folio the line was drawn on
|
|
@param crossings the conductors that line runs across, in drawing order
|
|
@param part the section which will carry the trunk line and its slashes
|
|
@param parent
|
|
*/
|
|
CableCreateDialog::CableCreateDialog(Diagram *diagram,
|
|
const QList<CableCrossing> &crossings,
|
|
const CablePartData &part,
|
|
QWidget *parent) :
|
|
QDialog(parent),
|
|
m_diagram(diagram),
|
|
m_crossings(crossings),
|
|
m_part(part)
|
|
{
|
|
build();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::CableCreateDialog
|
|
@param diagram the folio whose undo stack takes the change
|
|
@param cable the cable whose type is being changed
|
|
@param parent
|
|
*/
|
|
CableCreateDialog::CableCreateDialog(Diagram *diagram,
|
|
Cable *cable,
|
|
QWidget *parent) :
|
|
QDialog(parent),
|
|
m_diagram(diagram),
|
|
m_cable(cable),
|
|
m_change(cable)
|
|
{
|
|
build();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::build
|
|
Everything the two modes share: the header saying what this window
|
|
is about, the tabs, the property fields and the buttons. What the
|
|
header says and which tabs are built is where they part company --
|
|
a window which settles a line just drawn knows how many conductors
|
|
that line crossed, one which changes a cable knows how many cores
|
|
that cable has.
|
|
*/
|
|
void CableCreateDialog::build()
|
|
{
|
|
setWindowTitle(tr("Câble"));
|
|
|
|
auto *layout = new QVBoxLayout(this);
|
|
|
|
if (changeMode())
|
|
{
|
|
//What this window is for, and how many cores the cable has
|
|
//right now: the type he picks is measured against that number.
|
|
const QString name = m_change->type().isEmpty()
|
|
? tr("(aucun type)") : m_change->type();
|
|
m_header = new QLabel(tr("Changer le type : %1, %n âme(s)",
|
|
"which type this window changes, and how many cores that cable has now",
|
|
m_change->coreCount()).arg(name), this);
|
|
}
|
|
else
|
|
{
|
|
m_header = new QLabel(tr("%n âme(s) reconnue(s)",
|
|
"how many cores the line just crossed",
|
|
m_crossings.size()), this);
|
|
}
|
|
QFont header_font = m_header->font();
|
|
header_font.setBold(true);
|
|
m_header->setFont(header_font);
|
|
layout->addWidget(m_header);
|
|
|
|
m_tabs = new QTabWidget(this);
|
|
layout->addWidget(m_tabs);
|
|
|
|
buildNewTab();
|
|
//A window changing an existing cable has no line just drawn, so
|
|
//there is no other cable to hand that line over to: the second
|
|
//tab would only offer cables this one is to be replaced by,
|
|
//which is not what changing a type means.
|
|
if (!changeMode()) {
|
|
buildExistingTab();
|
|
}
|
|
buildPropertyRow();
|
|
layout->addWidget(m_property_row);
|
|
|
|
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
|
connect(m_buttons, &QDialogButtonBox::accepted, this, &CableCreateDialog::accept);
|
|
connect(m_buttons, &QDialogButtonBox::rejected, this, &CableCreateDialog::reject);
|
|
layout->addWidget(m_buttons);
|
|
|
|
connect(m_tabs, &QTabWidget::currentChanged, this, [this]() {selectionChanged();});
|
|
|
|
loadTypeFile();
|
|
refreshExisting();
|
|
selectionChanged();
|
|
|
|
resize(780, 540);
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::cell
|
|
One read only cell of one of the two tables. A row which cannot be
|
|
used is drawn struck through and greyed out, so the checkbox which
|
|
keeps such rows visible never leaves the user to guess why they are
|
|
there.
|
|
@param value
|
|
@param unsuitable
|
|
@return
|
|
*/
|
|
QStandardItem *CableCreateDialog::cell(const QString &value, bool unsuitable) const
|
|
{
|
|
auto *item = new QStandardItem(value);
|
|
item->setEditable(false);
|
|
if (unsuitable)
|
|
{
|
|
QFont font = item->font();
|
|
font.setStrikeOut(true);
|
|
item->setFont(font);
|
|
item->setForeground(QBrush(palette().color(QPalette::Disabled, QPalette::Text)));
|
|
}
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::buildNewTab
|
|
The tab choosing the type of a brand new cable, read from the cable
|
|
type file.
|
|
*/
|
|
void CableCreateDialog::buildNewTab()
|
|
{
|
|
auto *page = new QWidget(m_tabs);
|
|
auto *layout = new QVBoxLayout(page);
|
|
layout->setContentsMargins(4, 4, 4, 4);
|
|
|
|
auto *search_row = new QHBoxLayout;
|
|
search_row->addWidget(new QLabel(tr("Rechercher :"), page));
|
|
m_new_search = new QLineEdit(page);
|
|
m_new_search->setClearButtonEnabled(true);
|
|
m_new_search->setPlaceholderText(tr("Filtrer les types de câbles"));
|
|
search_row->addWidget(m_new_search, 1);
|
|
layout->addLayout(search_row);
|
|
|
|
//What the checkbox hides is what cannot be taken: too few cores
|
|
//to carry the line which was just drawn, and -- when a type is
|
|
//being changed rather than drawn -- too few cores to keep this
|
|
//cable as it stands, since taking such a type takes cores off it.
|
|
m_new_hide = new QCheckBox(changeMode()
|
|
? tr("Masquer les types qui ont moins d'âmes "
|
|
"que ce câble")
|
|
: tr("Masquer les types qui ont moins d'âmes "
|
|
"que de conducteurs reconnus"), page);
|
|
m_new_hide->setChecked(true);
|
|
layout->addWidget(m_new_hide);
|
|
|
|
//Said up front rather than only when a struck through row is
|
|
//picked: the user sees the lines crossed out the moment the
|
|
//checkbox shows them, and asks why straight away.
|
|
auto *why = new QLabel(changeMode()
|
|
? tr("Décochez pour voir aussi les types qui ont "
|
|
"moins d'âmes : en les prenant, les âmes "
|
|
"au-delà de leur nombre quittent le câble.")
|
|
: tr("Une ligne barrée est un type qui a moins "
|
|
"d'âmes que de conducteurs reconnus : il ne "
|
|
"peut pas porter ce câble."), page);
|
|
why->setWordWrap(true);
|
|
layout->addWidget(why);
|
|
|
|
m_new_model = new QStandardItemModel(this);
|
|
m_new_proxy = new CableFilterProxy(this);
|
|
m_new_proxy->setSourceModel(m_new_model);
|
|
|
|
m_new_table = new QTableView(page);
|
|
m_new_table->setModel(m_new_proxy);
|
|
m_new_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
m_new_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
m_new_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
m_new_table->setAlternatingRowColors(true);
|
|
m_new_table->setSortingEnabled(true);
|
|
m_new_table->horizontalHeader()->setStretchLastSection(true);
|
|
m_new_table->verticalHeader()->setVisible(false);
|
|
layout->addWidget(m_new_table, 1);
|
|
|
|
m_new_note = new QLabel(page);
|
|
m_new_note->setWordWrap(true);
|
|
m_new_note->setVisible(false);
|
|
layout->addWidget(m_new_note);
|
|
|
|
//A new line for the cable type file, filled in the way the
|
|
//material window fills in one of its own articles: he adds the
|
|
//type right here rather than leaving the window to find out how.
|
|
//Beside it, the button rewriting the line he picked -- which
|
|
//stays out of reach until a line really is picked, since there
|
|
//would then be nothing to rewrite.
|
|
auto *add_row = new QHBoxLayout;
|
|
add_row->addStretch(1);
|
|
auto *add_button = new QPushButton(tr("Nouvelle entrée"), page);
|
|
add_button->setToolTip(tr("Ajouter un nouveau type de câble à la liste"));
|
|
connect(add_button, &QPushButton::clicked,
|
|
this, &CableCreateDialog::addTypeRecord);
|
|
add_row->addWidget(add_button);
|
|
|
|
m_edit_button = new QPushButton(tr("Modifier l'entrée"), page);
|
|
m_edit_button->setToolTip(tr("Modifier le type de câble sélectionné dans "
|
|
"la liste."));
|
|
m_edit_button->setEnabled(false);
|
|
connect(m_edit_button, &QPushButton::clicked,
|
|
this, &CableCreateDialog::editTypeRecord);
|
|
add_row->addWidget(m_edit_button);
|
|
|
|
layout->addLayout(add_row);
|
|
|
|
m_tabs->addTab(page, tr("Nouveau câble"));
|
|
|
|
connect(m_new_search, &QLineEdit::textChanged, this, [this](const QString &text) {
|
|
m_new_proxy->setTokens(text.split(QLatin1Char(' '), Qt::SkipEmptyParts));
|
|
});
|
|
connect(m_new_hide, &QCheckBox::toggled, m_new_proxy, &CableFilterProxy::setHideUnsuitable);
|
|
connect(m_new_table->selectionModel(), &QItemSelectionModel::currentChanged,
|
|
this, [this]() {selectionChanged();});
|
|
//A double click settles the choice, exactly the way clicking
|
|
//OK would: he has come to pick that cable, and making him press
|
|
//OK after every pick is one click too many. A row which cannot
|
|
//carry this cable goes on being refused the same way it is
|
|
//refused by that button, greyed out as it is.
|
|
connect(m_new_table, &QTableView::doubleClicked, this, [this]() {
|
|
if (m_buttons->button(QDialogButtonBox::Ok)->isEnabled()) {
|
|
accept();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::buildExistingTab
|
|
The tab giving more cores to a cable the project already holds. That
|
|
cable keeps its type, its designation, its installation and its
|
|
location: choosing it is what inherits them.
|
|
*/
|
|
void CableCreateDialog::buildExistingTab()
|
|
{
|
|
auto *page = new QWidget(m_tabs);
|
|
auto *layout = new QVBoxLayout(page);
|
|
layout->setContentsMargins(4, 4, 4, 4);
|
|
|
|
auto *search_row = new QHBoxLayout;
|
|
search_row->addWidget(new QLabel(tr("Rechercher :"), page));
|
|
m_existing_search = new QLineEdit(page);
|
|
m_existing_search->setClearButtonEnabled(true);
|
|
m_existing_search->setPlaceholderText(tr("Filtrer les câbles existants"));
|
|
search_row->addWidget(m_existing_search, 1);
|
|
layout->addLayout(search_row);
|
|
|
|
m_existing_hide = new QCheckBox(tr("Masquer les câbles qui ont moins "
|
|
"d'adresses libres que de conducteurs reconnus"), page);
|
|
m_existing_hide->setChecked(true);
|
|
layout->addWidget(m_existing_hide);
|
|
|
|
//Same as on the first tab: the reason a line is crossed out is
|
|
//said where the crossed out lines appear, not only afterwards.
|
|
auto *why = new QLabel(tr("Une ligne barrée est un câble qui a moins "
|
|
"d'adresses libres que de conducteurs "
|
|
"reconnus : il ne peut pas les recevoir."), page);
|
|
why->setWordWrap(true);
|
|
layout->addWidget(why);
|
|
|
|
m_existing_model = new QStandardItemModel(this);
|
|
m_existing_proxy = new CableFilterProxy(this);
|
|
m_existing_proxy->setSourceModel(m_existing_model);
|
|
|
|
m_existing_table = new QTableView(page);
|
|
m_existing_table->setModel(m_existing_proxy);
|
|
m_existing_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
m_existing_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
m_existing_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
m_existing_table->setAlternatingRowColors(true);
|
|
m_existing_table->setSortingEnabled(true);
|
|
m_existing_table->horizontalHeader()->setStretchLastSection(true);
|
|
m_existing_table->verticalHeader()->setVisible(false);
|
|
layout->addWidget(m_existing_table, 1);
|
|
|
|
m_existing_note = new QLabel(page);
|
|
m_existing_note->setWordWrap(true);
|
|
m_existing_note->setVisible(false);
|
|
layout->addWidget(m_existing_note);
|
|
|
|
m_tabs->addTab(page, tr("Câbles existants"));
|
|
|
|
connect(m_existing_search, &QLineEdit::textChanged, this, [this](const QString &text) {
|
|
m_existing_proxy->setTokens(text.split(QLatin1Char(' '), Qt::SkipEmptyParts));
|
|
});
|
|
connect(m_existing_hide, &QCheckBox::toggled, m_existing_proxy, &CableFilterProxy::setHideUnsuitable);
|
|
connect(m_existing_table->selectionModel(), &QItemSelectionModel::currentChanged,
|
|
this, [this]() {selectionChanged();});
|
|
//A double click settles the choice here too: the cable goes
|
|
//straight onto the line he has just drawn, without a stop at
|
|
//the OK button on the way -- and a cable with no room left for
|
|
//these cores goes on being refused, since that button is greyed
|
|
//out for it.
|
|
connect(m_existing_table, &QTableView::doubleClicked, this, [this]() {
|
|
if (m_buttons->button(QDialogButtonBox::Ok)->isEnabled()) {
|
|
accept();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::buildPropertyRow
|
|
The properties of the cable itself, which belong to it rather than to
|
|
one of its parts: whatever is typed here applies to the whole cable.
|
|
|
|
There is one single field for the number of the cable, not one per
|
|
name it goes by: the Désignation is the tag of the cable -- the thing
|
|
one is tempted to call its BMK -- and it is what gets drawn next to
|
|
the line and written into the conductors.
|
|
*/
|
|
void CableCreateDialog::buildPropertyRow()
|
|
{
|
|
m_property_row = new QWidget(this);
|
|
auto *form = new QFormLayout(m_property_row);
|
|
form->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_designation = new QLineEdit(m_property_row);
|
|
m_installation = new QLineEdit(m_property_row);
|
|
m_location = new QLineEdit(m_property_row);
|
|
|
|
form->addRow(tr("Désignation"), m_designation);
|
|
form->addRow(tr("Installation (=)"), m_installation);
|
|
form->addRow(tr("Localisation (+)"), m_location);
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::loadTypeFile
|
|
Read the cable type file the same way the material file is read, and
|
|
fill the first tab with it. A file which is not there yet simply
|
|
leaves the tab empty and says so.
|
|
*/
|
|
void CableCreateDialog::loadTypeFile()
|
|
{
|
|
m_new_model->clear();
|
|
|
|
QString path = CableTypeList::configuredPath();
|
|
if (path.isEmpty()) {
|
|
path = CableTypeList::defaultPath();
|
|
}
|
|
m_types = CableTypeListData();
|
|
CableTypeList::load(path, &m_types);
|
|
|
|
m_new_model->setHorizontalHeaderLabels(CableTypeList::translatedHeader(m_types.columns));
|
|
|
|
const int needed = neededCores();
|
|
int current_row = -1;
|
|
for (const CableTypeRecord &record : std::as_const(m_types.records))
|
|
{
|
|
const int cores = CableTypeList::coreCount(record);
|
|
//A type saying how many cores it has, but fewer than the line
|
|
//crosses, cannot carry the cable: too few cores were chosen.
|
|
//When a type is being changed, such a row is not refused --
|
|
//he may well want a cable with fewer cores -- but it is the
|
|
//one the checkbox hides, and the one which has something to
|
|
//say about what it takes off the cable.
|
|
const bool fewer = cores > 0 && cores < needed;
|
|
const bool unsuitable = fewer && !changeMode();
|
|
if (changeMode() && !m_change->type().isEmpty()
|
|
&& CableTypeList::designation(record) == m_change->type()) {
|
|
current_row = m_types.records.indexOf(record);
|
|
}
|
|
|
|
QList<QStandardItem *> items;
|
|
items.reserve(m_types.columns.size());
|
|
for (const QString &column : std::as_const(m_types.columns))
|
|
{
|
|
items.append(cell(record.value(column), unsuitable));
|
|
}
|
|
if (!items.isEmpty())
|
|
{
|
|
items.first()->setData(fewer, CableFilterProxy::UnsuitableRole);
|
|
m_new_model->appendRow(items);
|
|
}
|
|
}
|
|
|
|
//The type the cable already has is the one he came here to
|
|
//change, so it is the one standing under his finger from the
|
|
//start -- and OK is enabled with something to say yes to.
|
|
if (current_row >= 0 && m_new_proxy)
|
|
{
|
|
const QModelIndex source = m_new_model->index(current_row, 0);
|
|
m_new_table->setCurrentIndex(m_new_proxy->mapFromSource(source));
|
|
m_new_table->scrollTo(m_new_table->currentIndex());
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::refreshExisting
|
|
Fill the second tab with the cables the project already holds,
|
|
saying how many of their cores are wired and how many are left.
|
|
*/
|
|
void CableCreateDialog::refreshExisting()
|
|
{
|
|
//No second tab when a type is being changed, so nothing to fill
|
|
//-- and nothing was built which could be filled either.
|
|
if (changeMode()) return;
|
|
|
|
m_existing_model->clear();
|
|
m_existing.clear();
|
|
|
|
if (!m_diagram || !m_diagram->project()) return;
|
|
|
|
m_existing_model->setHorizontalHeaderLabels({
|
|
tr("Désignation"),
|
|
tr("Type"),
|
|
tr("Âmes"),
|
|
tr("Installation"),
|
|
tr("Localisation")
|
|
});
|
|
|
|
const int needed = m_crossings.size();
|
|
for (Cable *cable : m_diagram->project()->cables())
|
|
{
|
|
if (!cable) continue;
|
|
|
|
const bool unsuitable = cable->freeCoreCount() < needed;
|
|
QList<QStandardItem *> items;
|
|
items << cell(cable->designation(), unsuitable)
|
|
<< cell(cable->type(), unsuitable)
|
|
<< cell(tr("%1 / %2")
|
|
.arg(cable->usedCoreCount())
|
|
.arg(cable->coreCount()), unsuitable)
|
|
<< cell(cable->installation(), unsuitable)
|
|
<< cell(cable->location(), unsuitable);
|
|
items.first()->setData(unsuitable, CableFilterProxy::UnsuitableRole);
|
|
|
|
m_existing_model->appendRow(items);
|
|
m_existing.append(cable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::currentSourceIndex
|
|
@return the row currently chosen on whichever tab is in front, in the
|
|
coordinates of the model behind the filter
|
|
*/
|
|
QModelIndex CableCreateDialog::currentSourceIndex() const
|
|
{
|
|
const bool on_new_tab = m_tabs->currentIndex() == 0;
|
|
QTableView *table = on_new_tab ? m_new_table : m_existing_table;
|
|
CableFilterProxy *proxy = on_new_tab ? m_new_proxy : m_existing_proxy;
|
|
|
|
const QModelIndex proxy_index = table->currentIndex();
|
|
if (!proxy_index.isValid()) return QModelIndex();
|
|
return proxy->mapToSource(proxy_index);
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::currentSelectionIsUnsuitable
|
|
@return true when the chosen row is one the filter only shows when
|
|
the checkbox is off, that is one which cannot carry this cable
|
|
*/
|
|
bool CableCreateDialog::currentSelectionIsUnsuitable() const
|
|
{
|
|
const QModelIndex index = currentSourceIndex();
|
|
if (!index.isValid()) return false;
|
|
|
|
const bool on_new_tab = m_tabs->currentIndex() == 0;
|
|
CableFilterProxy *proxy = on_new_tab ? m_new_proxy : m_existing_proxy;
|
|
return proxy->index(index.row(), 0).data(CableFilterProxy::UnsuitableRole).toBool();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::unsuitableNote
|
|
@return what has to be said about the struck through row which was
|
|
chosen, so the user learns why it cannot be taken
|
|
*/
|
|
QString CableCreateDialog::unsuitableNote() const
|
|
{
|
|
const int needed = neededCores();
|
|
const QModelIndex index = currentSourceIndex();
|
|
if (!index.isValid()) return QString();
|
|
|
|
if (m_tabs->currentIndex() == 0)
|
|
{
|
|
const int cores = CableTypeList::coreCount(m_types.records.at(index.row()));
|
|
//Changing a type may well take a cable with fewer cores:
|
|
//nothing is refused, but what that costs has to be said out
|
|
//loud before he says yes to it.
|
|
if (changeMode())
|
|
{
|
|
return tr("Ce type a moins d'âmes que ce câble : %1 au lieu de %2. "
|
|
"Les âmes au-delà de %1 quittent le câble et leurs marques "
|
|
"disparaissent du tracé.")
|
|
.arg(cores)
|
|
.arg(needed);
|
|
}
|
|
|
|
return tr("Trop d'âmes sélectionnées : le type n'en propose que %1, "
|
|
"%2 conducteurs sont reconnus")
|
|
.arg(cores)
|
|
.arg(needed);
|
|
}
|
|
|
|
const Cable *cable = m_existing.value(index.row());
|
|
return tr("Seulement %1 adresses libres, %2 demandées")
|
|
.arg(cable ? cable->freeCoreCount() : 0)
|
|
.arg(needed);
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::selectionChanged
|
|
Show the reason a struck through row cannot be taken, keep the OK
|
|
button in step with it, and fill the property fields from whatever is
|
|
chosen -- an existing cable hands its properties over, a new one gets
|
|
its number worked out.
|
|
*/
|
|
void CableCreateDialog::selectionChanged()
|
|
{
|
|
for (QLabel *note : {m_new_note, m_existing_note})
|
|
{
|
|
if (!note) continue;
|
|
note->clear();
|
|
note->hide();
|
|
}
|
|
|
|
const bool on_new_tab = m_tabs->currentIndex() == 0;
|
|
QLabel *note = on_new_tab ? m_new_note : m_existing_note;
|
|
const QModelIndex index = currentSourceIndex();
|
|
|
|
bool acceptable = index.isValid();
|
|
|
|
if (on_new_tab && m_types.columns.isEmpty())
|
|
{
|
|
note->setText(tr("Aucun type de câble disponible : configurez le fichier "
|
|
"des types de câbles dans les préférences."));
|
|
note->show();
|
|
acceptable = false;
|
|
}
|
|
else if (acceptable && currentSelectionIsUnsuitable())
|
|
{
|
|
note->setText(unsuitableNote());
|
|
note->show();
|
|
//Changing a type may well take one with fewer cores: what
|
|
//that costs is said out loud here, and he may still take it.
|
|
acceptable = !changeMode();
|
|
}
|
|
else if (!acceptable)
|
|
{
|
|
note->setText(on_new_tab ? tr("Choisissez un type de câble.")
|
|
: tr("Choisissez un câble existant."));
|
|
note->show();
|
|
}
|
|
|
|
//A type with more cores than this cable holds now loses nothing:
|
|
//the extra ones wait in the list of cores which are not placed
|
|
//yet, and he brings them in one by one.
|
|
if (acceptable && on_new_tab && changeMode() && index.isValid()
|
|
&& index.row() < m_types.records.size())
|
|
{
|
|
const int cores = CableTypeList::coreCount(m_types.records.at(index.row()));
|
|
if (cores > m_change->coreCount())
|
|
{
|
|
note->setText(tr("%1 âme(s) au lieu de %2 : les âmes supplémentaires "
|
|
"restent sans être placées.")
|
|
.arg(cores)
|
|
.arg(m_change->coreCount()));
|
|
note->show();
|
|
}
|
|
}
|
|
|
|
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(acceptable);
|
|
|
|
//The button rewriting a line of the file works on the line he
|
|
//picked and on nothing else: greyed out while his finger has
|
|
//chosen no line yet, and greyed out on the tab holding the
|
|
//cables of the project, which are not lines of that file.
|
|
if (m_edit_button)
|
|
{
|
|
m_edit_button->setEnabled(on_new_tab && index.isValid()
|
|
&& index.row() < m_types.records.size());
|
|
}
|
|
|
|
if (!acceptable) return;
|
|
|
|
if (on_new_tab)
|
|
{
|
|
if (changeMode())
|
|
{
|
|
//The cable already has its number, its installation and
|
|
//its place: those are what it holds, not something this
|
|
//window works out again.
|
|
fillDesignation(m_change->designation(),
|
|
m_change->designationByHand());
|
|
m_installation->setText(m_change->installation());
|
|
m_location->setText(m_change->location());
|
|
return;
|
|
}
|
|
|
|
fillDesignation(m_diagram
|
|
? CableManager::nextDesignation(m_diagram->project(), m_diagram.data())
|
|
: QString(),
|
|
false);
|
|
m_installation->clear();
|
|
m_location->clear();
|
|
return;
|
|
}
|
|
|
|
if (const Cable *cable = m_existing.value(index.row()))
|
|
{
|
|
fillDesignation(cable->designation(), cable->designationByHand());
|
|
m_installation->setText(cable->installation());
|
|
m_location->setText(cable->location());
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::fillDesignation
|
|
Put a name into the field and keep hold of what this window put
|
|
there: whatever he types over it is a name he chose rather than one
|
|
the program handed out.
|
|
@param text the name to show
|
|
@param by_hand whether that name was typed in by him
|
|
*/
|
|
void CableCreateDialog::fillDesignation(const QString &text, bool by_hand)
|
|
{
|
|
m_designation->setText(text);
|
|
m_designation_prefill = text;
|
|
m_designation_prefill_by_hand = by_hand;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::nextFreeSlots
|
|
@param count how many cores are needed
|
|
@return the lowest core numbers no conductor shows yet
|
|
*/
|
|
QList<int> CableCreateDialog::nextFreeSlots(int count) const
|
|
{
|
|
QList<int> core_slots;
|
|
if (!m_cable) return core_slots;
|
|
|
|
const int total = m_cable->coreCount();
|
|
for (int core = 0; core < total && core_slots.size() < count; ++core)
|
|
{
|
|
if (!m_cable->hasCore(core)) {
|
|
core_slots.append(core);
|
|
}
|
|
}
|
|
return core_slots;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::assignCrossings
|
|
Give the recognised conductors to the cable, one after the other
|
|
along the line, so each core takes the colour the type lists at its
|
|
place. A conductor another cable was holding is taken away from it:
|
|
a piece of wire belongs to one cable only.
|
|
@param slots the core numbers being handed out, in drawing order
|
|
*/
|
|
void CableCreateDialog::assignCrossings(const QList<int> &core_slots)
|
|
{
|
|
if (!m_cable || !m_diagram || !m_diagram->project()) return;
|
|
QETProject *project = m_diagram->project();
|
|
|
|
int position = 0;
|
|
for (const CableCrossing &crossing : std::as_const(m_crossings))
|
|
{
|
|
if (position >= core_slots.size()) break;
|
|
|
|
Conductor *conductor = crossing.conductor;
|
|
if (!conductor) continue;
|
|
|
|
//Already one of this cable's cores: it keeps the core it has,
|
|
//drawing the same cable twice must not give it two.
|
|
if (conductor->properties().m_cable_uuid == m_cable->uuid())
|
|
{
|
|
++position;
|
|
continue;
|
|
}
|
|
|
|
CableManager::claimConductor(project, conductor, m_cable);
|
|
|
|
//A cable binds a conductor by its identity, so a conductor
|
|
//read back from a file written before it had a persistent one
|
|
//is given one to keep from now on.
|
|
conductor->setUuid(conductor->uuid());
|
|
|
|
CableCore core;
|
|
core.core = core_slots.at(position);
|
|
core.part = m_part.uuid;
|
|
core.conductor = conductor->uuid();
|
|
core.position = crossing.position;
|
|
m_cable->setCore(core);
|
|
++position;
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::coresTakenFromOthers
|
|
Which cores of other cables the wires this line crosses already
|
|
belong to.
|
|
|
|
A conductor is one piece of wire, so handing it to the cable being
|
|
drawn takes that core away from whichever cable held it before. The
|
|
list is worked out before anything is changed, both to be able to
|
|
ask the user first and to be able to give those cores back when he
|
|
undoes the line.
|
|
@param target the cable about to claim those conductors; a null one
|
|
is a cable which does not exist yet, and then every core another
|
|
cable holds is one which would be taken away
|
|
@return one entry per core which is about to change hands
|
|
*/
|
|
QList<CableCoreTaken> CableCreateDialog::coresTakenFromOthers(const QUuid &target) const
|
|
{
|
|
QList<CableCoreTaken> taken;
|
|
if (!m_diagram || !m_diagram->project()) return taken;
|
|
|
|
QETProject *project = m_diagram->project();
|
|
for (const CableCrossing &crossing : std::as_const(m_crossings))
|
|
{
|
|
Conductor *conductor = crossing.conductor;
|
|
if (!conductor) continue;
|
|
|
|
const auto properties = conductor->properties();
|
|
if (properties.m_cable_uuid.isNull()
|
|
|| properties.m_cable_uuid == target) {
|
|
continue;
|
|
}
|
|
|
|
Cable *previous = CableManager::cableByUuid(project, properties.m_cable_uuid);
|
|
if (!previous || !previous->hasCore(properties.m_cable_slot)) continue;
|
|
|
|
CableCoreTaken item;
|
|
item.cable = previous;
|
|
item.core = previous->core(properties.m_cable_slot);
|
|
taken.append(item);
|
|
}
|
|
return taken;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::accept
|
|
Settle the cable the drawing gets: a new one from the chosen type, or
|
|
the chosen existing one, filled with the recognised conductors.
|
|
|
|
Nothing is written before the user has been asked about the cores
|
|
other cables are about to lose: saying yes to that question has to be
|
|
the last thing he does before the change really happens, so that
|
|
saying no leaves the project exactly as it was.
|
|
*/
|
|
void CableCreateDialog::accept()
|
|
{
|
|
//Changing the type of a cable which already exists has nothing
|
|
//to do with conductors a line crosses: it takes the chosen type
|
|
//onto that cable, and that alone.
|
|
if (changeMode())
|
|
{
|
|
acceptTypeChange();
|
|
return;
|
|
}
|
|
|
|
if (!m_diagram || !m_diagram->project()) {
|
|
QDialog::reject();
|
|
return;
|
|
}
|
|
QETProject *project = m_diagram->project();
|
|
const int needed = m_crossings.size();
|
|
QList<int> core_slots;
|
|
|
|
const QModelIndex index = currentSourceIndex();
|
|
if (!index.isValid() || currentSelectionIsUnsuitable()) return;
|
|
|
|
Cable *chosen = nullptr;
|
|
if (m_tabs->currentIndex() == 0)
|
|
{
|
|
if (index.row() >= m_types.records.size()) return;
|
|
const CableTypeRecord record = m_types.records.at(index.row());
|
|
const int cores = CableTypeList::coreCount(record);
|
|
if (cores > 0 && cores < needed) return;
|
|
}
|
|
else
|
|
{
|
|
chosen = m_existing.value(index.row());
|
|
if (!chosen) return;
|
|
if (chosen->freeCoreCount() < needed) return;
|
|
}
|
|
|
|
//The wires this line crosses which another cable already holds
|
|
//are about to change hands, and the drawing already shows that
|
|
//as the label of the first cable vanishing: better to say so
|
|
//here than to let the user discover it afterwards.
|
|
m_taken = coresTakenFromOthers(chosen ? chosen->uuid() : QUuid());
|
|
if (!m_taken.isEmpty())
|
|
{
|
|
const QString text = tr(
|
|
"%n conducteur(s) traversé(s) déjà rattaché(s) à un autre câble\n\n"
|
|
"En validant, l'autre câble perdra ces âmes. Voulez-vous continuer ?",
|
|
"ask before taking cores away from another cable",
|
|
m_taken.size());
|
|
|
|
if (QMessageBox::warning(this, tr("Câble"), text,
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::No) != QMessageBox::Yes) {
|
|
m_taken.clear();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (m_tabs->currentIndex() == 0)
|
|
{
|
|
const CableTypeRecord record = m_types.records.at(index.row());
|
|
const int cores = CableTypeList::coreCount(record);
|
|
|
|
m_cable = project->newCable();
|
|
m_cable->setType(CableTypeList::designation(record));
|
|
//A type saying how many cores it has keeps its own count, so
|
|
//"5 of 8" stays true; a type saying nothing gets the number of
|
|
//conductors which were recognised, since that is all we know.
|
|
m_cable->setCoreCount(qMax(cores, needed));
|
|
m_cable->setCoreColors(CableTypeList::coreColors(record));
|
|
m_cable->setDesignation(CableManager::nextDesignation(project, m_diagram.data()));
|
|
m_cable->addPart(m_part);
|
|
core_slots = nextFreeSlots(needed);
|
|
}
|
|
else
|
|
{
|
|
m_cable = chosen;
|
|
if (!m_cable->hasPart(m_part.uuid)) {
|
|
m_cable->addPart(m_part);
|
|
}
|
|
core_slots = nextFreeSlots(needed);
|
|
}
|
|
|
|
m_cable->setDesignation(m_designation->text().trimmed());
|
|
//A name this window filled in is the program's, a name he typed
|
|
//over it is his own -- and it stays his own when he puts the
|
|
//very same name back into the field
|
|
m_cable->setDesignationByHand(
|
|
m_designation->text().trimmed() != m_designation_prefill
|
|
|| m_designation_prefill_by_hand);
|
|
m_cable->setInstallation(m_installation->text().trimmed());
|
|
m_cable->setLocation(m_location->text().trimmed());
|
|
|
|
assignCrossings(core_slots);
|
|
|
|
QDialog::accept();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::neededCores
|
|
@return how many cores whatever he picks has to hold at least: the
|
|
conductors the line just crossed, or -- when a type is being changed
|
|
-- the cores this cable holds right now
|
|
*/
|
|
int CableCreateDialog::neededCores() const
|
|
{
|
|
return changeMode() ? m_change->coreCount() : m_crossings.size();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::typeFilePath
|
|
@return where the cable type file stands, read the same way the type
|
|
tab reads it
|
|
*/
|
|
QString CableCreateDialog::typeFilePath() const
|
|
{
|
|
QString path = CableTypeList::configuredPath();
|
|
if (path.isEmpty()) {
|
|
path = CableTypeList::defaultPath();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::addTypeRecord
|
|
Fill in a new line of the cable type file, the way the material
|
|
window adds one of its own articles: the form is appended to the
|
|
file, the search is cleared so nothing hides what was just written,
|
|
and the file is read again so the new line comes back from where it
|
|
really stands -- and it is the one under his finger.
|
|
*/
|
|
void CableCreateDialog::addTypeRecord()
|
|
{
|
|
QStringList columns = m_types.columns;
|
|
if (columns.isEmpty()) {
|
|
columns = CableTypeList::defaultColumns();
|
|
}
|
|
|
|
CableTypeEntryDialog entry(columns, this);
|
|
if (entry.exec() != QDialog::Accepted) {
|
|
return;
|
|
}
|
|
|
|
const QString path = typeFilePath();
|
|
QString error;
|
|
if (!CableTypeList::appendRecord(path, entry.record(), &error))
|
|
{
|
|
QMessageBox::critical(this,
|
|
tr("Écriture impossible"),
|
|
tr("Impossible d'écrire dans le fichier :\n%1\n%2")
|
|
.arg(path, error));
|
|
return;
|
|
}
|
|
|
|
//A search hiding the line he just filled in would leave him
|
|
//with a form he completed and no line to see.
|
|
m_new_search->clear();
|
|
loadTypeFile();
|
|
|
|
const CableTypeRecord written = entry.record();
|
|
for (int row = 0; row < m_types.records.size(); ++row)
|
|
{
|
|
if (!(m_types.records.at(row) == written)) continue;
|
|
|
|
const QModelIndex source = m_new_model->index(row, 0);
|
|
m_new_table->setCurrentIndex(m_new_proxy->mapFromSource(source));
|
|
m_new_table->scrollTo(m_new_table->currentIndex());
|
|
break;
|
|
}
|
|
|
|
selectionChanged();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::editTypeRecord
|
|
Rewrite the line of the cable type file he picked: the very window
|
|
which adds a line opens, already filled in with what that line says,
|
|
and only his yes writes it back -- saying no leaves the file exactly
|
|
as it was.
|
|
|
|
What he writes replaces the line he started from and nothing else.
|
|
The other lines of the file are read again first, so whatever he
|
|
saved from his spreadsheet in the meantime is written back too
|
|
rather than being thrown away.
|
|
*/
|
|
void CableCreateDialog::editTypeRecord()
|
|
{
|
|
const QModelIndex index = currentSourceIndex();
|
|
if (!index.isValid() || index.row() >= m_types.records.size()) return;
|
|
|
|
const CableTypeRecord before = m_types.records.at(index.row());
|
|
|
|
QStringList columns = m_types.columns;
|
|
if (columns.isEmpty()) {
|
|
columns = CableTypeList::defaultColumns();
|
|
}
|
|
|
|
CableTypeEntryDialog entry(columns, this);
|
|
entry.setValues(before);
|
|
if (entry.exec() != QDialog::Accepted) {
|
|
return;
|
|
}
|
|
|
|
CableTypeRecord after = entry.record();
|
|
//Cells standing beyond the header belong to the user, and the
|
|
//form knows nothing about them: they must not vanish from the
|
|
//line just because they could not be shown.
|
|
after.extra = before.extra;
|
|
|
|
const QString path = typeFilePath();
|
|
QString error;
|
|
if (!CableTypeList::updateRecord(path, before, after, &error))
|
|
{
|
|
QMessageBox::critical(this,
|
|
tr("Écriture impossible"),
|
|
tr("Impossible d'écrire dans le fichier :\n%1\n%2")
|
|
.arg(path, error));
|
|
return;
|
|
}
|
|
|
|
//A search hiding the line he just wrote would leave him with a
|
|
//form he filled in and no line to see.
|
|
m_new_search->clear();
|
|
loadTypeFile();
|
|
|
|
for (int row = 0; row < m_types.records.size(); ++row)
|
|
{
|
|
if (!(m_types.records.at(row) == after)) continue;
|
|
|
|
const QModelIndex source = m_new_model->index(row, 0);
|
|
m_new_table->setCurrentIndex(m_new_proxy->mapFromSource(source));
|
|
m_new_table->scrollTo(m_new_table->currentIndex());
|
|
break;
|
|
}
|
|
|
|
selectionChanged();
|
|
}
|
|
|
|
/**
|
|
@brief CableCreateDialog::acceptTypeChange
|
|
Take the chosen type onto the cable whose type is being changed:
|
|
its name, how many cores it has and the colour of each of them,
|
|
together with the cores that cable holds -- the ones beyond the new
|
|
count leave it, the ones which stay keep their place and their wire,
|
|
and the added ones wait in the list of cores not placed yet.
|
|
|
|
Everything lands as one single undo step, together with whatever he
|
|
typed into the property fields: the type, the cores and the fields
|
|
all come back together or not at all.
|
|
*/
|
|
void CableCreateDialog::acceptTypeChange()
|
|
{
|
|
if (!m_change) {
|
|
QDialog::reject();
|
|
return;
|
|
}
|
|
|
|
const QModelIndex index = currentSourceIndex();
|
|
if (!index.isValid() || index.row() >= m_types.records.size()) return;
|
|
|
|
const CableTypeRecord record = m_types.records.at(index.row());
|
|
|
|
const QString type = CableTypeList::designation(record);
|
|
int count = CableTypeList::coreCount(record);
|
|
//A type saying how many cores it has takes that count; one
|
|
//saying nothing leaves this cable the size it already is.
|
|
if (count <= 0) count = m_change->coreCount();
|
|
const QStringList colors = CableTypeList::coreColors(record);
|
|
|
|
const CableTypeState before = m_change->typeState();
|
|
CableTypeState after = before;
|
|
after.type = type;
|
|
after.core_count = count;
|
|
after.core_colors = colors;
|
|
after.cores.clear();
|
|
for (const CableCore &core : std::as_const(before.cores))
|
|
{
|
|
if (core.core < count) {
|
|
after.cores.append(core);
|
|
}
|
|
}
|
|
|
|
//Cores beyond the new count take their mark off the drawing.
|
|
//He is only asked about that when a wire really loses its
|
|
//label: a core which stood nowhere was not on the drawing at
|
|
//all, and nothing he can see goes away with it.
|
|
int wired = 0;
|
|
for (const CableCore &core : std::as_const(before.cores))
|
|
{
|
|
if (core.core >= count && !core.conductor.isNull()) {
|
|
++wired;
|
|
}
|
|
}
|
|
if (wired > 0)
|
|
{
|
|
const QString text = tr(
|
|
"%n âme(s) de ce câble est/sont rattachée(s) à un conducteur. "
|
|
"Avec le type choisi, elle(s) quitte(nt) ce câble et leur marque "
|
|
"disparaît du tracé. Continuer ?",
|
|
"ask before cores lose the mark they show on the drawing",
|
|
wired);
|
|
|
|
if (QMessageBox::warning(this, tr("Câble"), text,
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::Yes) != QMessageBox::Yes) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
//The type itself is written by the type step rather than by the
|
|
//fields, so the two cannot write it twice and undo cannot leave
|
|
//half of it standing.
|
|
const CableProperties properties_before = m_change->properties();
|
|
CableProperties properties_after = properties_before;
|
|
properties_after.designation = m_designation->text().trimmed();
|
|
//A name this window filled in is the one the cable already had;
|
|
//anything he typed over it is a name of his own
|
|
properties_after.designation_by_hand =
|
|
properties_after.designation != m_designation_prefill
|
|
|| m_designation_prefill_by_hand;
|
|
properties_after.installation = m_installation->text().trimmed();
|
|
properties_after.location = m_location->text().trimmed();
|
|
|
|
QList<QUndoCommand *> steps;
|
|
if (properties_after != properties_before) {
|
|
steps.append(new ChangeCablePropertiesCommand(m_change,
|
|
properties_before,
|
|
properties_after));
|
|
}
|
|
if (before != after) {
|
|
steps.append(new ChangeCableTypeCommand(m_change, before, after));
|
|
}
|
|
|
|
//Nothing changed at all: saying yes to an unchanged form is
|
|
//still saying yes, but it is not a step which has to be kept.
|
|
if (steps.isEmpty())
|
|
{
|
|
QDialog::accept();
|
|
return;
|
|
}
|
|
|
|
QUndoCommand *step = steps.size() == 1
|
|
? steps.takeFirst()
|
|
: new BatchCommand(steps, QCoreApplication::translate("ChangeCableTypeCommand",
|
|
"Changer le type du câble"));
|
|
|
|
//The folio is the usual place this step is taken back from. When
|
|
//no folio is known -- a cable picked without a line -- the
|
|
//project keeps them all anyway, and only when there is neither
|
|
//does the change stand without a way back.
|
|
QUndoStack *stack = m_diagram ? &m_diagram->undoStack() : nullptr;
|
|
if (!stack)
|
|
{
|
|
auto *project = qobject_cast<QETProject *>(m_change->parent());
|
|
if (project && project->undoStack()) {
|
|
stack = project->undoStack();
|
|
}
|
|
}
|
|
|
|
if (stack) {
|
|
stack->push(step);
|
|
} else {
|
|
if (properties_after != properties_before) {
|
|
m_change->setProperties(properties_after);
|
|
}
|
|
m_change->applyTypeState(after);
|
|
delete step;
|
|
}
|
|
|
|
QDialog::accept();
|
|
}
|
|
|
|
/**
|
|
@brief CableTypeEntryDialog::CableTypeEntryDialog
|
|
Build one field per column of the cable type file. Two columns get
|
|
the shape they need -- a number which counts, and colours written
|
|
the way the file separates them -- and every other column is a plain
|
|
field, since it may be one the user invented rather than one
|
|
QElectroTech knows.
|
|
@param columns the columns of the file, in file order
|
|
@param parent
|
|
*/
|
|
CableTypeEntryDialog::CableTypeEntryDialog(const QStringList &columns, QWidget *parent) :
|
|
QDialog(parent),
|
|
m_columns(columns)
|
|
{
|
|
setWindowTitle(tr("Nouvelle entrée"));
|
|
|
|
auto *main_layout = new QVBoxLayout(this);
|
|
|
|
auto *intro = new QLabel(tr("Renseignez le type de câble à ajouter. Les champs "
|
|
"laissés vides restent vides dans le fichier."), this);
|
|
intro->setWordWrap(true);
|
|
m_intro = intro;
|
|
main_layout->addWidget(intro);
|
|
|
|
auto *grid = new QGridLayout;
|
|
for (int i = 0; i < m_columns.size(); ++i)
|
|
{
|
|
const QString column = m_columns.at(i);
|
|
|
|
auto *label = new QLabel(CableTypeList::translatedColumn(column), this);
|
|
label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
QWidget *field = nullptr;
|
|
if (column == QLatin1String("cores"))
|
|
{
|
|
auto *spin = new QSpinBox(this);
|
|
spin->setRange(0, 99);
|
|
spin->setSpecialValueText(QStringLiteral("—"));
|
|
spin->setToolTip(tr("Combien d'âmes ce type a. Le tiret veut dire que "
|
|
"le fichier ne le dit pas."));
|
|
field = spin;
|
|
}
|
|
else
|
|
{
|
|
auto *edit = new QLineEdit(this);
|
|
edit->setClearButtonEnabled(true);
|
|
if (column == QLatin1String("core_colors"))
|
|
{
|
|
edit->setPlaceholderText(QStringLiteral("br,sw,gr,bl,gnge"));
|
|
edit->setToolTip(tr("La couleur de chaque âme, séparées par une "
|
|
"virgule et dans l'ordre des âmes."));
|
|
}
|
|
field = edit;
|
|
}
|
|
|
|
label->setBuddy(field);
|
|
grid->addWidget(label, i, 0, Qt::AlignRight | Qt::AlignVCenter);
|
|
grid->addWidget(field, i, 1);
|
|
m_fields.append(field);
|
|
}
|
|
grid->setColumnStretch(1, 1);
|
|
main_layout->addLayout(grid);
|
|
|
|
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
|
buttons->button(QDialogButtonBox::Ok)->setText(tr("Enregistrer"));
|
|
buttons->button(QDialogButtonBox::Cancel)->setText(tr("Annuler"));
|
|
connect(buttons, &QDialogButtonBox::accepted, this, &CableTypeEntryDialog::accept);
|
|
connect(buttons, &QDialogButtonBox::rejected, this, &CableTypeEntryDialog::reject);
|
|
main_layout->addWidget(buttons);
|
|
|
|
if (!m_fields.isEmpty()) {
|
|
m_fields.first()->setFocus();
|
|
}
|
|
|
|
setMinimumWidth(420);
|
|
}
|
|
|
|
/**
|
|
@brief CableTypeEntryDialog::record
|
|
@return the line the form describes. Empty fields are left out: an
|
|
empty column never means anything for a type being created.
|
|
*/
|
|
CableTypeRecord CableTypeEntryDialog::record() const
|
|
{
|
|
CableTypeRecord record;
|
|
|
|
for (int i = 0; i < m_columns.size() && i < m_fields.size(); ++i)
|
|
{
|
|
const QString column = m_columns.at(i);
|
|
|
|
QString value;
|
|
if (auto *spin = qobject_cast<QSpinBox *>(m_fields.at(i)))
|
|
{
|
|
//Zero means "not said": a type may leave its size to the
|
|
//drawing, and an empty cell says exactly that.
|
|
if (spin->value() > 0) {
|
|
value = QString::number(spin->value());
|
|
}
|
|
}
|
|
else if (auto *edit = qobject_cast<QLineEdit *>(m_fields.at(i)))
|
|
{
|
|
value = edit->text();
|
|
value.remove(QLatin1Char('\r'));
|
|
value.remove(QLatin1Char('\n'));
|
|
value = value.trimmed();
|
|
}
|
|
|
|
if (!value.isEmpty()) {
|
|
record.setValue(column, value);
|
|
}
|
|
}
|
|
|
|
return record;
|
|
}
|
|
|
|
/**
|
|
@brief CableTypeEntryDialog::setValues
|
|
Fill the form with a line which is already in the file. The window
|
|
then says it edits rather than adds, so the two can never be told
|
|
apart by mistake: what he writes is only written when he says yes to
|
|
it, and only about the line he started from.
|
|
@param values the line as it stands in the file
|
|
*/
|
|
void CableTypeEntryDialog::setValues(const CableTypeRecord &values)
|
|
{
|
|
setWindowTitle(tr("Modifier l'entrée"));
|
|
if (m_intro)
|
|
{
|
|
m_intro->setText(tr("Modifiez le type de câble sélectionné dans la liste. "
|
|
"Les champs laissés vides restent vides dans le fichier."));
|
|
}
|
|
|
|
for (int i = 0; i < m_columns.size() && i < m_fields.size(); ++i)
|
|
{
|
|
const QString value = values.value(m_columns.at(i));
|
|
|
|
if (auto *spin = qobject_cast<QSpinBox *>(m_fields.at(i)))
|
|
{
|
|
bool ok = false;
|
|
const int count = value.toInt(&ok);
|
|
spin->setValue(ok ? qBound(spin->minimum(), count, spin->maximum())
|
|
: spin->minimum());
|
|
}
|
|
else if (auto *edit = qobject_cast<QLineEdit *>(m_fields.at(i)))
|
|
{
|
|
edit->setText(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief CableTypeEntryDialog::accept
|
|
Refuse an empty form: a type with nothing said about it is not worth
|
|
a line in the file.
|
|
*/
|
|
void CableTypeEntryDialog::accept()
|
|
{
|
|
if (record().values.isEmpty())
|
|
{
|
|
QMessageBox::warning(this,
|
|
tr("Aucun renseignement"),
|
|
tr("Saisissez au moins un renseignement "
|
|
"pour créer une entrée."));
|
|
return;
|
|
}
|
|
|
|
QDialog::accept();
|
|
}
|