mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-28 04:54:13 +02:00
2269a4641b
QETProject::m_uuid was created in the constructor and never written, so
a project got a new uuid every time it was opened. Inside a running
instance it is only used to name the SQLite connection, but nothing
outside the instance could tell which project a file belongs to.
Motivation
A .qet file is increasingly handled by tools outside QElectroTech: Git
repositories on GitHub or GitLab, cloud storage, key-value stores,
per-project locks. All of them need a stable key for "this project":
- The file name and path are not stable: files get renamed, moved,
checked out in different places.
- The project title is user-editable and not unique.
- Folio uuids (persisted separately) are only unique within their
project; keying folios globally needs a project identifier as well,
e.g. projects/{projectUuid}/folios/{folioUuid}.
Change
Write the uuid as an attribute of <project> and restore it in
QETProject::openFile(), right after parsing and before the project is
built from the XML. Older versions ignore the attribute, so files stay
readable in both directions.
The project database is not affected: it takes its connection name
from the uuid created at construction (m_uuid is declared before
m_data_base), before the file is read. Two open projects carrying the
same persisted uuid therefore still get distinct connections.
Files without a uuid: why not a random one
Keeping the random uuid created by the constructor and saving it
conflicts with #754 / #779: saving an unmodified project must give the
same bytes every time. Every example project predates the attribute.
Measured on the 24 example projects (resaved 3x each from the same
original, QT_HASH_SEED=0 so that QDom's attribute order is stable,
isolated HOME per run):
upstream master 23/24 byte-identical
persist, random uuid 0/24
persist, derived uuid (this) 23/24
The remaining project, schema_indus.qet, differs only in element uuids,
the known residual #779 leaves for elements; its project uuid is
stable.
Instead, a project file without a uuid gets a name-based (version 5)
uuid derived from the raw content of the file:
QUuid::createUuidV5(<fixed QET project namespace>,
"qet-project-legacy\n" + file content without CR)
- The same file always yields the same uuid, so resaving an unmodified
legacy project stays reproducible.
- Different projects practically never share a uuid, because any
difference in content gives a different one. This is unlike folios,
where only data such as title and position could be used; the raw
file bytes are stable input for the whole project.
- Carriage returns are dropped before hashing. QFile's Text mode already
strips them on Windows but not elsewhere, and git's autocrlf can
change them on checkout; either way the uuid is the same on every
platform.
- The uuid is derived once, at load time, and saved from then on. After
that it is read, never recomputed: renaming the project, editing it
or changing it in the same session as the migration does not change
it.
- Two people opening the same legacy file on different branches get the
same project uuid.
The namespace uuid is fixed in the code and must never change, or every
legacy project would get a different uuid.
Known limitations, open for discussion
- Copies share the uuid. Two byte-identical legacy files get the same
uuid (examples/cablage-eclairages_sikli-v5.qet and
câblage-éclairages-sikli-v5.qet are such a pair), and so does a
migrated file copied in the file manager or saved with "Save as".
That is what identity means for a copy, and the same happens with Git,
but a tool that treats the uuid as globally unique has to cope with
it. Regenerating the uuid on "Save as" could be a follow-up, if that
is the preferred behaviour.
- A legacy file that differs from another only in formatting (e.g.
re-indented) gets a different uuid. The two sides of a merge only
agree if they started from the same bytes, which is the normal case.
Tests (Qt 6.4, offscreen, qelectrotech --resave / --set-titleblock /
--info)
- 24 example projects, 3 resaves each from the same original: results
above; the project uuid is identical across runs. All 24 uuids are
distinct, except the byte-identical pair mentioned above.
- Resaving an already migrated file is byte-identical to the first
output.
- The same legacy file with CRLF line endings gets the same uuid as
with LF.
- Changing the project title in a migrated file keeps its uuid.
- Migrating and modifying in the same run (--set-titleblock on a legacy
file) gives the same uuid as a plain resave.
- Re-indenting a legacy file gives a different uuid (expected).
- A migrated file opened with upstream master loads normally; the
attribute is ignored and dropped on save.
- --info on a migrated file still works.
Refs #754, #779
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDyt4txaott5JyPNGQaeVp
341 lines
13 KiB
C++
341 lines
13 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifndef QET_PROJECT_H
|
|
#define QET_PROJECT_H
|
|
|
|
#include "ElementsCollection/elementslocation.h"
|
|
#include "NameList/nameslist.h"
|
|
#include "project/projectpropertieshandler.h"
|
|
#include "borderproperties.h"
|
|
#include "conductorproperties.h"
|
|
#include "dataBase/projectdatabase.h"
|
|
#include "properties/reportproperties.h"
|
|
#include "properties/xrefproperties.h"
|
|
#include "titleblock/templatescollection.h"
|
|
#include "titleblockproperties.h"
|
|
#include "diagram.h"
|
|
#ifdef BUILD_WITHOUT_KF
|
|
# include "ui/nokde/kautosavefile.h"
|
|
#else
|
|
# include <KAutoSaveFile>
|
|
#endif
|
|
|
|
#include <QHash>
|
|
#include <QFuture>
|
|
|
|
class Diagram;
|
|
class ElementsLocation;
|
|
class QETResult;
|
|
class TitleBlockTemplate;
|
|
class MoveTitleBlockTemplatesHandler;
|
|
class NumerotationContext;
|
|
class QUndoStack;
|
|
class XmlElementCollection;
|
|
class QTimer;
|
|
class TerminalStrip;
|
|
|
|
|
|
#include <QColor>
|
|
|
|
struct GuideProperties {
|
|
int orientation; // 0 = Horizontal, 1 = Vertical
|
|
qreal position;
|
|
QColor color;
|
|
|
|
bool operator==(const GuideProperties &other) const {
|
|
return orientation == other.orientation &&
|
|
position == other.position &&
|
|
color == other.color;
|
|
}
|
|
bool operator!=(const GuideProperties &other) const {
|
|
return !(*this == other);
|
|
}
|
|
};
|
|
|
|
/**
|
|
This class represents a QET project. Typically saved as a .qet file, it
|
|
consists in an XML document grouping 0 to n diagrams and embedding an elements
|
|
collection. This collection enables users to export diagrams on remote
|
|
machines without wondering whether required elements are available to them.
|
|
*/
|
|
class QETProject : public QObject
|
|
{
|
|
friend class AddDiagramCommand;
|
|
friend class RemoveDiagramCommand;
|
|
Q_OBJECT
|
|
public :
|
|
//This enum lists possible states for a particular project.
|
|
enum ProjectState {
|
|
Ok = 0, /// no error
|
|
FileOpenFailed = 1, /// file opening failed
|
|
XmlParsingFailed = 2, /// XML parsing failed
|
|
ProjectParsingRunning = 3, /// the XML content is currently being processed
|
|
ProjectParsingFailed = 4, /// the parsing of the XML content failed
|
|
FileOpenDiscard = 5 /// the user cancelled the file opening
|
|
};
|
|
|
|
Q_PROPERTY(bool autoConductor READ autoConductor WRITE setAutoConductor)
|
|
Q_PROPERTY(bool autoBreakConductor READ autoBreakConductor WRITE setAutoBreakConductor)
|
|
|
|
// constructors, destructor
|
|
public:
|
|
QETProject (QObject *parent = nullptr);
|
|
QETProject (const QString &path, QObject * = nullptr);
|
|
QETProject (KAutoSaveFile *backup, QObject *parent=nullptr);
|
|
~QETProject() override;
|
|
|
|
private:
|
|
QETProject(const QETProject &);
|
|
|
|
// methods
|
|
public:
|
|
ProjectPropertiesHandler& projectPropertiesHandler();
|
|
projectDataBase *dataBase();
|
|
QUuid uuid() const;
|
|
ProjectState state() const;
|
|
QList<Diagram *> diagrams() const;
|
|
int folioIndex(const Diagram *) const;
|
|
XmlElementCollection *embeddedElementCollection()const;
|
|
TitleBlockTemplatesProjectCollection *embeddedTitleBlockTemplatesCollection();
|
|
QString filePath();
|
|
void setFilePath(const QString &);
|
|
QString currentDir() const;
|
|
QString pathNameTitle() const;
|
|
QString title() const;
|
|
QVersionNumber declaredQElectroTechVersion();
|
|
void setTitle(const QString &);
|
|
|
|
/// Enable/disable the asynchronous crash-recovery backup for all
|
|
/// projects. Disabled by the headless CLI: the backup write runs on a
|
|
/// background thread referencing the project, and a short-lived CLI
|
|
/// process can destroy the project before the write finishes (crash).
|
|
static void setBackupEnabled(bool enabled);
|
|
|
|
///DEFAULT PROPERTIES
|
|
BorderProperties defaultBorderProperties() const;
|
|
void setDefaultBorderProperties(const BorderProperties &);
|
|
|
|
QList<GuideProperties> defaultGuides() const;
|
|
void setDefaultGuides(const QList<GuideProperties> &guides);
|
|
|
|
TitleBlockProperties defaultTitleBlockProperties() const;
|
|
void setDefaultTitleBlockProperties(const TitleBlockProperties &);
|
|
|
|
ConductorProperties defaultConductorProperties() const;
|
|
void setDefaultConductorProperties(const ConductorProperties &);
|
|
|
|
QString defaultReportProperties() const;
|
|
void setDefaultReportProperties (const QString &properties);
|
|
|
|
XRefProperties defaultXRefProperties (const QString &type) const {return m_default_xref_properties[type];}
|
|
QHash <QString, XRefProperties> defaultXRefProperties() const {return m_default_xref_properties;}
|
|
void setDefaultXRefProperties(const QString& type, const XRefProperties &properties);
|
|
void setDefaultXRefProperties(QHash <QString, XRefProperties> hash);
|
|
|
|
QHash <QString, NumerotationContext> conductorAutoNum() const;
|
|
QHash <QString, NumerotationContext> elementAutoNum() const;
|
|
QHash <QString, NumerotationContext> folioAutoNum() const;
|
|
void addConductorAutoNum (const QString& key, const NumerotationContext& context);
|
|
void addElementAutoNum (const QString& key, const NumerotationContext& context);
|
|
void addFolioAutoNum (const QString& key, const NumerotationContext& context);
|
|
void removeConductorAutoNum (const QString& key);
|
|
void removeElementAutoNum (const QString& key);
|
|
void removeFolioAutoNum (const QString& key);
|
|
NumerotationContext conductorAutoNum(const QString &key) const;
|
|
NumerotationContext folioAutoNum(const QString &key) const;
|
|
NumerotationContext elementAutoNum(const QString &key);
|
|
|
|
QString conductorAutoNumFormula(const QString& key) const; //returns Formula
|
|
QString conductorCurrentAutoNum() const;
|
|
void setCurrentConductorAutoNum(QString autoNum);
|
|
|
|
QString elementAutoNumFormula(const QString& key) const;
|
|
QString elementAutoNumCurrentFormula() const;
|
|
QString elementCurrentAutoNum() const;
|
|
void setCurrrentElementAutonum(QString autoNum);
|
|
|
|
//Element
|
|
void freezeExistentElementLabel(bool freeze, int from, int to);
|
|
void freezeNewElementLabel(bool freeze, int from, int to);
|
|
bool isFreezeNewElements();
|
|
void setFreezeNewElements(bool);
|
|
|
|
//Conductor
|
|
void freezeExistentConductorLabel(bool freeze, int from, int to);
|
|
void freezeNewConductorLabel(bool freeze, int from, int to);
|
|
bool isFreezeNewConductors();
|
|
void setFreezeNewConductors(bool);
|
|
|
|
bool autoConductor () const;
|
|
bool autoBreakConductor () const;
|
|
bool autoElement () const;
|
|
bool autoFolio () const;
|
|
void setAutoConductor (bool ac);
|
|
void setAutoBreakConductor (bool abc);
|
|
void setAutoElement (bool ae);
|
|
void autoFolioNumberingNewFolios ();
|
|
void autoFolioNumberingSelectedFolios(int, int, const QString&);
|
|
|
|
QDomDocument toXml();
|
|
bool close();
|
|
QETResult write();
|
|
bool isReadOnly() const;
|
|
void setReadOnly(bool);
|
|
bool isEmpty() const;
|
|
ElementsLocation importElement(ElementsLocation &location);
|
|
QString integrateTitleBlockTemplate(const TitleBlockTemplateLocation &, MoveTitleBlockTemplatesHandler *handler);
|
|
bool usesElement(const ElementsLocation &) const;
|
|
QList <ElementsLocation> unusedElements() const;
|
|
bool usesTitleBlockTemplate(const TitleBlockTemplateLocation &);
|
|
bool projectWasModified();
|
|
bool projectOptionsWereModified();
|
|
DiagramContext projectProperties();
|
|
void setProjectProperties(const DiagramContext &);
|
|
QUndoStack* undoStack() {return m_undo_stack;}
|
|
|
|
QVector<TerminalStrip *> terminalStrip() const;
|
|
TerminalStrip * newTerminalStrip(QString installation = QString(), QString location = QString(), QString name = QString());
|
|
bool addTerminalStrip(TerminalStrip *strip);
|
|
bool removeTerminalStrip(TerminalStrip *strip);
|
|
|
|
public slots:
|
|
Diagram *addNewDiagram(int pos = -1);
|
|
void removeDiagram(Diagram *);
|
|
void diagramOrderChanged(int, int);
|
|
void setModified(bool);
|
|
|
|
signals:
|
|
void projectFilePathChanged(QETProject *, const QString &);
|
|
void projectTitleChanged(QETProject *, const QString &);
|
|
void projectInformationsChanged(QETProject *);
|
|
void diagramAdded(QETProject *, Diagram *);
|
|
void diagramRemoved(QETProject *, Diagram *);
|
|
void projectModified(QETProject *, bool);
|
|
void projectDiagramsOrderChanged(QETProject *, int, int);
|
|
void diagramUsedTemplate(TitleBlockTemplatesCollection *, const QString &);
|
|
void readOnlyChanged(QETProject *, bool);
|
|
void reportPropertiesChanged(const QString &old_str, const QString &new_str);
|
|
void XRefPropertiesChanged ();
|
|
void addAutoNumDiagram();
|
|
void elementAutoNumAdded(QString name);
|
|
void elementAutoNumRemoved(QString name);
|
|
void conductorAutoNumAdded();
|
|
void conductorAutoNumRemoved();
|
|
void folioAutoNumAdded();
|
|
/// A numerotation context's *values* changed -- as happens every
|
|
/// time an element or conductor consumes the next number, not
|
|
/// only when a rule is added or removed. Deliberately separate
|
|
/// from the *Added/*Removed signals above, which make listeners
|
|
/// rebuild their rule lists; this one just says "re-read me".
|
|
void autoNumContextUpdated();
|
|
void folioAutoNumRemoved();
|
|
void defaultTitleBlockPropertiesChanged();
|
|
void conductorAutoNumChanged();
|
|
|
|
private slots:
|
|
void updateDiagramsFolioData();
|
|
void updateDiagramsTitleBlockTemplate(TitleBlockTemplatesCollection *, const QString &);
|
|
void removeDiagramsTitleBlockTemplate(TitleBlockTemplatesCollection *, const QString &);
|
|
void usedTitleBlockTemplateChanged(const QString &);
|
|
void undoStackChanged (bool a) {if (!a) setModified(true);}
|
|
|
|
private:
|
|
void readProjectXml(QDomDocument &xml_project);
|
|
void readDiagramsXml(QDomDocument &xml_project);
|
|
void readElementsCollectionXml(QDomDocument &xml_project);
|
|
void readProjectPropertiesXml(QDomDocument &xml_project);
|
|
void readDefaultPropertiesXml(QDomDocument &xml_project);
|
|
void readTerminalStripXml(const QDomDocument &xml_project);
|
|
void readUsageXml(QDomDocument &xml_project);
|
|
|
|
void writeProjectPropertiesXml(QDomElement &);
|
|
void writeDefaultPropertiesXml(QDomElement &);
|
|
void writeUsageXml(QDomElement &);
|
|
void addDiagram(Diagram *diagram, int pos = -1);
|
|
void detachDiagram(Diagram *diagram);
|
|
void writeBackup();
|
|
void init();
|
|
ProjectState openFile(QFile *file);
|
|
static QUuid derivedUuid(const QByteArray &content);
|
|
void refresh();
|
|
|
|
// attributes
|
|
private:
|
|
/// When false, writeBackup() is a no-op (set by the headless CLI)
|
|
static bool m_backup_enabled;
|
|
/// File path this project is saved to
|
|
QString m_file_path;
|
|
/// Current state of the project
|
|
ProjectState m_state;
|
|
/// Diagrams carried by the project
|
|
QList<Diagram *> m_diagrams_list;
|
|
/// Project title
|
|
QString project_title_;
|
|
/// QElectroTech version declared in the XML document at opening time
|
|
QVersionNumber m_project_qet_version;
|
|
/// Whether options were modified
|
|
bool m_modified = false;
|
|
/// Whether the project is read only
|
|
bool m_read_only = false;
|
|
/// Filepath for which this project is considered read only
|
|
QString read_only_file_path_;
|
|
/// Default dimensions and properties for new diagrams created within the project
|
|
BorderProperties default_border_properties_ = BorderProperties::defaultProperties();
|
|
/// Default guides for new diagrams created within the project
|
|
QList<GuideProperties> m_default_guides;
|
|
/// Default conductor properties for new diagrams created within the project
|
|
ConductorProperties default_conductor_properties_ = ConductorProperties::defaultProperties();
|
|
/// Default title block properties for new diagrams created within the project
|
|
TitleBlockProperties default_titleblock_properties_;
|
|
/// Default report properties
|
|
QString m_default_report_properties = ReportProperties::defaultProperties();
|
|
/// Default xref properties
|
|
QHash <QString, XRefProperties> m_default_xref_properties = XRefProperties::defaultProperties();
|
|
/// Embedded title block templates collection
|
|
TitleBlockTemplatesProjectCollection m_titleblocks_collection;
|
|
/// project-wide variables that will be made available to child diagrams
|
|
DiagramContext m_project_properties;
|
|
/// undo stack for this project
|
|
QUndoStack *m_undo_stack;
|
|
/// Conductor auto numerotation
|
|
QHash <QString, NumerotationContext> m_conductor_autonum;//Title and NumContext hash
|
|
QString m_current_conductor_autonum;
|
|
/// Folio auto numbering
|
|
QHash <QString, NumerotationContext> m_folio_autonum;
|
|
/// Element Auto Numbering
|
|
QHash <QString, NumerotationContext> m_element_autonum; //Title and NumContext hash
|
|
QString m_current_element_autonum;
|
|
bool m_auto_conductor = true;
|
|
bool m_auto_break_conductor = false;
|
|
XmlElementCollection *m_elements_collection = nullptr;
|
|
bool m_freeze_new_elements = false;
|
|
bool m_freeze_new_conductors = false;
|
|
QTimer m_save_backup_timer,
|
|
m_autosave_timer;
|
|
QFuture<bool> m_backup_future;
|
|
KAutoSaveFile m_backup_file;
|
|
QUuid m_uuid = QUuid::createUuid();
|
|
projectDataBase m_data_base;
|
|
QVector<TerminalStrip *> m_terminal_strip_vector;
|
|
|
|
ProjectPropertiesHandler m_project_properties_handler;
|
|
};
|
|
|
|
Q_DECLARE_METATYPE(QETProject *)
|
|
#endif
|