6#include <QCoreApplication>
30 connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged,
31 this, &ICRegistry::onFileChanged, Qt::QueuedConnection);
36 Q_ASSERT(QCoreApplication::instance()->thread() == QThread::currentThread());
38 if (!m_fileCache.contains(filePath)) {
40 if (file.open(QIODevice::ReadOnly)) {
41 m_fileCache[filePath] = file.readAll();
47 qCWarning(zero) <<
"ICRegistry: cannot open IC file:" << filePath;
48 static const QByteArray empty;
52 return m_fileCache[filePath];
57 m_fileCache.remove(filePath);
62 if (!m_fileWatcher.files().contains(filePath)) {
63 m_fileWatcher.addPath(filePath);
69 const QFileInfo target(fileName);
70 QList<GraphicElement *> result;
71 for (
auto *elm : m_scene->elements()) {
72 if (elm->elementType() == ElementType::IC) {
73 auto *ic =
static_cast<IC *
>(elm);
74 if (QFileInfo(ic->file()) == target) {
82void ICRegistry::onFileChanged(
const QString &filePath)
94 qCDebug(zero) <<
"IC file changed:" << filePath;
100 if (!m_fileWatcher.files().contains(filePath) && QFileInfo::exists(filePath)) {
101 m_fileWatcher.addPath(filePath);
106 if (targets.isEmpty()) {
119 reloadTargetsAtomically(targets, oldData, [&](IC *ic) { ic->
loadFile(filePath); });
121 m_scene->setCircuitUpdateRequired();
125 m_scene->setCircuitUpdateRequired();
127 auto *cmd =
new UpdateBlobCommand(targets, oldData, connections, m_scene);
128 m_scene->undoStack()->push(cmd);
138 return m_blobs.contains(name);
143 return m_blobs.value(name);
148 m_blobs[name] = data;
153 QSet<QString> visited;
154 QMap<QString, QByteArray> workingBlobs;
155 workingBlobs[name] = data;
156 makeBlobSelfContained(name, visited, workingBlobs);
157 m_blobs[name] = workingBlobs[name];
162 m_blobs.remove(name);
171 if (!m_blobs.contains(oldName) || oldName == newName || m_blobs.contains(newName)) {
175 m_blobs[newName] = m_blobs.take(oldName);
178 for (
auto *elm : m_scene->elements()) {
179 if (elm->isEmbedded() && elm->blobName() == oldName) {
180 auto *ic =
static_cast<IC *
>(elm);
187 for (
auto it = m_blobs.begin(); it != m_blobs.end(); ++it) {
188 renameBlobReference(it.value(), oldName, newName);
201 QList<GraphicElement *> result;
202 for (
auto *elm : m_scene->elements()) {
203 if (elm->isEmbedded() && elm->blobName() == blobName) {
217 if (ic->
label().isEmpty()) {
228 for (
int i = 2; ; ++i) {
229 const QString candidate = baseName +
"_" + QString::number(i);
241 m_scene->undoStack()->beginMacro(QCoreApplication::tr(
"Add embedded IC"));
245 m_scene->undoStack()->endMacro();
251 const QString &blobName)
254 if (targets.isEmpty()) {
264 reloadTargetsAtomically(targets, oldData, [&](
IC *ic) {
269 ic->
loadFromBlob(m_blobs[blobName], m_scene->contextDir());
282 cmd->setOldBlob(QByteArray());
283 m_scene->undoStack()->push(cmd);
284 return static_cast<int>(targets.size());
290 QSaveFile saveFile(filePath);
291 if (!saveFile.open(QIODevice::WriteOnly)) {
292 throw PANDACEPTION(
"Could not open file: %1", saveFile.errorString());
294 saveFile.write(
blob(blobName));
295 if (!saveFile.commit()) {
299 throw PANDACEPTION(
"Could not save file: %1", saveFile.errorString());
304 if (targets.isEmpty()) {
310 const QByteArray oldBlob =
blob(blobName);
312 const QString fileDir = QFileInfo(filePath).absolutePath();
313 reloadTargetsAtomically(targets, oldData, [&](
IC *ic) { ic->
loadFile(filePath, fileDir); });
318 cmd->setOldBlob(oldBlob);
319 cmd->setBlobName(blobName);
320 m_scene->undoStack()->push(cmd);
321 return static_cast<int>(targets.size());
327 QByteArray data(snapshot);
328 QDataStream stream(&data, QIODevice::ReadOnly);
330 QHash<quint64, Port *> portMap;
332 for (
auto *elm : elements) {
333 elm->load(stream, ctx);
337void ICRegistry::reloadTargetsAtomically(
const QList<GraphicElement *> &targets,
const QByteArray &oldData,
338 const std::function<
void(
IC *)> &mutate)
345 QList<GraphicElement *> updated;
348 for (
auto *elm : targets) {
349 mutate(
static_cast<IC *
>(elm));
358void ICRegistry::makeBlobSelfContained(
const QString &name, QSet<QString> &visited,
359 QMap<QString, QByteArray> &blobs,
int depth)
361 if (depth >= kMaxBlobNestingDepth) {
362 throw PANDACEPTION(
"Embedded IC dependency chain exceeds the maximum nesting depth (%1) while resolving '%2'",
363 QString::number(kMaxBlobNestingDepth), name);
366 if (visited.contains(name)) {
367 qCWarning(zero) <<
"Circular blob reference detected:" << name <<
"— skipping.";
370 visited.insert(name);
372 QByteArray blobData(blobs[name]);
373 QDataStream readStream(&blobData, QIODevice::ReadOnly);
380 auto metadata = preamble.metadata;
382 bool modified =
false;
385 for (
auto it = embeddedICs.begin(); it != embeddedICs.end(); ++it) {
386 const QString &depName = it.key();
387 blobs[depName] = it.value();
388 makeBlobSelfContained(depName, visited, blobs, depth + 1);
389 it.value() = blobs[depName];
393 if (metadata.contains(
"fileBackedICs")) {
394 const QStringList files = metadata.value(
"fileBackedICs").toStringList();
395 const QString contextDir = m_scene->contextDir();
397 for (
const QString &fileName : files) {
398 const QString baseName = QFileInfo(fileName).baseName();
399 if (embeddedICs.contains(baseName)) {
403 QFileInfo fi(QDir(contextDir), fileName);
405 qCWarning(zero) <<
"makeBlobSelfContained: dependency" << fileName <<
"not found for blob" << name <<
"— skipping.";
409 QFile file(fi.absoluteFilePath());
410 if (!file.open(QIODevice::ReadOnly)) {
411 qCWarning(zero) <<
"makeBlobSelfContained: cannot open dependency" << fi.absoluteFilePath() <<
"for blob" << name <<
"— blob will be incomplete.";
414 QByteArray fileBytes = file.readAll();
418 blobs[baseName] = fileBytes;
419 makeBlobSelfContained(baseName, visited, blobs, depth + 1);
420 embeddedICs[baseName] = blobs[baseName];
423 metadata.remove(
"fileBackedICs");
436 const QByteArray elements = preamble.remainingPayload;
445 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
446 payloadStream.setVersion(QDataStream::Qt_5_12);
447 payloadStream << metadata;
448 payloadStream.writeRawData(elements.constData(),
static_cast<int>(elements.size()));
451 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
455 blobs[name] = newBlob;
458void ICRegistry::renameBlobReference(QByteArray &blobData,
const QString &oldName,
const QString &newName)
460 QDataStream readStream(&blobData, QIODevice::ReadOnly);
468 if (!embeddedICs.contains(oldName)) {
473 embeddedICs[newName] = embeddedICs.take(oldName);
480 const QByteArray elements = preamble.remainingPayload;
481 auto metadata = preamble.metadata;
489 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
490 payloadStream.setVersion(QDataStream::Qt_5_12);
491 payloadStream << metadata;
492 payloadStream.writeRawData(elements.constData(),
static_cast<int>(elements.size()));
495 QDataStream writeStream(&newBlob, QIODevice::WriteOnly);
505 QDataStream stream(&data, QIODevice::WriteOnly);
507 for (
auto *elm : targets) {
Custom QApplication subclass with exception handling and main-window access.
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.
static void guardedSlot(const QObject *context, Body &&body) noexcept
Wraps a slot body in try/catch and reports any exception synchronously, inside the slot's own stack f...
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.