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()) {
250 throw PANDACEPTION(
"IC migration: failed to commit re-saved file: %1", fileInfo.absoluteFilePath());
254void ICLoader::processLoadedItems(
IC &ic, QList<QGraphicsItem *> &items)
269 while (!items.isEmpty()) {
270 auto *item = items.takeFirst();
272 if (
auto *conn = qgraphicsitem_cast<Connection *>(item)) {
273 ic.m_internalConnections.append(conn);
277 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
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;
292 sortPorts(ic.m_internalInputs);
293 sortPorts(ic.m_internalOutputs);
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);
310void ICLoader::deserializeAndLoad(
IC &ic,
const QByteArray &bytes,
const QString &contextDir)
312 if (s_icLoadDepth >= kMaxICNestingDepth) {
313 throw PANDACEPTION(
"IC nesting depth limit (%1) exceeded — blob may be maliciously crafted",
314 QString::number(kMaxICNestingDepth));
318 const auto depthGuard = qScopeGuard([] { --s_icLoadDepth; });
321 QByteArray data(bytes);
322 QDataStream stream(&data, QIODevice::ReadOnly);
327 QHash<quint64, Port *> portMap;
329 subCtx.
blobRegistry = blobRegistry.isEmpty() ? nullptr : &blobRegistry;
330 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
331 elementsStream.setVersion(QDataStream::Qt_5_12);
344 auto itemsGuard = qScopeGuard([&items] {
345 for (qsizetype i = 0; i < items.size(); ++i) {
355 ic.resetInternalState();
356 processLoadedItems(ic, items);
361 qCDebug(zero) <<
"Loading IC from blob.";
363 deserializeAndLoad(ic, blob, contextDir);
368 ic.setToolTip(QString());
370 qCDebug(zero) <<
"Finished loading IC from blob.";
373void ICLoader::loadBoundaryElement(
IC &ic,
GraphicElement *elm,
const bool isInput)
383 for (
int p = 0; p < portCount; ++p) {
385 nodeElm->setPos(elm->pos());
386 nodeElm->setLabel(elm->
label().isEmpty()
392 auto *nodeInput = nodeElm->inputPort();
394 nodeInput->setName(srcPort->name());
397 nodeInput->setDefaultStatus(srcPort->status());
398 nodeInput->setStatus(srcPort->status());
399 ic.m_internalInputs.append(nodeInput);
402 const auto conns = srcPort->connections();
403 for (
auto *conn : conns) {
404 conn->setStartPort(nodeElm->outputPort());
408 auto *nodeOutput = nodeElm->outputPort();
410 nodeOutput->setName(srcPort->name());
412 ic.m_internalOutputs.append(nodeOutput);
415 for (
auto *conn : srcPort->connections()) {
416 conn->setEndPort(nodeElm->inputPort());
420 ic.m_internalElements.append(nodeElm);
430 for (
int p = 0; p < elm->
inputSize(); ++p) {
432 for (
auto *c : conns) { c->setEndPort(
nullptr); }
436 for (
auto *c : conns) { c->setStartPort(
nullptr); }
442void ICLoader::loadBoundaryPorts(
IC &ic,
const bool isInput,
const QVector<QString> &labels)
444 const auto &internalPorts = isInput ? ic.m_internalInputs : ic.m_internalOutputs;
445 const int count =
static_cast<int>(internalPorts.size());
459 for (
int i = 0; i < count; ++i) {
465 port->setRequired(internalPorts.at(i)->isRequired());
466 port->setDefaultStatus(internalPorts.at(i)->status());
467 port->setStatus(internalPorts.at(i)->status());
473 qCDebug(three) <<
"IC" << ic.m_file <<
"->" << (isInput ?
"Inputs" :
"Outputs")
482 QVector<Port *> inputPorts, outputPorts;
484 for (
auto *elm : elements) {
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); }
492 sortPorts(inputPorts);
493 sortPorts(outputPorts);
495 meta.
inputCount =
static_cast<int>(inputPorts.size());
496 meta.
outputCount =
static_cast<int>(outputPorts.size());
498 QVector<QString> inLabels(inputPorts.size());
499 QVector<QString> outLabels(outputPorts.size());
500 buildPortLabels(inputPorts, inLabels);
501 buildPortLabels(outputPorts, outLabels);
503 meta.
inputLabels = QStringList(inLabels.begin(), inLabels.end());
504 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.