wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ICLoader.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <algorithm>
7
8#include <QSaveFile>
9#include <QScopeGuard>
10#include <QSet>
11
13#include "App/Core/Common.h"
19#include "App/IO/VersionInfo.h"
21#include "App/Scene/Scene.h"
23#include "App/Wiring/Port.h"
24
25namespace {
26
27bool comparePorts(Port *port1, Port *port2)
28{
29 auto *elem1 = port1->graphicElement();
30 auto *elem2 = port2->graphicElement();
31 if (!elem1 || !elem2) {
32 return false;
33 }
34
35 // Primary sort: top-to-bottom by parent element Y, then left-to-right by X.
36 // This gives an intuitive pin order that matches the visual layout in the sub-circuit.
37 QPointF p1 = elem1->pos();
38 QPointF p2 = elem2->pos();
39
40 if (p1 != p2) {
41 return (p1.y() < p2.y()) || (qFuzzyCompare(p1.y(), p2.y()) && (p1.x() < p2.x()));
42 }
43
44 // Secondary sort: when two ports share the same parent element position, sort by
45 // the port's own local coordinates (left-to-right, top-to-bottom)
46 p1 = port1->pos();
47 p2 = port2->pos();
48
49 return (p1.x() < p2.x()) || (qFuzzyCompare(p1.x(), p2.x()) && (p1.y() < p2.y()));
50}
51
52void sortPorts(QVector<Port *> &ports)
53{
54 std::stable_sort(ports.begin(), ports.end(), comparePorts);
55}
56
57void buildPortLabels(const QVector<Port *> &ports, QVector<QString> &labels)
58{
59 for (int i = 0; i < ports.size(); ++i) {
60 auto *port = ports.at(i);
61 auto *elm = port->graphicElement();
62 if (!elm) {
63 continue;
64 }
65 QString lb = elm->label();
66
67 if (!port->name().isEmpty()) {
68 lb += " " + port->name();
69 }
70
71 // Append generic properties (e.g. clock frequency) in brackets so the IC pin tooltip
72 // carries enough context for the user to identify the signal without opening the sub-circuit
73 if (!elm->genericProperties().isEmpty()) {
74 lb += " [" + elm->genericProperties() + "]";
75 }
76
77 labels[i] = lb;
78 }
79}
80
81// Maximum nesting depth for IC-within-IC blob loading.
82// Each level deserializes a full panda stream; deep nesting exhausts the call stack.
83constexpr int kMaxICNestingDepth = 16;
84thread_local int s_icLoadDepth = 0;
85
86} // anonymous namespace
87
88void ICLoader::loadFile(IC &ic, const QString &fileName, const QString &contextDir)
89{
90 qCDebug(zero) << "Reading IC.";
91
92 QFileInfo fileInfo(ExternalFilePath::resolve(fileName, contextDir));
93
94 if (!fileInfo.exists() || !fileInfo.isFile()) {
95 throw PANDACEPTION("%1 not found.", fileInfo.absoluteFilePath());
96 }
97
98 // Clear blob name only after validation so the IC remains consistent if
99 // the file is not found and an exception is thrown above.
100 ic.m_blobName.clear();
101
102 // Use cached file bytes from ICRegistry when available (avoids re-reading from disk)
103 if (auto *scene_ = qobject_cast<Scene *>(ic.scene())) {
104 auto *reg = scene_->icRegistry();
105 const QByteArray &cached = reg->cachedFileBytes(fileInfo.absoluteFilePath());
106 if (!cached.isEmpty()) {
107 deserializeAndLoad(ic, cached, fileInfo.absolutePath());
108 ic.m_file = fileInfo.absoluteFilePath();
109 // No Qt tooltip: the filename is shown in the hover preview popup
110 // (see ICPreviewPopup) so the two don't overlap. Clear the base
111 // class's translated-name tooltip set at construction.
112 ic.setToolTip(QString());
113 if (ic.label().isEmpty()) {
114 ic.setLabel(fileInfo.baseName().toUpper());
115 }
116 qCDebug(zero) << "Finished reading IC (via cache).";
117 return;
118 }
119 }
120
121 // Fallback: direct file load (IC not yet in a scene, e.g. during deserialization).
122 // loadFileDirectly() mirrors deserializeAndLoad()'s parse-first, reset-after shape:
123 // a failed parse (corrupt file, missing dependency, circular reference) propagates
124 // without ever leaving m_sortedInternalElements pointing at freed elements.
125 loadFileDirectly(ic, fileInfo);
126 ic.m_file = fileInfo.absoluteFilePath();
127 // Name is carried by the hover preview popup, not a Qt tooltip — see above.
128 ic.setToolTip(QString());
129
130 qCDebug(zero) << "Finished reading IC.";
131}
132
133void ICLoader::loadFileDirectly(IC &ic, const QFileInfo &fileInfo)
134{
135 // Depth cap: shares s_icLoadDepth/kMaxICNestingDepth with deserializeAndLoad() rather than
136 // using a separate counter, so a chain that mixes file-backed and embedded-blob hops can't
137 // bypass the limit by crossing between the two loading paths — depth bounds the overall
138 // recursion, not "how many hops happened to go through this particular function." Cycle
139 // detection (below) alone isn't enough: a long, non-cyclic chain of distinct legitimate
140 // files (A embeds B embeds C embeds ... , no repeats) would otherwise recurse unbounded here
141 // and exhaust the call stack.
142 if (s_icLoadDepth >= kMaxICNestingDepth) {
143 throw PANDACEPTION("IC nesting depth limit (%1) exceeded while loading %2",
144 QString::number(kMaxICNestingDepth), fileInfo.absoluteFilePath());
145 }
146 ++s_icLoadDepth;
147 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
148
149 // Cycle detection: if this file is already being loaded up the call stack,
150 // a circular IC reference exists (A→B→A→…). Throw instead of stack-overflowing.
151 static QSet<QString> s_loadingFiles;
152 const QString canonicalPath = fileInfo.canonicalFilePath();
153 if (s_loadingFiles.contains(canonicalPath)) {
154 throw PANDACEPTION("Circular IC reference detected: %1", canonicalPath);
155 }
156 s_loadingFiles.insert(canonicalPath);
157 auto removeGuard = qScopeGuard([&] { s_loadingFiles.remove(canonicalPath); });
158
159 QFile file(fileInfo.absoluteFilePath());
160 if (!file.open(QIODevice::ReadOnly)) {
161 throw PANDACEPTION("Error opening file: %1", file.errorString());
162 }
163
164 QDataStream stream(&file);
165 auto preamble = Serialization::readPreamble(stream);
166 auto fileRegistry = Serialization::deserializeBlobRegistry(preamble.metadata, preamble.version);
167
168 QHash<quint64, Port *> portMap;
169 SerializationContext subCtx = {.portMap = portMap, .version = preamble.version, .purpose = SerializationPurpose::PortableFile, .contextDir = fileInfo.absolutePath()};
170 subCtx.blobRegistry = fileRegistry.isEmpty() ? nullptr : &fileRegistry;
171 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
172 elementsStream.setVersion(QDataStream::Qt_5_12);
173 QList<QGraphicsItem *> items = Serialization::deserialize(elementsStream, subCtx);
174 file.close(); // must be closed before QSaveFile can write on Windows (mandatory file locking)
175
176 // Cleans up whatever is still in `items` if an exception unwinds through
177 // migrateFile()/processLoadedItems() below. processLoadedItems() drains
178 // items via takeFirst() as ownership transfers, so on success this guard
179 // finds an empty list and does nothing. Connections are nulled out before
180 // qDeleteAll(): a Connection* still owned by an element's port at this
181 // point would otherwise be deleted twice — once directly here, once via
182 // that port's destructor (~InputPort/~OutputPort drain their
183 // connections).
184 auto itemsGuard = qScopeGuard([&items] {
185 for (qsizetype i = 0; i < items.size(); ++i) {
186 if (items[i] && items[i]->type() == Connection::Type) {
187 delete items[i];
188 items[i] = nullptr;
189 }
190 }
191 qDeleteAll(items);
192 });
193
194 if ((preamble.version < FormatRev::current) && Application::migrationEnabled) {
195 migrateFile(fileInfo, items, preamble.version, fileRegistry);
196 }
197
198 // Parsing (and migration, if triggered) succeeded — only now is it safe to
199 // clear the old internal state and apply the freshly-parsed items. If any
200 // step above had thrown, resetInternalState() would never have run and the
201 // IC's previous internal graph would remain intact.
202 ic.resetInternalState();
203 processLoadedItems(ic, items);
204
205 if (ic.label().isEmpty()) {
206 ic.setLabel(fileInfo.baseName().toUpper());
207 }
208}
209
210void ICLoader::migrateFile(const QFileInfo &fileInfo, const QList<QGraphicsItem *> &items,
211 const QVersionNumber &version, const QMap<QString, QByteArray> &fileRegistry)
212{
213 Serialization::createVersionedBackup(fileInfo.absoluteFilePath(), version);
214
215 // Build port metadata for the migrated file header
216 QVector<GraphicElement *> elements;
217 for (auto *item : items) {
218 if (item->type() == GraphicElement::Type) {
219 if (auto *elm = qgraphicsitem_cast<GraphicElement *>(item)) {
220 elements.append(elm);
221 }
222 }
223 }
224 const auto portMeta = buildPortMetadata(elements);
225
226 QMap<QString, QVariant> migrationMeta;
227 migrationMeta["inputCount"] = portMeta.inputCount;
228 migrationMeta["outputCount"] = portMeta.outputCount;
229 migrationMeta["inputLabels"] = portMeta.inputLabels;
230 migrationMeta["outputLabels"] = portMeta.outputLabels;
231 Serialization::serializeBlobRegistry(fileRegistry, migrationMeta);
232
233 QByteArray payload;
234 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
235 payloadStream.setVersion(QDataStream::Qt_5_12);
236 payloadStream << migrationMeta;
237 Serialization::serialize(items, payloadStream, {.purpose = SerializationPurpose::PortableFile});
238
239 QSaveFile saveFile(fileInfo.absoluteFilePath());
240 if (!saveFile.open(QIODevice::WriteOnly)) {
241 throw PANDACEPTION("IC migration: cannot open file for writing: %1", fileInfo.absoluteFilePath());
242 }
243 QDataStream outStream(&saveFile);
245 Serialization::writePayload(outStream, payload);
246 if (!saveFile.commit()) {
247 throw PANDACEPTION("IC migration: failed to commit re-saved file: %1", fileInfo.absoluteFilePath());
248 }
249}
250
251void ICLoader::processLoadedItems(IC &ic, QList<QGraphicsItem *> &items)
252{
253 // Snapshot the preview now, while the original Input/Output elements (buttons,
254 // switches, LEDs, …) are still alive in `items`. loadBoundaryElement() below
255 // replaces each of them with a proxy Node, so a later render would only see
256 // the simulation graph.
258
259 // Drain items one at a time: as ownership of each transfers (to
260 // m_internalConnections/m_internalElements) or the item is deleted
261 // (loadBoundaryElement()'s original Input/Output element), remove it from
262 // `items` immediately. This keeps the invariant that `items` only ever
263 // holds pointers this function has NOT yet taken ownership of — the
264 // caller's qScopeGuard relies on that to avoid double-deleting/deleting a
265 // dangling pointer if an exception unwinds through this loop.
266 while (!items.isEmpty()) {
267 auto *item = items.takeFirst();
268
269 if (auto *conn = qgraphicsitem_cast<Connection *>(item)) {
270 ic.m_internalConnections.append(conn);
271 continue;
272 }
273
274 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
275 if (!elm) {
276 continue;
277 }
278
279 // Input/Output elements become the IC's external ports; everything else is internal logic
280 switch (elm->elementGroup()) {
281 case ElementGroup::Input: loadBoundaryElement(ic, elm, true); break;
282 case ElementGroup::Output: loadBoundaryElement(ic, elm, false); break;
283 default: ic.m_internalElements.append(elm); break;
284 }
285 }
286
287 // --- Build sorted, labelled port lists ---
288 // Sort top-to-bottom by Y position so port order on the IC body matches visual layout
289 sortPorts(ic.m_internalInputs);
290 sortPorts(ic.m_internalOutputs);
291
292 QVector<QString> inputLabels(ic.m_internalInputs.size());
293 QVector<QString> outputLabels(ic.m_internalOutputs.size());
294 buildPortLabels(ic.m_internalInputs, inputLabels);
295 buildPortLabels(ic.m_internalOutputs, outputLabels);
296 loadBoundaryPorts(ic, true, inputLabels);
297 loadBoundaryPorts(ic, false, outputLabels);
298
299 // --- Update visual representation ---
300 // Position label just below the IC body, which grows with port count
301 const qreal bottom = ic.renderBodyBounds().bottom();
302 ic.setLabelAnchor(QPointF(30, bottom + 5));
303
305}
306
307void ICLoader::deserializeAndLoad(IC &ic, const QByteArray &bytes, const QString &contextDir)
308{
309 if (s_icLoadDepth >= kMaxICNestingDepth) {
310 throw PANDACEPTION("IC nesting depth limit (%1) exceeded — blob may be maliciously crafted",
311 QString::number(kMaxICNestingDepth));
312 }
313
314 ++s_icLoadDepth;
315 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
316
317 // Parse the bytes before clearing state so a corrupt input leaves the IC unchanged.
318 QByteArray data(bytes);
319 QDataStream stream(&data, QIODevice::ReadOnly);
320
321 auto preamble = Serialization::readPreamble(stream);
322 auto blobRegistry = Serialization::deserializeBlobRegistry(preamble.metadata, preamble.version);
323
324 QHash<quint64, Port *> portMap;
325 SerializationContext subCtx = {.portMap = portMap, .version = preamble.version, .purpose = SerializationPurpose::PortableFile, .contextDir = contextDir};
326 subCtx.blobRegistry = blobRegistry.isEmpty() ? nullptr : &blobRegistry;
327 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
328 elementsStream.setVersion(QDataStream::Qt_5_12);
329 QList<QGraphicsItem *> items = Serialization::deserialize(elementsStream, subCtx);
330
331 // See loadFileDirectly()'s itemsGuard for why this nulls connections
332 // before qDeleteAll() and why it's a no-op on the success path.
333 auto itemsGuard = qScopeGuard([&items] {
334 for (qsizetype i = 0; i < items.size(); ++i) {
335 if (items[i] && items[i]->type() == Connection::Type) {
336 delete items[i];
337 items[i] = nullptr;
338 }
339 }
340 qDeleteAll(items);
341 });
342
343 // Parsing succeeded — now clear old state and apply
344 ic.resetInternalState();
345 processLoadedItems(ic, items);
346}
347
348void ICLoader::loadFromBlob(IC &ic, const QByteArray &blob, const QString &contextDir)
349{
350 qCDebug(zero) << "Loading IC from blob.";
351
352 deserializeAndLoad(ic, blob, contextDir);
353 ic.m_file.clear(); // switching to blob-backed, no file association
354
355 // Name is carried by the hover preview popup, not a Qt tooltip; clear the
356 // base class's translated-name tooltip so no bubble fights the preview.
357 ic.setToolTip(QString());
358
359 qCDebug(zero) << "Finished loading IC from blob.";
360}
361
362void ICLoader::loadBoundaryElement(IC &ic, GraphicElement *elm, const bool isInput)
363{
364 // Each port of a boundary element (input or output) becomes one external pin on the IC.
365 // A proxy Node element is inserted as a bridge between the IC's external port and the
366 // internal sub-circuit wiring.
367 //
368 // Input elements: [IC external input] → Node.input → Node.output → [internal wires]
369 // Output elements: [internal wires] → Node.input → Node.output → [IC external output]
370 const int portCount = isInput ? elm->outputSize() : elm->inputSize();
371
372 for (int p = 0; p < portCount; ++p) {
373 auto *nodeElm = ElementFactory::buildElement(ElementType::Node);
374 nodeElm->setPos(elm->pos());
375 nodeElm->setLabel(elm->label().isEmpty()
377 : elm->label());
378
379 if (isInput) {
380 auto *srcPort = elm->outputPort(p);
381 auto *nodeInput = nodeElm->inputPort();
382 if (portCount > 1) {
383 nodeInput->setName(srcPort->name());
384 }
385 nodeInput->setRequired(elm->elementType() == ElementType::Clock);
386 nodeInput->setDefaultStatus(srcPort->status());
387 nodeInput->setStatus(srcPort->status());
388 ic.m_internalInputs.append(nodeInput);
389
390 // Re-route connections from original output to proxy Node's output
391 const auto conns = srcPort->connections();
392 for (auto *conn : conns) {
393 conn->setStartPort(nodeElm->outputPort());
394 }
395 } else {
396 auto *srcPort = elm->inputPort(p);
397 auto *nodeOutput = nodeElm->outputPort();
398 if (portCount > 1) {
399 nodeOutput->setName(srcPort->name());
400 }
401 ic.m_internalOutputs.append(nodeOutput);
402
403 // Re-route connections from original input to proxy Node's input
404 for (auto *conn : srcPort->connections()) {
405 conn->setEndPort(nodeElm->inputPort());
406 }
407 }
408
409 ic.m_internalElements.append(nodeElm);
410 }
411
412 // Detach any connections still attached to elm's ports before deleting it.
413 // In a valid circuit all connections were re-routed above, so these lists
414 // are empty. In a fuzz-corrupted blob, extra ports may have connections that
415 // were not re-routed; drainConnections() in the port destructor would free
416 // them while their pointers are still live in the items list passed to
417 // processLoadedItems(), causing a heap-use-after-free. Calling setEndPort /
418 // setStartPort(nullptr) detaches cleanly without deleting the connection.
419 for (int p = 0; p < elm->inputSize(); ++p) {
420 const auto conns = elm->inputPort(p)->connections();
421 for (auto *c : conns) { c->setEndPort(nullptr); }
422 }
423 for (int p = 0; p < elm->outputSize(); ++p) {
424 const auto conns = elm->outputPort(p)->connections();
425 for (auto *c : conns) { c->setStartPort(nullptr); }
426 }
427
428 delete elm;
429}
430
431void ICLoader::loadBoundaryPorts(IC &ic, const bool isInput, const QVector<QString> &labels)
432{
433 const auto &internalPorts = isInput ? ic.m_internalInputs : ic.m_internalOutputs;
434 const int count = static_cast<int>(internalPorts.size());
435
436 // Lock port count to exactly the number found in the sub-circuit file;
437 // min == max == actual count prevents the user from adding/removing IC ports
438 if (isInput) {
439 ic.setMaxInputSize(count);
440 ic.setMinInputSize(count);
441 ic.setInputSize(count);
442 } else {
443 ic.setMaxOutputSize(count);
444 ic.setMinOutputSize(count);
445 ic.setOutputSize(count);
446 }
447
448 for (int i = 0; i < count; ++i) {
449 if (isInput) {
450 auto *port = ic.inputPort(i);
451 port->setName(labels.at(i));
452 // Mirror required/default-status from the sub-circuit's input elements so that
453 // unconnected optional inputs (e.g. enable lines) don't flag the IC as invalid
454 port->setRequired(internalPorts.at(i)->isRequired());
455 port->setDefaultStatus(internalPorts.at(i)->status());
456 port->setStatus(internalPorts.at(i)->status());
457 } else {
458 ic.outputPort(i)->setName(labels.at(i));
459 }
460 }
461
462 qCDebug(three) << "IC" << ic.m_file << "->" << (isInput ? "Inputs" : "Outputs")
463 << "min:" << (isInput ? ic.minInputSize() : ic.minOutputSize())
464 << "max:" << (isInput ? ic.maxInputSize() : ic.maxOutputSize())
465 << "current:" << (isInput ? ic.inputSize() : ic.outputSize());
466}
467
468IC::PortMetadata ICLoader::buildPortMetadata(const QVector<GraphicElement *> &elements)
469{
470 IC::PortMetadata meta;
471 QVector<Port *> inputPorts, outputPorts;
472
473 for (auto *elm : elements) {
474 if (elm->elementGroup() == ElementGroup::Input) {
475 for (auto *port : elm->outputs()) { inputPorts.append(port); }
476 } else if (elm->elementGroup() == ElementGroup::Output) {
477 for (auto *port : elm->inputs()) { outputPorts.append(port); }
478 }
479 }
480
481 sortPorts(inputPorts);
482 sortPorts(outputPorts);
483
484 meta.inputCount = static_cast<int>(inputPorts.size());
485 meta.outputCount = static_cast<int>(outputPorts.size());
486
487 QVector<QString> inLabels(inputPorts.size());
488 QVector<QString> outLabels(outputPorts.size());
489 buildPortLabels(inputPorts, inLabels);
490 buildPortLabels(outputPorts, outLabels);
491
492 meta.inputLabels = QStringList(inLabels.begin(), inLabels.end());
493 meta.outputLabels = QStringList(outLabels.begin(), outLabels.end());
494
495 return meta;
496}
Custom QApplication subclass with exception handling and main-window access.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
Definition Common.h:98
#define qCDebug(category)
Definition Common.h:29
Connection: a wire that connects an output port to an input port in the circuit scene.
Singleton factory for all circuit element types.
Single, shared implementation of how an element's external-file reference (an appearance image,...
ICLoader: loads an IC's sub-circuit from file or embedded blob.
IC definition registry with file watching and embedded blob storage.
ICRenderer: draws an IC's body and builds its cached pixmaps.
Port classes: Port (base), InputPort, and OutputPort.
Main circuit editing scene with undo/redo and user interaction.
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.
static bool migrationEnabled
Definition Application.h:85
static QString typeToText(const ElementType type)
Converts type to its internal name string.
static GraphicElement * buildElement(const ElementType type)
Constructs and returns a new graphic element of the given type.
Abstract base class for all graphical circuit elements in wiRedPanda.
ElementType elementType() const
Returns the type identifier for this element.
int inputSize() const
Returns the current number of input ports.
void setLabelAnchor(const QPointF &pos)
QRectF renderBodyBounds() const
Footprint of a "procedural render body" (IC/Mux/Demux/TruthTable): the nominal 64x64 body unioned wit...
QString label() const
Returns the user-visible label text for this element.
int maxOutputSize() const
Returns the maximum allowed number of output ports.
InputPort * inputPort(const int index=0) const
Returns the input port at index (default 0).
int minInputSize() const
Returns the minimum allowed number of input ports.
virtual void setInputSize(const int size)
Adjusts the number of input ports to size, adding or removing ports as needed.
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.
OutputPort * outputPort(const int index=0) const
Returns the output port at index (default 0).
ElementGroup elementGroup() const
Returns the group this element belongs to.
int maxInputSize() const
Returns the maximum allowed number of input ports.
void setMaxInputSize(const int maxInputSize)
Sets the maximum number of input ports to maxInputSize.
void setMaxOutputSize(const int maxOutputSize)
Sets the maximum number of output ports to maxOutputSize.
int minOutputSize() const
Returns the minimum allowed number of output ports.
void setMinInputSize(const int minInputSize)
Sets the minimum number of input ports to minInputSize.
const QVector< OutputPort * > & outputs() const
Returns a const reference to the vector of all output ports.
virtual void setOutputSize(const int size)
Adjusts the number of output ports to size, adding or removing ports as needed.
void setMinOutputSize(const int minOutputSize)
Sets the minimum number of output ports to minOutputSize.
const QVector< InputPort * > & inputs() const
Returns a const reference to the vector of all input ports.
static IC::PortMetadata buildPortMetadata(const QVector< GraphicElement * > &elements)
Scans elements for Input/Output groups, sorts by Y/X position, and builds labels.
Definition ICLoader.cpp:468
static void loadFromBlob(IC &ic, const QByteArray &blob, const QString &contextDir)
Loads ic from in-memory blob bytes (full .panda file format).
Definition ICLoader.cpp:348
static void loadFile(IC &ic, const QString &fileName, const QString &contextDir={})
Loads ic's circuit from fileName and rebuilds the logic mapping.
Definition ICLoader.cpp:88
static void generatePixmap(IC &ic)
static void generatePreviewPixmap(IC &ic, const QList< QGraphicsItem * > &items)
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
Abstract base class for circuit element ports (connection endpoints).
Definition Port.h:39
void setRequired(const bool required)
Marks whether a wire to this port is mandatory.
Definition Port.cpp:199
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
Definition Port.h:66
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
Definition Port.cpp:57
void setName(const QString &name)
Sets the label text shown next to the port.
Definition Port.cpp:166
static QList< QGraphicsItem * > deserialize(QDataStream &stream, SerializationContext &context)
Deserializes items from stream until the stream is exhausted.
static void serialize(const QList< QGraphicsItem * > &items, QDataStream &stream, SerializationOptions options)
Serializes items to stream in the current .panda binary format.
static void writePandaHeader(QDataStream &stream)
Writes the .panda circuit file header to stream.
static void serializeBlobRegistry(const QMap< QString, QByteArray > &blobs, QMap< QString, QVariant > &metadata)
Serializes embedded ICs into a metadata map (sets the "embeddedICs" key).
static void writePayload(QDataStream &stream, const QByteArray &payload)
Compresses payload (qCompress) and writes it to stream.
static Preamble readPreamble(QDataStream &stream)
Reads the full .panda preamble: header, dolphin filename, rect, and metadata (V_4_5+).
static QMap< QString, QByteArray > deserializeBlobRegistry(const QMap< QString, QVariant > &metadata, const QVersionNumber &fileVersion)
Extracts the embedded IC registry from a metadata map.
static void createVersionedBackup(const QString &fileName, const QVersionNumber &version)
Copies fileName to a versioned sidecar before overwriting it during migration.
QString resolve(const QString &storedPath, const QString &contextDir)
const QVersionNumber current
Definition Versions.h:63
Port count and label metadata extracted from Input/Output elements.
Definition IC.h:96
QStringList inputLabels
Definition IC.h:99
int inputCount
Definition IC.h:97
int outputCount
Definition IC.h:98
QStringList outputLabels
Definition IC.h:100
QMap< QString, QByteArray > * blobRegistry
Blob registry for resolving embedded IC blobNames during deserialization.