From ee164f1bf10f6e10b5541fdd1117335fdb6bd205 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 18:05:27 +1300 Subject: [PATCH 1/7] Fix a split table losing its data and crashing after a folio is deleted A summary or parts-list table split over several folios saves its data with its first part only; each later part names the part before it. Deleting a folio keeps its tables in memory for undo, still in the chain, so saving then named the deleted part as the previous table of the next one. On reopening, every part after the gap had no data, and closing the project crashed: the first orphaned part, destroyed, handed its missing data on to the next part through a null model. toXml() now names the nearest previous part still in the project, or saves the data if there is none. setPreviousTable() no longer copies a null model, so files already saved this way open and close. Tests (tst_resaveunchanged): missingPreviousTableOpens, deletedFolioKeepsTableChain; both fail without the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf --- .../ViewItem/qetgraphicstableitem.cpp | 20 ++++-- tests/qttest/tst_resaveunchanged.cpp | 64 +++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp index 75660104a..df090c36d 100644 --- a/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp +++ b/sources/qetgraphicsitem/ViewItem/qetgraphicstableitem.cpp @@ -448,7 +448,7 @@ void QetGraphicsTableItem::setPreviousTable(QetGraphicsTableItem *table) m_previous_table->setNextTable(this); setModel(m_previous_table->m_model); } - else //Copie the model of old previous table + else if (old_previous_table->model()) //Copie the model of old previous table { setModel(new ProjectDBModel(*static_cast(old_previous_table->model()))); } @@ -596,11 +596,23 @@ QDomElement QetGraphicsTableItem::toXml(QDomDocument &dom_document) const //Add the header xml dom_table.appendChild(m_header_item->toXml(dom_document)); - //Add previous table, the model is save by the previous table - if (m_previous_table) + //Add previous table, the model is save by the previous table. + //A folio deleted with undo still possible keeps its tables in the + //chain, but it is not saved: name the nearest previous table that + //is, or save the model if none is. + const auto project_ = diagram() ? diagram()->project() : nullptr; + auto in_project = [project_](const QetGraphicsTableItem *table) { + return project_ && table->diagram() + && project_->diagrams().contains(table->diagram()); + }; + QetGraphicsTableItem *previous_ = m_previous_table; + while (previous_ && !in_project(previous_)) { + previous_ = previous_->previousTable(); + } + if (previous_) { auto dom_previous_table = dom_document.createElement("previous_table"); - dom_previous_table.setAttribute("uuid", m_previous_table->m_uuid.toString()); + dom_previous_table.setAttribute("uuid", previous_->m_uuid.toString()); dom_table.appendChild(dom_previous_table); } else if (m_model) //There is not a previous table, we need to save the model diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index e80b4cb50..360d540ba 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -160,6 +160,70 @@ private slots: QVERIFY2(sb < sa, "the coil's contacts were saved in another order than the file's"); } + // A table split over several folios whose part names a previous part + // that is not in the file: opening and closing it crashed (the part + // has no data of its own and handed its missing data on when it was + // destroyed). industrial.qet's third is changed. + void missingPreviousTableOpens() + { + QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet")); + int at = -1; + for (int n = 0 ; n < 3 ; ++n) { + at = xml.indexOf(" 0); + } + at += int(qstrlen(" in the file names a table that is in + // it. It used to name the deleted part, so every part after it opened + // with no data. industrial.qet's folio 46 (index 45) holds such a part. + void deletedFolioKeepsTableChain() + { + const QString out = m_dir.filePath(QStringLiteral("deleted_folio.qet")); + const QString script = m_dir.filePath(QStringLiteral("deleted_folio.js")); + QFile js(script); + QVERIFY(js.open(QIODevice::WriteOnly)); + js.write(QStringLiteral("qet.log('REMOVED ' + qet.removeFolio(45));\n" + "qet.log('SAVED ' + qet.save('%1'));\n").arg(out).toUtf8()); + js.close(); + const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++)); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen")); + env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1")); + env.insert(QStringLiteral("HOME"), home); + env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config")); + env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data")); + QProcess proc; + proc.setProcessEnvironment(env); + proc.start(QStringLiteral(QET_TEST_BINARY_PATH), + {QStringLiteral("--run"), script, QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet")}); + QVERIFY(proc.waitForFinished(180000)); + const QString log = QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError()); + QVERIFY2(log.contains(QStringLiteral("REMOVED true")) && log.contains(QStringLiteral("SAVED true")), + qPrintable(log.right(400))); + + const QString saved = QString::fromUtf8(read(out)); + QSet tables; + for (const auto &m : QRegularExpression(QStringLiteral("]*uuid=\"([^\"]+)\"")).globalMatch(saved)) + tables.insert(m.captured(1)); + int references = 0; + for (const auto &m : QRegularExpression(QStringLiteral(" 0); + } + // A title-block value that is a single space is kept through two saves // (#973), and a value with accents comes back as it went in. void singleSpaceValueKept() From 5e6677ebeccedd437efbe0a5e1c2db406a8422cd Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 18:39:27 +1300 Subject: [PATCH 2/7] Keep the project database current when folios, symbols and wires change The project database is kept up to date edit by edit, and a script's qet.query() and the folio tables read it without a full rebuild. Three edits left it out of date until the next rebuild: - adding or removing a folio: the folios after it kept their old position and folio number, and a removed folio's diagram_info row stayed; - moving a symbol: its folio cell (element.pos) stayed where the symbol was added; - deleting a wire: its two ends stayed in the terminal table, which lists the terminals a wire ends on. Positions and folio numbers are now written again after a folio is added, removed or moved (one function for the three), the removed folio's diagram_info row goes, a moved symbol's cell is written when the database is next read (as drawing items are), and a deleted wire's ends go unless another wire ends there. Found by comparing the tables after random scripted edits with those a fresh open of the saved project gives. Left alone: labels and wire numbers made from a folio formula, which the drawing itself keeps until the project is reopened. Tests (tst_databasefromdocument): folioAddedAndRemoved, movedElementCell, deletedWireTerminals; each fails without its part of the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf --- sources/dataBase/projectdatabase.cpp | 143 ++++++++++++++++++---- sources/dataBase/projectdatabase.h | 8 ++ tests/qttest/tst_databasefromdocument.cpp | 105 ++++++++++++++++ 3 files changed, 232 insertions(+), 24 deletions(-) diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 15a10ce7b..52c7d5b87 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -75,18 +75,7 @@ projectDataBase::projectDataBase(QETProject *project, QObject *parent) : connect(m_project, &QETProject::projectDiagramsOrderChanged, [this]() { m_content_changed = true; - for (auto diagram : m_project->diagrams()) - { - m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1); - m_diagram_order_changed.bindValue(":uuid", diagram->uuid()); - m_diagram_order_changed.exec(); - - - m_diagram_info_order_changed.bindValue(":folio", diagram->border_and_titleblock.titleblockInformation().value("folio")); - m_diagram_info_order_changed.bindValue(":uuid", diagram->uuid()); - m_diagram_info_order_changed.exec(); - - } + updateFolioPositions(); emit dataBaseUpdated(); }); } @@ -122,6 +111,7 @@ void projectDataBase::updateDB() { flushDrawingItems(); flushLinks(); + flushElementPositions(); emit dataBaseUpdated(); return; } @@ -649,6 +639,7 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString query.exec(QStringLiteral("DELETE FROM ") + table); } m_dirty_link_elements.clear(); + m_moved_elements.clear(); for (int i = 0 ; i < diagram_uuids.size() ; ++i) { @@ -824,9 +815,11 @@ QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) { //Drawing-item rows are rewritten lazily, see drawingItemChanged(). //Every read from outside comes through here, so this is the one //place the queue has to be emptied for a reader to see current rows. - //The same goes for link rows, see linksChanged(). + //The same goes for link rows and elements' folio cells, see + //linksChanged() and elementMoved(). flushDrawingItems(); flushLinks(); + flushElementPositions(); // First gate: which kind of statement is acceptable here at all. A // textual check is the right tool for that and the wrong tool for @@ -920,6 +913,8 @@ void projectDataBase::addElement(Element *element) } connect(element, &Element::linkedElementChanged, this, &projectDataBase::linksChanged, Qt::UniqueConnection); + connect(element, &QGraphicsObject::xChanged, this, &projectDataBase::elementMoved, Qt::UniqueConnection); + connect(element, &QGraphicsObject::yChanged, this, &projectDataBase::elementMoved, Qt::UniqueConnection); bindElementInfoValues(m_insert_element_info_query, element); if (!m_insert_element_info_query.exec()) { @@ -1022,18 +1017,78 @@ void projectDataBase::addDiagram(Diagram *diagram) addDrawingItem(item); } - //The information "folio" of other existing diagram can have the variable %total, - //so when a new diagram is added this variable change. - //We need to update this information in the database. - for (auto diagram : project()->diagrams()) + //The folios after the new one moved down, and a folio number made + //from %id or %total changed on every folio. + updateFolioPositions(); + emit dataBaseUpdated(); +} + +/** + @brief projectDataBase::elementMoved + The sender() element moved: its folio cell is written again by the + next flushElementPositions(). +*/ +void projectDataBase::elementMoved() +{ + auto *element = qobject_cast(sender()); + if (element && !m_moved_elements.contains(element)) { + m_moved_elements << element; + } +} + +/** + @brief projectDataBase::flushElementPositions + Write the folio cell of every element queued by elementMoved() +*/ +void projectDataBase::flushElementPositions() +{ + if (m_moved_elements.isEmpty()) { + return; + } + + const auto moved = m_moved_elements; + m_moved_elements.clear(); + const bool own_transaction = m_data_base.transaction(); + QSqlQuery update(m_data_base); + update.prepare(QStringLiteral("UPDATE element SET pos = :pos WHERE uuid = :uuid")); + for (const QPointer &element : moved) { - m_diagram_info_order_changed.bindValue(":folio", diagram->border_and_titleblock.titleblockInformation().value("folio")); - m_diagram_info_order_changed.bindValue(":uuid", diagram->uuid()); - if (!m_diagram_info_order_changed.exec()) { - qDebug() << "projectDataBase::addDiagram update diagram infp order error : " << m_diagram_info_order_changed.lastError(); + if (!element || !element->diagram()) { + continue; + } + update.bindValue(QStringLiteral(":pos"), + element->diagram()->convertPosition(element->scenePos()).toString()); + update.bindValue(QStringLiteral(":uuid"), element->uuid().toString()); + if (!update.exec()) { + qDebug() << "projectDataBase::flushElementPositions error : " << update.lastError(); + } + } + if (own_transaction) { + m_data_base.commit(); + } +} + +/** + @brief projectDataBase::updateFolioPositions + Write every folio's position and folio number again, after a folio was + added, removed or moved. +*/ +void projectDataBase::updateFolioPositions() +{ + for (auto diagram : m_project->diagrams()) + { + m_diagram_order_changed.bindValue(":pos", m_project->folioIndex(diagram)+1); + m_diagram_order_changed.bindValue(":uuid", diagram->uuid().toString()); + if (!m_diagram_order_changed.exec()) { + qDebug() << "projectDataBase::updateFolioPositions position error : " << m_diagram_order_changed.lastError(); + } + + m_diagram_info_order_changed.bindValue(":folio", diagram->border_and_titleblock.titleblockInformation().value("folio")); + m_diagram_info_order_changed.bindValue(":uuid", diagram->uuid().toString()); + if (!m_diagram_info_order_changed.exec()) { + qDebug() << "projectDataBase::updateFolioPositions folio error : " << m_diagram_info_order_changed.lastError(); } } - emit dataBaseUpdated(); } void projectDataBase::removeDiagram(Diagram *diagram) @@ -1118,6 +1173,15 @@ void projectDataBase::removeDiagram(Diagram *diagram) } } + QSqlQuery remove_info(m_data_base); + remove_info.prepare(QStringLiteral("DELETE FROM diagram_info WHERE diagram_uuid = :uuid")); + remove_info.bindValue(QStringLiteral(":uuid"), uuid_str); + if (!remove_info.exec()) { + qDebug() << "projectDataBase::removeDiagram diagram_info delete error : " << remove_info.lastError(); + m_data_base.rollback(); + return; + } + m_remove_diagram_query.bindValue(":uuid", uuid_str); if (!m_remove_diagram_query.exec()) { qDebug() << "projectDataBase::removeDiagram delete error : " << m_remove_diagram_query.lastError(); @@ -1126,6 +1190,8 @@ void projectDataBase::removeDiagram(Diagram *diagram) } m_data_base.commit(); + //The folios after it moved up, and %id / %total changed. + updateFolioPositions(); emit dataBaseUpdated(); } @@ -1187,12 +1253,39 @@ void projectDataBase::addConductor(Conductor *conductor) void projectDataBase::removeConductor(Conductor *conductor) { m_content_changed = true; + //The terminal table lists the terminals a conductor ends on: the + //conductor's two ends go with it unless another conductor ends there. + QList> ends; + QSqlQuery read_ends(m_data_base); + read_ends.prepare(QStringLiteral("SELECT terminal1_uuid, terminal1_element_uuid, " + "terminal2_uuid, terminal2_element_uuid " + "FROM conductor WHERE uuid = :uuid")); + read_ends.bindValue(QStringLiteral(":uuid"), conductor->uuid().toString()); + if (read_ends.exec() && read_ends.next()) { + ends << qMakePair(read_ends.value(0).toString(), read_ends.value(1).toString()) + << qMakePair(read_ends.value(2).toString(), read_ends.value(3).toString()); + } + m_remove_conductor_query.bindValue(":uuid", conductor->uuid().toString()); if (!m_remove_conductor_query.exec()) { qDebug() << "projectDataBase::removeConductor delete error : " << m_remove_conductor_query.lastError(); - } else { - emit dataBaseUpdated(); + return; } + + QSqlQuery remove_end(m_data_base); + remove_end.prepare(QStringLiteral( + "DELETE FROM terminal WHERE uuid = :uuid AND element_uuid = :element_uuid " + "AND NOT EXISTS (SELECT 1 FROM conductor WHERE " + "(terminal1_uuid = :uuid AND terminal1_element_uuid = :element_uuid) OR " + "(terminal2_uuid = :uuid AND terminal2_element_uuid = :element_uuid))")); + for (const auto &end : std::as_const(ends)) { + remove_end.bindValue(QStringLiteral(":uuid"), end.first); + remove_end.bindValue(QStringLiteral(":element_uuid"), end.second); + if (!remove_end.exec()) { + qDebug() << "projectDataBase::removeConductor terminal delete error : " << remove_end.lastError(); + } + } + emit dataBaseUpdated(); } /** @@ -2061,6 +2154,7 @@ static ElementData::Types allElementTypes() */ void projectDataBase::populateElementTable() { + m_moved_elements.clear(); QSqlQuery query_(m_data_base); query_.exec("DELETE FROM element"); @@ -2597,6 +2691,7 @@ void projectDataBase::exportDb(projectDataBase *db, const auto escaped_path = path_.replace("'", "''"); db->flushDrawingItems(); db->flushLinks(); + db->flushElementPositions(); QSqlQuery query(db->m_data_base); if (!query.exec("VACUUM INTO '" % escaped_path % "'")) { qWarning() << "Unable to export project database:" << query.lastError().text(); diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index b15cd9286..9f3779c7c 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -126,6 +126,8 @@ class projectDataBase : public QObject void drawingItemDestroyed(QObject *object); //Queue the sender()'s link rows for rewriting. void linksChanged(); + //Queue the sender() element's folio cell for rewriting. + void elementMoved(); public: @@ -139,6 +141,8 @@ class projectDataBase : public QObject void createWiringListView(); void createDrawingItemView(); void populateDiagramTable(); + void updateFolioPositions(); + void flushElementPositions(); void populateElementTable(); void populateElementInfoTable(); void populateDiagramInfoTable(); @@ -214,6 +218,10 @@ class projectDataBase : public QObject //written: a link is made first and its contact group set after, //so the rows are written when next read, as drawing items are. QList> m_dirty_link_elements; + //Elements moved since their folio cell was last written: a move + //sends xChanged and yChanged for every step of a drag, so the + //cell is written when next read, as drawing items are. + QList> m_moved_elements; #ifdef QET_EXPORT_PROJECT_DB public: diff --git a/tests/qttest/tst_databasefromdocument.cpp b/tests/qttest/tst_databasefromdocument.cpp index 1660803d8..9fba39225 100644 --- a/tests/qttest/tst_databasefromdocument.cpp +++ b/tests/qttest/tst_databasefromdocument.cpp @@ -56,6 +56,12 @@ class tst_databasefromdocument : public QObject { const QString out = run({QStringLiteral("--run"), m_dir.filePath(QStringLiteral("dump.js")), project}, from_folios); + return tablesIn(out, how); + } + + // The DUMP line of a --run output, each table as a sorted list of rows + static QJsonObject tablesIn(const QString &out, QString *how) + { QJsonObject tables; for (const QString &line : out.split(QLatin1Char('\n'))) { if (line.contains(QStringLiteral("Project database filled"))) @@ -345,6 +351,105 @@ private slots: "a link is not one the folios would make as saved")); } + // After a folio is added and another removed, the folio tables hold + // what a fresh open of the saved project gives: every folio's position + // and folio number, and no row for the removed folio. Positions only + // changed when folios were reordered, and the removed folio's + // diagram_info row stayed. + void folioAddedAndRemoved() + { + const QString saved = m_dir.filePath(QStringLiteral("folios.qet")); + const QString edited = m_dir.filePath(QStringLiteral("folios-edited.qet")); + run({QStringLiteral("--resave"), QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), saved}); + QVERIFY(QFile::exists(saved)); + QFile js(m_dir.filePath(QStringLiteral("folios.js"))); + QVERIFY(js.open(QIODevice::WriteOnly)); + js.write(QStringLiteral( + "qet.log('ADDED ' + qet.insertFolio(0));\n" + "qet.log('REMOVED ' + qet.removeFolio(2));\n" + "var out = {};\n" + "['diagram', 'diagram_info'].forEach(function (t) { out[t] = qet.query('SELECT * FROM ' + t); });\n" + "qet.log('DUMP ' + JSON.stringify(out));\n" + "qet.log('SAVED ' + qet.save('%1'));\n").arg(edited).toUtf8()); + js.close(); + const QString log = run({QStringLiteral("--run"), js.fileName(), saved}); + QVERIFY2(log.contains(QStringLiteral("ADDED 0")) && log.contains(QStringLiteral("REMOVED true")) + && log.contains(QStringLiteral("SAVED true")), qPrintable(log.right(400))); + QString unused; + const QJsonObject after_edits = tablesIn(log, &unused); + const QJsonObject reopened = dump(edited, false, &unused); + for (const QString &table : {QStringLiteral("diagram"), QStringLiteral("diagram_info")}) { + const QJsonArray a = after_edits.value(table).toArray(), b = reopened.value(table).toArray(); + QVERIFY2(!a.isEmpty(), qPrintable(table)); + QVERIFY2(a == b, qPrintable(QStringLiteral("%1: %2 rows after the edits, %3 on reopening, or different") + .arg(table).arg(a.size()).arg(b.size()))); + } + } + + // After an element is moved several columns, its folio cell in the + // element table is the one a fresh open of the saved project gives. + // It used to stay where the element was added. + void movedElementCell() + { + const QString saved = m_dir.filePath(QStringLiteral("moved.qet")); + const QString edited = m_dir.filePath(QStringLiteral("moved-edited.qet")); + run({QStringLiteral("--resave"), QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), saved}); + QVERIFY(QFile::exists(saved)); + QFile js(m_dir.filePath(QStringLiteral("moved.js"))); + QVERIFY(js.open(QIODevice::WriteOnly)); + js.write(QStringLiteral( + "var e = qet.elementUuids(0)[0];\n" + "var before = qet.query(\"SELECT pos FROM element WHERE uuid = '\" + e + \"'\")[0].pos;\n" + "qet.log('MOVED ' + qet.moveElement(0, e, 300, 0));\n" + "var after = qet.query(\"SELECT pos FROM element WHERE uuid = '\" + e + \"'\")[0].pos;\n" + "qet.log('CELL ' + before + ' ' + after);\n" + "qet.log('DUMP ' + JSON.stringify({element: qet.query('SELECT * FROM element')}));\n" + "qet.log('SAVED ' + qet.save('%1'));\n").arg(edited).toUtf8()); + js.close(); + const QString log = run({QStringLiteral("--run"), js.fileName(), saved}); + QVERIFY2(log.contains(QStringLiteral("MOVED true")) && log.contains(QStringLiteral("SAVED true")), + qPrintable(log.right(400))); + QString unused; + const QJsonArray a = tablesIn(log, &unused).value(QStringLiteral("element")).toArray(); + const QJsonArray b = dump(edited, false, &unused).value(QStringLiteral("element")).toArray(); + QVERIFY(!a.isEmpty()); + QVERIFY2(a == b, qPrintable(log.mid(log.indexOf(QStringLiteral("CELL")), 40))); + } + + // After every conductor on a folio is deleted, the terminal table + // holds what a fresh open of the saved project gives: a terminal is + // listed while a conductor ends on it. The ends of a deleted conductor + // used to stay. + void deletedWireTerminals() + { + const QString saved = m_dir.filePath(QStringLiteral("unwired.qet")); + const QString edited = m_dir.filePath(QStringLiteral("unwired-edited.qet")); + run({QStringLiteral("--resave"), QStringLiteral(QET_EXAMPLES_DIR "/tremie_vibrante.qet"), saved}); + QVERIFY(QFile::exists(saved)); + QFile js(m_dir.filePath(QStringLiteral("unwired.js"))); + QVERIFY(js.open(QIODevice::WriteOnly)); + js.write(QStringLiteral( + "var n = 0;\n" + "qet.conductorUuids(0).forEach(function (c) {\n" + " var end = qet.conductorEnds(0, c)[0];\n" + " if (!end) return;\n" + " var m = end.match(/^(\\{[^}]+\\}) terminal (\\d+)$/);\n" + " if (m && qet.deleteConductor(0, m[1], parseInt(m[2]))) ++n;\n" + "});\n" + "qet.log('DELETED ' + n);\n" + "qet.log('DUMP ' + JSON.stringify({terminal: qet.query('SELECT * FROM terminal')}));\n" + "qet.log('SAVED ' + qet.save('%1'));\n").arg(edited).toUtf8()); + js.close(); + const QString log = run({QStringLiteral("--run"), js.fileName(), saved}); + QVERIFY2(!log.contains(QStringLiteral("DELETED 0")) && log.contains(QStringLiteral("SAVED true")), + qPrintable(log.right(400))); + QString unused; + const QJsonArray a = tablesIn(log, &unused).value(QStringLiteral("terminal")).toArray(); + const QJsonArray b = dump(edited, false, &unused).value(QStringLiteral("terminal")).toArray(); + QVERIFY2(a == b, qPrintable(QStringLiteral("%1 terminal rows after the edits, %2 on reopening") + .arg(a.size()).arg(b.size()))); + } + // A file whose items carry no saved uuid is filled from the folios, // which work those uuids out as they are built, and says why. void olderFileFallsBack() From 8c6ac0aeeca8660122ce58cefe333588713de26b Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 20:27:01 +1300 Subject: [PATCH 3/7] Fix the lupdate warning on the dropped-picture error texts lupdate printed "Class 'ImageDrop' lacks Q_OBJECT macro" at imagedrop.cpp:77 on every "update_translations" run (#1446). ImageDrop is a namespace, not a class, and the file routed its texts through a local tr() helper; lupdate reads tr() inside an ImageDrop:: function as a member of a class without Q_OBJECT. Call QCoreApplication::translate("ImageDrop", ...) directly instead, which lupdate reads without a class. The context and the four source texts are unchanged, so the existing translations still apply. Closes #1446 Co-Authored-By: Claude Opus 5.5 --- sources/imagedrop.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/sources/imagedrop.cpp b/sources/imagedrop.cpp index 06aa5ed85..76ac259d6 100644 --- a/sources/imagedrop.cpp +++ b/sources/imagedrop.cpp @@ -35,11 +35,6 @@ namespace const QString suffix = QFileInfo(url.toLocalFile()).suffix().toLower(); return ImageDrop::supportedSuffixes().contains(suffix); } - - QString tr(const char *text) - { - return QCoreApplication::translate("ImageDrop", text); - } } QStringList ImageDrop::supportedSuffixes() @@ -74,18 +69,18 @@ QImage ImageDrop::load(const QString &path, QString *error) const QFileInfo info(path); if (!info.isFile()) - return fail(tr("not a file")); + return fail(QCoreApplication::translate("ImageDrop", "not a file")); if (info.size() > maxFileBytes) - return fail(tr("the file is larger than 10 MB")); + return fail(QCoreApplication::translate("ImageDrop", "the file is larger than 10 MB")); QImageReader reader(path); const QSize size = reader.size(); if (size.isValid() && qint64(size.width()) * size.height() > maxPixels) - return fail(tr("the image has too many pixels")); + return fail(QCoreApplication::translate("ImageDrop", "the image has too many pixels")); const QImage image = reader.read(); if (image.isNull()) - return fail(tr("unable to read the image")); + return fail(QCoreApplication::translate("ImageDrop", "unable to read the image")); return image; } From bd50e17297ced38d5813d26ae7dc2fbd2197d47a Mon Sep 17 00:00:00 2001 From: plc-user <74435298+plc-user@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:36:42 +0200 Subject: [PATCH 4/7] shorten some German texts --- lang/qet_de.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lang/qet_de.ts b/lang/qet_de.ts index 108d3b071..6eefdced3 100644 --- a/lang/qet_de.ts +++ b/lang/qet_de.ts @@ -826,7 +826,7 @@ Auto Numbering - Automatische Nummerierung + Autom. Nummerierung @@ -1512,7 +1512,7 @@ Bemerkung: diese Optionen verhindern NICHT das automatische Nummerieren. Auto Numbering - Automatische Nummerierung: + Autom. Nummerierung: @@ -3468,7 +3468,7 @@ Der angezeigte Name des Elements lässt sich separat in den Eigenschaften des El Exclude from the bill of materials - Aus der Materialliste ausschließen + Aus Materialliste ausschließen @@ -3504,7 +3504,7 @@ Soll sie erstellt werden? Automatic numbering - Automatische Nummerierung + Autom. Nummerierung @@ -3565,7 +3565,7 @@ Soll sie erstellt werden? None (name entered by hand) - Keine (von Hand eingegebener Name) + Keine (von Hand vergeben) @@ -3702,7 +3702,7 @@ Fortfahren? Exclude from the bill of materials - Aus der Materialliste ausschließen + Aus Materialliste ausschließen From daa648099bf911f54eab92b3702dccb245b7b84b Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 10 Oct 2026 09:49:42 +0200 Subject: [PATCH 5/7] Terminal strip: save the layout (font, cell sizes...) in the project The default terminal strip layout was rebuilt at every project load, so the font size set in the layout editor was reset to 15 px. Add TerminalStripLayoutPattern::toXml/fromXml and TerminalStripLayoutsHandler::toXml/fromXml, write a element in QETProject::toXml() and read it before the diagrams are loaded. Projects without it keep the defaults. --- .../properties/terminalstriplayoutpattern.cpp | 175 ++++++++++++++++++ .../properties/terminalstriplayoutpattern.h | 6 + .../terminalstriplayoutshandler.cpp | 25 +++ .../properties/terminalstriplayoutshandler.h | 4 + sources/qetproject.cpp | 6 + 5 files changed, 216 insertions(+) diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp index 2d672f9c4..e11ede4a3 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.cpp @@ -18,6 +18,8 @@ #include "terminalstriplayoutpattern.h" #include "../../../utils/qetutils.h" +#include + TerminalStripLayoutPattern::TerminalStripLayoutPattern() { m_font.setPixelSize(15); @@ -116,3 +118,176 @@ void TerminalStripLayoutPattern::updateTerminalsTextOption() m_xref_text_option.setAlignment(m_xref_text_alignment); m_xref_text_option.setWrapMode(QTextOption::WordWrap); } + +namespace +{ + void rectToXml(QDomElement &element, const QString &name, const QRectF &rect) + { + element.setAttribute(name + QStringLiteral("_x"), rect.x()); + element.setAttribute(name + QStringLiteral("_y"), rect.y()); + element.setAttribute(name + QStringLiteral("_w"), rect.width()); + element.setAttribute(name + QStringLiteral("_h"), rect.height()); + } + + QRectF rectFromXml(const QDomElement &element, const QString &name, const QRectF &default_rect) + { + auto value = [&element, &name](const QString &suffix, qreal default_value) + { + bool ok = false; + const auto v = element.attribute(name + suffix).toDouble(&ok); + return ok ? v : default_value; + }; + + return QRectF(value(QStringLiteral("_x"), default_rect.x()), + value(QStringLiteral("_y"), default_rect.y()), + value(QStringLiteral("_w"), default_rect.width()), + value(QStringLiteral("_h"), default_rect.height())); + } + + qreal realFromXml(const QDomElement &element, const QString &name, qreal default_value) + { + bool ok = false; + const auto v = element.attribute(name).toDouble(&ok); + return ok ? v : default_value; + } + + QString orientationToString(Qt::Orientation orientation) { + return orientation == Qt::Horizontal ? QStringLiteral("horizontal") : QStringLiteral("vertical"); + } + + Qt::Orientation orientationFromXml(const QDomElement &element, const QString &name, Qt::Orientation default_value) + { + const auto v = element.attribute(name); + if (v == QLatin1String("horizontal")) return Qt::Horizontal; + if (v == QLatin1String("vertical")) return Qt::Vertical; + return default_value; + } + + Qt::Alignment alignmentFromXml(const QDomElement &element, const QString &name, Qt::Alignment default_value) + { + bool ok = false; + const auto v = element.attribute(name).toInt(&ok); + return ok && v ? Qt::Alignment(static_cast(v)) : default_value; + } +} + +/** + * @brief TerminalStripLayoutPattern::xmlTagName + * @return the tag name of the xml element used by toXml / fromXml + */ +QString TerminalStripLayoutPattern::xmlTagName() { + return QStringLiteral("terminal_strip_layout"); +} + +/** + * @brief TerminalStripLayoutPattern::toXml + * Save all the values of this layout, including the font. + * The font is saved by hand (family, pixel size, bold, italic) and not with + * QFont::toString() because the format of this string is different between Qt5 and Qt6. + * @param document + * @return the xml element + */ +QDomElement TerminalStripLayoutPattern::toXml(QDomDocument &document) const +{ + auto element = document.createElement(xmlTagName()); + + rectToXml(element, QStringLiteral("header"), m_header_rect); + element.setAttribute(QStringLiteral("header_text_orientation"), orientationToString(m_header_text_orientation)); + element.setAttribute(QStringLiteral("header_text_alignment"), static_cast(m_header_text_alignment)); + + rectToXml(element, QStringLiteral("spacer"), m_spacer_rect); + + element.setAttribute(QStringLiteral("font_family"), m_font.family()); + element.setAttribute(QStringLiteral("font_pixel_size"), m_font.pixelSize()); + element.setAttribute(QStringLiteral("font_bold"), m_font.bold() ? 1 : 0); + element.setAttribute(QStringLiteral("font_italic"), m_font.italic() ? 1 : 0); + + for (auto i = 0; i < m_terminal_rect.size(); ++i) { + rectToXml(element, QStringLiteral("terminal_%1").arg(i), m_terminal_rect.at(i)); + } + + element.setAttribute(QStringLiteral("terminals_text_height"), m_terminals_text_height); + element.setAttribute(QStringLiteral("terminals_text_y"), m_terminals_text_y); + element.setAttribute(QStringLiteral("terminals_text_orientation"), orientationToString(m_terminals_text_orientation)); + element.setAttribute(QStringLiteral("terminals_text_alignment"), static_cast(m_terminals_text_alignment)); + + element.setAttribute(QStringLiteral("xref_text_height"), m_xref_text_height); + element.setAttribute(QStringLiteral("xref_text_y"), m_xref_text_y); + element.setAttribute(QStringLiteral("xref_text_orientation"), orientationToString(m_xref_text_orientation)); + element.setAttribute(QStringLiteral("xref_text_alignment"), static_cast(m_xref_text_alignment)); + + element.setAttribute(QStringLiteral("type_symbol_y"), m_type_symbol_y); + element.setAttribute(QStringLiteral("type_symbol_height"), m_type_symbol_height); + element.setAttribute(QStringLiteral("connection_length"), m_connection_length); + element.setAttribute(QStringLiteral("cable_wire_length"), m_cable_wire_length); + element.setAttribute(QStringLiteral("cable_length"), m_cable_length); + element.setAttribute(QStringLiteral("cable_end_length"), m_cable_end_length); + + element.setAttribute(QStringLiteral("bridge_point_d"), m_bridge_point_d); + for (auto i = 0; i < m_bridge_point_y_offset.size(); ++i) { + element.setAttribute(QStringLiteral("bridge_point_y_offset_%1").arg(i), m_bridge_point_y_offset.at(i)); + } + + return element; +} + +/** + * @brief TerminalStripLayoutPattern::fromXml + * Load the values saved by toXml. A value missing in @a layout_element + * (project saved with an older version) keep the current value of this layout. + * @param layout_element + */ +void TerminalStripLayoutPattern::fromXml(const QDomElement &layout_element) +{ + if (layout_element.isNull()) { + return; + } + + m_header_rect = rectFromXml(layout_element, QStringLiteral("header"), m_header_rect); + m_header_text_orientation = orientationFromXml(layout_element, QStringLiteral("header_text_orientation"), m_header_text_orientation); + setHeaderTextAlignment(alignmentFromXml(layout_element, QStringLiteral("header_text_alignment"), m_header_text_alignment)); + + m_spacer_rect = rectFromXml(layout_element, QStringLiteral("spacer"), m_spacer_rect); + + auto font_ = m_font; + if (layout_element.hasAttribute(QStringLiteral("font_family"))) { + font_.setFamily(layout_element.attribute(QStringLiteral("font_family"))); + } + const auto pixel_size = layout_element.attribute(QStringLiteral("font_pixel_size")).toInt(); + if (pixel_size > 0) { + font_.setPixelSize(pixel_size); + } + if (layout_element.hasAttribute(QStringLiteral("font_bold"))) { + font_.setBold(layout_element.attribute(QStringLiteral("font_bold")).toInt() != 0); + } + if (layout_element.hasAttribute(QStringLiteral("font_italic"))) { + font_.setItalic(layout_element.attribute(QStringLiteral("font_italic")).toInt() != 0); + } + setFont(font_); + + for (auto i = 0; i < m_terminal_rect.size(); ++i) { + m_terminal_rect[i] = rectFromXml(layout_element, QStringLiteral("terminal_%1").arg(i), m_terminal_rect.at(i)); + } + + m_terminals_text_height = realFromXml(layout_element, QStringLiteral("terminals_text_height"), m_terminals_text_height); + m_terminals_text_y = realFromXml(layout_element, QStringLiteral("terminals_text_y"), m_terminals_text_y); + m_terminals_text_orientation = orientationFromXml(layout_element, QStringLiteral("terminals_text_orientation"), m_terminals_text_orientation); + setTerminalsTextAlignment(alignmentFromXml(layout_element, QStringLiteral("terminals_text_alignment"), m_terminals_text_alignment)); + + m_xref_text_height = realFromXml(layout_element, QStringLiteral("xref_text_height"), m_xref_text_height); + m_xref_text_y = realFromXml(layout_element, QStringLiteral("xref_text_y"), m_xref_text_y); + m_xref_text_orientation = orientationFromXml(layout_element, QStringLiteral("xref_text_orientation"), m_xref_text_orientation); + setXrefTextAlignment(alignmentFromXml(layout_element, QStringLiteral("xref_text_alignment"), m_xref_text_alignment)); + + m_type_symbol_y = realFromXml(layout_element, QStringLiteral("type_symbol_y"), m_type_symbol_y); + m_type_symbol_height = realFromXml(layout_element, QStringLiteral("type_symbol_height"), m_type_symbol_height); + m_connection_length = realFromXml(layout_element, QStringLiteral("connection_length"), m_connection_length); + m_cable_wire_length = realFromXml(layout_element, QStringLiteral("cable_wire_length"), m_cable_wire_length); + m_cable_length = realFromXml(layout_element, QStringLiteral("cable_length"), m_cable_length); + m_cable_end_length = realFromXml(layout_element, QStringLiteral("cable_end_length"), m_cable_end_length); + + m_bridge_point_d = realFromXml(layout_element, QStringLiteral("bridge_point_d"), m_bridge_point_d); + for (auto i = 0; i < m_bridge_point_y_offset.size(); ++i) { + m_bridge_point_y_offset[i] = realFromXml(layout_element, QStringLiteral("bridge_point_y_offset_%1").arg(i), m_bridge_point_y_offset.at(i)); + } +} diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h index ee5e4e8ee..b432783b2 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutpattern.h @@ -18,6 +18,7 @@ #ifndef TERMINALSTRIPLAYOUTPATTERN_H #define TERMINALSTRIPLAYOUTPATTERN_H +#include #include #include #include @@ -103,6 +104,11 @@ class TerminalStripLayoutPattern QUuid m_uuid{QUuid::createUuid()}; QString m_name; + //Save / load of the layout in the project xml + QDomElement toXml(QDomDocument &document) const; + void fromXml(const QDomElement &layout_element); + static QString xmlTagName(); + private: void updateHeaderTextOption(); void updateTerminalsTextOption(); diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.cpp b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.cpp index 66a0bc92f..1dc905d73 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.cpp +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.cpp @@ -17,6 +17,7 @@ */ #include "terminalstriplayoutshandler.h" +#include #include TerminalStripLayoutsHandler::TerminalStripLayoutsHandler() @@ -30,3 +31,27 @@ QSharedPointer TerminalStripLayoutsHandler::defaultL { return m_default_layout; } + +/** + * @brief TerminalStripLayoutsHandler::toXml + * Append the default layout (font included) as a child of @a parent_element + * @param parent_element + */ +void TerminalStripLayoutsHandler::toXml(QDomElement &parent_element) const +{ + auto document = parent_element.ownerDocument(); + parent_element.appendChild(m_default_layout->toXml(document)); +} + +/** + * @brief TerminalStripLayoutsHandler::fromXml + * Load the default layout from the child of @a parent_element, if any. + * The existing layout object is modified in place because the terminal + * strip items share it. + * @param parent_element + */ +void TerminalStripLayoutsHandler::fromXml(const QDomElement &parent_element) +{ + m_default_layout->fromXml( + parent_element.firstChildElement(TerminalStripLayoutPattern::xmlTagName())); +} diff --git a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.h b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.h index cddb842f2..d9275c4da 100644 --- a/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.h +++ b/sources/TerminalStrip/GraphicsItem/properties/terminalstriplayoutshandler.h @@ -18,6 +18,7 @@ #ifndef TERMINALSTRIPLAYOUTSHANDLER_H #define TERMINALSTRIPLAYOUTSHANDLER_H +#include #include #include @@ -33,6 +34,9 @@ class TerminalStripLayoutsHandler TerminalStripLayoutsHandler(); QSharedPointer defaultLayout(); + void toXml(QDomElement &parent_element) const; + void fromXml(const QDomElement &parent_element); + private: QSet> m_layout_set; QSharedPointer m_default_layout; diff --git a/sources/qetproject.cpp b/sources/qetproject.cpp index d6b070f49..829ffd3b6 100644 --- a/sources/qetproject.cpp +++ b/sources/qetproject.cpp @@ -1587,6 +1587,8 @@ QDomDocument QETProject::toXml() //Write terminal strip to xml if (m_terminal_strip_vector.count()) { + m_project_properties_handler.terminalStripLayoutHandler().toXml(project_root); + auto xml_strip = xml_doc.createElement(QStringLiteral("terminal_strips")); for (auto &strip : m_terminal_strip_vector) { xml_strip.appendChild(strip->toXml(xml_doc)); @@ -2092,6 +2094,10 @@ void QETProject::readProjectXml(QDomDocument &xml_project) readElementsCollectionXml(xml_project); const qint64 elements_ms = phase_timer.restart(); + //Load the layout of the terminal strip (font, size of cells...), + //before the diagrams because the terminal strip items use it + m_project_properties_handler.terminalStripLayoutHandler().fromXml(xml_project.documentElement()); + //Load the diagrams readDiagramsXml(xml_project); const qint64 diagrams_ms = phase_timer.restart(); From 5b98f1a1e932525cfe7ca7d1b620c026185b8888 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 20:56:25 +1300 Subject: [PATCH 6/7] Skip the folio-deletion test where --run is not built CI builds without Qt Qml, so --run is not compiled in and the test's QElectroTech opened the project and waited until the 180 s timeout. The test now skips when QET_HAS_SCRIPTING is not defined, as the other --run tests are left out of such builds. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf --- tests/qttest/tst_resaveunchanged.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index 360d540ba..9daf85886 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -188,6 +188,9 @@ private slots: // with no data. industrial.qet's folio 46 (index 45) holds such a part. void deletedFolioKeepsTableChain() { +#ifndef QET_HAS_SCRIPTING + QSKIP("needs --run: this QElectroTech is built without Qt Qml"); +#endif const QString out = m_dir.filePath(QStringLiteral("deleted_folio.qet")); const QString script = m_dir.filePath(QStringLiteral("deleted_folio.js")); QFile js(script); From fb226d435e55d1e96f50c27f25d05c81913583c7 Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Sat, 10 Oct 2026 10:01:12 +0200 Subject: [PATCH 7/7] Terminal strip: add an anti-parallel diodes terminal type Add ElementData::TTAntiParallelDiodes (xml value "antiparallel_diodes"), selectable in the element editor, the terminal strip editor and the free terminal editor, and drawn in the terminal strip plan as two diodes wired in opposite directions between the top and the bottom of the terminal. --- .../GraphicsItem/terminalstripdrawer.cpp | 26 +++++++++++++++++++ .../TerminalStrip/ui/freeterminaleditor.cpp | 2 ++ .../TerminalStrip/ui/freeterminaleditor.ui | 5 ++++ .../TerminalStrip/ui/freeterminalmodel.cpp | 1 + .../TerminalStrip/ui/terminalstripeditor.cpp | 2 ++ .../TerminalStrip/ui/terminalstripeditor.ui | 5 ++++ .../TerminalStrip/ui/terminalstripmodel.cpp | 1 + .../ui/elementpropertieseditorwidget.cpp | 1 + sources/properties/elementdata.cpp | 6 +++++ sources/properties/elementdata.h | 3 ++- 10 files changed, 51 insertions(+), 1 deletion(-) diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index be933ab63..8eccb75df 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -90,6 +90,31 @@ void drawGroundSymbol(QPainter *painter) painter->drawLine(QPointF{-1, 4}, QPointF{1, 4}); } + //Two diodes side by side, wired in anti-parallel between the top and + //the bottom of the terminal (current flows through the left one from + //the top to the bottom and through the right one the other way) +void drawAntiParallelDiodesSymbol(QPainter *painter) +{ + painter->drawLine(QPointF{0, -7}, QPointF{0, -5}); + painter->drawLine(QPointF{-5, -5}, QPointF{5, -5}); + painter->drawLine(QPointF{0, 5}, QPointF{0, 7}); + painter->drawLine(QPointF{-5, 5}, QPointF{5, 5}); + + //Left diode, anode on top + const QPointF left_diode[3] { {-8, -3}, {-2, -3}, {-5, 2} }; + painter->drawLine(QPointF{-5, -5}, QPointF{-5, -3}); + painter->drawPolygon(left_diode, 3); + painter->drawLine(QPointF{-8, 2}, QPointF{-2, 2}); + painter->drawLine(QPointF{-5, 2}, QPointF{-5, 5}); + + //Right diode, anode at the bottom + const QPointF right_diode[3] { {2, 2}, {8, 2}, {5, -3} }; + painter->drawLine(QPointF{5, -5}, QPointF{5, -3}); + painter->drawPolygon(right_diode, 3); + painter->drawLine(QPointF{2, -3}, QPointF{8, -3}); + painter->drawLine(QPointF{5, 2}, QPointF{5, 5}); +} + /** * @brief drawScaled * Call @a draw with the painter moved by @a dx and scaled by @a factor. @@ -147,6 +172,7 @@ void drawTypeSymbol(QPainter *painter, const QRectF &box, ElementData::TerminalT case ElementData::TTSectional : drawSectionalSymbol(p); break; case ElementData::TTDiode : drawDiodeSymbol(p); break; case ElementData::TTGround : drawGroundSymbol(p); break; + case ElementData::TTAntiParallelDiodes : drawAntiParallelDiodesSymbol(p); break; default: break; } }; diff --git a/sources/TerminalStrip/ui/freeterminaleditor.cpp b/sources/TerminalStrip/ui/freeterminaleditor.cpp index 50e006d7b..3f08e39d6 100644 --- a/sources/TerminalStrip/ui/freeterminaleditor.cpp +++ b/sources/TerminalStrip/ui/freeterminaleditor.cpp @@ -191,6 +191,8 @@ void FreeTerminalEditor::on_m_type_cb_activated(int index) override_type = ElementData::TTDiode; break; case 4: override_type = ElementData::TTGround; break; + case 5: + override_type = ElementData::TTAntiParallelDiodes; break; default: override_type = ElementData::TTGeneric; break; } diff --git a/sources/TerminalStrip/ui/freeterminaleditor.ui b/sources/TerminalStrip/ui/freeterminaleditor.ui index 56f6c4c2a..736c2b12d 100644 --- a/sources/TerminalStrip/ui/freeterminaleditor.ui +++ b/sources/TerminalStrip/ui/freeterminaleditor.ui @@ -122,6 +122,11 @@ Ground + + + Anti-parallel diodes + + diff --git a/sources/TerminalStrip/ui/freeterminalmodel.cpp b/sources/TerminalStrip/ui/freeterminalmodel.cpp index 27183100e..7eb2eb1f6 100644 --- a/sources/TerminalStrip/ui/freeterminalmodel.cpp +++ b/sources/TerminalStrip/ui/freeterminalmodel.cpp @@ -429,6 +429,7 @@ QWidget *FreeTerminalModelDelegate::createEditor(QWidget *parent, const QStyleOp qcb->addItem(ElementData::translatedTerminalType(ElementData::TTSectional), ElementData::TTSectional); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTDiode), ElementData::TTDiode); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTGround), ElementData::TTGround); + qcb->addItem(ElementData::translatedTerminalType(ElementData::TTAntiParallelDiodes), ElementData::TTAntiParallelDiodes); return qcb; } diff --git a/sources/TerminalStrip/ui/terminalstripeditor.cpp b/sources/TerminalStrip/ui/terminalstripeditor.cpp index 9a56339b0..ba3964f1e 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditor.cpp @@ -569,6 +569,8 @@ void TerminalStripEditor::on_m_type_cb_activated(int index) override_type = ElementData::TTDiode; break; case 4: override_type = ElementData::TTGround; break; + case 5: + override_type = ElementData::TTAntiParallelDiodes; break; default: override_type = ElementData::TTGeneric; break; } diff --git a/sources/TerminalStrip/ui/terminalstripeditor.ui b/sources/TerminalStrip/ui/terminalstripeditor.ui index 35fbb9f5d..46998a04e 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.ui +++ b/sources/TerminalStrip/ui/terminalstripeditor.ui @@ -127,6 +127,11 @@ Ground + + + Anti-parallel diodes + + diff --git a/sources/TerminalStrip/ui/terminalstripmodel.cpp b/sources/TerminalStrip/ui/terminalstripmodel.cpp index c6e3fac46..2f363ba1c 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.cpp +++ b/sources/TerminalStrip/ui/terminalstripmodel.cpp @@ -833,6 +833,7 @@ QWidget *TerminalStripModelDelegate::createEditor(QWidget *parent, const QStyleO qcb->addItem(ElementData::translatedTerminalType(ElementData::TTSectional), ElementData::TTSectional); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTDiode), ElementData::TTDiode); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTGround), ElementData::TTGround); + qcb->addItem(ElementData::translatedTerminalType(ElementData::TTAntiParallelDiodes), ElementData::TTAntiParallelDiodes); return qcb; } diff --git a/sources/editor/ui/elementpropertieseditorwidget.cpp b/sources/editor/ui/elementpropertieseditorwidget.cpp index 178d08a21..d45477483 100644 --- a/sources/editor/ui/elementpropertieseditorwidget.cpp +++ b/sources/editor/ui/elementpropertieseditorwidget.cpp @@ -225,6 +225,7 @@ void ElementPropertiesEditorWidget::setUpInterface() ui->m_terminal_type_cb->addItem(tr("Sectionable"), ElementData::TTSectional); ui->m_terminal_type_cb->addItem(tr("Diode"), ElementData::TTDiode); ui->m_terminal_type_cb->addItem(tr("Ground"), ElementData::TTGround); + ui->m_terminal_type_cb->addItem(tr("Anti-parallel diodes"), ElementData::TTAntiParallelDiodes); ui->m_terminal_func_cb->addItem(tr("Generic"), ElementData::TFGeneric); ui->m_terminal_func_cb->addItem(tr("Phase"), ElementData::TFPhase); diff --git a/sources/properties/elementdata.cpp b/sources/properties/elementdata.cpp index 8586c243e..2804295ed 100644 --- a/sources/properties/elementdata.cpp +++ b/sources/properties/elementdata.cpp @@ -840,6 +840,8 @@ QString ElementData::terminalTypeToString(ElementData::TerminalType type) return QStringLiteral("diode"); case ElementData::TTGround: return QStringLiteral("ground"); + case ElementData::TTAntiParallelDiodes: + return QStringLiteral("antiparallel_diodes"); } return QStringLiteral("generic"); } @@ -856,6 +858,8 @@ ElementData::TerminalType ElementData::terminalTypeFromString(const QString &str return ElementData::TTDiode; } else if (string == QLatin1String("ground")) { return ElementData::TTGround; + } else if (string == QLatin1String("antiparallel_diodes")) { + return ElementData::TTAntiParallelDiodes; } qDebug() << "ElementData::terminalTypeFromString : string : " @@ -877,6 +881,8 @@ QString ElementData::translatedTerminalType(ElementData::TerminalType type) return QObject::tr("Diode", "diode terminal element type"); case ElementData::TTGround: return QObject::tr("Ground", "ground terminal element type"); + case ElementData::TTAntiParallelDiodes: + return QObject::tr("Anti-parallel diodes", "anti-parallel diodes terminal element type"); } return QObject::tr("Generic", "generic terminal element type"); } diff --git a/sources/properties/elementdata.h b/sources/properties/elementdata.h index 51b9c9678..97a834840 100644 --- a/sources/properties/elementdata.h +++ b/sources/properties/elementdata.h @@ -175,7 +175,8 @@ class ElementData : public PropertiesInterface TTFuse, TTSectional, TTDiode, - TTGround + TTGround, + TTAntiParallelDiodes }; Q_ENUM(TerminalType)