mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-29 14:14:15 +02:00
0d08a4e265
Saving an unmodified project produced a different byte stream on every run: QGraphicsScene::items() returns items in stacking order, and ties between same-Z items follow the scene's internal index -- not any content-derived order -- so it isn't reproducible across process runs. The legacy terminal-id table inherits the same instability, since ids are assigned sequentially in element order. Sort list_elements and list_conductors into a deterministic order before serializing, using a key built from data that's actually stable across loads (position), not Element::uuid()/Conductor::uuid(): for an item with no persisted uuid attribute, fromXml() invents a fresh random one on every load, so sorting by uuid would still be non-deterministic across process runs for any legacy file -- which this corpus has plenty of. Also fixes a second, related source of byte-level non-determinism found while verifying the above: Conductor::toXml() unconditionally wrote m_uuid back out, including the synthetic value fromXml() just invented for a conductor with no uuid attribute in the file. Every conductor in every example project checked has no persisted uuid at all, so this alone meant no project with conductors could ever resave identically, regardless of ordering. Conductor gets a m_persist_uuid flag, false only when the uuid it's holding was synthesized rather than loaded, so toXml() stops writing a value that was never meant to be permanent. Deliberately NOT applying the same uuid-persistence fix to Element: element uuids are cross-referenced by other elements' <links_uuids> blocks for master/slave/report linking (element.cpp, tmp_uuids_link, matched by elmt->uuid() == stored uuid on load). Making an element's own uuid non-persistent would silently break that match for any linked element without one already -- a real regression, not a theoretical one. Left as a smaller, separate residual: 1-6 elements per project across the corpus (a few tenths of a percent) still get a fresh uuid on each load, same class of bug, needs the link-aware version of this fix instead of this one. Verified against 8 example projects (the ones with conductors, plus the two zero-conductor control cases from FINDINGS.md F002), 5 resaves each in isolated HOME/XDG environments: - Element and conductor ORDER: 0 churning sections across the whole corpus (previously the majority of diagrams in industrial.qet, m_000.qet and tremie_vibrante.qet churned on every run). - Conductor uuid VALUES: 0 churn (previously every conductor in every project, since none have a persisted uuid). - 6 of 8 projects are now byte-for-byte identical (md5) across all 5 runs. The remaining 2 (industrial.qet, m_000.qet) differ only in the handful of element uuids covered by the known Element residual above -- confirmed by checking those uuids specifically, not inferred. - Element/conductor counts before and after resave match exactly on every project (no data loss from the sort). Fixes #754.
254 lines
8.1 KiB
C++
254 lines
8.1 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 CONDUCTOR_H
|
|
#define CONDUCTOR_H
|
|
#include "../autoNum/assignvariables.h"
|
|
#include "../conductorproperties.h"
|
|
|
|
#include <QGraphicsPathItem>
|
|
#include <QUuid>
|
|
|
|
class ConductorProfile;
|
|
class ConductorSegmentProfile;
|
|
class Diagram;
|
|
class Terminal;
|
|
class ConductorSegment;
|
|
class ConductorTextItem;
|
|
class Element;
|
|
class QETDiagramEditor;
|
|
class NumerotationContext;
|
|
class QetGraphicsHandlerItem;
|
|
|
|
typedef QPair<QPointF, Qt::Corner> ConductorBend;
|
|
typedef QHash<Qt::Corner, ConductorProfile> ConductorProfilesGroup;
|
|
/**
|
|
This class represents a conductor, i.e. a wire between two element
|
|
terminals.
|
|
*/
|
|
class Conductor : public QGraphicsObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
|
|
Q_PROPERTY(int animPath READ fakePath WRITE updatePathAnimate)
|
|
Q_PROPERTY(ConductorProperties properties READ properties WRITE setProperties)
|
|
Q_PROPERTY(autonum::sequentialNumbers sequenceNum READ sequenceNum WRITE setSequenceNum)
|
|
|
|
signals:
|
|
void propertiesChange();
|
|
|
|
public:
|
|
Conductor(Terminal *, Terminal *);
|
|
~Conductor() override;
|
|
|
|
bool isValid() const;
|
|
|
|
private:
|
|
Conductor(const Conductor &);
|
|
|
|
public:
|
|
enum { Type = UserType + 1001 };
|
|
enum Highlight { None, Normal, Alert };
|
|
|
|
Terminal *terminal1;
|
|
Terminal *terminal2;
|
|
|
|
public:
|
|
/**
|
|
@brief type
|
|
Enable the use of qgraphicsitem_cast
|
|
to safely cast a QGraphicsItem into a conductor.
|
|
@return the QGraphicsItem type
|
|
*/
|
|
int type() const override { return Type; }
|
|
Diagram *diagram() const;
|
|
ConductorTextItem *textItem() const;
|
|
QUuid uuid() const {return m_uuid;}
|
|
void newUuid() {m_uuid = QUuid::createUuid(); m_persist_uuid = true;} //create new uuid for this conductor
|
|
void updatePath(const QRectF & = QRectF());
|
|
|
|
//This method do nothing, it's only made to be used with Q_PROPERTY
|
|
//It's used to anim the path when is change
|
|
void updatePathAnimate(const int = 1) {updatePath();}
|
|
int fakePath() {return 1;}
|
|
|
|
void paint(
|
|
QPainter *,
|
|
const QStyleOptionGraphicsItem *,
|
|
QWidget *) override;
|
|
QRectF boundingRect() const override;
|
|
QPainterPath shape() const override;
|
|
virtual QPainterPath nearShape() const;
|
|
qreal length() const;
|
|
ConductorSegment *middleSegment();
|
|
QPointF posForText(Qt::Orientations &flag);
|
|
void refreshText();
|
|
void setPath(const QPainterPath &path);
|
|
QPainterPath path() const;
|
|
|
|
public:
|
|
static bool valideXml (QDomElement &);
|
|
bool fromXml (QDomElement &);
|
|
QDomElement toXml (
|
|
QDomDocument &,
|
|
QHash<Terminal *,
|
|
int> &) const;
|
|
private:
|
|
bool pathFromXml(const QDomElement &);
|
|
|
|
public:
|
|
QVector <QPointF> handlerPoints() const;
|
|
const QList<ConductorSegment *> segmentsList() const;
|
|
|
|
void setPropertyToPotential(
|
|
const ConductorProperties &property,
|
|
bool only_text = false);
|
|
void setProperties(const ConductorProperties &property);
|
|
ConductorProperties properties() const;
|
|
|
|
void setProfile(const ConductorProfile &, Qt::Corner);
|
|
ConductorProfile profile(Qt::Corner) const;
|
|
void setProfiles(const ConductorProfilesGroup &);
|
|
ConductorProfilesGroup profiles() const;
|
|
void calculateTextItemPosition();
|
|
virtual Highlight highlight() const;
|
|
virtual void setHighlighted(Highlight);
|
|
QSet<Conductor *> relatedPotentialConductors(
|
|
const bool all_diagram = true,
|
|
QList <Terminal *> *t_list=nullptr);
|
|
QETDiagramEditor* diagramEditor() const;
|
|
void editProperty ();
|
|
|
|
autonum::sequentialNumbers sequenceNum () const
|
|
{return m_autoNum_seq;}
|
|
autonum::sequentialNumbers& rSequenceNum()
|
|
{return m_autoNum_seq;}
|
|
void setSequenceNum(const autonum::sequentialNumbers& sn);
|
|
|
|
QList<QPointF> junctions() const;
|
|
|
|
private:
|
|
void setUpConnectionForFormula(
|
|
QString old_formula, QString new_formula);
|
|
autonum::sequentialNumbers m_autoNum_seq;
|
|
|
|
public:
|
|
void setFreezeLabel(bool freeze);
|
|
|
|
public slots:
|
|
void displayedTextChanged();
|
|
|
|
protected:
|
|
void mouseDoubleClickEvent(
|
|
QGraphicsSceneMouseEvent *event) override;
|
|
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
|
void mouseReleaseEvent(
|
|
QGraphicsSceneMouseEvent *event) override;
|
|
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
|
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
|
|
QVariant itemChange(
|
|
GraphicsItemChange, const QVariant &) override;
|
|
bool sceneEventFilter(
|
|
QGraphicsItem *watched, QEvent *event) override;
|
|
|
|
private:
|
|
void adjustHandlerPos();
|
|
|
|
void handlerMousePressEvent(
|
|
QetGraphicsHandlerItem *qghi,
|
|
QGraphicsSceneMouseEvent *event);
|
|
void handlerMouseMoveEvent(
|
|
QetGraphicsHandlerItem *qghi,
|
|
QGraphicsSceneMouseEvent *event);
|
|
void handlerMouseReleaseEvent(
|
|
QetGraphicsHandlerItem *qghi,
|
|
QGraphicsSceneMouseEvent *event);
|
|
void addHandler();
|
|
void removeHandler();
|
|
|
|
|
|
QVector<QetGraphicsHandlerItem *> m_handler_vector;
|
|
int m_vector_index = -1;
|
|
bool m_mouse_over;
|
|
/// Functional properties
|
|
ConductorProperties m_properties;
|
|
/// Text input for non simple, non-singleline conductors
|
|
ConductorTextItem *m_text_item;
|
|
/// Segments composing the conductor
|
|
ConductorSegment *segments;
|
|
/// Attributes related to mouse interaction
|
|
bool m_moving_segment;
|
|
int moved_point;
|
|
qreal m_previous_z_value;
|
|
ConductorSegment *m_moved_segment;
|
|
QPointF before_mov_text_pos_;
|
|
/// Whether the conductor was manually modified by users
|
|
bool modified_path;
|
|
/// Whether the current profile should be saved as soon as possible
|
|
bool has_to_save_profile;
|
|
/// conductor profile: "photography" of what the conductor is supposed to look
|
|
/// like - there is one profile per kind of traject
|
|
ConductorProfilesGroup conductor_profiles;
|
|
/// Define whether and how the conductor should be highlighted
|
|
Highlight must_highlight_;
|
|
bool m_valid;
|
|
bool m_freeze_label = false;
|
|
QUuid m_uuid;
|
|
/// false when m_uuid was synthesized by fromXml() because the
|
|
/// file had none -- toXml() must not persist that value, or
|
|
/// every reload mints and saves a new random one (see #754).
|
|
bool m_persist_uuid = true;
|
|
|
|
/// QPen et QBrush objects used to draw conductors
|
|
static QPen conductor_pen;
|
|
static QBrush conductor_brush;
|
|
static bool pen_and_brush_initialized;
|
|
QPainterPath m_path;
|
|
|
|
private:
|
|
void segmentsToPath();
|
|
void saveProfile(bool = true);
|
|
void generateConductorPath(const QPointF &, Qet::Orientation, const QPointF &, Qet::Orientation);
|
|
void updateConductorPath(const QPointF &, Qet::Orientation, const QPointF &, Qet::Orientation);
|
|
uint segmentsCount(QET::ConductorSegmentType = QET::Both) const;
|
|
QList<QPointF> segmentsToPoints() const;
|
|
QList<ConductorBend> bends() const;
|
|
|
|
void pointsToSegments(const QList<QPointF>&);
|
|
Qt::Corner currentPathType() const;
|
|
void deleteSegments();
|
|
static int getCoeff(const qreal &, const qreal &);
|
|
static int getSign(const qreal &);
|
|
QHash<ConductorSegmentProfile *, qreal> shareOffsetBetweenSegments(const qreal &offset, const QList<ConductorSegmentProfile *> &, const qreal & = 0.01) const;
|
|
static QPointF extendTerminal(const QPointF &, Qet::Orientation, qreal = 10);
|
|
static Qt::Corner movementType(const QPointF &, const QPointF &);
|
|
static QPointF movePointIntoPolygon(const QPointF &, const QPainterPath &);
|
|
};
|
|
|
|
Conductor * longestConductorInPotential (Conductor *conductor, bool all_diagram = false);
|
|
QList <Conductor *> relatedConductors (const Conductor *conductor);
|
|
|
|
|
|
//return true if @a is between or at @b and @c.
|
|
template <typename T>
|
|
bool isBetween (const T a, const T b, const T c) {
|
|
return (b <= c)? (a >= b && a <= c) : (a <= b && a >= c);
|
|
}
|
|
|
|
#endif
|