Add cable management: drawing, cores, numbering, cross-references and cable list

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.
This commit is contained in:
Kellermorph
2026-10-08 15:56:00 +02:00
parent 1ca13ce62d
commit bf9b34398d
108 changed files with 18482 additions and 293 deletions
@@ -21,6 +21,7 @@
#include "../../qeticons.h"
#include "ui_generalconfigurationpage.h"
#include "../../materiallist/materiallist.h"
#include "../../cablelist/cabletypelist.h"
#include "../../utils/qetsettings.h"
#include "../../utils/qetutils.h"
#include "../../qetmessagebox.h"
@@ -29,6 +30,7 @@
#include "../../ElementsCollection/qetlabelsfile.h"
#include "../prefixconfigurationdialog.h"
#include "../nokde/kcolorbutton.h"
#include <QCoreApplication>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
@@ -36,6 +38,35 @@
#include <QMessageBox>
#include <QSettings>
#include "../../diagram.h"
#include "../../qetproject.h"
namespace {
/**
@brief What one of those font buttons says: the family, the size
and the style, the way it would read on the sheet.
@param font the font that text is written with
@return e.g. "Liberation Sans 8,1 (Regular)"
*/
QString fontButtonLabel(const QFont &font)
{
QString style = font.styleName();
if (font.bold()) {
style = font.italic()
? QCoreApplication::translate("GeneralConfigurationPage",
"Gras et italique")
: QCoreApplication::translate("GeneralConfigurationPage", "Gras");
} else if (font.italic()) {
style = QCoreApplication::translate("GeneralConfigurationPage", "Italique");
} else if (style.isEmpty()) {
style = QCoreApplication::translate("GeneralConfigurationPage", "Normal");
}
return font.family() + QLatin1Char(' ')
+ QString::number(font.pointSizeF(), 'g', 3)
+ QLatin1String(" (") + style + QLatin1Char(')');
}
}
/**
@brief GeneralConfigurationPage::GeneralConfigurationPage
@param parent
@@ -168,6 +199,17 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
font.styleName() + ")";
ui->m_indi_text_font_pb->setText(fontInfos);
} else { ui->m_indi_text_font_pb->setText("Liberation Sans 9 (Regular)"); }
//Cable texts: what every cable writes along its own line when
//it has not been given a font of its own.
ui->m_cable_text_font_pb->setText(fontButtonLabel(QETApp::cableTextsFont()));
ui->m_cable_core_font_pb->setText(fontButtonLabel(QETApp::cableCoreFont()));
//Where those texts stop in relation to the beginning of their
//own line, for the cables which say nothing about it themselves
const int align_index = int(QETApp::cableTextAlignment()) == int(Qt::AlignLeft) ? 2
: (int(QETApp::cableTextAlignment()) == int(Qt::AlignHCenter) ? 1 : 0);
ui->m_cable_align_cb->setCurrentIndex(align_index);
ui->m_highlight_integrated_elements->setChecked(settings.value("diagrameditor/highlight-integrated-elements", true).toBool());
ui->m_default_elements_info->setPlainText(settings.value("elementeditor/default-informations", "").toString());
@@ -247,6 +289,17 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
.arg(MaterialList::defaultPath()));
}
//CABLE TYPE FILE
path = CableTypeList::configuredPath();
ui->m_cable_type_list_path_le->setText(path);
if (path.isEmpty())
{
ui->m_cable_type_list_path_le->setPlaceholderText(
tr("Non configuré (par défaut : %1)",
"hint shown in the cable type file field when no file is configured yet")
.arg(CableTypeList::defaultPath()));
}
fillLang();
}
@@ -438,6 +491,30 @@ void GeneralConfigurationPage::applyConf()
//kept as chosen even when it doesn't exist yet : the user may well
//point QElectroTech at a file he intends to write later.
MaterialList::setConfiguredPath(ui->m_material_list_path_le->text().trimmed());
//CABLE TYPE FILE
//Same rule as the material file : it is a plain file, kept as
//chosen even when it doesn't exist yet.
CableTypeList::setConfiguredPath(ui->m_cable_type_list_path_le->text().trimmed());
//The way a cable lines up its texts at the beginning of its
//line, for every cable which says nothing about it itself --
//written now, so the sheets drawn again just below show it.
const int align_index = ui->m_cable_align_cb->currentIndex();
QETApp::setCableTextAlignment(align_index == 2 ? Qt::AlignLeft
: (align_index == 1
? Qt::AlignHCenter
: Qt::AlignRight));
//Every folio is drawn again: the texts of the cables are written
//from the settings, so that what he just picked reaches the
//sheets without his having to touch anything on them first.
for (QETProject *project : QETApp::registeredProjects())
{
for (Diagram *diagram : project->diagrams()) {
diagram->update();
}
}
}
/**
@@ -556,6 +633,45 @@ void GeneralConfigurationPage::on_m_dyn_text_font_pb_clicked()
}
}
/**
@brief GeneralConfigurationPage::on_m_cable_text_font_pb_clicked
Let him pick the font the texts of a cable are written with by
default: designation, type, installation, location and length.
Every cable which has not been given a font of its own follows it,
from now on: one which has keeps the one it was given, which is why
those are set on the cable itself rather than here.
*/
void GeneralConfigurationPage::on_m_cable_text_font_pb_clicked()
{
bool ok = false;
const QFont font = QFontDialog::getFont(&ok, QETApp::cableTextsFont(), this,
tr("Police des textes de câble"));
if (!ok) return;
QSettings settings;
settings.setValue("cable-management/cable-text-font", QETUtils::fontToString(font));
ui->m_cable_text_font_pb->setText(fontButtonLabel(QETApp::cableTextsFont()));
}
/**
@brief GeneralConfigurationPage::on_m_cable_core_font_pb_clicked
Let him pick the font the colours of the cores are written with by
default -- all of them together, the way they are drawn: one row of
colours beside the line, never set apart from each other.
*/
void GeneralConfigurationPage::on_m_cable_core_font_pb_clicked()
{
bool ok = false;
const QFont font = QFontDialog::getFont(&ok, QETApp::cableCoreFont(), this,
tr("Police des couleurs des âmes"));
if (!ok) return;
QSettings settings;
settings.setValue("cable-management/cable-core-font", QETUtils::fontToString(font));
ui->m_cable_core_font_pb->setText(fontButtonLabel(QETApp::cableCoreFont()));
}
@@ -791,6 +907,70 @@ void GeneralConfigurationPage::on_m_material_list_create_pb_clicked()
ui->m_material_list_path_le->setText(path);
}
/**
@brief GeneralConfigurationPage::on_m_cable_type_list_browse_pb_clicked
Let the user pick an existing cable type file.
*/
void GeneralConfigurationPage::on_m_cable_type_list_browse_pb_clicked()
{
QString start_dir = ui->m_cable_type_list_path_le->text();
start_dir = start_dir.isEmpty()
? QETApp::documentDir()
: QFileInfo(start_dir).absolutePath();
const QString path = QFileDialog::getOpenFileName(
this,
tr("Sélectionner le fichier de la liste des types de câbles"),
start_dir,
tr("Fichiers csv (*.csv)"));
if (!path.isEmpty()) {
ui->m_cable_type_list_path_le->setText(path);
}
}
/**
@brief GeneralConfigurationPage::on_m_cable_type_list_create_pb_clicked
Create the cable type file with its header line, so the columns are
known before the user fills them from his spreadsheet.
*/
void GeneralConfigurationPage::on_m_cable_type_list_create_pb_clicked()
{
QString path = ui->m_cable_type_list_path_le->text();
path = path.isEmpty()
? CableTypeList::defaultPath()
: QFileInfo(path).absolutePath() + QLatin1Char('/') + CableTypeList::defaultFileName();
path = QFileDialog::getSaveFileName(
this,
tr("Créer le fichier de la liste des types de câbles"),
path,
tr("Fichiers csv (*.csv)"));
if (path.isEmpty()) {
return;
}
if (QFileInfo(path).suffix().isEmpty()) {
path += QStringLiteral(".csv");
}
//An existing file is kept as it is : this button creates the
//header, it never overwrites a catalogue.
if (CableTypeList::isEmptyFile(path))
{
QString error;
if (!CableTypeList::createFile(path, &error))
{
QET::QetMessageBox::critical(this,
tr("Création impossible"),
tr("Impossible de créer le fichier :\n%1\n%2")
.arg(path, error));
return;
}
}
ui->m_cable_type_list_path_le->setText(path);
}
void GeneralConfigurationPage::on_m_indi_text_font_pb_clicked()
{
bool ok;