mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 12:34:15 +02:00
Merge master into fix/joined-wire-uuid
The conflict was tests/qttest/CMakeLists.txt, where #1520 added tst_savedstamps at the place this branch adds tst_joinedwireuuid. Both are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -847,13 +847,15 @@ bool projectDataBase::populateFromDocument(const QDomDocument &document, QString
|
||||
}
|
||||
}
|
||||
|
||||
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index)"));
|
||||
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index, ord) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index, :ord)"));
|
||||
for (const DocumentElement &element : std::as_const(elements)) {
|
||||
int ord = 0;
|
||||
for (const auto &link : element.links) {
|
||||
query.bindValue(QStringLiteral(":element_uuid"), element.uuid);
|
||||
query.bindValue(QStringLiteral(":linked_uuid"), link.first);
|
||||
query.bindValue(QStringLiteral(":group_index"), link.second >= 0 ? QVariant(link.second) : QVariant());
|
||||
query.bindValue(QStringLiteral(":ord"), ord++);
|
||||
if (!query.exec()) {
|
||||
qDebug() << "projectDataBase::populateFromDocument link insert error : " << query.lastError();
|
||||
}
|
||||
@@ -1092,7 +1094,7 @@ QList<QUuid> projectDataBase::linkedSymbols(const QUuid &element) const
|
||||
{
|
||||
QList<QUuid> linked;
|
||||
QSqlQuery query(m_data_base);
|
||||
query.prepare(QStringLiteral("SELECT linked_uuid FROM link WHERE element_uuid = :uuid ORDER BY rowid"));
|
||||
query.prepare(QStringLiteral("SELECT linked_uuid FROM link WHERE element_uuid = :uuid ORDER BY ord, rowid"));
|
||||
query.bindValue(QStringLiteral(":uuid"), element.toString());
|
||||
if (query.exec()) {
|
||||
while (query.next())
|
||||
@@ -2602,12 +2604,14 @@ bool projectDataBase::createDataBase()
|
||||
|
||||
//Create the link table: one row per element and element it is
|
||||
//linked to -- a coil and its contacts, a pair of folio reports --
|
||||
//from each side, with the contact group the element saved for it.
|
||||
//from each side, with the contact group the element saved for it,
|
||||
//and its place (ord) in the order the element saves its links.
|
||||
const QString link_table(QStringLiteral(
|
||||
"CREATE TABLE link ("
|
||||
"element_uuid VARCHAR(50) NOT NULL, "
|
||||
"linked_uuid VARCHAR(50) NOT NULL, "
|
||||
"group_index INTEGER, "
|
||||
"ord INTEGER NOT NULL DEFAULT 0, "
|
||||
"PRIMARY KEY (element_uuid, linked_uuid), "
|
||||
"FOREIGN KEY (element_uuid) REFERENCES element (uuid))"));
|
||||
if (!query_.exec(link_table)) {
|
||||
@@ -3868,10 +3872,11 @@ QString projectDataBase::prefillReport() const
|
||||
void projectDataBase::populateLinkTable()
|
||||
{
|
||||
m_dirty_link_elements.clear();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
QSqlQuery query(m_data_base);
|
||||
query.exec(QStringLiteral("DELETE FROM link"));
|
||||
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index)"));
|
||||
query.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index, ord) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index, :ord)"));
|
||||
|
||||
for (const auto &diagram : m_project->folios())
|
||||
{
|
||||
@@ -3885,23 +3890,7 @@ void projectDataBase::populateLinkTable()
|
||||
}
|
||||
const ElementProvider ep(diagram);
|
||||
for (const auto &elmt : ep.find(allElementTypes()))
|
||||
{
|
||||
QList<std::pair<QString, int>> rows;
|
||||
for (Element *linked : elmt->linkedElements())
|
||||
rows.append({linked->uuid().toString(), elmt->groupIndexForElement(linked)});
|
||||
//Partners on a folio not built, which it will be linked to
|
||||
for (const auto &waiting : elmt->waitingLinks())
|
||||
rows.append({waiting.first.toString(), waiting.second});
|
||||
for (const auto &row : std::as_const(rows))
|
||||
{
|
||||
query.bindValue(QStringLiteral(":element_uuid"), elmt->uuid().toString());
|
||||
query.bindValue(QStringLiteral(":linked_uuid"), row.first);
|
||||
query.bindValue(QStringLiteral(":group_index"), row.second >= 0 ? QVariant(row.second) : QVariant());
|
||||
if (!query.exec()) {
|
||||
qDebug() << "projectDataBase::populateLinkTable insert error : " << query.lastError();
|
||||
}
|
||||
}
|
||||
}
|
||||
insertLinkRows(query, elmt, waiting);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3927,6 +3916,83 @@ void projectDataBase::queueLinks(Element *element)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::linkRowMismatches
|
||||
A test and debugging aid (QET_CHECK_LINK_ROWS): the placed
|
||||
symbols whose link rows are not the links they save, in order
|
||||
(Element::linksToSave(), insertLinkRows())
|
||||
@return one entry per such symbol: its uuid
|
||||
*/
|
||||
QStringList projectDataBase::linkRowMismatches()
|
||||
{
|
||||
flushLinks();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
QStringList mismatches;
|
||||
QSqlQuery query(m_data_base);
|
||||
query.prepare(QStringLiteral("SELECT linked_uuid, group_index FROM link "
|
||||
"WHERE element_uuid = :uuid ORDER BY ord, rowid"));
|
||||
for (auto it = m_placed_elements.cbegin() ; it != m_placed_elements.cend() ; ++it)
|
||||
{
|
||||
Element *element = it.value();
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
QList<std::pair<QString, int>> expected;
|
||||
for (const Element::SavedLink &link : element->linksToSave(waiting))
|
||||
if (!link.built || m_placed_elements.contains(link.uuid, link.built))
|
||||
expected.append({link.uuid.toString(), link.group_index});
|
||||
QList<std::pair<QString, int>> rows;
|
||||
query.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
|
||||
if (query.exec())
|
||||
while (query.next())
|
||||
rows.append({query.value(0).toString(),
|
||||
query.value(1).isNull() ? -1 : query.value(1).toInt()});
|
||||
if (rows != expected) {
|
||||
mismatches << element->uuid().toString();
|
||||
}
|
||||
}
|
||||
return mismatches;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::waitingSymbols
|
||||
@return the symbols on folios not built yet (lazy folios): partners a
|
||||
symbol keeps its saved links to until they are built
|
||||
*/
|
||||
QSet<QUuid> projectDataBase::waitingSymbols() const
|
||||
{
|
||||
return m_project && m_project->unloadedFolioCount() ? m_project->symbolsOnUnbuiltFolios()
|
||||
: QSet<QUuid>();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::insertLinkRows
|
||||
Write @p element's link rows with @p insert (prepared with :element_uuid,
|
||||
:linked_uuid, :group_index and :ord): its partners as it saves them
|
||||
(Element::linksToSave()), in that order, so the rows and the file say
|
||||
the same: @p waiting is the symbols on folios not built, as a save
|
||||
takes them (waitingSymbols()). A built partner the project no longer has (on a removed
|
||||
folio, linked still for the undo) gets no row.
|
||||
*/
|
||||
void projectDataBase::insertLinkRows(QSqlQuery &insert, Element *element,
|
||||
const QSet<QUuid> &waiting)
|
||||
{
|
||||
int ord = 0;
|
||||
for (const Element::SavedLink &link : element->linksToSave(waiting))
|
||||
{
|
||||
if (link.built && !m_placed_elements.contains(link.uuid, link.built)) {
|
||||
continue;
|
||||
}
|
||||
insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
|
||||
insert.bindValue(QStringLiteral(":linked_uuid"), link.uuid.toString());
|
||||
insert.bindValue(QStringLiteral(":group_index"), link.group_index >= 0 ? QVariant(link.group_index) : QVariant());
|
||||
insert.bindValue(QStringLiteral(":ord"), ord++);
|
||||
if (!insert.exec()) {
|
||||
qDebug() << "projectDataBase::insertLinkRows insert error : " << insert.lastError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief projectDataBase::flushLinks
|
||||
Write the link rows of every element queued by linksChanged()
|
||||
@@ -3939,12 +4005,13 @@ void projectDataBase::flushLinks()
|
||||
|
||||
const auto dirty = m_dirty_link_elements;
|
||||
m_dirty_link_elements.clear();
|
||||
const QSet<QUuid> waiting = waitingSymbols();
|
||||
const bool own_transaction = m_data_base.transaction();
|
||||
QSqlQuery remove(m_data_base);
|
||||
remove.prepare(QStringLiteral("DELETE FROM link WHERE element_uuid = :uuid"));
|
||||
QSqlQuery insert(m_data_base);
|
||||
insert.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index)"));
|
||||
insert.prepare(QStringLiteral("INSERT INTO link (element_uuid, linked_uuid, group_index, ord) "
|
||||
"VALUES (:element_uuid, :linked_uuid, :group_index, :ord)"));
|
||||
for (const QPointer<Element> &element : dirty)
|
||||
{
|
||||
if (!element) {
|
||||
@@ -3960,32 +4027,7 @@ void projectDataBase::flushLinks()
|
||||
if (!m_placed_elements.contains(element->uuid(), element)) {
|
||||
continue;
|
||||
}
|
||||
for (Element *linked : element->linkedElements())
|
||||
{
|
||||
//A symbol on a removed folio stays linked, for the undo,
|
||||
//but the project no longer has it
|
||||
if (!m_placed_elements.contains(linked->uuid(), linked)) {
|
||||
continue;
|
||||
}
|
||||
const int group = element->groupIndexForElement(linked);
|
||||
insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
|
||||
insert.bindValue(QStringLiteral(":linked_uuid"), linked->uuid().toString());
|
||||
insert.bindValue(QStringLiteral(":group_index"), group >= 0 ? QVariant(group) : QVariant());
|
||||
if (!insert.exec()) {
|
||||
qDebug() << "projectDataBase::flushLinks insert error : " << insert.lastError();
|
||||
}
|
||||
}
|
||||
//Partners on a folio not built (lazy folios), which it will
|
||||
//be linked to once that folio is
|
||||
for (const auto &waiting : element->waitingLinks())
|
||||
{
|
||||
insert.bindValue(QStringLiteral(":element_uuid"), element->uuid().toString());
|
||||
insert.bindValue(QStringLiteral(":linked_uuid"), waiting.first.toString());
|
||||
insert.bindValue(QStringLiteral(":group_index"), waiting.second >= 0 ? QVariant(waiting.second) : QVariant());
|
||||
if (!insert.exec()) {
|
||||
qDebug() << "projectDataBase::flushLinks insert error : " << insert.lastError();
|
||||
}
|
||||
}
|
||||
insertLinkRows(insert, element, waiting);
|
||||
}
|
||||
if (own_transaction) {
|
||||
m_data_base.commit();
|
||||
|
||||
@@ -154,6 +154,7 @@ class projectDataBase : public QObject
|
||||
void conductorPropertiesStored(Conductor *conductor, const ConductorProperties &properties);
|
||||
void conductorUuidChanged(Conductor *conductor, const QUuid &old_uuid);
|
||||
QStringList conductorPropertiesMismatches() const;
|
||||
QStringList linkRowMismatches();
|
||||
|
||||
//Fill the three stores from a project's document before its
|
||||
//folios are built, then, once
|
||||
@@ -253,6 +254,8 @@ class projectDataBase : public QObject
|
||||
void populateConductorTable();
|
||||
void populateLinkTable();
|
||||
void flushLinks();
|
||||
void insertLinkRows(QSqlQuery &insert, Element *element, const QSet<QUuid> &waiting);
|
||||
QSet<QUuid> waitingSymbols() const;
|
||||
void populateDrawingItemTables();
|
||||
bool populateFromDocument(const QDomDocument &document, QString *why = nullptr);
|
||||
bool writeDrawingItem(QObject *object);
|
||||
|
||||
@@ -2342,6 +2342,23 @@ bool Diagram::fromXml(QDomElement &document,
|
||||
<< "wire end(s) on a terminal that moved since the project was saved,"
|
||||
<< "matched to it by place";
|
||||
}
|
||||
//A copy of a symbol sharing its uuid with another (older versions'
|
||||
//copies, F100) gets its own, now that its wires are connected to
|
||||
//it: wires and links then name one symbol each
|
||||
//(QETProject::renewedSharedUuid(), only while the project opens)
|
||||
if (consider_informations && m_project) {
|
||||
const int folio_index = m_project->folioIndex(this);
|
||||
QHash<QUuid, int> ordinal;
|
||||
for (Element *element : std::as_const(added_elements)) {
|
||||
const QUuid shared = element->uuid();
|
||||
const QUuid renewed = m_project->renewedSharedUuid(folio_index, ordinal[shared]++,
|
||||
shared, uuid());
|
||||
if (!renewed.isNull()) {
|
||||
element->setUuid(renewed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (consider_informations && !m_wires_not_reconnected.isEmpty()) {
|
||||
qWarning().noquote() << "Diagram::fromXml():"
|
||||
<< m_wires_not_reconnected.size()
|
||||
|
||||
@@ -1437,32 +1437,11 @@ QDomElement Element::toXml(
|
||||
element.appendChild(inputs);
|
||||
|
||||
//if this element is linked to other elements,
|
||||
//save the uuid of each other elements
|
||||
//A partner on a folio not built yet (lazy folios), which this
|
||||
//symbol will be linked to, is saved too, in the order the symbol was
|
||||
//saved with: saving needs no partner built (stage D5a)
|
||||
//save the uuid of each other elements (linksToSave())
|
||||
QList<std::pair<QUuid, int>> links;
|
||||
if (keepsSavedLinks() && diagram() && diagram()->project()) {
|
||||
const QSet<QUuid> waiting = diagram()->project()->waitingLinkTargets();
|
||||
QSet<Element *> written;
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
|
||||
Element *linked = nullptr;
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (elmt->uuid() == link_info.uuid && !written.contains(elmt)) { linked = elmt; break; }
|
||||
if (linked) {
|
||||
written.insert(linked);
|
||||
links.append({linked->uuid(), m_group_index_map.value(linked, -1)});
|
||||
} else if (waiting.contains(link_info.uuid)) {
|
||||
links.append({link_info.uuid, link_info.group_index});
|
||||
}
|
||||
}
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (!written.contains(elmt))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1)});
|
||||
} else {
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1)});
|
||||
}
|
||||
for (const SavedLink &link : linksToSave(diagram() && diagram()->project()
|
||||
? diagram()->project()->waitingLinkTargets() : QSet<QUuid>()))
|
||||
links.append({link.uuid, link.group_index});
|
||||
if (!links.isEmpty()) {
|
||||
QDomElement links_uuids = document.createElement(QStringLiteral("links_uuids"));
|
||||
for (const auto &link : std::as_const(links)) {
|
||||
@@ -1869,6 +1848,45 @@ void Element::initLink(QETProject *prj)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::linksToSave
|
||||
The partners this symbol saves, in the order it saves them: the order
|
||||
it was saved with (tmp_uuids_link), then partners linked since. A
|
||||
partner on a folio not built yet (lazy folios), which this symbol will
|
||||
be linked to, is in the list too: saving needs no partner built (stage
|
||||
D5a). toXml() writes this list, and the project database's link rows
|
||||
are written from it, so the two are the same (QETZ-DB-FIRST stage 2a).
|
||||
@param waiting the symbols on folios not built: a saved partner among
|
||||
them is kept (QETProject::symbolsOnUnbuiltFolios())
|
||||
@return each partner's uuid, its contact group (-1 if none), and the
|
||||
partner itself when it is built (nullptr for one waiting)
|
||||
*/
|
||||
QList<Element::SavedLink> Element::linksToSave(const QSet<QUuid> &waiting) const
|
||||
{
|
||||
QList<SavedLink> links;
|
||||
if (keepsSavedLinks() && diagram() && diagram()->project()) {
|
||||
QSet<Element *> written;
|
||||
for (const auto &link_info : std::as_const(tmp_uuids_link)) {
|
||||
Element *linked = nullptr;
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (elmt->uuid() == link_info.uuid && !written.contains(elmt)) { linked = elmt; break; }
|
||||
if (linked) {
|
||||
written.insert(linked);
|
||||
links.append({linked->uuid(), m_group_index_map.value(linked, -1), linked});
|
||||
} else if (waiting.contains(link_info.uuid)) {
|
||||
links.append({link_info.uuid, link_info.group_index, nullptr});
|
||||
}
|
||||
}
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
if (!written.contains(elmt))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1), elmt});
|
||||
} else {
|
||||
for (Element *elmt : std::as_const(connected_elements))
|
||||
links.append({elmt->uuid(), m_group_index_map.value(elmt, -1), elmt});
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Element::pendingLinks
|
||||
@return the uuids of the partners this symbol was saved linked to and
|
||||
|
||||
@@ -227,6 +227,13 @@ class Element : public QetGraphicsItem
|
||||
virtual void unlinkElement(Element *) {}
|
||||
virtual void initLink(QETProject *);
|
||||
QSet<QUuid> pendingLinks() const;
|
||||
/// A partner as the symbol saves it (linksToSave())
|
||||
struct SavedLink {
|
||||
QUuid uuid;
|
||||
int group_index = -1;
|
||||
Element *built = nullptr; ///< nullptr: on a folio not built yet
|
||||
};
|
||||
QList<SavedLink> linksToSave(const QSet<QUuid> &waiting) const;
|
||||
bool keepsSavedLinks() const {return !tmp_uuids_link.isEmpty();}
|
||||
QList<std::pair<QUuid, int>> waitingLinks() const;
|
||||
/**
|
||||
|
||||
+118
-24
@@ -299,6 +299,27 @@ QByteArray QETProject::containerPicture(const QString &file) const
|
||||
return m_container_pictures.value(file);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::renewedSharedUuid
|
||||
While the project opens: the uuid of the @p ordinal -th symbol carrying
|
||||
@p uuid on the folio at @p folio_index (in the file's order), when
|
||||
other symbols of the project carry the same uuid and this copy is not
|
||||
the one keeping it (readDiagramsXml()). Derived from the uuid and the
|
||||
folio's uuid, so the same file gives the same uuid on every open.
|
||||
@return a null uuid when this symbol keeps its uuid
|
||||
*/
|
||||
QUuid QETProject::renewedSharedUuid(int folio_index, int ordinal, const QUuid &uuid,
|
||||
const QUuid &folio_uuid)
|
||||
{
|
||||
const auto keeper = m_shared_uuid_keepers.constFind(uuid);
|
||||
if (keeper == m_shared_uuid_keepers.constEnd()
|
||||
|| (keeper->first == folio_index && keeper->second == ordinal)) {
|
||||
return QUuid();
|
||||
}
|
||||
return derivedItemUuid(QStringLiteral("element-copy"),
|
||||
uuid.toString() + QLatin1Char('\n') + folio_uuid.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::derivedItemUuid
|
||||
A uuid for an item of this project that was saved without one, the same
|
||||
@@ -474,6 +495,8 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
|
||||
}
|
||||
QFileInfo fi(*file);
|
||||
setFilePath(fi.absoluteFilePath());
|
||||
//Saved again unchanged, the file keeps its saved date and path
|
||||
m_stamped_path = m_file_path;
|
||||
|
||||
//Extract the content of the xml. The raw bytes are kept : a project
|
||||
//file without a persisted uuid derives its uuid from them.
|
||||
@@ -1161,16 +1184,8 @@ void QETProject::setFilePath(const QString &filepath)
|
||||
setReadOnly(false);
|
||||
}
|
||||
|
||||
//title block variables should be updated after file save as dialog is confirmed, before file is saved.
|
||||
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
||||
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
||||
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
||||
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
||||
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
||||
m_project_properties.addValue("savedfilepath", filePath());
|
||||
|
||||
|
||||
|
||||
//The saved date, time and path of the title block are set by
|
||||
//write(), when the project is saved (stampSaved())
|
||||
emit projectFilePathChanged(this, m_file_path);
|
||||
emit projectInformationsChanged(this);
|
||||
updateDiagramsFolioData();
|
||||
@@ -2636,6 +2651,16 @@ QDomDocument QETProject::toXml(QHash<int, QByteArray> *kept_folios, QStringList
|
||||
// Write the elements collection.
|
||||
project_root.appendChild(m_elements_collection->root().cloneNode(true));
|
||||
|
||||
//QET_CHECK_LINK_ROWS=1: say whether the project database's link rows
|
||||
//are the links each placed symbol saves, in its order, once the
|
||||
//folios are written. Builds nothing (lazy folios stay as they are):
|
||||
//a test and debugging aid; it changes nothing.
|
||||
if (qEnvironmentVariableIntValue("QET_CHECK_LINK_ROWS") == 1) {
|
||||
const QStringList links = m_data_base.linkRowMismatches();
|
||||
qWarning().noquote() << "link rows:" << links.size() << "differ"
|
||||
<< links.mid(0, 5).join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
m_waiting_link_targets.clear();
|
||||
return(xml_doc);
|
||||
}
|
||||
@@ -2801,6 +2826,33 @@ bool QETProject::changedOnDisk(QDateTime *when) const
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::stampSaved
|
||||
Set the title block variables of the save (saveddate, saveddate-eu,
|
||||
saveddate-us, savedtime, savedfilename, savedfilepath) to now and to
|
||||
the file being written, when there is something new to save: the
|
||||
project changed since it was opened or last saved, or it is written to
|
||||
another file. Otherwise they keep what the file holds, so saving an
|
||||
unchanged project gives an identical file (F124).
|
||||
@return true if they were set
|
||||
*/
|
||||
bool QETProject::stampSaved()
|
||||
{
|
||||
const bool changed = m_modified || m_undo_stack->index() != m_stamped_undo_index;
|
||||
if (!changed && m_file_path == m_stamped_path) {
|
||||
return false;
|
||||
}
|
||||
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
||||
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
||||
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
||||
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
||||
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
||||
m_project_properties.addValue("savedfilepath", filePath());
|
||||
emit projectInformationsChanged(this);
|
||||
updateDiagramsFolioData();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETProject::write
|
||||
Save the project in a file.
|
||||
@@ -2868,13 +2920,28 @@ QETResult QETProject::write(bool overwrite_changed)
|
||||
if (!PreviousVersions::keep(m_file_path, PreviousVersions::count(), &backup_error))
|
||||
qWarning().noquote() << "Previous version of" << m_file_path << "not kept:" << backup_error;
|
||||
|
||||
//The saved date, time and path go into the file being written, and
|
||||
//only when there is something new to save: an unchanged project
|
||||
//saves to an identical file (F124, a project kept in git). A
|
||||
//failed write puts the previous values back.
|
||||
const DiagramContext stamped_before = m_project_properties;
|
||||
const bool stamped = stampSaved();
|
||||
const auto failed_stamped = [&](const QString &error) {
|
||||
if (stamped) {
|
||||
m_project_properties = stamped_before;
|
||||
emit projectInformationsChanged(this);
|
||||
updateDiagramsFolioData();
|
||||
}
|
||||
return failed(error);
|
||||
};
|
||||
|
||||
QString error_message;
|
||||
if (isContainerPath(m_file_path))
|
||||
{
|
||||
//A container is written from the whole document: every folio
|
||||
//built (stage D4 writes it from the database)
|
||||
if (!writeContainerFile(m_file_path, &error_message))
|
||||
return(failed(error_message));
|
||||
return(failed_stamped(error_message));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2884,7 +2951,7 @@ QETResult QETProject::write(bool overwrite_changed)
|
||||
QDomDocument xml_project(toXml(&kept_folios));
|
||||
if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
|
||||
m_file_path, &error_message))
|
||||
return(failed(error_message));
|
||||
return(failed_stamped(error_message));
|
||||
}
|
||||
|
||||
rememberDiskState();
|
||||
@@ -2905,17 +2972,8 @@ QETResult QETProject::write(bool overwrite_changed)
|
||||
}
|
||||
}
|
||||
|
||||
//title block variables should be updated after file save dialog is confirmed, before file is saved.
|
||||
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
|
||||
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
|
||||
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
|
||||
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
|
||||
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
|
||||
m_project_properties.addValue("savedfilepath", filePath());
|
||||
|
||||
emit projectInformationsChanged(this);
|
||||
updateDiagramsFolioData();
|
||||
|
||||
m_stamped_path = m_file_path;
|
||||
m_stamped_undo_index = m_undo_stack->index();
|
||||
setModified(false);
|
||||
return(QETResult());
|
||||
}
|
||||
@@ -3637,6 +3695,41 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project)
|
||||
}
|
||||
}
|
||||
|
||||
//Symbols sharing a uuid (copies made by older versions, F100): the
|
||||
//project database, links and wires name a symbol by its uuid, so
|
||||
//each copy but one gets its own (renewedSharedUuid()). The copy
|
||||
//that keeps it is the first one linked to another symbol (a
|
||||
//partner's link names the uuid), else the first. The folios with
|
||||
//a copy to renew are built now, also with lazy folios, so the
|
||||
//whole project has one uuid per symbol from the start.
|
||||
m_shared_uuid_keepers.clear();
|
||||
QSet<int> build_now;
|
||||
{
|
||||
QHash<QUuid, QList<std::tuple<int, int, bool>>> copies;
|
||||
for (int i = 0 ; i < diagram_nodes.length() ; ++i) {
|
||||
const QDomElement elements = diagram_nodes.at(i).toElement()
|
||||
.firstChildElement(QStringLiteral("elements"));
|
||||
QHash<QUuid, int> ordinal;
|
||||
for (QDomElement e = elements.firstChildElement(QStringLiteral("element"));
|
||||
!e.isNull() ; e = e.nextSiblingElement(QStringLiteral("element"))) {
|
||||
const QUuid uuid(e.attribute(QStringLiteral("uuid")));
|
||||
if (uuid.isNull()) continue;
|
||||
const bool linked = !e.firstChildElement(QStringLiteral("links_uuids"))
|
||||
.firstChildElement(QStringLiteral("link_uuid")).isNull();
|
||||
copies[uuid].append({i, ordinal[uuid]++, linked});
|
||||
}
|
||||
}
|
||||
for (auto it = copies.cbegin() ; it != copies.cend() ; ++it) {
|
||||
if (it.value().size() < 2) continue;
|
||||
auto keeper = it.value().first();
|
||||
for (const auto © : it.value())
|
||||
if (std::get<2>(copy)) { keeper = copy; break; }
|
||||
m_shared_uuid_keepers.insert(it.key(), {std::get<0>(keeper), std::get<1>(keeper)});
|
||||
for (const auto © : it.value())
|
||||
if (copy != keeper) build_now.insert(std::get<0>(copy));
|
||||
}
|
||||
}
|
||||
|
||||
if(dlgWaiting)
|
||||
dlgWaiting->setProgressBarRange(0, diagram_nodes.length()*3);
|
||||
|
||||
@@ -3690,7 +3783,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project)
|
||||
connect(diagram, &Diagram::usedTitleBlockTemplateChanged,
|
||||
this, &QETProject::usedTitleBlockTemplateChanged);
|
||||
|
||||
m_deferring_folios = m_lazy_open;
|
||||
m_deferring_folios = m_lazy_open && !build_now.contains(i);
|
||||
diagram->initFromXml(diagram_xml_element);
|
||||
m_deferring_folios = false;
|
||||
if (!diagram->isLoaded() && !line_starts.isEmpty())
|
||||
@@ -3703,6 +3796,7 @@ void QETProject::readDiagramsXml(QDomDocument &xml_project)
|
||||
}
|
||||
}
|
||||
|
||||
m_shared_uuid_keepers.clear();
|
||||
updateDiagramsFolioData();
|
||||
|
||||
//Initialise links between elements in this project
|
||||
|
||||
+12
-1
@@ -123,6 +123,8 @@ class QETProject : public QObject
|
||||
projectDataBase *dataBase();
|
||||
QUuid uuid() const;
|
||||
QUuid derivedItemUuid(const QString &kind, const QString &key);
|
||||
QUuid renewedSharedUuid(int folio_index, int ordinal, const QUuid &uuid,
|
||||
const QUuid &folio_uuid);
|
||||
QByteArray containerPicture(const QString &file) const;
|
||||
ProjectState state() const;
|
||||
QString openError() const;
|
||||
@@ -441,6 +443,7 @@ class QETProject : public QObject
|
||||
}
|
||||
|
||||
private:
|
||||
bool stampSaved();
|
||||
void readProjectXml(QDomDocument &xml_project);
|
||||
void readDiagramsXml(QDomDocument &xml_project);
|
||||
void readElementsCollectionXml(QDomDocument &xml_project);
|
||||
@@ -570,6 +573,10 @@ class QETProject : public QObject
|
||||
bool m_kept_folios_trusted = false;
|
||||
/// Read from a .qet whose path nobody chose since: saved beside it
|
||||
bool m_opened_from_qet = false;
|
||||
/// The file the saved date and path were set for, and the undo
|
||||
/// index then: an unchanged project keeps them (stampSaved())
|
||||
QString m_stamped_path;
|
||||
int m_stamped_undo_index = 0;
|
||||
/// The project file as this QET last read or wrote it, to see
|
||||
/// whether something else changed it since (changedOnDisk())
|
||||
QString m_disk_path;
|
||||
@@ -610,7 +617,11 @@ class QETProject : public QObject
|
||||
int m_next_backup_slot = 0;
|
||||
QUuid m_uuid = QUuid::createUuid();
|
||||
QHash<QString, int> m_derived_uuid_keys;
|
||||
QSet<QUuid> m_saved_item_uuids; //symbol and wire uuids the file carries, see derivedItemUuid()
|
||||
QSet<QUuid> m_saved_item_uuids;
|
||||
/// While opening: per uuid carried by several symbols, the copy
|
||||
/// keeping it (folio index, ordinal on that folio), see
|
||||
/// renewedSharedUuid()
|
||||
QHash<QUuid, std::pair<int, int>> m_shared_uuid_keepers; //symbol and wire uuids the file carries, see derivedItemUuid()
|
||||
projectDataBase m_data_base;
|
||||
QVector<TerminalStrip *> m_terminal_strip_vector;
|
||||
QVector<Cable *> m_cables;
|
||||
|
||||
@@ -889,6 +889,18 @@ if(QET_HAS_SCRIPTING)
|
||||
target_compile_definitions(tst_scriptconductoruuid PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# The saved date, time and path of the title block change only when the
|
||||
# project changed, and go into the file that save writes (F124): an
|
||||
# unchanged project saved twice gives an identical file.
|
||||
add_executable(
|
||||
tst_savedstamps
|
||||
tst_savedstamps.cpp)
|
||||
add_test(NAME tst_savedstamps COMMAND tst_savedstamps)
|
||||
add_dependencies(tst_savedstamps qelectrotech)
|
||||
target_link_libraries(tst_savedstamps PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_savedstamps PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# Deleting a symbol whose terminal held several wires joins them with a
|
||||
# new wire; its uuid comes from its two ends, so the same deletion saves
|
||||
# the same file every time (F115): examples/affuteuse_250h.qet.
|
||||
|
||||
@@ -214,7 +214,9 @@ class tst_projectlock : public QObject
|
||||
|
||||
void readOnlyFolderDoesNotBlock()
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN
|
||||
QSKIP("a read-only folder still takes new files on Windows");
|
||||
#else
|
||||
if (geteuid() == 0)
|
||||
QSKIP("root can write anywhere");
|
||||
#endif
|
||||
|
||||
@@ -69,10 +69,12 @@ private slots:
|
||||
f.close();
|
||||
|
||||
// Columns numbered from 1, as QElectroTech's settings page
|
||||
// shows by default
|
||||
// shows by default. Written to an INI file QET_SETTINGS_DIR
|
||||
// points at: on Windows the settings are in the registry
|
||||
// otherwise, and the columns would count from 0
|
||||
const QString home = m_dir.filePath(tag + QStringLiteral("-home"));
|
||||
QVERIFY(QDir().mkpath(home + QStringLiteral("/config/QElectroTech")));
|
||||
QFile conf(home + QStringLiteral("/config/QElectroTech/QElectroTech.conf"));
|
||||
QVERIFY(QDir().mkpath(home + QStringLiteral("/settings/QElectroTech")));
|
||||
QFile conf(home + QStringLiteral("/settings/QElectroTech/QElectroTech.ini"));
|
||||
QVERIFY(conf.open(QIODevice::WriteOnly));
|
||||
conf.write("[General]\nborder-columns_0=false\n");
|
||||
conf.close();
|
||||
@@ -80,6 +82,7 @@ private slots:
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
env.insert(QStringLiteral("QET_SETTINGS_DIR"), home + QStringLiteral("/settings"));
|
||||
env.insert(QStringLiteral("HOME"), home);
|
||||
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
|
||||
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
|
||||
|
||||
@@ -411,14 +411,15 @@ private slots:
|
||||
|
||||
// QElectroTech run in a sandbox of its own with @p args, the store
|
||||
// check on (QET_CHECK_ELEMENT_INFO_STORE); its output
|
||||
QString runChecked(const QStringList &args, bool eager = false)
|
||||
QString runChecked(const QStringList &args, bool eager = false,
|
||||
const char *check = "QET_CHECK_ELEMENT_INFO_STORE")
|
||||
{
|
||||
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(m_run++));
|
||||
QDir().mkpath(home);
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
env.insert(QStringLiteral("QET_CHECK_ELEMENT_INFO_STORE"), QStringLiteral("1"));
|
||||
env.insert(QString::fromLatin1(check), QStringLiteral("1"));
|
||||
if (eager) {
|
||||
env.insert(QStringLiteral("QET_EAGER_FOLIOS"), QStringLiteral("1")); //every folio built on opening
|
||||
}
|
||||
@@ -452,6 +453,31 @@ private slots:
|
||||
QVERIFY2(out.contains(QStringLiteral("wire properties store: 0 differ")), qPrintable(out.right(600)));
|
||||
}
|
||||
|
||||
// The link rows say what each symbol saves, in its order, also after
|
||||
// an edit builds a partner's folio (lazy folios): m_000 as a .qetz
|
||||
// saved with every folio built, a symbol of its middle folio moved. The rows used to follow the order
|
||||
// the partners were built in (QETZ-DB-FIRST stage 2a).
|
||||
void linkRowsAfterLazyEdit()
|
||||
{
|
||||
const QString qetz = m_dir.filePath(QStringLiteral("links%1.qetz").arg(m_run));
|
||||
QVERIFY(!runChecked({QStringLiteral("--resave"),
|
||||
QStringLiteral(QET_EXAMPLES_DIR "/m_000.qet"), qetz}, true).isEmpty());
|
||||
const QString saved = m_dir.filePath(QStringLiteral("links-moved%1.qetz").arg(m_run));
|
||||
const QString js = m_dir.filePath(QStringLiteral("links%1.js").arg(m_run));
|
||||
QFile script(js);
|
||||
QVERIFY(script.open(QIODevice::WriteOnly));
|
||||
script.write(QStringLiteral(
|
||||
"var f = Math.floor(qet.folioCount() / 2);\n"
|
||||
"var u = qet.elementUuids(f).slice().sort()[0];\n"
|
||||
"qet.log('MOVED ' + qet.moveElement(f, u, 0, 100));\n"
|
||||
"qet.save('%1');\n").arg(saved).toUtf8());
|
||||
script.close();
|
||||
const QString out = runChecked({QStringLiteral("--run"), js, qetz}, false,
|
||||
"QET_CHECK_LINK_ROWS");
|
||||
QVERIFY2(out.contains(QStringLiteral("MOVED true")), qPrintable(out.right(600)));
|
||||
QVERIFY2(out.contains(QStringLiteral("link rows: 0 differ")), qPrintable(out.right(600)));
|
||||
}
|
||||
|
||||
// The stores filled from the document before the folios are built
|
||||
// hold what building them gives, for a
|
||||
// project saved by this version (every symbol, folio and wire carries
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <QtTest>
|
||||
#include <QDate>
|
||||
#include <QDomDocument>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
/**
|
||||
The title block variables of a save (saveddate, savedtime,
|
||||
savedfilename, savedfilepath...) are set when the project is saved
|
||||
with something new in it, and go into the file that save writes. They
|
||||
used to be set after writing, so each save wrote the time of the one
|
||||
before: a project saved twice without a change gave two different
|
||||
files, and a changed project was saved with the previous date.
|
||||
|
||||
Runs the real binary on fixtures/unlinked_contact_label.qet, whose
|
||||
stamps say 2026-10-02 18:39 in a temporary folder:
|
||||
- saved twice in its own file without a change: byte-identical;
|
||||
- one symbol moved, then saved: today's date and the file's own path.
|
||||
*/
|
||||
class tst_savedstamps : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
QString m_base;
|
||||
|
||||
QString run(const QStringList &arguments)
|
||||
{
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
//Its own settings and single-instance socket: another test's
|
||||
//QElectroTech running at the same time would otherwise be
|
||||
//handed this run (SingleApplication)
|
||||
const QString home = m_dir.filePath(QStringLiteral("home"));
|
||||
QDir().mkpath(home);
|
||||
env.insert(QStringLiteral("HOME"), home);
|
||||
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
|
||||
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
|
||||
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.setProcessChannelMode(QProcess::MergedChannels);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), arguments);
|
||||
if (!proc.waitForFinished(120000) || proc.exitStatus() != QProcess::NormalExit
|
||||
|| proc.exitCode() != 0)
|
||||
return QString();
|
||||
return QString::fromUtf8(proc.readAll()) + QStringLiteral("\nEXIT 0");
|
||||
}
|
||||
|
||||
QString script(const QString &name, const QString &body)
|
||||
{
|
||||
const QString path = m_dir.filePath(name);
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly)) return QString();
|
||||
file.write(body.toUtf8());
|
||||
return path;
|
||||
}
|
||||
|
||||
/// The saved-* properties of @p qetz, read back through --resave
|
||||
QMap<QString, QString> stamps(const QString &qetz)
|
||||
{
|
||||
const QString xml = qetz + QStringLiteral(".qet");
|
||||
if (run({QStringLiteral("--resave"), qetz, xml}).isEmpty()) return {};
|
||||
QFile file(xml);
|
||||
QDomDocument doc;
|
||||
if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) return {};
|
||||
QMap<QString, QString> out;
|
||||
const QDomNodeList props = doc.documentElement().firstChildElement(
|
||||
QStringLiteral("properties")).elementsByTagName(QStringLiteral("property"));
|
||||
for (int i = 0; i < props.size(); ++i) {
|
||||
const QDomElement p = props.at(i).toElement();
|
||||
if (p.attribute(QStringLiteral("name")).startsWith(QLatin1String("saved")))
|
||||
out.insert(p.attribute(QStringLiteral("name")), p.text());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static QByteArray bytes(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY2(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)), "qelectrotech binary not found");
|
||||
QVERIFY(m_dir.isValid());
|
||||
const QString fixture = QFINDTESTDATA("fixtures/unlinked_contact_label.qet");
|
||||
QVERIFY2(!fixture.isEmpty(), "fixture project not found");
|
||||
//The fixture as a .qetz, its stamps as they were: a copy
|
||||
//written by --resave is not a save of the project
|
||||
m_base = m_dir.filePath(QStringLiteral("base.qetz"));
|
||||
QVERIFY(!run({QStringLiteral("--resave"), fixture, m_base}).isEmpty());
|
||||
QCOMPARE(stamps(m_base).value(QStringLiteral("saveddate-us")), QStringLiteral("2026-10-02"));
|
||||
}
|
||||
|
||||
void unchangedSavesAreIdentical()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("unchanged.qetz"));
|
||||
QVERIFY(QFile::copy(m_base, project));
|
||||
const QString js = script(QStringLiteral("unchanged.js"),
|
||||
QStringLiteral("qet.save('');\nqet.save('');\nqet.log('SAVED twice');\n"));
|
||||
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("SAVED twice")));
|
||||
QCOMPARE(bytes(project), bytes(m_base));
|
||||
}
|
||||
|
||||
void changedSaveIsStampedNow()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("changed.qetz"));
|
||||
QVERIFY(QFile::copy(m_base, project));
|
||||
const QString js = script(QStringLiteral("changed.js"), QStringLiteral(
|
||||
"var u = qet.elementUuids(0)[0];\n"
|
||||
"qet.log('MOVED ' + qet.moveElement(0, u, 10, 0));\n"
|
||||
"qet.save('');\n"));
|
||||
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("MOVED true")));
|
||||
const QMap<QString, QString> s = stamps(project);
|
||||
QCOMPARE(s.value(QStringLiteral("saveddate-us")), QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd")));
|
||||
QCOMPARE(s.value(QStringLiteral("savedfilepath")), QFileInfo(project).absoluteFilePath());
|
||||
QCOMPARE(s.value(QStringLiteral("savedfilename")), QStringLiteral("changed"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_savedstamps)
|
||||
#include "tst_savedstamps.moc"
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
#include <QSet>
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
@@ -150,13 +151,66 @@ private slots:
|
||||
QVERIFY(!twice.isEmpty());
|
||||
QCOMPARE(nets(twice), expected);
|
||||
|
||||
//No wire names a lamp by the uuid they share
|
||||
for (const QDomElement &c : childElements(firstDiagram(load(twice)),
|
||||
QStringLiteral("conductors"),
|
||||
QStringLiteral("conductor"))) {
|
||||
QVERIFY(c.attribute(QStringLiteral("element1")) != shared);
|
||||
QVERIFY(c.attribute(QStringLiteral("element2")) != shared);
|
||||
//Each lamp has a uuid of its own after a save: one keeps the
|
||||
//shared uuid, the others get one derived from it
|
||||
QSet<QString> uuids;
|
||||
for (const QDomElement &e : lamps(load(twice)))
|
||||
uuids.insert(e.attribute(QStringLiteral("uuid")));
|
||||
QCOMPARE(uuids.size(), 3);
|
||||
QVERIFY(uuids.contains(shared));
|
||||
}
|
||||
|
||||
// A linked contact and an unlinked symbol carrying the same uuid (a copy
|
||||
// made by an older version): the linked one keeps the uuid, the other
|
||||
// gets its own, and every link and wire stays where it was.
|
||||
void linkedCopyKeepsUuid()
|
||||
{
|
||||
const QString source = QStringLiteral(QET_EXAMPLES_DIR "/affuteuse_250h.qet");
|
||||
QDomDocument doc = load(source);
|
||||
QList<QDomElement> all;
|
||||
const QDomNodeList folios = doc.documentElement().elementsByTagName(QStringLiteral("diagram"));
|
||||
for (int i = 0; i < folios.size(); ++i)
|
||||
all << childElements(folios.at(i).toElement(), QStringLiteral("elements"),
|
||||
QStringLiteral("element"));
|
||||
auto linked = [](const QDomElement &e) {
|
||||
return !e.firstChildElement(QStringLiteral("links_uuids"))
|
||||
.firstChildElement(QStringLiteral("link_uuid")).isNull();
|
||||
};
|
||||
QDomElement keeper, copy;
|
||||
for (const QDomElement &e : std::as_const(all)) {
|
||||
if (keeper.isNull() && linked(e)) keeper = e;
|
||||
else if (copy.isNull() && !linked(e)) copy = e;
|
||||
}
|
||||
QVERIFY(!keeper.isNull() && !copy.isNull());
|
||||
const QString shared = keeper.attribute(QStringLiteral("uuid"));
|
||||
copy.setAttribute(QStringLiteral("uuid"), shared);
|
||||
const int wires = doc.elementsByTagName(QStringLiteral("conductor")).size();
|
||||
|
||||
const QString saved = resave(write(doc));
|
||||
QVERIFY(!saved.isEmpty());
|
||||
const QDomDocument out = load(saved);
|
||||
QHash<QString, QDomElement> by_uuid;
|
||||
QList<std::pair<QString, QString>> links;
|
||||
const QDomNodeList saved_folios = out.documentElement().elementsByTagName(QStringLiteral("diagram"));
|
||||
for (int i = 0; i < saved_folios.size(); ++i)
|
||||
for (const QDomElement &e : childElements(saved_folios.at(i).toElement(),
|
||||
QStringLiteral("elements"),
|
||||
QStringLiteral("element"))) {
|
||||
const QString u = e.attribute(QStringLiteral("uuid"));
|
||||
QVERIFY2(!by_uuid.contains(u), qPrintable(QStringLiteral("uuid still shared: ") + u));
|
||||
by_uuid.insert(u, e);
|
||||
for (QDomElement l = e.firstChildElement(QStringLiteral("links_uuids"))
|
||||
.firstChildElement(QStringLiteral("link_uuid"));
|
||||
!l.isNull(); l = l.nextSiblingElement(QStringLiteral("link_uuid")))
|
||||
links.append({u, l.attribute(QStringLiteral("uuid"))});
|
||||
}
|
||||
QVERIFY(by_uuid.contains(shared));
|
||||
QVERIFY(linked(by_uuid.value(shared)));
|
||||
for (const auto &link : std::as_const(links)) {
|
||||
QVERIFY2(by_uuid.contains(link.second), "a link names a symbol not in the project");
|
||||
QVERIFY2(links.contains(std::make_pair(link.second, link.first)), "a link only one side has");
|
||||
}
|
||||
QCOMPARE(out.elementsByTagName(QStringLiteral("conductor")).size(), wires);
|
||||
}
|
||||
|
||||
void savedByUuidReopensRight()
|
||||
|
||||
Reference in New Issue
Block a user