mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-21 07:54:13 +02:00
Compare commits
15 Commits
6d09041dce
...
2926ca7306
| Author | SHA1 | Date | |
|---|---|---|---|
| 2926ca7306 | |||
| c7893c8229 | |||
| 412bc7f71f | |||
| c147e6562d | |||
| bee25a4ca9 | |||
| a48124a27a | |||
| e19d60ae55 | |||
| 030e6ebf00 | |||
| b855d760a8 | |||
| 5b8d05fc1e | |||
| b034b1a634 | |||
| 44ed01ff5d | |||
| 6061c63809 | |||
| 9285d12333 | |||
| e3d11a4992 |
@@ -48,7 +48,7 @@ PROJECT_NAME = QElectroTech
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = " 0.200.1-dev"
|
||||
PROJECT_NUMBER = " 0.200.1"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -85,6 +85,11 @@ parts:
|
||||
- qt6-tools-dev
|
||||
- qt6-base-private-dev
|
||||
- pkgconf
|
||||
# Qt6 PrintSupport records Cups::Cups as a third-party dependency
|
||||
# (qprint_p.h includes <cups/ppd.h>), so find_package(Qt6 PrintSupport)
|
||||
# runs FindCups at configure time and fails without the CUPS headers.
|
||||
# Build-time only: nothing from it is staged into the snap.
|
||||
- libcups2-dev
|
||||
override-build: |
|
||||
displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
|
||||
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
|
||||
|
||||
@@ -174,6 +174,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
|
||||
|
||||
@@ -70,6 +70,7 @@ const QHash<QString, QString> &exportFlags()
|
||||
{"--export-cables", "cables"},
|
||||
{"--export-wires", "wires"},
|
||||
{"--export-bom", "bom"},
|
||||
{"--export-wiring", "wiring"},
|
||||
{"--export-nets", "nets"},
|
||||
{"--export-links", "links"},
|
||||
{"--info", "info"},
|
||||
@@ -543,6 +544,54 @@ QHash<Element *, int> folioIndex(QETProject &project)
|
||||
return folio;
|
||||
}
|
||||
|
||||
/// From-to wiring list: one row per conductor, each endpoint resolved to its
|
||||
/// element label and terminal name.
|
||||
///
|
||||
/// Reads wiring_list_view out of the project database. --export-cables produces
|
||||
/// the same logical list from the document XML instead, and the two are meant
|
||||
/// to agree: running both and diffing them is a direct check that the database
|
||||
/// still describes the project, which is otherwise only observable through the
|
||||
/// GUI.
|
||||
int exportWiring(QETProject &project, const QString &output)
|
||||
{
|
||||
// The project database is built lazily; force a (re)build before querying.
|
||||
project.dataBase()->updateDB();
|
||||
|
||||
static const QStringList columns {
|
||||
"wire_number", "from_element_label", "from_terminal",
|
||||
"to_element_label", "to_terminal", "diagram_position", "conductor_uuid"
|
||||
};
|
||||
|
||||
QSqlQuery query = project.dataBase()->newQuery(
|
||||
"SELECT " % columns.join(", ") %
|
||||
" FROM wiring_list_view ORDER BY diagram_position, wire_number");
|
||||
if (!query.exec()) {
|
||||
err << "Wiring list query failed: " << query.lastError().text() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
QString csv = columns.join(";") % "\n";
|
||||
int rows = 0;
|
||||
while (query.next()) {
|
||||
QStringList values;
|
||||
for (int i = 0; i < columns.size(); ++i)
|
||||
values << csvField(query.value(i).toString());
|
||||
csv += values.join(";") % "\n";
|
||||
++rows;
|
||||
}
|
||||
|
||||
QFile file(output);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
err << "Cannot open '" << output << "' for writing.\n";
|
||||
return 1;
|
||||
}
|
||||
QTextStream fout(&file);
|
||||
fout << csv;
|
||||
file.close();
|
||||
out << "Exported " << rows << " conductor(s) -> " << output << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Electrical nets: groups of terminals joined into one potential.
|
||||
/// Walks QET's own potential graph, so each net is a connected component
|
||||
/// of terminals across all folios. The ground truth for connectivity.
|
||||
@@ -847,6 +896,8 @@ int run(const QStringList &args)
|
||||
return exportCsv(project, format, output);
|
||||
if (format == "bom")
|
||||
return exportBom(project, output);
|
||||
if (format == "wiring")
|
||||
return exportWiring(project, output);
|
||||
if (format == "nets")
|
||||
return exportNets(project, output);
|
||||
if (format == "links")
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace CLIExport {
|
||||
qelectrotech --export-cables <project.qet> <output.csv>
|
||||
qelectrotech --export-wires <project.qet> <output.csv>
|
||||
qelectrotech --export-bom <project.qet> <output.csv>
|
||||
qelectrotech --export-wiring <project.qet> <output.csv>
|
||||
qelectrotech --export-nets <project.qet> <output.json>
|
||||
qelectrotech --export-links <project.qet> <output.csv>
|
||||
qelectrotech --info <project.qet> [output.json]
|
||||
@@ -64,6 +65,11 @@ namespace CLIExport {
|
||||
cables: wiring list (one row per conductor) as CSV.
|
||||
wires: list of distinct wire numbers as CSV.
|
||||
bom: bill of materials (one row per element) as CSV.
|
||||
wiring: from-to wiring list (one row per conductor) as CSV, read from
|
||||
the project database. Same logical list as `cables`, which
|
||||
reads the document XML instead; the two are meant to agree,
|
||||
so diffing them checks that the database still describes the
|
||||
project.
|
||||
nets: electrical nets (connected-terminal groups) as JSON.
|
||||
links: element cross-references (coil/contact) as CSV, with
|
||||
unresolved links flagged.
|
||||
|
||||
@@ -111,6 +111,47 @@ QSqlQuery projectDataBase::newQuery(const QString &query) {
|
||||
return QSqlQuery(query, m_data_base);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::excludedConductorCount
|
||||
@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*.
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::addElement
|
||||
@param element
|
||||
@@ -122,24 +163,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 {
|
||||
@@ -550,6 +579,7 @@ bool projectDataBase::createDataBase()
|
||||
|
||||
createElementNomenclatureView();
|
||||
createSummaryView();
|
||||
createWiringListView();
|
||||
prepareQuery();
|
||||
updateDB();
|
||||
return true;
|
||||
@@ -628,7 +658,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)) {
|
||||
@@ -667,6 +703,64 @@ void projectDataBase::createSummaryView()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::createWiringListView
|
||||
A from-to wiring list: one row per conductor, each endpoint resolved to
|
||||
its element label and terminal name.
|
||||
|
||||
Two deliberate differences from an ordinary inner-join view like
|
||||
element_nomenclature_view:
|
||||
|
||||
- No join to the element table. A terminal row already carries its
|
||||
element_uuid, so joining element back just to read the same uuid adds
|
||||
nothing -- and would actively drop rows, because populateElementTable()
|
||||
only inserts elements matching Simple|Terminal|Master|Thumbnail. Slave
|
||||
elements (relay contacts and the like, extremely common at the end of a
|
||||
wire) and report elements are absent from that table after a project
|
||||
load, so an inner join through it silently loses their conductors.
|
||||
- element_info is LEFT joined for the same reason. A wire whose endpoint
|
||||
element carries no info row still belongs in a wiring list; it comes
|
||||
back with an empty label rather than vanishing. Losing a wire from a
|
||||
wiring list is a worse failure than showing one with a blank end.
|
||||
|
||||
- diagram is LEFT joined for the same reason. It should
|
||||
always match, since QETProject::diagramAdded is wired to addDiagram()
|
||||
and a conductor cannot exist before its folio -- but an inner join here
|
||||
would make that an assumption the view silently enforces, and a wire
|
||||
missing from a wiring list is the one failure this view must not have.
|
||||
|
||||
The result is that this view returns exactly as many rows as the
|
||||
conductor table holds -- what is already excluded upstream (conductors
|
||||
on legacy terminals without uuids) stays excluded, and nothing new is
|
||||
dropped here. Only the terminal joins are inner, and both are guaranteed
|
||||
by insertTerminal() running for each endpoint before the conductor row
|
||||
is written.
|
||||
*/
|
||||
void projectDataBase::createWiringListView()
|
||||
{
|
||||
QString create_view ("CREATE VIEW wiring_list_view AS SELECT "
|
||||
"c.uuid AS conductor_uuid,"
|
||||
"c.text AS wire_number,"
|
||||
"t1.element_uuid AS from_element_uuid,"
|
||||
"ei1.label AS from_element_label,"
|
||||
"t1.name AS from_terminal,"
|
||||
"t2.element_uuid AS to_element_uuid,"
|
||||
"ei2.label AS to_element_label,"
|
||||
"t2.name AS to_terminal,"
|
||||
"d.pos AS diagram_position"
|
||||
" FROM conductor c"
|
||||
" JOIN terminal t1 ON c.terminal1_uuid = t1.uuid AND c.terminal1_element_uuid = t1.element_uuid"
|
||||
" JOIN terminal t2 ON c.terminal2_uuid = t2.uuid AND c.terminal2_element_uuid = t2.element_uuid"
|
||||
" LEFT JOIN element_info ei1 ON t1.element_uuid = ei1.element_uuid"
|
||||
" LEFT JOIN element_info ei2 ON t2.element_uuid = ei2.element_uuid"
|
||||
" LEFT JOIN diagram d ON c.diagram_uuid = d.uuid");
|
||||
|
||||
QSqlQuery query(m_data_base);
|
||||
if (!query.exec(create_view)) {
|
||||
qDebug() << query.lastError();
|
||||
}
|
||||
}
|
||||
|
||||
void projectDataBase::populateDiagramTable()
|
||||
{
|
||||
QSqlQuery query_(m_data_base);
|
||||
@@ -682,6 +776,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
|
||||
@@ -694,16 +812,11 @@ 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -723,20 +836,12 @@ 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
@@ -943,6 +1048,52 @@ QHash<QString, QString> 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());
|
||||
|
||||
@@ -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);
|
||||
@@ -77,6 +79,7 @@ class projectDataBase : public QObject
|
||||
bool createDataBase();
|
||||
void createElementNomenclatureView();
|
||||
void createSummaryView();
|
||||
void createWiringListView();
|
||||
void populateDiagramTable();
|
||||
void populateElementTable();
|
||||
void populateElementInfoTable();
|
||||
@@ -89,6 +92,8 @@ class projectDataBase : public QObject
|
||||
static QHash<QString, QString> 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<QETProject> m_project;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "machine_info.h"
|
||||
#include "qet.h"
|
||||
#include "qetapp.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "qetproject.h"
|
||||
#include "singleapplication.h"
|
||||
#include "utils/qetsettings.h"
|
||||
@@ -127,6 +128,11 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto
|
||||
// runs on a background thread referencing the project and races the
|
||||
// process exit (intermittent segfault in QET::writeToFile).
|
||||
QETProject::setBackupEnabled(false);
|
||||
// Answer message boxes instead of showing them: opening a project
|
||||
// saved by an older QElectroTech raises a warning from
|
||||
// QETProject::readProjectXml(), and with nobody able to dismiss it
|
||||
// QDialog::exec() would spin its event loop forever.
|
||||
QET::QetMessageBox::setNonInteractive(true);
|
||||
return CLIExport::run(export_app.arguments());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "ui/diagrameditorhandlersizewidget.h"
|
||||
#include "TerminalStrip/ui/addterminalstripitemdialog.h"
|
||||
#include "wiringlistexport.h"
|
||||
#include "ui/wiringlistdialog.h"
|
||||
#include "ui/terminalnumberingdialog.h"
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
@@ -519,6 +520,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);
|
||||
@@ -940,6 +952,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();
|
||||
@@ -1789,6 +1802,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);
|
||||
|
||||
@@ -209,6 +209,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
*m_project_terminalBloc, ///< generate terminal block
|
||||
*m_project_export_conductor_num,///<Export the wire num to csv
|
||||
*m_project_export_wiring_list, ///< Action to export the wiring list
|
||||
*m_project_wiring_list_view, ///< Action to show the wiring list read from the project database
|
||||
*m_terminal_numbering, ///< Action to launch terminal numbering
|
||||
*m_export_project_db, ///Export to file the internal database of the current project
|
||||
*m_tile_window, ///< Show MDI subwindows as tile
|
||||
|
||||
@@ -17,6 +17,84 @@
|
||||
*/
|
||||
#include "qetmessagebox.h"
|
||||
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
bool g_non_interactive = false;
|
||||
|
||||
/**
|
||||
@brief autoAnswer
|
||||
Report a message box on stderr and pick an answer, for use when there
|
||||
is no user to click anything. @see QET::QetMessageBox::setNonInteractive
|
||||
@param severity : short word naming the kind of box, for the log line
|
||||
@param title
|
||||
@param text
|
||||
@param buttons : the buttons the caller offered
|
||||
@param defaultButton : the caller's preferred answer, may be NoButton
|
||||
@return the button to report as pressed
|
||||
*/
|
||||
QMessageBox::StandardButton autoAnswer(
|
||||
const char *severity,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
//Honour the caller's own default when it named one.
|
||||
if (defaultButton != QMessageBox::NoButton
|
||||
&& (buttons & defaultButton)) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: answered with the caller's default button)\n";
|
||||
return defaultButton;
|
||||
}
|
||||
|
||||
//Otherwise prefer a "carry on" answer over one that cancels, so a
|
||||
//batch run completes rather than silently doing nothing.
|
||||
static const QMessageBox::StandardButton preference[] = {
|
||||
QMessageBox::Ok, QMessageBox::Open, QMessageBox::Yes,
|
||||
QMessageBox::Save, QMessageBox::Apply, QMessageBox::YesToAll,
|
||||
QMessageBox::Retry, QMessageBox::Ignore, QMessageBox::Close
|
||||
};
|
||||
for (auto candidate : preference) {
|
||||
if (buttons & candidate) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: continuing)\n";
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
//Nothing affirmative on offer -- fall back to whatever is set.
|
||||
for (int bit = QMessageBox::Ok; bit <= QMessageBox::RestoreDefaults; bit <<= 1) {
|
||||
auto candidate = static_cast<QMessageBox::StandardButton>(bit);
|
||||
if (buttons & candidate) {
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: answered automatically)\n";
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
QTextStream(stderr) << severity << ": " << title << " -- " << text
|
||||
<< "\n(no display: no button offered)\n";
|
||||
return QMessageBox::NoButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::QetMessageBox::setNonInteractive
|
||||
@param non_interactive
|
||||
*/
|
||||
void QET::QetMessageBox::setNonInteractive(bool non_interactive) {
|
||||
g_non_interactive = non_interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QET::QetMessageBox::isNonInteractive
|
||||
@return true when message boxes are answered without a user
|
||||
*/
|
||||
bool QET::QetMessageBox::isNonInteractive() {
|
||||
return g_non_interactive;
|
||||
}
|
||||
|
||||
/**
|
||||
@see Documentation Qt pour QMessageBox::critical
|
||||
*/
|
||||
@@ -27,6 +105,9 @@ QMessageBox::StandardButton QET::QetMessageBox::critical (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Critical", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Critical,
|
||||
@@ -59,6 +140,9 @@ QMessageBox::StandardButton QET::QetMessageBox::information(
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Information", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Information,
|
||||
@@ -91,6 +175,9 @@ QMessageBox::StandardButton QET::QetMessageBox::question (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Question", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Question,
|
||||
@@ -123,6 +210,9 @@ QMessageBox::StandardButton QET::QetMessageBox::warning (
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
{
|
||||
if (g_non_interactive) {
|
||||
return autoAnswer("Warning", title, text, buttons, defaultButton);
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
QMessageBox message_box(
|
||||
QMessageBox::Warning,
|
||||
|
||||
@@ -27,6 +27,28 @@ namespace QET {
|
||||
Qt:Sheet flag, thus enabling a better MacOS integration.
|
||||
*/
|
||||
namespace QetMessageBox {
|
||||
/**
|
||||
Enable non-interactive mode.
|
||||
|
||||
In non-interactive mode the functions below never construct a
|
||||
dialog. They write the message to stderr and return an answer
|
||||
immediately, so a headless run cannot block on a modal box that
|
||||
nobody is there to dismiss.
|
||||
|
||||
This is needed because these are reachable from the command-line
|
||||
tools: opening a project written by an older QElectroTech raises
|
||||
a warning from QETProject::readProjectXml(), and with no display
|
||||
to click it, QDialog::exec() spins its event loop forever.
|
||||
|
||||
The answer is chosen as: the caller's defaultButton when it gave
|
||||
one, otherwise the first "carry on" button among those offered
|
||||
(Ok, Open, Yes, Save, Apply...), otherwise the first button set.
|
||||
So the two warnings above resolve to Open and the project loads,
|
||||
which is what a batch invocation wants.
|
||||
*/
|
||||
void setNonInteractive(bool non_interactive);
|
||||
bool isNonInteractive();
|
||||
|
||||
QMessageBox::StandardButton critical (
|
||||
QWidget *,
|
||||
const QString &,
|
||||
|
||||
@@ -1252,7 +1252,7 @@ ElementsLocation QETProject::importElement(ElementsLocation &location)
|
||||
// Warn if the new element introduces slave contact groups
|
||||
QDomElement new_kind = location.xml().firstChildElement("kindInformations");
|
||||
if (!new_kind.firstChildElement("slaveContactGroups").isNull()) {
|
||||
QMessageBox::StandardButton answer = QMessageBox::warning(nullptr,
|
||||
QMessageBox::StandardButton answer = QET::QetMessageBox::warning(nullptr,
|
||||
tr("Système de contacts modifié"),
|
||||
tr("Le nouvel élément définit des groupes de contacts esclaves.\n"
|
||||
"Les éléments esclaves existants ne seront pas automatiquement "
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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 "wiringlistdialog.h"
|
||||
|
||||
#include "../dataBase/projectdatabase.h"
|
||||
#include "../qetproject.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QSqlQueryModel>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
@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);
|
||||
|
||||
//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,"
|
||||
" 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();
|
||||
|
||||
//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);
|
||||
summary->setWordWrap(true);
|
||||
if (excluded > 0)
|
||||
{
|
||||
//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) : une extrémité n'est rattachée"
|
||||
" à aucun élément.",
|
||||
"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);
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef WIRINGLISTDIALOG_H
|
||||
#define WIRINGLISTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user