27bool comparePorts(
Port *port1,
Port *port2)
31 if (!elem1 || !elem2) {
37 QPointF p1 = elem1->pos();
38 QPointF p2 = elem2->pos();
41 return (p1.y() < p2.y()) || (qFuzzyCompare(p1.y(), p2.y()) && (p1.x() < p2.x()));
49 return (p1.x() < p2.x()) || (qFuzzyCompare(p1.x(), p2.x()) && (p1.y() < p2.y()));
52void sortPorts(QVector<Port *> &ports)
54 std::stable_sort(ports.begin(), ports.end(), comparePorts);
57void buildPortLabels(
const QVector<Port *> &ports, QVector<QString> &labels)
59 for (
int i = 0; i < ports.size(); ++i) {
60 auto *port = ports.at(i);
61 auto *elm = port->graphicElement();
65 QString lb = elm->label();
67 if (!port->name().isEmpty()) {
68 lb +=
" " + port->name();
73 if (!elm->genericProperties().isEmpty()) {
74 lb +=
" [" + elm->genericProperties() +
"]";
83constexpr int kMaxICNestingDepth = 16;
84thread_local int s_icLoadDepth = 0;
94 if (!fileInfo.exists() || !fileInfo.isFile()) {
95 throw PANDACEPTION(
"%1 not found.", fileInfo.absoluteFilePath());
100 ic.m_blobName.clear();
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();
112 ic.setToolTip(QString());
113 if (ic.
label().isEmpty()) {
114 ic.
setLabel(fileInfo.baseName().toUpper());
116 qCDebug(zero) <<
"Finished reading IC (via cache).";
125 loadFileDirectly(ic, fileInfo);
126 ic.m_file = fileInfo.absoluteFilePath();
128 ic.setToolTip(QString());
130 qCDebug(zero) <<
"Finished reading IC.";
133void ICLoader::loadFileDirectly(
IC &ic,
const QFileInfo &fileInfo)
142 if (s_icLoadDepth >= kMaxICNestingDepth) {
143 throw PANDACEPTION(
"IC nesting depth limit (%1) exceeded while loading %2",
144 QString::number(kMaxICNestingDepth), fileInfo.absoluteFilePath());
147 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
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);
156 s_loadingFiles.insert(canonicalPath);
157 auto removeGuard = qScopeGuard([&] { s_loadingFiles.remove(canonicalPath); });
159 QFile file(fileInfo.absoluteFilePath());
160 if (!file.open(QIODevice::ReadOnly)) {
161 throw PANDACEPTION(
"Error opening file: %1", file.errorString());
164 QDataStream stream(&file);
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);
184 auto itemsGuard = qScopeGuard([&items] {
185 for (qsizetype i = 0; i < items.size(); ++i) {
195 migrateFile(fileInfo, items, preamble.version, fileRegistry);
202 ic.resetInternalState();
203 processLoadedItems(ic, items);
205 if (ic.
label().isEmpty()) {
206 ic.
setLabel(fileInfo.baseName().toUpper());
210void ICLoader::migrateFile(
const QFileInfo &fileInfo,
const QList<QGraphicsItem *> &items,
211 const QVersionNumber &version,
const QMap<QString, QByteArray> &fileRegistry)
216 QVector<GraphicElement *> elements;
217 for (
auto *item : items) {
219 if (
auto *elm = qgraphicsitem_cast<GraphicElement *>(item)) {
220 elements.append(elm);
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;
234 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
235 payloadStream.setVersion(QDataStream::Qt_5_12);
236 payloadStream << migrationMeta;
239 QSaveFile saveFile(fileInfo.absoluteFilePath());
240 if (!saveFile.open(QIODevice::WriteOnly)) {
241 throw PANDACEPTION(
"IC migration: cannot open file for writing: %1", fileInfo.absoluteFilePath());
243 QDataStream outStream(&saveFile);
246 if (!saveFile.commit()) {
247 throw PANDACEPTION(
"IC migration: failed to commit re-saved file: %1", fileInfo.absoluteFilePath());
251void ICLoader::processLoadedItems(
IC &ic, QList<QGraphicsItem *> &items)
266 while (!items.isEmpty()) {
267 auto *item = items.takeFirst();
269 if (
auto *conn = qgraphicsitem_cast<Connection *>(item)) {
270 ic.m_internalConnections.append(conn);
274 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
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;
289 sortPorts(ic.m_internalInputs);
290 sortPorts(ic.m_internalOutputs);
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);
307void ICLoader::deserializeAndLoad(
IC &ic,
const QByteArray &bytes,
const QString &contextDir)
309 if (s_icLoadDepth >= kMaxICNestingDepth) {
310 throw PANDACEPTION(
"IC nesting depth limit (%1) exceeded — blob may be maliciously crafted",
311 QString::number(kMaxICNestingDepth));
315 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
318 QByteArray data(bytes);
319 QDataStream stream(&data, QIODevice::ReadOnly);
324 QHash<quint64, Port *> portMap;
326 subCtx.
blobRegistry = blobRegistry.isEmpty() ? nullptr : &blobRegistry;
327 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
328 elementsStream.setVersion(QDataStream::Qt_5_12);
333 auto itemsGuard = qScopeGuard([&items] {
334 for (qsizetype i = 0; i < items.size(); ++i) {
344 ic.resetInternalState();
345 processLoadedItems(ic, items);
350 qCDebug(zero) <<
"Loading IC from blob.";
352 deserializeAndLoad(ic, blob, contextDir);
357 ic.setToolTip(QString());
359 qCDebug(zero) <<
"Finished loading IC from blob.";
362void ICLoader::loadBoundaryElement(
IC &ic,
GraphicElement *elm,
const bool isInput)
372 for (
int p = 0; p < portCount; ++p) {
374 nodeElm->setPos(elm->pos());
375 nodeElm->setLabel(elm->
label().isEmpty()
381 auto *nodeInput = nodeElm->inputPort();
383 nodeInput->setName(srcPort->name());
386 nodeInput->setDefaultStatus(srcPort->status());
387 nodeInput->setStatus(srcPort->status());
388 ic.m_internalInputs.append(nodeInput);
391 const auto conns = srcPort->connections();
392 for (
auto *conn : conns) {
393 conn->setStartPort(nodeElm->outputPort());
397 auto *nodeOutput = nodeElm->outputPort();
399 nodeOutput->setName(srcPort->name());
401 ic.m_internalOutputs.append(nodeOutput);
404 for (
auto *conn : srcPort->connections()) {
405 conn->setEndPort(nodeElm->inputPort());
409 ic.m_internalElements.append(nodeElm);
419 for (
int p = 0; p < elm->
inputSize(); ++p) {
421 for (
auto *c : conns) { c->setEndPort(
nullptr); }
425 for (
auto *c : conns) { c->setStartPort(
nullptr); }
431void ICLoader::loadBoundaryPorts(
IC &ic,
const bool isInput,
const QVector<QString> &labels)
433 const auto &internalPorts = isInput ? ic.m_internalInputs : ic.m_internalOutputs;
434 const int count =
static_cast<int>(internalPorts.size());
448 for (
int i = 0; i < count; ++i) {
454 port->setRequired(internalPorts.at(i)->isRequired());
455 port->setDefaultStatus(internalPorts.at(i)->status());
456 port->setStatus(internalPorts.at(i)->status());
462 qCDebug(three) <<
"IC" << ic.m_file <<
"->" << (isInput ?
"Inputs" :
"Outputs")
471 QVector<Port *> inputPorts, outputPorts;
473 for (
auto *elm : elements) {
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); }
481 sortPorts(inputPorts);
482 sortPorts(outputPorts);
484 meta.
inputCount =
static_cast<int>(inputPorts.size());
485 meta.
outputCount =
static_cast<int>(outputPorts.size());
487 QVector<QString> inLabels(inputPorts.size());
488 QVector<QString> outLabels(outputPorts.size());
489 buildPortLabels(inputPorts, inLabels);
490 buildPortLabels(outputPorts, outLabels);
492 meta.
inputLabels = QStringList(inLabels.begin(), inLabels.end());
493 meta.
outputLabels = QStringList(outLabels.begin(), outLabels.end());
Custom QApplication subclass with exception handling and main-window access.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
#define qCDebug(category)
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
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.
static void loadFromBlob(IC &ic, const QByteArray &blob, const QString &contextDir)
Loads ic from in-memory blob bytes (full .panda file format).
static void loadFile(IC &ic, const QString &fileName, const QString &contextDir={})
Loads ic's circuit from fileName and rebuilds the logic mapping.
static void generatePixmap(IC &ic)
static void generatePreviewPixmap(IC &ic, const QList< QGraphicsItem * > &items)
Graphic element representing an Integrated Circuit (sub-circuit) box.
Abstract base class for circuit element ports (connection endpoints).
void setRequired(const bool required)
Marks whether a wire to this port is mandatory.
GraphicElement * graphicElement()
Returns the graphic element that owns this port.
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
void setName(const QString &name)
Sets the label text shown next to the port.
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)
QMap< QString, QByteArray > * blobRegistry
Blob registry for resolving embedded IC blobNames during deserialization.