6#include <QCoreApplication>
29 connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged,
30 this, &ICRegistry::onFileChanged, Qt::QueuedConnection);
35 Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
37 if (!m_fileCache.contains(filePath)) {
39 if (file.open(QIODevice::ReadOnly)) {
40 m_fileCache[filePath] = file.readAll();
46 qCWarning(zero) <<
"ICRegistry: cannot open IC file:" << filePath;
47 static const QByteArray empty;
51 return m_fileCache[filePath];
56 m_fileCache.remove(filePath);
61 if (!m_fileWatcher.files().contains(filePath)) {
62 m_fileWatcher.addPath(filePath);
68 const QFileInfo target(fileName);
69 QList<GraphicElement *> result;
70 for (
auto *elm : m_scene->elements()) {
71 if (elm->elementType() == ElementType::IC) {
72 auto *ic =
static_cast<IC *
>(elm);
73 if (QFileInfo(ic->file()) == target) {
81void ICRegistry::onFileChanged(
const QString &filePath)
83 qCDebug(zero) <<
"IC file changed:" << filePath;
89 if (!m_fileWatcher.files().contains(filePath) && QFileInfo::exists(filePath)) {
90 m_fileWatcher.addPath(filePath);
95 if (targets.isEmpty()) {
108 reloadTargetsAtomically(targets, oldData, [&](IC *ic) { ic->
loadFile(filePath); });
110 m_scene->setCircuitUpdateRequired();
114 m_scene->setCircuitUpdateRequired();
116 auto *cmd =
new UpdateBlobCommand(targets, oldData, connections, m_scene);
117 m_scene->undoStack()->push(cmd);
126 return m_blobs.contains(name);
131 return m_blobs.value(name);
136 m_blobs[name] = data;
141 QSet<QString> visited;
142 QMap<QString, QByteArray> workingBlobs;
143 workingBlobs[name] = data;
144 makeBlobSelfContained(name, visited, workingBlobs);
145 m_blobs[name] = workingBlobs[name];
150 m_blobs.remove(name);
159 if (!m_blobs.contains(oldName) || oldName == newName || m_blobs.contains(newName)) {
163 m_blobs[newName] = m_blobs.take(oldName);
166 for (
auto *elm : m_scene->elements()) {
167 if (elm->isEmbedded() && elm->blobName() == oldName) {
168 auto *ic =
static_cast<IC *
>(elm);
175 for (
auto it = m_blobs.begin(); it != m_blobs.end(); ++it) {
176 renameBlobReference(it.value(), oldName, newName);
189 QList<GraphicElement *> result;
190 for (
auto *elm : m_scene->elements()) {
191 if (elm->isEmbedded() && elm->blobName() == blobName) {
205 if (ic->
label().isEmpty()) {
216 for (
int i = 2; ; ++i) {
217 const QString candidate = baseName +
"_" + QString::number(i);
229 m_scene->undoStack()->beginMacro(QCoreApplication::tr(
"Add embedded IC"));
233 m_scene->undoStack()->endMacro();
239 const QString &blobName)
242 if (targets.isEmpty()) {
252 reloadTargetsAtomically(targets, oldData, [&](
IC *ic) {
254 ic->
loadFromBlob(m_blobs[blobName], m_scene->contextDir());
266 cmd->setOldBlob(QByteArray());
267 m_scene->undoStack()->push(cmd);
268 return static_cast<int>(targets.size());
274 QSaveFile saveFile(filePath);
275 if (!saveFile.open(QIODevice::WriteOnly)) {
276 throw PANDACEPTION(
"Could not open file: %1", saveFile.errorString());
278 saveFile.write(
blob(blobName));
279 if (!saveFile.commit()) {
280 throw PANDACEPTION(
"Could not save file: %1", saveFile.errorString());
285 if (targets.isEmpty()) {
291 const QByteArray oldBlob =
blob(blobName);
293 const QString fileDir = QFileInfo(filePath).absolutePath();
294 reloadTargetsAtomically(targets, oldData, [&](
IC *ic) { ic->
loadFile(filePath, fileDir); });
299 cmd->setOldBlob(oldBlob);
300 cmd->setBlobName(blobName);
301 m_scene->undoStack()->push(cmd);
302 return static_cast<int>(targets.size());
308 QByteArray data(snapshot);
309 QDataStream stream(&data, QIODevice::ReadOnly);
311 QHash<quint64, Port *> portMap;
313 for (
auto *elm : elements) {
314 elm->load(stream, ctx);
318void ICRegistry::reloadTargetsAtomically(
const QList<GraphicElement *> &targets,
const QByteArray &oldData,
319 const std::function<
void(
IC *)> &mutate)
326 QList<GraphicElement *> updated;
329 for (
auto *elm : targets) {
330 mutate(
static_cast<IC *
>(elm));
339void ICRegistry::makeBlobSelfContained(
const QString &name, QSet<QString> &visited,
340 QMap<QString, QByteArray> &blobs,
int depth)
342 if (depth >= kMaxBlobNestingDepth) {
343 throw PANDACEPTION(
"Embedded IC dependency chain exceeds the maximum nesting depth (%1) while resolving '%2'",
344 QString::number(kMaxBlobNestingDepth), name);
347 if (visited.contains(name)) {
348 qCWarning(zero) <<
"Circular blob reference detected:" << name <<
"— skipping.";
351 visited.insert(name);
353 QByteArray blobData(blobs[name]);
354 QDataStream readStream(&blobData, QIODevice::ReadOnly);
361 auto metadata = preamble.metadata;
363 bool modified =
false;
366 for (
auto it = embeddedICs.begin(); it != embeddedICs.end(); ++it) {
367 const QString &depName = it.key();
368 blobs[depName] = it.value();
369 makeBlobSelfContained(depName, visited, blobs, depth + 1);
370 it.value() = blobs[depName];
374 if (metadata.contains(
"fileBackedICs")) {
375 const QStringList files = metadata.value(
"fileBackedICs").toStringList();
376 const QString contextDir = m_scene->contextDir();
378 for (
const QString &fileName : files) {
379 const QString baseName = QFileInfo(fileName).baseName();
380 if (embeddedICs.contains(baseName)) {
384 QFileInfo fi(QDir(contextDir), fileName);
386 qCWarning(zero) <<
"makeBlobSelfContained: dependency" << fileName
387 <<
"not found for blob" << name <<
"— skipping.";
391 QFile file(fi.absoluteFilePath());
392 if (!file.open(QIODevice::ReadOnly)) {
393 qCWarning(zero) <<
"makeBlobSelfContained: cannot open dependency" << fi.absoluteFilePath()
394 <<
"for blob" << name <<
"— blob will be incomplete.";
397 QByteArray fileBytes = file.readAll();
401 blobs[baseName] = fileBytes;
402 makeBlobSelfContained(baseName, visited, blobs, depth + 1);
403 embeddedICs[baseName] = blobs[baseName];
406 metadata.remove(
"fileBackedICs");
419 const QByteArray elements = preamble.remainingPayload;
428 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
429 payloadStream.setVersion(QDataStream::Qt_5_12);
430 payloadStream << metadata;
431 payloadStream.writeRawData(elements.constData(),
static_cast<int>(elements.size()));
434 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
438 blobs[name] = newBlob;
441void ICRegistry::renameBlobReference(QByteArray &blobData,
const QString &oldName,
const QString &newName)
443 QDataStream readStream(&blobData, QIODevice::ReadOnly);
451 if (!embeddedICs.contains(oldName)) {
456 embeddedICs[newName] = embeddedICs.take(oldName);
463 const QByteArray elements = preamble.remainingPayload;
464 auto metadata = preamble.metadata;
472 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
473 payloadStream.setVersion(QDataStream::Qt_5_12);
474 payloadStream << metadata;
475 payloadStream.writeRawData(elements.constData(),
static_cast<int>(elements.size()));
478 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
488 QDataStream stream(&data, QIODevice::WriteOnly);
490 for (
auto *elm : targets) {
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION(msg,...)
#define qCDebug(category)
Abstract base class for all graphical circuit elements.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
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.
RAII guard that temporarily stops the simulation while in scope.
Synchronous cycle-based simulation engine with event-driven clock support.
Named version predicates for file-format compatibility checks.
File-format version constants and application version accessor.
Undo command that adds a list of graphic elements to the scene.
QString label() const
Returns the user-visible label text for this element.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
void renameBlob(const QString &oldName, const QString &newName)
Renames a blob from oldName to newName, updating the cache key.
void setBlob(const QString &name, const QByteArray &data)
Stores or replaces the blob data under name and invalidates the cached definition.
int embedICsByFile(const QString &fileName, const QByteArray &fileBytes, const QString &blobName)
Converts all file-backed IC elements referencing fileName to embedded ICs using blobName.
bool initEmbeddedIC(IC *ic, const QString &blobName)
Initializes an embedded IC by looking up its blob in the registry.
QByteArray blob(const QString &name) const
Returns the raw .panda bytes for the embedded IC named name.
void clearBlobs()
Removes all stored blobs.
QString uniqueBlobName(const QString &baseName) const
Returns baseName if available, or appends a numeric suffix to avoid collision.
QList< GraphicElement * > findICsByBlobName(const QString &blobName) const
Finds all embedded IC elements with blobName.
int extractToFile(const QString &blobName, const QString &filePath)
Writes the blob to disk and converts all embedded ICs with blobName to file-backed.
void invalidate(const QString &filePath)
Invalidates a cached definition (e.g., after file change).
const QByteArray & cachedFileBytes(const QString &filePath)
Returns cached file bytes, reading from disk on first access. Returns empty on failure.
void definitionChanged(const QString &filePath)
Emitted when an IC definition file changes on disk and its cached definition is invalidated.
void blobRenamed(const QString &oldName, const QString &newName)
void removeBlob(const QString &name)
Removes the blob named name from the registry.
IC * createEmbeddedIC(const QString &blobName, const QByteArray &fileBytes, const QString &contextDir)
Creates a new embedded IC from file bytes, registers the blob, and pushes an undo command.
QList< GraphicElement * > findICsByFile(const QString &fileName) const
Finds all IC elements in the scene that reference fileName.
static void rollbackElements(const QList< GraphicElement * > &elements, const QByteArray &snapshot, Scene *scene)
Restores elements from a previously captured snapshot (used for atomic rollback).
void registerBlob(const QString &name, const QByteArray &data)
Stores blob data under name without invalidating the definition cache.
void watchFile(const QString &filePath)
Registers a file for watching. Called when an IC element is added to the scene.
bool hasBlob(const QString &name) const
Returns true if a blob named name is stored in the registry.
static QByteArray captureSnapshot(const QList< GraphicElement * > &targets)
Serializes targets into a self-contained .panda byte array (used for embedding).
Graphic element representing an Integrated Circuit (sub-circuit) box.
void loadFromBlob(const QByteArray &blob, const QString &contextDir)
Loads the IC from in-memory blob bytes (full .panda file format).
void setBlobName(const QString &name)
void loadFile(const QString &fileName, const QString &contextDir={})
Loads the IC circuit from fileName and rebuilds the logic mapping.
Undo command that registers/unregisters a blob in the IC registry.
Main circuit editing scene.
SerializationContext deserializationContext(QHash< quint64, Port * > &portMap, const QVersionNumber &version, SerializationPurpose purpose)
Simulation * simulation()
Returns the simulation engine associated with this scene.
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 QVersionNumber readPandaHeader(QDataStream &stream)
Reads and validates the .panda circuit file header; returns the stored version number.
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.
RAII guard that stops the simulation on construction and restarts it on destruction.
Undo command for embedded IC blob changes that may alter port counts.
static QList< ConnectionInfo > captureConnections(const QList< GraphicElement * > &targets)
Captures connection topology for all target elements before a blob operation.
bool hasMetadata(const QVersionNumber &v)
V4.5: File-level metadata map and embedded IC blob registry.