12#include <QStandardPaths>
42bool isReadOnlyFailure(QFileDevice::FileError error)
44 return error == QFileDevice::PermissionsError
45 || error == QFileDevice::OpenError
46 || error == QFileDevice::WriteError;
54 m_view.setCacheMode(QGraphicsView::CacheBackground);
55 m_view.setScene(&m_scene);
57 m_scene.setView(&m_view);
58 m_scene.setSceneRect(m_view.rect());
59 setLayout(
new QHBoxLayout());
60 layout()->addWidget(&m_view);
66 m_minimap->setObjectName(
"minimap");
77 m_autosaveDebounceTimer.setSingleShot(
true);
78 m_autosaveDebounceTimer.setInterval(500);
79 connect(&m_autosaveDebounceTimer, &QTimer::timeout,
this, &WorkSpace::autosave);
85 connect(m_scene.undoStack(), &QUndoStack::cleanChanged,
this, [
this](
bool ) {
86 emit fileChanged(m_fileInfo);
89 setAutosaveFileName();
91 m_scene.setLastId(m_lastId);
118 m_autosaveDebounceTimer.stop();
119 if (!m_autosaveFileName.isEmpty()) {
121 autosaves.removeAll(m_autosaveFileName);
123 QFile::remove(m_autosaveFileName);
124 m_autosaveFileName.clear();
130 QWidget::resizeEvent(event);
140 if (m_minimap && isVisible())
141 applyMinimapGeometry();
143 if (m_exerciseOverlay && m_exerciseOverlay->isVisible())
144 m_exerciseOverlay->repositionToParent();
149 QWidget::showEvent(event);
158 QTimer::singleShot(100,
this, [
this] { applyMinimapGeometry(); });
163 if (!m_minimap)
return;
164 m_minimap->setVisible(visible);
167void WorkSpace::applyMinimapGeometry()
172 const int margin = 12;
173 const QRect viewGeom = m_view.geometry();
175 if (!m_minimapPositioned) {
176 m_minimapPositioned =
true;
179 if (restored.isValid()) {
182 const int maxWidth = qMax(m_minimap->minimumWidth(), viewGeom.width() - 2 * margin);
183 const int maxHeight = qMax(m_minimap->minimumHeight(), viewGeom.height() - 2 * margin);
184 const int width = qBound(m_minimap->minimumWidth(), restored.width(), maxWidth);
185 const int height = qBound(m_minimap->minimumHeight(), restored.height(), maxHeight);
186 const int x = qBound(margin, restored.x(), viewGeom.width() - width - margin);
187 const int y = qBound(margin, restored.y(), viewGeom.height() - height - margin);
188 m_minimap->setGeometry(x, y, width, height);
194 const int x = viewGeom.right() - m_minimap->width() - margin;
195 const int y = viewGeom.bottom() - m_minimap->height() - margin;
196 m_minimap->move(qMax(x, margin), qMax(y, margin));
204 const QRect
current = m_minimap->geometry();
205 const int maxWidth = qMax(m_minimap->minimumWidth(), viewGeom.width() - 2 * margin);
206 const int maxHeight = qMax(m_minimap->minimumHeight(), viewGeom.height() - 2 * margin);
207 const int width = qBound(m_minimap->minimumWidth(),
current.width(), maxWidth);
208 const int height = qBound(m_minimap->minimumHeight(),
current.height(), maxHeight);
209 const int x = qBound(margin,
current.x(), viewGeom.width() - width - margin);
210 const int y = qBound(margin,
current.y(), viewGeom.height() - height - margin);
211 m_minimap->setGeometry(x, y, width, height);
214void WorkSpace::onMinimapGeometryChangeFinished(
const QRect &geometry)
236 return m_scene.simulation();
255 m_autosaveDebounceTimer.stop();
259 const QString message = tr(
"This file was saved with a newer file format (version %1).\n"
260 "Your wiRedPanda version (%2) supports file format %3.\n\n"
261 "Please update wiRedPanda to save changes to this file.")
263 QMessageBox::warning(
this, tr(
"Cannot save."), message);
269 if (!m_parentWorkspace) {
270 qCWarning(zero) <<
"Inline IC tab: parent workspace was closed. Save is a no-op.";
275 const QString contextDir = m_scene.contextDir();
278 for (
auto *elm : m_scene.elements()) {
279 if (elm->elementType() == ElementType::IC && !elm->isEmbedded()) {
280 auto *ic =
static_cast<IC *
>(elm);
281 const QString icFile = ic->
file();
282 const QString baseName = QFileInfo(icFile).baseName();
283 if (!m_scene.icRegistry()->hasBlob(baseName)) {
284 QFileInfo fi(QDir(contextDir), icFile);
285 QFile f(fi.absoluteFilePath());
292 if (!fi.exists() || !f.open(QIODevice::ReadOnly)) {
293 throw PANDACEPTION(
"Cannot save: sub-circuit \"%1\" could not be read to embed it.", icFile);
295 m_scene.icRegistry()->registerBlob(baseName, f.readAll());
303 ic->setBlobName(baseName);
308 QMap<QString, QVariant> metadata;
312 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
313 payloadStream.setVersion(QDataStream::Qt_5_12);
314 payloadStream << metadata;
318 QDataStream stream(&blob, QIODevice::WriteOnly);
322 m_scene.undoStack()->setClean();
327 Q_ASSERT_X(!fileName.isEmpty() && fileName.endsWith(
".panda"),
328 "WorkSpace::save",
"caller must resolve a non-empty, .panda-suffixed path first");
329 const QString &fileName_ = fileName;
331 qCDebug(zero) <<
"FileName: " << fileName_;
339 const QString oldContextDir = m_scene.contextDir();
340 const QString newContextDir = QFileInfo(fileName_).absolutePath();
341 for (
auto *elm : m_scene.elements()) {
342 for (
const QString &file : elm->externalFiles()) {
343 if (file.endsWith(
".panda")) {
348 const QFileInfo srcInfo(file);
357 if (!m_dolphinFileName.isEmpty()) {
358 const QString resolved = QDir(oldContextDir).absoluteFilePath(m_dolphinFileName);
364 QSaveFile saveFile(fileName_);
366 if (!saveFile.open(QIODevice::WriteOnly)) {
374 throw PANDACEPTION(
"Error opening file: %1", saveFile.errorString());
383 m_scene.resizeScene();
385 QDataStream stream(&saveFile);
389 if (!saveFile.commit()) {
398 throw PANDACEPTION(
"Could not save file: %1", saveFile.errorString());
405 setCurrentFile(fileName_);
408 m_scene.undoStack()->setClean();
411 if (!m_autosaveFileName.isEmpty() && QFile::exists(m_autosaveFileName)) {
412 qCDebug(zero) <<
"Remove autosave from settings and delete it.";
414 autosaves.removeAll(m_autosaveFileName);
416 QFile::remove(m_autosaveFileName);
417 m_autosaveFileName.clear();
418 qCDebug(zero) <<
"All auto save file names after removing autosave: " << autosaves;
428 QMap<QString, QVariant> metadata;
430 if (!m_dolphinFileName.isEmpty()) {
431 metadata[
"dolphinFileName"] = m_dolphinFileName;
437 if (portMeta.inputCount > 0 || portMeta.outputCount > 0) {
438 metadata[
"inputCount"] = portMeta.inputCount;
439 metadata[
"outputCount"] = portMeta.outputCount;
440 metadata[
"inputLabels"] = portMeta.inputLabels;
441 metadata[
"outputLabels"] = portMeta.outputLabels;
447 QStringList fileBackedICs;
448 for (
auto *elm : m_scene.elements()) {
449 if (elm->elementType() == ElementType::IC && !elm->isEmbedded()) {
450 const QString icFile =
static_cast<IC *
>(elm)->file();
451 if (!icFile.isEmpty() && !fileBackedICs.contains(QFileInfo(icFile).fileName())) {
452 fileBackedICs.append(QFileInfo(icFile).fileName());
456 if (!fileBackedICs.isEmpty()) {
457 metadata[
"fileBackedICs"] = fileBackedICs;
464 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
465 payloadStream.setVersion(QDataStream::Qt_5_12);
467 payloadStream << metadata;
477 QFile file(fileName);
479 if (!file.exists()) {
480 qCDebug(zero) <<
"This file does not exist: " << fileName;
481 throw PANDACEPTION(
"This file does not exist: %1", fileName);
484 setCurrentFile(fileName);
486 qCDebug(zero) <<
"File exists.";
488 if (!file.open(QIODevice::ReadOnly)) {
489 qCDebug(zero) <<
"Could not open file: " << file.errorString();
490 throw PANDACEPTION(
"Could not open file: %1", file.errorString());
493 QDataStream stream(&file);
495 m_loadedVersion = version;
498 if (needsMigration) {
499 createVersionedBackup(fileName, version);
502 load(stream, version, QFileInfo(fileName).absolutePath());
505 if (needsMigration) {
509 QString migratedFileName = fileName;
510 if (!migratedFileName.endsWith(
".panda")) {
511 migratedFileName.append(
".panda");
518 qCWarning(zero) <<
"Could not migrate" << migratedFileName <<
"to the current format: target is read-only.";
525void WorkSpace::load(QDataStream &stream,
const QVersionNumber &version,
const QString &contextDir)
527 qCDebug(zero) <<
"Loading file.";
531 qCDebug(zero) <<
"Stopped simulation.";
532 qCDebug(zero) <<
"Version: " << version;
537 const QString fileVersion = version.toString();
538 const QString message = tr(
"This file was saved with a newer file format (version %1).\n"
539 "Your version supports file format %2.\n\n"
540 "The file will be opened but saving is blocked.\n"
541 "Please update wiRedPanda to edit and save this file.")
542 .arg(fileVersion, fmtVersion);
543 QMessageBox::warning(
this, tr(
"Newer version file."), message);
545 const QString backupFileName = m_fileInfo.completeBaseName() +
".v" + version.toString() +
"." + m_fileInfo.suffix();
546 const QString message = tr(
"This file is in an older format (version %1) and will be automatically upgraded to the current format (version %2).\n"
547 "A backup of the original file has been created with name: %3")
549 QMessageBox::information(
this, tr(
"File upgraded."), message);
558 QDataStream payloadStream(&payload, QIODevice::ReadOnly);
559 payloadStream.setVersion(QDataStream::Qt_5_12);
563 QMap<QString, QVariant> metadata;
566 m_dolphinFileName = metadata.value(
"dolphinFileName").toString();
574 qCDebug(zero) <<
"Dolphin name: " << m_dolphinFileName;
579 for (
auto it = blobRegistry.cbegin(); it != blobRegistry.cend(); ++it) {
580 m_scene.icRegistry()->setBlob(it.key(), it.value());
583 QHash<quint64, Port *> portMap;
584 if (!contextDir.isEmpty()) {
585 m_scene.setContextDir(contextDir);
588 context.contextDir = contextDir;
590 qCDebug(zero) <<
"Finished loading items.";
592 for (
auto *item : items) {
593 m_scene.addItem(item);
597 if (
auto *ge = qgraphicsitem_cast<GraphicElement *>(item)) {
598 m_lastId = (std::max)(m_lastId, ge->id());
602 m_scene.setLastId(m_lastId);
604 m_scene.setSceneRect(m_scene.itemsBoundingRect());
606 qCDebug(zero) <<
"Finished loading file.";
611 m_dolphinFileName = fileName;
616 return m_dolphinFileName;
619void WorkSpace::setAutosaveFileName()
623 QDir autosavePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/autosaves");
624 if (!autosavePath.exists()) {
625 autosavePath.mkpath(autosavePath.absolutePath());
627 m_autosaveFileName.clear();
637 m_lastId = newLastId;
640void WorkSpace::autosave()
650 qCDebug(two) <<
"Starting autosave.";
652 qCDebug(three) <<
"All auto save file names before autosaving: " << autosaves;
654 auto *undoStack = m_scene.undoStack();
655 qCDebug(zero) <<
"Undo stack element: " << undoStack->index() <<
" of " << undoStack->count();
659 if (undoStack->isClean()) {
660 qCDebug(three) <<
"Undo stack is clean.";
661 if (!m_autosaveFileName.isEmpty()) {
662 autosaves.removeAll(m_autosaveFileName);
664 QFile::remove(m_autosaveFileName);
665 m_autosaveFileName.clear();
671 qCDebug(three) <<
"Undo is !clean. Must set autosave file.";
684 const QString globalAutosaves =
685 QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/autosaves";
686 if (m_fileInfo.fileName().isEmpty()) {
687 path.setPath(globalAutosaves);
689 const QString projectDir = m_fileInfo.absolutePath();
690 const QFileInfo dirInfo(projectDir);
692 path.setPath(projectDir);
694 path.setPath(globalAutosaves);
697 if (!path.exists()) {
698 path.mkpath(path.absolutePath());
700 qCDebug(three) <<
"Autosavepath: " << path.absolutePath();
705 const QString prefix = m_fileInfo.fileName().isEmpty() ? QStringLiteral(
".") :
"." + m_fileInfo.baseName() +
".";
706 if (!m_autosaveFileName.isEmpty() && QFileInfo(m_autosaveFileName).absolutePath() != path.absolutePath()) {
707 autosaves.removeAll(m_autosaveFileName);
708 QFile::remove(m_autosaveFileName);
709 m_autosaveFileName.clear();
711 if (m_autosaveFileName.isEmpty()) {
712 const QString tag = QUuid::createUuid().toString(QUuid::Id128);
713 m_autosaveFileName = path.absoluteFilePath(prefix + tag +
".panda");
717 if (autosaves.contains(m_autosaveFileName)) {
718 autosaves.removeAll(m_autosaveFileName);
719 Settings::setAutosaveFiles(autosaves);
722 qCDebug(three) <<
"Writing to autosave file.";
727 QSaveFile autosaveFile(m_autosaveFileName);
728 if (!autosaveFile.open(QIODevice::WriteOnly)) {
729 throw PANDACEPTION(
"Error opening autosave file: %1", autosaveFile.errorString());
732 QDataStream stream(&autosaveFile);
736 if (!autosaveFile.commit()) {
740 throw PANDACEPTION(
"Could not commit autosave file: %1", autosaveFile.errorString());
743 autosaves.append(m_autosaveFileName);
746 qCDebug(three) <<
"All auto save file names after adding autosave: " << autosaves;
757 m_autosaveFileName = m_fileInfo.filePath();
762 if (m_autosaveDebounceTimer.isActive()) {
763 m_autosaveDebounceTimer.stop();
768void WorkSpace::createVersionedBackup(
const QString &fileName,
const QVersionNumber &version)
777 if (!parentContextDir.isEmpty()) {
778 m_scene.setContextDir(parentContextDir);
782 QByteArray blobData(blob);
783 QDataStream stream(&blobData, QIODevice::ReadOnly);
787 for (
auto it = blobRegistry.cbegin(); it != blobRegistry.cend(); ++it) {
788 m_scene.icRegistry()->setBlob(it.key(), it.value());
791 QHash<quint64, Port *> portMap;
793 context.contextDir = parentContextDir;
794 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
795 elementsStream.setVersion(QDataStream::Qt_5_12);
798 for (
auto *item : items) {
799 m_scene.addItem(item);
801 if (
auto *ge = qgraphicsitem_cast<GraphicElement *>(item)) {
802 m_lastId = (std::max)(m_lastId, ge->id());
806 m_scene.setLastId(m_lastId);
807 m_scene.setSceneRect(m_scene.itemsBoundingRect());
811 m_parentWorkspace = parent;
812 m_parentICElementId = icElementId;
818 m_inlineBlobName = elm->blobName();
826 auto *item = m_scene.itemById(icElementId);
832 if (!elm || !elm->isEmbedded()) {
836 const QString targetBlobName = elm->
blobName();
837 const auto targets = m_scene.icRegistry()->findICsByBlobName(targetBlobName);
838 if (targets.isEmpty()) {
847 QByteArray oldBlob = m_scene.icRegistry()->blob(targetBlobName);
852 m_scene.icRegistry()->setBlob(targetBlobName, blob);
853 QList<GraphicElement *> updated;
855 for (
auto *target : targets) {
856 auto *ic =
static_cast<IC *
>(target);
858 updated.append(target);
862 m_scene.icRegistry()->setBlob(targetBlobName, oldBlob);
867 cmd->setOldBlob(oldBlob);
868 m_scene.undoStack()->push(cmd);
873 QList<QGraphicsItem *> toDelete;
875 for (
auto *item : m_scene.items()) {
879 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
880 if (elm && elm->isEmbedded() && elm->blobName() == blobName) {
881 toDelete.append(item);
883 for (
int i = 0; i < elm->inputSize(); ++i) {
884 for (
auto *conn : elm->inputPort(i)->connections()) {
885 if (!toDelete.contains(conn)) {
886 toDelete.append(conn);
890 for (
int i = 0; i < elm->outputSize(); ++i) {
891 for (
auto *conn : elm->outputPort(i)->connections()) {
892 if (!toDelete.contains(conn)) {
893 toDelete.append(conn);
900 const bool hasBlob = m_scene.icRegistry()->hasBlob(blobName);
901 if (toDelete.isEmpty() && !hasBlob) {
908 m_scene.undoStack()->beginMacro(tr(
"Remove embedded IC \"%1\"").arg(blobName));
909 if (!toDelete.isEmpty()) {
915 m_scene.undoStack()->endMacro();
918void WorkSpace::setCurrentFile(
const QString &filePath)
920 m_fileInfo = QFileInfo(filePath);
926 m_exerciseOverlay = overlay;
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)
Connection: a wire that connects an output port to an input port in the circuit scene.
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.
Per-platform resolution of install-relative content directories.
Port classes: Port (base), InputPort, and OutputPort.
Lightweight Sentry helpers gated behind HAVE_SENTRY.
void sentryBreadcrumb(const char *category, const QString &message)
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
Typed wrappers around QSettings for all application preferences.
RAII guard that temporarily stops the simulation while in scope.
Named version predicates for file-format compatibility checks.
File-format version constants and application version accessor.
WorkSpace widget: the complete circuit editing environment for one tab.
static bool migrationEnabled
static bool interactiveMode
Undo command that removes a list of items from the scene.
Semi-transparent overlay displayed at the bottom of the canvas during a circuit exercise.
Abstract base class for all graphical circuit elements in wiRedPanda.
virtual const QString & blobName() const
Returns the blob name for embedded ICs, empty string otherwise. Base returns empty.
Extended QGraphicsView with enhanced navigation capabilities.
void zoomChanged()
Emitted whenever the zoom level changes.
static void rollbackElements(const QList< GraphicElement * > &elements, const QByteArray &snapshot, Scene *scene)
Restores elements from a previously captured snapshot (used for atomic rollback).
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).
static PortMetadata buildPortMetadata(const QVector< GraphicElement * > &elements)
Scans elements for Input/Output groups, sorts by Y/X position, and builds labels.
const QString & file() const
static bool isCandidate(const QString &category, const QString &directory)
Undo command that removes/restores a blob in the IC registry.
Main circuit editing scene.
void resizeScene()
Tightens the scene rect to item bounds while preserving the viewport position.
ItemWithId * itemById(int id) const
Returns the item registered under id, or nullptr if not found.
void circuitHasChanged()
Emitted whenever the circuit changes (element added/removed/moved).
void setContextDir(const QString &dir)
Sets the directory of the .panda file associated with this scene.
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 copyPandaFile(const QFileInfo &srcPath, const QFileInfo &destPath, QSet< QString > *visited=nullptr, int depth=0)
Copies a .panda file and its file-backed IC dependencies to destPath.
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 QString loadDolphinFileName(QDataStream &stream, const QVersionNumber &version)
Returns the BeWavedDolphin waveform file name stored in stream at version.
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 QByteArray readPayload(QDataStream &stream, const QVersionNumber &version)
Reads the remainder of stream's device, decompressing it first if version indicates the compressed-pa...
static QMap< QString, QVariant > readBoundedMetadata(QDataStream &stream)
Reads the file-level metadata QMap<QString,QVariant> from stream without calling QList::reserve() wit...
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.
static QRectF loadRect(QDataStream &stream, const QVersionNumber &version)
Returns the canvas viewport rectangle from the last saved session.
static void setMinimapGeometry(const QRect &geometry)
static QStringList autosaveFiles()
static void setAutosaveFiles(const QStringList &files)
static QRect minimapGeometry()
RAII guard that stops the simulation on construction and restarts it on destruction.
Manages the digital circuit simulation loop.
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.
void setLastId(int newLastId)
Forces the element-ID counter to newLastId.
void setDolphinFileName(const QString &fileName)
Sets the associated BeWavedDolphin waveform file path to fileName.
void icBlobSaved(int icElementId, const QByteArray &blob)
Emitted when an inline IC tab saves its blob (propagated to parent).
bool isFromNewerVersion() const
Returns true if the loaded file was saved by a newer version of wiRedPanda.
void setAutosaveFile()
Creates or replaces the autosave temporary file.
void fileChanged(const QFileInfo &fileInfo)
Emitted whenever the file info of this workspace changes (load/save).
Scene * scene()
Returns the Scene embedded in this workspace.
void onChildICBlobSaved(int icElementId, const QByteArray &blob)
Receives a saved blob from a child inline tab.
WorkSpace(QWidget *parent=nullptr)
Constructs the workspace with optional parent widget.
void setMinimapVisible(bool visible)
void removeEmbeddedIC(const QString &blobName)
Removes all IC instances with the given blob name.
QString dolphinFileName() const
Returns the path of the associated BeWavedDolphin waveform file.
int lastId() const
Returns the highest element ID assigned in this workspace.
~WorkSpace() override
Flushes any pending debounced autosave on destruction.
void flushPendingAutosave()
Forces any pending debounced autosave to run synchronously.
void setExerciseOverlay(ExerciseOverlay *overlay)
void loadFromBlob(const QByteArray &blob, WorkSpace *parent, int icElementId, const QString &parentContextDir)
Loads a blob for editing in an inline tab.
void load(const QString &fileName)
Loads a circuit from the file at fileName.
void resizeEvent(QResizeEvent *event) override
Simulation * simulation()
Returns the embedded Simulation.
GraphicsView * view()
Returns the GraphicsView embedded in this workspace.
void showEvent(QShowEvent *event) override
SaveOutcome save(const QString &fileName)
Saves the current circuit to fileName.
QFileInfo fileInfo() const
Returns the file info for the currently open circuit file.
const QVersionNumber current
void copyToDir(const QString &srcPath, const QString &destDir)
bool hasMetadata(const QVersionNumber &v)
V4.5: File-level metadata map and embedded IC blob registry.
bool hasUnifiedMetadata(const QVersionNumber &v)
V4.6: Dolphin filename moved into metadata map; scene rect no longer stored.