From 5b8d05fc1ef7c355968a5dec986308eedd7738e4 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 2 Aug 2026 14:15:00 +1200 Subject: [PATCH 1/5] Add a wiring list dialog and an excluded-conductor count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice 4 of discussion #503, on top of slice 3 (#629): the smallest surface that makes wiring_list_view visible, plus the diagnostic the view needs to be honest about what it is missing. Projet > "Liste de câblage (base de données)" opens a read-only table of wiring_list_view, headed by a line stating how many conductors are listed and, when non-zero, how many were excluded and why. Deliberately not another exporter. QET already ships a wiring-list CSV export (Projet > Exporter le plan de câblage, and --export-cables) which walks the project XML; measured on the same projects it produces a row per conductor and resolves labels correctly when the project has them. Adding a second, competing CSV would be worse, not better -- the database path's value is what it unlocks (terminal plans, BOM joins), not replacing that export. projectDataBase::excludedConductorCount() counts, from the live scene, the conductors deliberately absent from the conductor table because a terminal has no uuid. Counted from the scene precisely because the database is where those conductors are not. Verified: 671 on examples/industrial.qet (which has 1794 terminals and no terminal uuids at all, so its list is empty and now says so), 0 on a project whose elements do carry terminal uuids. KNOWN GAP, not fixed here and the reason this is opened for discussion rather than merge: after a save/reload the component columns are blank for slave elements. populateElementTable()/populateElementInfoTable() only insert Simple|Terminal|Master|Thumbnail, so slave elements -- relay contacts, i.e. a large share of real wire endpoints -- have no row in element_info for the view to read a label from. Measured on a two-slave- contact project after reload: element rows 0, element_info rows 0, terminal rows 2, conductor rows 1; the wire is listed (slice 3's LEFT JOIN keeps it) but both component names are empty, where the existing CSV export shows K1 -> K2 for the same file. Closing that gap means widening a filter shared with the nomenclature and summary views, which would change what those existing, shipped features contain. That is a maintainer decision, not one to take unilaterally inside an additive slice. --- cmake/qet_compilation_vars.cmake | 2 + sources/dataBase/projectdatabase.cpp | 36 +++++++++++ sources/dataBase/projectdatabase.h | 2 + sources/qetdiagrameditor.cpp | 14 +++++ sources/qetdiagrameditor.h | 1 + sources/ui/wiringlistdialog.cpp | 93 ++++++++++++++++++++++++++++ sources/ui/wiringlistdialog.h | 48 ++++++++++++++ 7 files changed, 196 insertions(+) create mode 100644 sources/ui/wiringlistdialog.cpp create mode 100644 sources/ui/wiringlistdialog.h diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake index 57a012916..ffde16f63 100644 --- a/cmake/qet_compilation_vars.cmake +++ b/cmake/qet_compilation_vars.cmake @@ -155,6 +155,8 @@ set(QET_SRC_FILES ${QET_DIR}/sources/conductornumexport.cpp ${QET_DIR}/sources/wiringlistexport.h ${QET_DIR}/sources/wiringlistexport.cpp + ${QET_DIR}/sources/ui/wiringlistdialog.h + ${QET_DIR}/sources/ui/wiringlistdialog.cpp ${QET_DIR}/sources/conductornumexport.h ${QET_DIR}/sources/conductorprofile.cpp ${QET_DIR}/sources/conductorprofile.h diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index f2b3df670..942691af7 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -111,6 +111,42 @@ QSqlQuery projectDataBase::newQuery(const QString &query) { return QSqlQuery(query, m_data_base); } +/** + @brief projectDataBase::excludedConductorCount + @return how many conductors of the project are deliberately absent from + the conductor table because at least one of their terminals has no uuid. + + Counted from the live scene rather than from the database, precisely + because the database is where these conductors are *not*. See + addConductor() for why they are omitted: a terminal uuid comes from the + catalog .elmt definition, so an element whose definition predates that + field yields terminals with no stable identity to key on. + + This is what lets a caller tell the user "N wires are missing and here + is why", instead of silently presenting a short list as if it were + complete. +*/ +int projectDataBase::excludedConductorCount() const +{ + if (!m_project) { + return 0; + } + + int count = 0; + for (auto *diagram : m_project->diagrams()) + { + const auto conductor_list = diagram->conductors(); + for (auto *conductor : conductor_list) + { + if (conductor->terminal1->uuid().isNull() + || conductor->terminal2->uuid().isNull()) { + ++count; + } + } + } + return count; +} + /** @brief projectDataBase::addElement @param element diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 65ca17950..5873d825f 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -49,6 +49,8 @@ class projectDataBase : public QObject void updateDB(); QETProject *project() const; QSqlQuery newQuery(const QString &query = QString()); + QSqlDatabase database() const {return m_data_base;} + int excludedConductorCount() const; void addElement (Element *element); void removeElement (Element *element); diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index 5b2ba6779..f686f6daf 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -53,6 +53,7 @@ #include "ui/diagrameditorhandlersizewidget.h" #include "TerminalStrip/ui/addterminalstripitemdialog.h" #include "wiringlistexport.h" +#include "ui/wiringlistdialog.h" #include "ui/terminalnumberingdialog.h" #include #include @@ -503,6 +504,17 @@ void QETDiagramEditor::setUpActions() } }); + // Show the wiring list read from the project database + m_project_wiring_list_view = new QAction(QET::Icons::DocumentSpreadsheet, tr("Liste de câblage (base de données)"), this); + connect(m_project_wiring_list_view, &QAction::triggered, [this]() { + QETProject *project = this->currentProject(); + if (project) + { + WiringListDialog dialog(project, this); + dialog.exec(); + } + }); + // Terminal Numbering m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this); connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering); @@ -892,6 +904,7 @@ void QETDiagramEditor::setUpMenu() menu_project -> addAction(m_terminal_strip_dialog); menu_project -> addAction(m_project_terminalBloc); menu_project -> addAction(m_project_export_wiring_list); + menu_project -> addAction(m_project_wiring_list_view); menu_project -> addAction(m_terminal_numbering); #ifdef QET_EXPORT_PROJECT_DB menu_project -> addSeparator(); @@ -1680,6 +1693,7 @@ void QETDiagramEditor::slot_updateActions() m_project_export_conductor_num-> setEnabled(opened_project); m_terminal_strip_dialog -> setEnabled(editable_project); m_project_export_wiring_list -> setEnabled(opened_project); + m_project_wiring_list_view -> setEnabled(opened_project); m_terminal_numbering -> setEnabled(editable_project); #ifdef QET_EXPORT_PROJECT_DB m_export_project_db -> setEnabled(editable_project); diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h index 0114a186e..7efa706a9 100644 --- a/sources/qetdiagrameditor.h +++ b/sources/qetdiagrameditor.h @@ -207,6 +207,7 @@ class QETDiagramEditor : public QETMainWindow *m_project_terminalBloc, ///< generate terminal block *m_project_export_conductor_num,///. +*/ +#include "wiringlistdialog.h" + +#include "../dataBase/projectdatabase.h" +#include "../qetproject.h" + +#include +#include +#include +#include +#include +#include + +/** + @brief WiringListDialog::WiringListDialog + @param project : project whose wiring list is shown + @param parent : parent widget +*/ +WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) : + QDialog(parent), + m_project(project) +{ + setWindowTitle(tr("Liste de câblage", "window title")); + resize(900, 500); + + auto *layout = new QVBoxLayout(this); + + auto *model = new QSqlQueryModel(this); + model->setQuery(QStringLiteral( + "SELECT wire_number, from_element_label, from_terminal," + " to_element_label, to_terminal, diagram_position" + " FROM wiring_list_view" + " ORDER BY diagram_position, wire_number"), + m_project->dataBase()->database()); + + model->setHeaderData(0, Qt::Horizontal, tr("Fil", "column title")); + model->setHeaderData(1, Qt::Horizontal, tr("Composant 1", "column title")); + model->setHeaderData(2, Qt::Horizontal, tr("Borne 1", "column title")); + model->setHeaderData(3, Qt::Horizontal, tr("Composant 2", "column title")); + model->setHeaderData(4, Qt::Horizontal, tr("Borne 2", "column title")); + model->setHeaderData(5, Qt::Horizontal, tr("Folio", "column title")); + + const int excluded = m_project->dataBase()->excludedConductorCount(); + const int listed = model->rowCount(); + + auto *summary = new QLabel(this); + summary->setWordWrap(true); + if (excluded > 0) + { + //The count matters more than it looks: a project whose elements + //all predate terminal uuids yields an entirely empty list, and + //without this line that is indistinguishable from a project with + //no wires in it. + summary->setText(tr("%n conducteur(s) listé(s).", "wiring list summary", listed) + % QStringLiteral(" ") + % tr("%n conducteur(s) exclu(s) : leurs éléments ne définissent pas" + " d'identifiant de borne (définition d'élément trop ancienne).", + "wiring list exclusion warning", excluded)); + } + else { + summary->setText(tr("%n conducteur(s) listé(s).", "wiring list summary", listed)); + } + layout->addWidget(summary); + + auto *view = new QTableView(this); + view->setModel(model); + view->setEditTriggers(QAbstractItemView::NoEditTriggers); + view->setSelectionBehavior(QAbstractItemView::SelectRows); + view->setAlternatingRowColors(true); + view->verticalHeader()->setVisible(false); + view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + layout->addWidget(view); + + auto *buttons = new QDialogButtonBox(QDialogButtonBox::Close, this); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + layout->addWidget(buttons); +} diff --git a/sources/ui/wiringlistdialog.h b/sources/ui/wiringlistdialog.h new file mode 100644 index 000000000..73251473c --- /dev/null +++ b/sources/ui/wiringlistdialog.h @@ -0,0 +1,48 @@ +/* + 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 . +*/ +#ifndef WIRINGLISTDIALOG_H +#define WIRINGLISTDIALOG_H + +#include + +class QETProject; + +/** + @brief The WiringListDialog class + Read-only view of the project's from-to wiring list, read from the + wiring_list_view of projectDataBase. + + Deliberately not an exporter: QET already ships a wiring-list CSV export + (Projet > Exporter le plan de câblage, and --export-cables), which walks + the project XML and covers that need. This dialog exists to make the + database view inspectable, and above all to state how many conductors + are missing from it and why -- a count the CSV export cannot give, + because it never excludes anything in the first place. +*/ +class WiringListDialog : public QDialog +{ + Q_OBJECT + + public: + explicit WiringListDialog(QETProject *project, QWidget *parent = nullptr); + + private: + QETProject *m_project = nullptr; +}; + +#endif // WIRINGLISTDIALOG_H From b855d760a86839b01b4739b4a1209e2865483042 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 2 Aug 2026 15:29:18 +1200 Subject: [PATCH 2/5] Populate every element type; move the nomenclature filter into its view Closes the gap left open by the previous commit, at the root rather than around it. populateElementTable()/populateElementInfoTable() only inserted elements matching Simple|Terminal|Master|Thumbnail. That quietly made the element table mean "the elements a nomenclature cares about" rather than "the elements of the project": slave elements (relay contacts) and report elements -- ordinary conductor endpoints -- had no row at all after a project load, so the wiring list could not name either end of a wire that terminated on one. Both tables are now populated with every ElementData::Type, and the type restriction moves into element_nomenclature_view, which is where a "what belongs in a bill of materials" decision belongs. The mask in the view is character-for-character the one the population used to apply, so a relay contact is still not a BOM line item. This is safe to do in one place because every consumer of the project database goes through a view: element_nomenclature_view (the on-diagram nomenclature table via ElementQueryWidget, the BOM dialog, and the --export-bom CLI) or project_summary_view (which does not reference element at all). Nothing queries the element or element_info tables directly -- checked across the whole tree. Regression evidence. --export-bom runs updateDB() and then queries element_nomenclature_view, so it is an exact harness for what the GUI BOM shows. Captured for 19 projects (all 17 usable examples/ plus two slave-element fixtures) before and after: - BOM content byte-identical on all 19, compared as a multiset. - 18 of 19 are also identical line-for-line in order. - photovoltaique differs only in the position of three byte-identical rows among themselves. Its query is ORDER BY label and those rows share an empty label, so their relative order was never defined; they are indistinguishable in the output. The on-diagram nomenclature orders by every displayed column, so a tie there means the rows are identical on screen too. Effect on the wiring list, same project and same reload path: element_info rows 0 -> 2, and the two component columns go from blank to K2 -> K1. Cost: the database phase of loading examples/industrial.qet (150 folios, 1794 terminals) moves from 0.210 s to 0.233 s. --- sources/dataBase/projectdatabase.cpp | 36 +++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 942691af7..6e2d8bc22 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -620,7 +620,13 @@ void projectDataBase::createElementNomenclatureView() "di.folio AS folio," "e.pos AS position " " FROM element_info ei, diagram_info di, element e, diagram d" - " WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')"); + " WHERE ei.element_uuid = e.uuid AND e.diagram_uuid = d.uuid AND di.diagram_uuid = d.uuid AND (ei.exclude_from_bom IS NOT 'true')" + //The element table holds every element of the project; which + //kinds belong in a nomenclature is this view's business, not + //the table's. Kept identical to the mask populateElementTable() + //used to apply, so what this view returns does not change -- + //a slave element (a relay contact) is still not a line item. + " AND e.type IN ('simple', 'terminal', 'master', 'thumbnail')"); QSqlQuery query(m_data_base); if (!query.exec(create_view)) { @@ -732,6 +738,30 @@ void projectDataBase::populateDiagramTable() } } +/** + @brief allElementTypes + Every ElementData::Type, i.e. no filtering at all. + + The element table used to be populated with only + Simple|Terminal|Master|Thumbnail, which quietly made it "the elements a + nomenclature cares about" rather than "the elements of the project". + Anything else reading the table -- the wiring list, and terminal plans + later -- then could not see slave elements (relay contacts) or report + elements, which are ordinary conductor endpoints. The filter now lives in + element_nomenclature_view, where it belongs; see createElementNomenclatureView(). +*/ +static ElementData::Types allElementTypes() +{ + return ElementData::Simple + | ElementData::NextReport + | ElementData::PreviousReport + | ElementData::Master + | ElementData::Slave + | ElementData::Terminal + | ElementData::Thumbnail + | ElementData::ConductorDefinition; +} + /** @brief projectDataBase::populateElementTable Populate the element table @@ -744,7 +774,7 @@ void projectDataBase::populateElementTable() for (auto diagram : m_project->diagrams()) { const ElementProvider ep(diagram); - const auto elmt_vector = ep.find(ElementData::Simple | ElementData::Terminal | ElementData::Master | ElementData::Thumbnail); + const auto elmt_vector = ep.find(allElementTypes()); //Insert all values into the database for (const auto &elmt : elmt_vector) { @@ -773,7 +803,7 @@ void projectDataBase::populateElementInfoTable() for (const auto &diagram : m_project->diagrams()) { const ElementProvider ep(diagram); - const auto elmt_vector = ep.find(ElementData::Simple | ElementData::Terminal | ElementData::Master | ElementData::Thumbnail); + const auto elmt_vector = ep.find(allElementTypes()); //Insert all values into the database for (const auto &elmt : elmt_vector) From 030e6ebf0039c47113d498b15c55dd2a89ced38c Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sun, 2 Aug 2026 16:10:48 +1200 Subject: [PATCH 3/5] Insert an element the same way whether it is added live or on load The element and element_info tables had two independent insert paths -- addElement() for an element added to a live diagram, and populateElementTable()/populateElementInfoTable() for a full rebuild -- which bound the same row differently. The incremental path wrote kindInformations()["type"] into element.sub_type; the bulk path wrote elementData().masterTypeToString(). So the table held different values depending on whether the project had been reloaded since the element was placed, and element_nomenclature_view exposes that column as element_sub_type, which ElementQueryWidget filters on for the Coil, Protection, Commutator and PLC nomenclature options. That divergence is the same shape as the type-filter one fixed in the previous commit, and it is the reason this stack kept finding bugs that were invisible while editing and only appeared after a reload. Rather than correct a second instance of it, both paths now go through bindElementValues() and bindElementInfoValues(), following the bindDiagramInfoValues() helper this class already had. Live and reloaded now agree by construction instead of by coincidence. The bulk path's values are the ones kept, because they are what every already saved project contains: nothing a reload produces changes, and the previous commit's 19-project BOM regression stays valid. It is the live path that moves, onto the values a reload would have given it anyway. Measured, placing one element into a new project and then saving and reopening it: live element table: slave/ x1 reloaded element table: slave/ x1 and for the same element, what the two paths would have stored: bulk (now shared): "" incremental (before this commit): "simple" Re-ran the BOM regression over the same 19 projects after this change: content identical to the pre-change baseline on all 19, and identical line-for-line on 18, the exception being the three byte-identical photovoltaique rows already described in the previous commit. Note for anyone reading masterTypeToString(): the const no-argument overload returns an empty string for anything that is not a Master, so the "coil" fallback in the static overload is only reached for real master elements. Non-master elements get an empty sub_type, not a spurious "coil". --- sources/dataBase/projectdatabase.cpp | 79 ++++++++++++++++++---------- sources/dataBase/projectdatabase.h | 2 + 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 6e2d8bc22..a63ea6e1a 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -158,24 +158,12 @@ void projectDataBase::addElement(Element *element) return; } - m_insert_elements_query.bindValue(":uuid", element->uuid().toString()); - m_insert_elements_query.bindValue(":diagram_uuid", element->diagram()->uuid().toString()); - m_insert_elements_query.bindValue(":pos", element->diagram()->convertPosition(element->scenePos()).toString()); - m_insert_elements_query.bindValue(":type", element->elementData().typeToString()); - m_insert_elements_query.bindValue(":sub_type", element->kindInformations()["type"].toString()); + bindElementValues(m_insert_elements_query, element, element->diagram()); if (!m_insert_elements_query.exec()) { qDebug() << "projectDataBase::addElement insert element error : " << m_insert_elements_query.lastError(); } - m_insert_element_info_query.bindValue(":uuid", element->uuid().toString()); - auto hash = elementInfoToString(element); - for (auto key : hash.keys()) - { - QString value = hash.value(key); - QString bind = key.prepend(":"); - m_insert_element_info_query.bindValue(bind, value); - } - + bindElementInfoValues(m_insert_element_info_query, element); if (!m_insert_element_info_query.exec()) { qDebug() << "projectDataBase::addElement insert element info error : " << m_insert_element_info_query.lastError(); } else { @@ -778,12 +766,7 @@ void projectDataBase::populateElementTable() //Insert all values into the database for (const auto &elmt : elmt_vector) { - const auto elmt_data = elmt->elementData(); - m_insert_elements_query.bindValue(":uuid", elmt->uuid().toString()); - m_insert_elements_query.bindValue(":diagram_uuid", diagram->uuid().toString()); - m_insert_elements_query.bindValue(":pos", diagram->convertPosition(elmt->scenePos()).toString()); - m_insert_elements_query.bindValue(":type", elmt_data.typeToString()); - m_insert_elements_query.bindValue(":sub_type", elmt_data.masterTypeToString()); + bindElementValues(m_insert_elements_query, elmt, diagram); if (!m_insert_elements_query.exec()) { qDebug() << "projectDataBase::populateElementTable insert error : " << m_insert_elements_query.lastError(); } @@ -808,15 +791,7 @@ void projectDataBase::populateElementInfoTable() //Insert all values into the database for (const auto &elmt : elmt_vector) { - m_insert_element_info_query.bindValue(QStringLiteral(":uuid"), elmt->uuid().toString()); - const auto hash = elementInfoToString(elmt); - for (const auto &key : hash.keys()) - { - QString value = hash.value(key); - QString bind = QStringLiteral(":") + key; - m_insert_element_info_query.bindValue(bind, value); - } - + bindElementInfoValues(m_insert_element_info_query, elmt); if (!m_insert_element_info_query.exec()) { qDebug() << "projectDataBase::populateElementInfoTable insert error : " << m_insert_element_info_query.lastError(); } @@ -1003,6 +978,52 @@ QHash projectDataBase::elementInfoToString(Element *elmt) return hash; } +/** + @brief projectDataBase::bindElementValues + Bind one element's row for the element table. + + Shared by addElement() (a single element added to a live diagram) and + populateElementTable() (a full rebuild), because those two used to bind + the same row differently: the incremental path wrote + kindInformations()["type"] into sub_type while the bulk path wrote + elementData().masterTypeToString(). The element table therefore held + different values depending on whether the project had been reloaded + since the element was placed. One binder means live and reloaded agree + by construction rather than by coincidence. + + The bulk path's values are the ones kept: they are what every already + saved project contains, so nothing a reload produces changes. + @param query : prepared insert query to bind into + @param element : element to bind + @param diagram : diagram holding @element +*/ +void projectDataBase::bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram) +{ + const auto element_data = element->elementData(); + query.bindValue(QStringLiteral(":uuid"), element->uuid().toString()); + query.bindValue(QStringLiteral(":diagram_uuid"), diagram->uuid().toString()); + query.bindValue(QStringLiteral(":pos"), diagram->convertPosition(element->scenePos()).toString()); + query.bindValue(QStringLiteral(":type"), element_data.typeToString()); + query.bindValue(QStringLiteral(":sub_type"), element_data.masterTypeToString()); +} + +/** + @brief projectDataBase::bindElementInfoValues + Bind one element's row for the element info table. + Shared by addElement() and populateElementInfoTable() for the same + reason as bindElementValues(). + @param query : prepared insert query to bind into + @param element : element to bind +*/ +void projectDataBase::bindElementInfoValues(QSqlQuery &query, Element *element) +{ + query.bindValue(QStringLiteral(":uuid"), element->uuid().toString()); + const auto hash = elementInfoToString(element); + for (const auto &key : hash.keys()) { + query.bindValue(QStringLiteral(":") + key, hash.value(key)); + } +} + void projectDataBase::bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram) { query.bindValue(":uuid", diagram->uuid()); diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 5873d825f..3186994cd 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -92,6 +92,8 @@ class projectDataBase : public QObject static QHash elementInfoToString( Element *elmt); void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram); + static void bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram); + static void bindElementInfoValues(QSqlQuery &query, Element *element); private: QPointer m_project; From e19d60ae55f59d987d2ae028a5d680fecfad3a0a Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 21 Aug 2026 19:18:42 +1200 Subject: [PATCH 4/5] Report the real wire count, and show current data The summary line exists so that an empty wiring list is distinguishable from one where every conductor was excluded, and it was reporting the wrong number to do it. QSqlQueryModel fetches lazily, so rowCount() straight after setQuery() returns the rows fetched so far -- 256 -- not the size of the query. Measured with Qt's own QSQLITE driver: a 1000-row view reports 256 until the model is drained, then 1000. The test project quoted in slice 2 has 280 conductors, so this was already displaying 256 on our own data, plausibly enough that nobody looked twice. Drain the model before reading the count. Also refresh the database before building the model. The dialog queries the database rather than the diagrams, so anything not yet written through was invisible here; with conductor text now updated on change that gap is smaller, but a project loaded before this dialog was ever opened still relies on the repopulate. Co-Authored-By: Claude Opus 5 --- sources/ui/wiringlistdialog.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sources/ui/wiringlistdialog.cpp b/sources/ui/wiringlistdialog.cpp index 791c5e5cf..581b7426b 100644 --- a/sources/ui/wiringlistdialog.cpp +++ b/sources/ui/wiringlistdialog.cpp @@ -41,6 +41,12 @@ WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) : auto *layout = new QVBoxLayout(this); + //The wiring list reads the database rather than the diagrams, and a + //conductor's row is only as fresh as the last thing that touched it. + //Refresh before querying so the dialog cannot show a wire number that + //was edited earlier in the session. + m_project->dataBase()->updateDB(); + auto *model = new QSqlQueryModel(this); model->setQuery(QStringLiteral( "SELECT wire_number, from_element_label, from_terminal," @@ -57,6 +63,14 @@ WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) : model->setHeaderData(5, Qt::Horizontal, tr("Folio", "column title")); const int excluded = m_project->dataBase()->excludedConductorCount(); + + //QSqlQueryModel fetches lazily, so rowCount() straight after + //setQuery() reports the first batch (256) rather than the query's + //size. Draining it first is what makes the count below true for a + //project with more wires than that. + while (model->canFetchMore()) { + model->fetchMore(); + } const int listed = model->rowCount(); auto *summary = new QLabel(this); From a48124a27ae4e1557933475e626aa54218f140d7 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Fri, 21 Aug 2026 20:47:07 +1200 Subject: [PATCH 5/5] Stop reporting wires as excluded that the list is now showing excludedConductorCount() counted conductors whose terminals had no uuid, which was the right rule when that was the reason they were dropped. It no longer is: Terminal::stableUuid() derives an identity from the terminal's geometry, so those conductors are in the table. Left unchanged, the dialog would have told the user "671 conductors excluded" on industrial.qet while listing all 671 of them -- a worse failure than the one the count exists to prevent, because it undermines a list that is now correct. The count and the dialog's explanation both now describe the case that actually remains: an endpoint attached to no element at all, which has no identity to key on under any scheme. Co-Authored-By: Claude Opus 5 --- sources/dataBase/projectdatabase.cpp | 21 +++++++++++++-------- sources/ui/wiringlistdialog.cpp | 12 ++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index a63ea6e1a..5c39d3b5a 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -113,14 +113,17 @@ QSqlQuery projectDataBase::newQuery(const QString &query) { /** @brief projectDataBase::excludedConductorCount - @return how many conductors of the project are deliberately absent from - the conductor table because at least one of their terminals has no uuid. + @return how many conductors of the project are absent from the conductor + table because an endpoint has no parent element to key on. Counted from the live scene rather than from the database, precisely - because the database is where these conductors are *not*. See - addConductor() for why they are omitted: a terminal uuid comes from the - catalog .elmt definition, so an element whose definition predates that - field yields terminals with no stable identity to key on. + because the database is where these conductors are *not*. + + This used to count conductors whose terminals had no uuid, which was most + of them on most projects. Terminal::stableUuid() now derives an identity + from the terminal's geometry when the definition provides no uuid, so that + is no longer a reason to exclude anything, and this counts only the case + that remains genuinely unkeyable. This is what lets a caller tell the user "N wires are missing and here is why", instead of silently presenting a short list as if it were @@ -138,8 +141,10 @@ int projectDataBase::excludedConductorCount() const const auto conductor_list = diagram->conductors(); for (auto *conductor : conductor_list) { - if (conductor->terminal1->uuid().isNull() - || conductor->terminal2->uuid().isNull()) { + //Must match addConductor()'s guard exactly, or this reports + //wires as missing that the list is in fact showing. + if (!conductor->terminal1->parentElement() + || !conductor->terminal2->parentElement()) { ++count; } } diff --git a/sources/ui/wiringlistdialog.cpp b/sources/ui/wiringlistdialog.cpp index 581b7426b..cf80a6025 100644 --- a/sources/ui/wiringlistdialog.cpp +++ b/sources/ui/wiringlistdialog.cpp @@ -77,14 +77,14 @@ WiringListDialog::WiringListDialog(QETProject *project, QWidget *parent) : summary->setWordWrap(true); if (excluded > 0) { - //The count matters more than it looks: a project whose elements - //all predate terminal uuids yields an entirely empty list, and - //without this line that is indistinguishable from a project with - //no wires in it. + //Rare now that Terminal::stableUuid() gives every terminal an + //identity: what is left is a conductor whose endpoint has no + //parent element at all. Still worth saying out loud rather than + //presenting a short list as if it were complete. summary->setText(tr("%n conducteur(s) listé(s).", "wiring list summary", listed) % QStringLiteral(" ") - % tr("%n conducteur(s) exclu(s) : leurs éléments ne définissent pas" - " d'identifiant de borne (définition d'élément trop ancienne).", + % tr("%n conducteur(s) exclu(s) : une extrémité n'est rattachée" + " à aucun élément.", "wiring list exclusion warning", excluded)); } else {