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
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/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/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/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/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;
}
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)
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/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();
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()
diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp
index 9198bf850..f372f99d7 100644
--- a/tests/qttest/tst_resaveunchanged.cpp
+++ b/tests/qttest/tst_resaveunchanged.cpp
@@ -160,6 +160,73 @@ 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()
+ {
+#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);
+ 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 folio report arrow linked to one that has no terminal -- such as
// ref_voyant_2_h.elmt from the collection -- crashed QElectroTech on
// opening: the arrow's text looked for a wire on the other arrow's