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; // LCOV_EXCL_LINE — ElementPorts::addPort() unconditionally calls setGraphicElement() right after constructing a port, so every port reachable here already has a non-null owner.
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; // LCOV_EXCL_LINE — same invariant as comparePorts() above: every port has a non-null owner from construction onward.
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 // Covered: TestICUnit::testMigrateFileCommitFailureThrows() forces this via
248 // RLIMIT_FSIZE (ScopedTinyFsizeLimit) -- Qt defers write() errors, so a failed
249 // write during writePayload() above only surfaces here, at commit().
250 throw PANDACEPTION("IC migration: failed to commit re-saved file: %1", fileInfo.absoluteFilePath());
251 }
252}
253
254void ICLoader::processLoadedItems(IC &ic, QList<QGraphicsItem *> &items)
255{
256 // Snapshot the preview now, while the original Input/Output elements (buttons,
257 // switches, LEDs, …) are still alive in `items`. loadBoundaryElement() below
258 // replaces each of them with a proxy Node, so a later render would only see
259 // the simulation graph.
261
262 // Drain items one at a time: as ownership of each transfers (to
263 // m_internalConnections/m_internalElements) or the item is deleted
264 // (loadBoundaryElement()'s original Input/Output element), remove it from
265 // `items` immediately. This keeps the invariant that `items` only ever
266 // holds pointers this function has NOT yet taken ownership of — the
267 // caller's qScopeGuard relies on that to avoid double-deleting/deleting a
268 // dangling pointer if an exception unwinds through this loop.
269 while (!items.isEmpty()) {
270 auto *item = items.takeFirst();
271
272 if (auto *conn = qgraphicsitem_cast<Connection *>(item)) {
273 ic.m_internalConnections.append(conn);
274 continue;
275 }
276
277 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
278 if (!elm) {
279 continue; // LCOV_EXCL_LINE — the Connection case above already handled the only other type Serialization::deserialize() ever produces; every remaining item is a GraphicElement.
280 }
281
282 // Input/Output elements become the IC's external ports; everything else is internal logic
283 switch (elm->elementGroup()) {
284 case ElementGroup::Input: loadBoundaryElement(ic, elm, true); break;
285 case ElementGroup::Output: loadBoundaryElement(ic, elm, false); break;
286 default: ic.m_internalElements.append(elm); break;
287 }
288 }
289
290 // --- Build sorted, labelled port lists ---
291 // Sort top-to-bottom by Y position so port order on the IC body matches visual layout
292 sortPorts(ic.m_internalInputs);
293 sortPorts(ic.m_internalOutputs);
294
295 QVector<QString> inputLabels(ic.m_internalInputs.size());
296 QVector<QString> outputLabels(ic.m_internalOutputs.size());
297 buildPortLabels(ic.m_internalInputs, inputLabels);
298 buildPortLabels(ic.m_internalOutputs, outputLabels);
299 loadBoundaryPorts(ic, true, inputLabels);
300 loadBoundaryPorts(ic, false, outputLabels);
301
302 // --- Update visual representation ---
303 // Position label just below the IC body, which grows with port count
304 const qreal bottom = ic.renderBodyBounds().bottom();
305 ic.setLabelAnchor(QPointF(30, bottom + 5));
306
308}
309
310void ICLoader::deserializeAndLoad(IC &ic, const QByteArray &bytes, const QString &contextDir)
311{
312 if (s_icLoadDepth >= kMaxICNestingDepth) {
313 throw PANDACEPTION("IC nesting depth limit (%1) exceeded — blob may be maliciously crafted",
314 QString::number(kMaxICNestingDepth));
315 }
316
317 ++s_icLoadDepth;
318 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
319
320 // Parse the bytes before clearing state so a corrupt input leaves the IC unchanged.
321 QByteArray data(bytes);
322 QDataStream stream(&data, QIODevice::ReadOnly);
323
324 auto preamble = Serialization::readPreamble(stream);
325 auto blobRegistry = Serialization::deserializeBlobRegistry(preamble.metadata, preamble.version);
326
327 QHash<quint64, Port *> portMap;
328 SerializationContext subCtx = {.portMap = portMap, .version = preamble.version, .purpose = SerializationPurpose::PortableFile, .contextDir = contextDir};
329 subCtx.blobRegistry = blobRegistry.isEmpty() ? nullptr : &blobRegistry;
330 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
331 elementsStream.setVersion(QDataStream::Qt_5_12);
332 QList<QGraphicsItem *> items = Serialization::deserialize(elementsStream, subCtx);
333
334 // See loadFileDirectly()'s itemsGuard for why this nulls connections
335 // before qDeleteAll() and why it's a no-op on the success path. Unlike
336 // loadFileDirectly() (whose migrateFile() call can throw between items being
337 // populated and processLoadedItems() draining them), nothing on this path can
338 // currently throw in that window: resetInternalState() only asserts already-
339 // guaranteed invariants, and processLoadedItems()'s one step before its drain
340 // loop (generatePreviewPixmap()) has no throwing path of its own. The
341 // Connection-first deletion order below is therefore unreached today — kept
342 // for structural symmetry with loadFileDirectly()'s identical-purpose guard,
343 // which genuinely needs it.
344 auto itemsGuard = qScopeGuard([&items] {
345 for (qsizetype i = 0; i < items.size(); ++i) {
346 if (items[i] && items[i]->type() == Connection::Type) { // LCOV_EXCL_LINE
347 delete items[i]; // LCOV_EXCL_LINE
348 items[i] = nullptr; // LCOV_EXCL_LINE
349 }
350 }
351 qDeleteAll(items);
352 });
353
354 // Parsing succeeded — now clear old state and apply
355 ic.resetInternalState();
356 processLoadedItems(ic, items);
357}
358
359void ICLoader::loadFromBlob(IC &ic, const QByteArray &blob, const QString &contextDir)
360{
361 qCDebug(zero) << "Loading IC from blob.";
362
363 deserializeAndLoad(ic, blob, contextDir);
364 ic.m_file.clear(); // switching to blob-backed, no file association
365
366 // Name is carried by the hover preview popup, not a Qt tooltip; clear the
367 // base class's translated-name tooltip so no bubble fights the preview.
368 ic.setToolTip(QString());
369
370 qCDebug(zero) << "Finished loading IC from blob.";
371}
372
373void ICLoader::loadBoundaryElement(IC &ic, GraphicElement *elm, const bool isInput)
374{
375 // Each port of a boundary element (input or output) becomes one external pin on the IC.
376 // A proxy Node element is inserted as a bridge between the IC's external port and the
377 // internal sub-circuit wiring.
378 //
379 // Input elements: [IC external input] → Node.input → Node.output → [internal wires]
380 // Output elements: [internal wires] → Node.input → Node.output → [IC external output]
381 const int portCount = isInput ? elm->outputSize() : elm->inputSize();
382
383 for (int p = 0; p < portCount; ++p) {
384 auto *nodeElm = ElementFactory::buildElement(ElementType::Node);
385 nodeElm->setPos(elm->pos());
386 nodeElm->setLabel(elm->label().isEmpty()
388 : elm->label());
389
390 if (isInput) {
391 auto *srcPort = elm->outputPort(p);
392 auto *nodeInput = nodeElm->inputPort();
393 if (portCount > 1) {
394 nodeInput->setName(srcPort->name());
395 }
396 nodeInput->setRequired(elm->elementType() == ElementType::Clock);
397 nodeInput->setDefaultStatus(srcPort->status());
398 nodeInput->setStatus(srcPort->status());
399 ic.m_internalInputs.append(nodeInput);
400
401 // Re-route connections from original output to proxy Node's output
402 const auto conns = srcPort->connections();
403 for (auto *conn : conns) {
404 conn->setStartPort(nodeElm->outputPort());
405 }
406 } else {
407 auto *srcPort = elm->inputPort(p);
408 auto *nodeOutput = nodeElm->outputPort();
409 if (portCount > 1) {
410 nodeOutput->setName(srcPort->name());
411 }
412 ic.m_internalOutputs.append(nodeOutput);
413
414 // Re-route connections from original input to proxy Node's input
415 for (auto *conn : srcPort->connections()) {
416 conn->setEndPort(nodeElm->inputPort());
417 }
418 }
419
420 ic.m_internalElements.append(nodeElm);
421 }
422
423 // Detach any connections still attached to elm's ports before deleting it.
424 // In a valid circuit all connections were re-routed above, so these lists
425 // are empty. In a fuzz-corrupted blob, extra ports may have connections that
426 // were not re-routed; drainConnections() in the port destructor would free
427 // them while their pointers are still live in the items list passed to
428 // processLoadedItems(), causing a heap-use-after-free. Calling setEndPort /
429 // setStartPort(nullptr) detaches cleanly without deleting the connection.
430 for (int p = 0; p < elm->inputSize(); ++p) {
431 const auto conns = elm->inputPort(p)->connections();
432 for (auto *c : conns) { c->setEndPort(nullptr); }
433 }
434 for (int p = 0; p < elm->outputSize(); ++p) {
435 const auto conns = elm->outputPort(p)->connections();
436 for (auto *c : conns) { c->setStartPort(nullptr); }
437 }
438
439 delete elm;
440}
441
442void ICLoader::loadBoundaryPorts(IC &ic, const bool isInput, const QVector<QString> &labels)
443{
444 const auto &internalPorts = isInput ? ic.m_internalInputs : ic.m_internalOutputs;
445 const int count = static_cast<int>(internalPorts.size());
446
447 // Lock port count to exactly the number found in the sub-circuit file;
448 // min == max == actual count prevents the user from adding/removing IC ports
449 if (isInput) {
450 ic.setMaxInputSize(count);
451 ic.setMinInputSize(count);
452 ic.setInputSize(count);
453 } else {
454 ic.setMaxOutputSize(count);
455 ic.setMinOutputSize(count);
456 ic.setOutputSize(count);
457 }
458
459 for (int i = 0; i < count; ++i) {
460 if (isInput) {
461 auto *port = ic.inputPort(i);
462 port->setName(labels.at(i));
463 // Mirror required/default-status from the sub-circuit's input elements so that
464 // unconnected optional inputs (e.g. enable lines) don't flag the IC as invalid
465 port->setRequired(internalPorts.at(i)->isRequired());
466 port->setDefaultStatus(internalPorts.at(i)->status());
467 port->setStatus(internalPorts.at(i)->status());
468 } else {
469 ic.outputPort(i)->setName(labels.at(i));
470 }
471 }
472
473 qCDebug(three) << "IC" << ic.m_file << "->" << (isInput ? "Inputs" : "Outputs")
474 << "min:" << (isInput ? ic.minInputSize() : ic.minOutputSize())
475 << "max:" << (isInput ? ic.maxInputSize() : ic.maxOutputSize())
476 << "current:" << (isInput ? ic.inputSize() : ic.outputSize());
477}
478
479IC::PortMetadata ICLoader::buildPortMetadata(const QVector<GraphicElement *> &elements)
480{
481 IC::PortMetadata meta;
482 QVector<Port *> inputPorts, outputPorts;
483
484 for (auto *elm : elements) {
485 if (elm->elementGroup() == ElementGroup::Input) {
486 for (auto *port : elm->outputs()) { inputPorts.append(port); }
487 } else if (elm->elementGroup() == ElementGroup::Output) {
488 for (auto *port : elm->inputs()) { outputPorts.append(port); }
489 }
490 }
491
492 sortPorts(inputPorts);
493 sortPorts(outputPorts);
494
495 meta.inputCount = static_cast<int>(inputPorts.size());
496 meta.outputCount = static_cast<int>(outputPorts.size());
497
498 QVector<QString> inLabels(inputPorts.size());
499 QVector<QString> outLabels(outputPorts.size());
500 buildPortLabels(inputPorts, inLabels);
501 buildPortLabels(outputPorts, outLabels);
502
503 meta.inputLabels = QStringList(inLabels.begin(), inLabels.end());
504 meta.outputLabels = QStringList(outLabels.begin(), outLabels.end());
505
506 return meta;
507}
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:93
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:479
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:359
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:71
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:97
QStringList inputLabels
Definition IC.h:100
int inputCount
Definition IC.h:98
int outputCount
Definition IC.h:99
QStringList outputLabels
Definition IC.h:101
QMap< QString, QByteArray > * blobRegistry
Blob registry for resolving embedded IC blobNames during deserialization.