31constexpr quint32 kMaxPortsPerElement = 1024;
38QList<QMap<QString, QVariant>> GraphicElementSerializer::readPortList(QDataStream &stream,
const char *label)
40 const QString labelStr = QString::fromUtf8(label);
43 if (stream.status() != QDataStream::Ok) {
44 throw PANDACEPTION(
"Stream error reading %1 count at position %2",
45 labelStr, QString::number(stream.device()->pos()));
47 if (count > kMaxPortsPerElement) {
48 throw PANDACEPTION(
"Refusing to read %1 with implausible count %2 (max %3)",
50 QString::number(count),
51 QString::number(kMaxPortsPerElement));
53 QList<QMap<QString, QVariant>> result;
54 result.reserve(
static_cast<int>(count));
55 for (quint32 i = 0; i < count; ++i) {
57 if (stream.status() != QDataStream::Ok) {
58 throw PANDACEPTION(
"Stream error reading %1 entry %2 at position %3",
61 QString::number(stream.device()->pos()));
63 result.append(std::move(entry));
68void GraphicElementSerializer::removePortFromMap(
Port *deletedPort, QHash<quint64, Port *> &portMap)
70 for (
auto it = portMap.begin(); it != portMap.end();) {
71 if (it.value() == deletedPort) {
72 it = portMap.erase(it);
95 qCDebug(four) <<
"Saving element. Type: " << element.objectName();
105 QMap<QString, QVariant> map;
106 map.insert(
"pos", element.pos());
110 map.insert(
"id", element.
id());
112 if (!slim || element.
rotation() != 0.0) {
113 map.insert(
"rotation", element.
rotation());
115 if (!slim || !element.
label().isEmpty()) {
116 map.insert(
"label", element.
label());
121 if (!slim || !element.
trigger().isEmpty()) {
122 map.insert(
"trigger", element.
trigger());
124 if (element.
isFlippedX()) { map.insert(
"flippedX",
true); }
125 if (element.
isFlippedY()) { map.insert(
"flippedY",
true); }
135 const bool keepNames = !slim || (element.
elementType() == ElementType::IC);
137 QList<QMap<QString, QVariant>> inputMap;
141 QMap<QString, QVariant> tempMap;
145 tempMap.insert(
"serialId", serialId);
148 tempMap.insert(
"name", port->name());
158 QList<QMap<QString, QVariant>> outputMap;
162 QMap<QString, QVariant> tempMap;
166 tempMap.insert(
"serialId", serialId);
169 tempMap.insert(
"name", port->name());
172 outputMap << tempMap;
189 QList<QMap<QString, QVariant>> appearancesMap;
190 bool anyCustom =
false;
193 QMap<QString, QVariant> tempMap;
194 if (!slim || !appearance.startsWith(
":/")) {
198 appearancesMap << tempMap;
201 if (slim && !anyCustom) {
202 appearancesMap.clear();
205 stream << appearancesMap;
209 qCDebug(four) <<
"Finished saving element.";
214 qCDebug(four) <<
"Loading element. Type: " << element.objectName();
220 qCDebug(four) <<
"Updating port positions.";
226 qCDebug(four) <<
"Finished loading element.";
233 loadPos(element, stream);
234 loadRotation(element, stream, context.
version);
236 loadLabel(element, stream, context.
version);
239 loadPortsSize(stream, context.
version);
242 loadTrigger(element, stream, context.
version);
245 quint64 unusedPriority; stream >> unusedPriority;
248 loadInputPorts(element, stream, context);
249 loadOutputPorts(element, stream, context);
251 loadPixmapAppearanceNames(element, stream, context);
261 if (stream.status() != QDataStream::Ok) {
262 throw PANDACEPTION(
"Stream error reading element properties at position %1",
263 QString::number(stream.device()->pos()));
266 if (map.contains(
"pos")) {
267 const QPointF pos = map.value(
"pos").toPointF();
268 validateFinitePos(pos);
272 if (map.contains(
"rotation")) {
273 const qreal angle = map.value(
"rotation").toReal();
274 validateFiniteAngle(angle);
278 if (map.contains(
"label")) {
279 element.
setLabel(map.value(
"label").toString());
287 if (map.contains(
"trigger")) {
288 element.
setTrigger(map.value(
"trigger").toString());
291 element.m_orientation.
setFlippedXRaw(map.value(
"flippedX",
false).toBool());
292 element.m_orientation.
setFlippedYRaw(map.value(
"flippedY",
false).toBool());
299 const bool hasSavedId = map.contains(
"id");
300 const quint64 savedId =
static_cast<quint64
>(map.value(
"id").toLongLong());
304 QList<QMap<QString, QVariant>> inputMap = readPortList(stream,
"input ports");
308 for (
const auto &input : std::as_const(inputMap)) {
309 const QString name = input.value(
"name").toString();
315 if (input.contains(
"serialId")) {
316 serialId = input.value(
"serialId").toULongLong();
317 }
else if (hasSavedId) {
327 quint64 oldPtr = input.value(
"ptr").toULongLong();
337 quint64 oldPtr = input.value(
"ptr").toULongLong();
350 removeSurplusInputs(element,
static_cast<quint64
>(inputMap.size()), context);
354 QList<QMap<QString, QVariant>> outputMap = readPortList(stream,
"output ports");
358 for (
const auto &output : std::as_const(outputMap)) {
359 const QString name = output.value(
"name").toString();
363 if (output.contains(
"serialId")) {
364 serialId = output.value(
"serialId").toULongLong();
365 }
else if (hasSavedId) {
376 quint64 oldPtr = output.value(
"ptr").toULongLong();
386 quint64 oldPtr = output.value(
"ptr").toULongLong();
399 removeSurplusOutputs(element,
static_cast<quint64
>(outputMap.size()), context);
403 QList<QMap<QString, QVariant>> appearancesMap = readPortList(stream,
"appearances");
405 if (stream.status() != QDataStream::Ok) {
406 throw PANDACEPTION(
"Stream error reading appearances at position %1",
407 QString::number(stream.device()->pos()));
412 for (
const auto &entry : std::as_const(appearancesMap)) {
414 throw PANDACEPTION(
"Appearance index %1 out of range (size=%2) — stream may be corrupt",
415 QString::number(index),
419 const QString name = entry.value(
"skinName").toString();
425 if (name.isEmpty()) {
457void GraphicElementSerializer::validateFinitePos(
const QPointF &pos)
459 if (!std::isfinite(pos.x()) || !std::isfinite(pos.y())) {
460 throw PANDACEPTION(
"Non-finite element position — stream may be corrupt");
464void GraphicElementSerializer::loadPos(
GraphicElement &element, QDataStream &stream)
466 QPointF pos; stream >> pos;
467 validateFinitePos(pos);
475void GraphicElementSerializer::validateFiniteAngle(
const qreal angle)
477 if (!std::isfinite(angle)) {
478 throw PANDACEPTION(
"Non-finite element rotation — stream may be corrupt");
482void GraphicElementSerializer::loadRotation(
GraphicElement &element, QDataStream &stream,
const QVersionNumber &version)
484 qreal angle; stream >> angle;
485 validateFiniteAngle(angle);
507void GraphicElementSerializer::loadLabel(
GraphicElement &element, QDataStream &stream,
const QVersionNumber &version)
514void GraphicElementSerializer::loadPortsSize(QDataStream &stream,
const QVersionNumber &version)
519 quint64 unused; stream >> unused; stream >> unused; stream >> unused; stream >> unused;
523void GraphicElementSerializer::loadTrigger(
GraphicElement &element, QDataStream &stream,
const QVersionNumber &version)
535 qCDebug(four) <<
"Loading input ports.";
536 quint64 inputSize; stream >> inputSize;
537 if (inputSize > kMaxPortsPerElement) {
538 throw PANDACEPTION(
"Refusing old-format input port list with implausible count %1 (max %2)",
539 QString::number(inputSize),
540 QString::number(kMaxPortsPerElement));
543 for (
size_t port = 0; port < inputSize; ++port) {
544 loadInputPort(element, stream, context,
static_cast<int>(port));
547 removeSurplusInputs(element, inputSize, context);
552 quint64 ptr; stream >> ptr;
554 int flags; stream >> flags;
569 qCDebug(four) <<
"Loading output ports.";
570 quint64 outputSize; stream >> outputSize;
571 if (outputSize > kMaxPortsPerElement) {
572 throw PANDACEPTION(
"Refusing old-format output port list with implausible count %1 (max %2)",
573 QString::number(outputSize),
574 QString::number(kMaxPortsPerElement));
577 for (
size_t port = 0; port < outputSize; ++port) {
578 loadOutputPort(element, stream, context,
static_cast<int>(port));
581 removeSurplusOutputs(element, outputSize, context);
586 quint64 ptr; stream >> ptr;
588 int flags; stream >> flags;
608 while ((element.
inputSize() >
static_cast<int>(inputSize_)) && (inputSize_ >=
static_cast<quint64
>(element.
minInputSize()))) {
612 removePortFromMap(deletedPort, context.
portMap);
620 while ((element.
outputSize() >
static_cast<int>(outputSize_)) && (outputSize_ >=
static_cast<quint64
>(element.
minOutputSize()))) {
624 removePortFromMap(deletedPort, context.
portMap);
634 qCDebug(four) <<
"Loading pixmap appearance names.";
635 quint64 outputSize; stream >> outputSize;
636 if (outputSize > kMaxPortsPerElement) {
637 throw PANDACEPTION(
"Refusing old-format appearance list with implausible count %1 (max %2)",
638 QString::number(outputSize),
639 QString::number(kMaxPortsPerElement));
645 for (quint64 i = 0; i < outputSize; ++i) {
651 for (
size_t i = 0; i < outputSize; ++i) {
652 loadPixmapAppearanceName(element, stream,
static_cast<int>(i), context);
666 throw PANDACEPTION(
"Appearance index %1 out of range (size=%2) for appearance name \"%3\" — stream may be corrupt",
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
#define qCDebug(category)
Single, shared implementation of how an element's external-file reference (an appearance image,...
GraphicElementSerializer: reads/writes a GraphicElement to a versioned QDataStream.
Abstract base class for all graphical circuit elements.
Port classes: Port (base), InputPort, and OutputPort.
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Named version predicates for file-format compatibility checks.
void recomputeUsingDefault()
Recomputes the using-default flag by comparing the alternative and default lists.
const QStringList & alternativeAppearances() const
Returns the active (possibly user-customised) appearance paths.
void setAlternativeAppearanceAt(const int index, const QString &path)
Sets one alternative-appearance slot without reloading the pixmap (serializer / subclass use).
void applyLoadedOrientation()
void setFlippedXRaw(bool flipped)
Sets the horizontal flip flag without re-applying the transform. Load-time only.
void setFlippedYRaw(bool flipped)
Sets the vertical flip flag without re-applying the transform. Load-time only.
qreal angle() const
Returns the current rotation angle in degrees.
void setAngleRaw(qreal angle)
Sets the angle without re-applying the transform. Load-time only.
InputPort * takeLastInput()
const QVector< OutputPort * > & outputs() const
Returns the output port vector.
void addPort(const QString &name, bool isOutput)
Appends a new port (parented to the owner) with name; isOutput selects direction.
const QVector< InputPort * > & inputs() const
Returns the input port vector.
OutputPort * takeLastOutput()
Removes and returns the last output port WITHOUT deleting it. Returns nullptr if empty.
static void save(const GraphicElement &element, QDataStream &stream, SerializationOptions options)
Writes element to stream (current keyed-QMap format).
static void load(GraphicElement &element, QDataStream &stream, SerializationContext &context)
Reads element from stream, dispatching on the format version in context.
Abstract base class for all graphical circuit elements in wiRedPanda.
ElementType elementType() const
Returns the type identifier for this element.
ElementAppearance m_appearance
int inputSize() const
Returns the current number of input ports.
qreal rotation() const
Returns the current rotation angle of this element in degrees.
bool isFlippedY() const
Returns true if this element is mirrored along the Y axis (vertical flip).
QString label() const
Returns the user-visible label text for this element.
virtual void load(QDataStream &stream, SerializationContext &context)
Loads the graphic element through a binary data stream.
void setTrigger(const QKeySequence &trigger)
Sets the keyboard shortcut to trigger and updates the label.
int minInputSize() const
Returns the minimum allowed number of input ports.
QKeySequence trigger() const
Returns the keyboard shortcut that activates this element.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
int outputSize() const
Returns the current number of output ports.
ElementGroup elementGroup() const
Returns the group this element belongs to.
virtual void save(QDataStream &stream, SerializationOptions options) const
int minOutputSize() const
Returns the minimum allowed number of output ports.
bool isFlippedX() const
Returns true if this element is mirrored along the X axis (horizontal flip).
virtual void updatePortsProperties()
Repositions and reconfigures all ports after the port count changes.
virtual void refresh()
Forces a visual refresh of the element's pixmap and ports.
int id() const
Returns the unique integer identifier of this item, or -1 if unassigned.
Abstract base class for circuit element ports (connection endpoints).
static quint64 makeSerialId(quint64 elementBase, int globalIndex)
static QString readBoundedString(QDataStream &stream)
Reads a QString from stream, refusing to allocate more bytes than remain in the stream (prevents OOM ...
static QKeySequence readBoundedKeySequence(QDataStream &stream)
Reads a QKeySequence from stream, rejecting an implausible/oversized internal key-combination count b...
static QMap< QString, QVariant > readBoundedMetadata(QDataStream &stream)
Reads the file-level metadata QMap<QString,QVariant> from stream without calling QList::reserve() wit...
std::optional< QString > forReading(const QString &storedValue, const QString &contextDir, SerializationPurpose purpose)
QString forWriting(const QString &path, SerializationPurpose purpose)
bool hasQMapFormat(const QVersionNumber &v)
V4.1: Format changed from flat binary to keyed QMap; port serial IDs; rotation fix.
bool hasTrigger(const QVersionNumber &v)
V1.9: Keyboard trigger sequences added.
bool hasUnusedPriority(const QVersionNumber &v)
V4.0.1: Unused priority field in old format.
bool hasAppearanceNames(const QVersionNumber &v)
V2.7: Pixmap appearance names stored in file.
bool hasPortSizes(const QVersionNumber &v)
V1.3: Port-size constraints stored in file.
bool hasLabels(const QVersionNumber &v)
V1.2: Labels added to elements; IC filename field.
Bundles all per-deserialization state so that load() overrides receive it through one explicit parame...
QVersionNumber version
File-format version read from the stream header.
QHash< quint64, quint64 > oldPtrToSerialId
SerializationPurpose purpose
What this deserialization is for; see SerializationPurpose.
QHash< quint64, Port * > & portMap
Accumulated port-pointer map built during deserialization.
QString contextDir
Directory of the .panda file (for relative path resolution).
Options passed to GraphicElement::save() (and friends); the save-side counterpart of SerializationCon...
SerializationPurpose purpose
What this serialization is for; see SerializationPurpose. Mandatory, no default.