12#include <QStandardPaths>
41bool isReadOnlyFailure(QFileDevice::FileError error)
43 return error == QFileDevice::PermissionsError
44 || error == QFileDevice::OpenError
45 || error == QFileDevice::WriteError;
53 m_view.setCacheMode(QGraphicsView::CacheBackground);
54 m_view.setScene(&m_scene);
56 m_scene.setView(&m_view);
57 m_scene.setSceneRect(m_view.rect());
58 setLayout(
new QHBoxLayout());
59 layout()->addWidget(&m_view);
65 m_minimap->setObjectName(
"minimap");
76 m_autosaveDebounceTimer.setSingleShot(
true);
77 m_autosaveDebounceTimer.setInterval(500);
78 connect(&m_autosaveDebounceTimer, &QTimer::timeout,
this, &WorkSpace::autosave);
84 connect(m_scene.undoStack(), &QUndoStack::cleanChanged,
this, [
this](
bool ) {
85 emit fileChanged(m_fileInfo);
88 setAutosaveFileName();
90 m_scene.setLastId(m_lastId);
117 m_autosaveDebounceTimer.stop();
118 if (!m_autosaveFileName.isEmpty()) {
120 autosaves.removeAll(m_autosaveFileName);
122 QFile::remove(m_autosaveFileName);
123 m_autosaveFileName.clear();
129 QWidget::resizeEvent(event);
139 if (m_minimap && isVisible())
140 applyMinimapGeometry();
142 if (m_exerciseOverlay && m_exerciseOverlay->isVisible())
143 m_exerciseOverlay->repositionToParent();
148 QWidget::showEvent(event);
157 QTimer::singleShot(100,
this, [
this] { applyMinimapGeometry(); });
162 if (!m_minimap)
return;
163 m_minimap->setVisible(visible);
166void WorkSpace::applyMinimapGeometry()
171 const int margin = 12;
172 const QRect viewGeom = m_view.geometry();
174 if (!m_minimapPositioned) {
175 m_minimapPositioned =
true;
178 if (restored.isValid()) {
181 const int maxWidth = qMax(m_minimap->minimumWidth(), viewGeom.width() - 2 * margin);
182 const int maxHeight = qMax(m_minimap->minimumHeight(), viewGeom.height() - 2 * margin);
183 const int width = qBound(m_minimap->minimumWidth(), restored.width(), maxWidth);
184 const int height = qBound(m_minimap->minimumHeight(), restored.height(), maxHeight);
185 const int x = qBound(margin, restored.x(), viewGeom.width() - width - margin);
186 const int y = qBound(margin, restored.y(), viewGeom.height() - height - margin);
187 m_minimap->setGeometry(x, y, width, height);
193 const int x = viewGeom.right() - m_minimap->width() - margin;
194 const int y = viewGeom.bottom() - m_minimap->height() - margin;
195 m_minimap->move(qMax(x, margin), qMax(y, margin));
203 const QRect
current = m_minimap->geometry();
204 const int maxWidth = qMax(m_minimap->minimumWidth(), viewGeom.width() - 2 * margin);
205 const int maxHeight = qMax(m_minimap->minimumHeight(), viewGeom.height() - 2 * margin);
206 const int width = qBound(m_minimap->minimumWidth(),
current.width(), maxWidth);
207 const int height = qBound(m_minimap->minimumHeight(),
current.height(), maxHeight);
208 const int x = qBound(margin,
current.x(), viewGeom.width() - width - margin);
209 const int y = qBound(margin,
current.y(), viewGeom.height() - height - margin);
210 m_minimap->setGeometry(x, y, width, height);
213void WorkSpace::onMinimapGeometryChangeFinished(
const QRect &geometry)
235 return m_scene.simulation();
254 m_autosaveDebounceTimer.stop();
258 const QString message = tr(
"This file was saved with a newer file format (version %1).\n"
259 "Your wiRedPanda version (%2) supports file format %3.\n\n"
260 "Please update wiRedPanda to save changes to this file.")
262 QMessageBox::warning(
this, tr(
"Cannot save."), message);
268 if (!m_parentWorkspace) {
269 qCWarning(zero) <<
"Inline IC tab: parent workspace was closed. Save is a no-op.";
274 const QString contextDir = m_scene.contextDir();
277 for (
auto *elm : m_scene.elements()) {
278 if (elm->elementType() == ElementType::IC && !elm->isEmbedded()) {
279 auto *ic =
static_cast<IC *
>(elm);
280 const QString icFile = ic->
file();
281 const QString baseName = QFileInfo(icFile).baseName();
282 if (!m_scene.icRegistry()->hasBlob(baseName)) {
283 QFileInfo fi(QDir(contextDir), icFile);
284 QFile f(fi.absoluteFilePath());
291 if (!fi.exists() || !f.open(QIODevice::ReadOnly)) {
292 throw PANDACEPTION(
"Cannot save: sub-circuit \"%1\" could not be read to embed it.", icFile);
294 m_scene.icRegistry()->registerBlob(baseName, f.readAll());
302 ic->setBlobName(baseName);
307 QMap<QString, QVariant> metadata;
311 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
312 payloadStream.setVersion(QDataStream::Qt_5_12);
313 payloadStream << metadata;
317 QDataStream stream(&blob, QIODevice::WriteOnly);
321 m_scene.undoStack()->setClean();
326 Q_ASSERT_X(!fileName.isEmpty() && fileName.endsWith(
".panda"),
327 "WorkSpace::save",
"caller must resolve a non-empty, .panda-suffixed path first");
328 const QString &fileName_ = fileName;
330 qCDebug(zero) <<
"FileName: " << fileName_;
338 const QString oldContextDir = m_scene.contextDir();
339 const QString newContextDir = QFileInfo(fileName_).absolutePath();
340 for (
auto *elm : m_scene.elements()) {
341 for (
const QString &file : elm->externalFiles()) {
342 if (file.endsWith(
".panda")) {
347 const QFileInfo srcInfo(file);
356 if (!m_dolphinFileName.isEmpty()) {
357 const QString resolved = QDir(oldContextDir).absoluteFilePath(m_dolphinFileName);
363 QSaveFile saveFile(fileName_);
365 if (!saveFile.open(QIODevice::WriteOnly)) {
373 throw PANDACEPTION(
"Error opening file: %1", saveFile.errorString());
382 m_scene.resizeScene();
384 QDataStream stream(&saveFile);
388 if (!saveFile.commit()) {
392 throw PANDACEPTION(
"Could not save file: %1", saveFile.errorString());
399 setCurrentFile(fileName_);
402 m_scene.undoStack()->setClean();
405 if (!m_autosaveFileName.isEmpty() && QFile::exists(m_autosaveFileName)) {
406 qCDebug(zero) <<
"Remove autosave from settings and delete it.";
408 autosaves.removeAll(m_autosaveFileName);
410 QFile::remove(m_autosaveFileName);
411 m_autosaveFileName.clear();
412 qCDebug(zero) <<
"All auto save file names after removing autosave: " << autosaves;
422 QMap<QString, QVariant> metadata;
424 if (!m_dolphinFileName.isEmpty()) {
425 metadata[
"dolphinFileName"] = m_dolphinFileName;
431 if (portMeta.inputCount > 0 || portMeta.outputCount > 0) {
432 metadata[
"inputCount"] = portMeta.inputCount;
433 metadata[
"outputCount"] = portMeta.outputCount;
434 metadata[
"inputLabels"] = portMeta.inputLabels;
435 metadata[
"outputLabels"] = portMeta.outputLabels;
441 QStringList fileBackedICs;
442 for (
auto *elm : m_scene.elements()) {
443 if (elm->elementType() == ElementType::IC && !elm->isEmbedded()) {
444 const QString icFile =
static_cast<IC *
>(elm)->file();
445 if (!icFile.isEmpty() && !fileBackedICs.contains(QFileInfo(icFile).fileName())) {
446 fileBackedICs.append(QFileInfo(icFile).fileName());
450 if (!fileBackedICs.isEmpty()) {
451 metadata[
"fileBackedICs"] = fileBackedICs;
458 QDataStream payloadStream(&payload, QIODevice::WriteOnly);
459 payloadStream.setVersion(QDataStream::Qt_5_12);
461 payloadStream << metadata;
471 QFile file(fileName);
473 if (!file.exists()) {
474 qCDebug(zero) <<
"This file does not exist: " << fileName;
475 throw PANDACEPTION(
"This file does not exist: %1", fileName);
478 setCurrentFile(fileName);
480 qCDebug(zero) <<
"File exists.";
482 if (!file.open(QIODevice::ReadOnly)) {
483 qCDebug(zero) <<
"Could not open file: " << file.errorString();
484 throw PANDACEPTION(
"Could not open file: %1", file.errorString());
487 QDataStream stream(&file);
489 m_loadedVersion = version;
492 if (needsMigration) {
493 createVersionedBackup(fileName, version);
496 load(stream, version, QFileInfo(fileName).absolutePath());
499 if (needsMigration) {
503 QString migratedFileName = fileName;
504 if (!migratedFileName.endsWith(
".panda")) {
505 migratedFileName.append(
".panda");
512 qCWarning(zero) <<
"Could not migrate" << migratedFileName <<
"to the current format: target is read-only.";
519void WorkSpace::load(QDataStream &stream,
const QVersionNumber &version,
const QString &contextDir)
521 qCDebug(zero) <<
"Loading file.";
525 qCDebug(zero) <<
"Stopped simulation.";
526 qCDebug(zero) <<
"Version: " << version;
531 const QString fileVersion = version.toString();
532 const QString message = tr(
"This file was saved with a newer file format (version %1).\n"
533 "Your version supports file format %2.\n\n"
534 "The file will be opened but saving is blocked.\n"
535 "Please update wiRedPanda to edit and save this file.")
536 .arg(fileVersion, fmtVersion);
537 QMessageBox::warning(
this, tr(
"Newer version file."), message);
539 const QString backupFileName = m_fileInfo.completeBaseName() +
".v" + version.toString() +
"." + m_fileInfo.suffix();
540 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"
541 "A backup of the original file has been created with name: %3")
543 QMessageBox::information(
this, tr(
"File upgraded."), message);
552 QDataStream payloadStream(&payload, QIODevice::ReadOnly);
553 payloadStream.setVersion(QDataStream::Qt_5_12);
557 QMap<QString, QVariant> metadata;
560 m_dolphinFileName = metadata.value(
"dolphinFileName").toString();
568 qCDebug(zero) <<
"Dolphin name: " << m_dolphinFileName;
573 for (
auto it = blobRegistry.cbegin(); it != blobRegistry.cend(); ++it) {
574 m_scene.icRegistry()->setBlob(it.key(), it.value());
577 QHash<quint64, Port *> portMap;
578 if (!contextDir.isEmpty()) {
579 m_scene.setContextDir(contextDir);
582 context.contextDir = contextDir;
584 qCDebug(zero) <<
"Finished loading items.";
586 for (
auto *item : items) {
587 m_scene.addItem(item);
591 if (
auto *ge = qgraphicsitem_cast<GraphicElement *>(item)) {
592 m_lastId = (std::max)(m_lastId, ge->id());
596 m_scene.setLastId(m_lastId);
598 m_scene.setSceneRect(m_scene.itemsBoundingRect());
600 qCDebug(zero) <<
"Finished loading file.";
605 m_dolphinFileName = fileName;
610 return m_dolphinFileName;
613void WorkSpace::setAutosaveFileName()
617 QDir autosavePath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/autosaves");
618 if (!autosavePath.exists()) {
619 autosavePath.mkpath(autosavePath.absolutePath());
621 m_autosaveFileName.clear();
631 m_lastId = newLastId;
634void WorkSpace::autosave()
644 qCDebug(two) <<
"Starting autosave.";
646 qCDebug(three) <<
"All auto save file names before autosaving: " << autosaves;
648 auto *undoStack = m_scene.undoStack();
649 qCDebug(zero) <<
"Undo stack element: " << undoStack->index() <<
" of " << undoStack->count();
653 if (undoStack->isClean()) {
654 qCDebug(three) <<
"Undo stack is clean.";
655 if (!m_autosaveFileName.isEmpty()) {
656 autosaves.removeAll(m_autosaveFileName);
658 QFile::remove(m_autosaveFileName);
659 m_autosaveFileName.clear();
665 qCDebug(three) <<
"Undo is !clean. Must set autosave file.";
671 if (m_fileInfo.fileName().isEmpty()) {
672 path.setPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/autosaves");
674 const QFileInfo dirInfo(m_fileInfo.absolutePath());
675 if (dirInfo.isWritable()) {
676 path.setPath(m_fileInfo.absolutePath());
678 path.setPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/autosaves");
681 if (!path.exists()) {
682 path.mkpath(path.absolutePath());
684 qCDebug(three) <<
"Autosavepath: " << path.absolutePath();
689 const QString prefix = m_fileInfo.fileName().isEmpty() ? QStringLiteral(
".") :
"." + m_fileInfo.baseName() +
".";
690 if (!m_autosaveFileName.isEmpty() && QFileInfo(m_autosaveFileName).absolutePath() != path.absolutePath()) {
691 autosaves.removeAll(m_autosaveFileName);
692 QFile::remove(m_autosaveFileName);
693 m_autosaveFileName.clear();
695 if (m_autosaveFileName.isEmpty()) {
696 const QString tag = QUuid::createUuid().toString(QUuid::Id128);
697 m_autosaveFileName = path.absoluteFilePath(prefix + tag +
".panda");
701 if (autosaves.contains(m_autosaveFileName)) {
702 autosaves.removeAll(m_autosaveFileName);
703 Settings::setAutosaveFiles(autosaves);
706 qCDebug(three) <<
"Writing to autosave file.";
711 QSaveFile autosaveFile(m_autosaveFileName);
712 if (!autosaveFile.open(QIODevice::WriteOnly)) {
713 throw PANDACEPTION(
"Error opening autosave file: %1", autosaveFile.errorString());
716 QDataStream stream(&autosaveFile);
720 if (!autosaveFile.commit()) {
721 throw PANDACEPTION(
"Could not commit autosave file: %1", autosaveFile.errorString());
724 autosaves.append(m_autosaveFileName);
727 qCDebug(three) <<
"All auto save file names after adding autosave: " << autosaves;
738 m_autosaveFileName = m_fileInfo.filePath();
743 if (m_autosaveDebounceTimer.isActive()) {
744 m_autosaveDebounceTimer.stop();
749void WorkSpace::createVersionedBackup(
const QString &fileName,
const QVersionNumber &version)
758 if (!parentContextDir.isEmpty()) {
759 m_scene.setContextDir(parentContextDir);
763 QByteArray blobData(blob);
764 QDataStream stream(&blobData, QIODevice::ReadOnly);
768 for (
auto it = blobRegistry.cbegin(); it != blobRegistry.cend(); ++it) {
769 m_scene.icRegistry()->setBlob(it.key(), it.value());
772 QHash<quint64, Port *> portMap;
774 context.contextDir = parentContextDir;
775 QDataStream elementsStream(&preamble.remainingPayload, QIODevice::ReadOnly);
776 elementsStream.setVersion(QDataStream::Qt_5_12);
779 for (
auto *item : items) {
780 m_scene.addItem(item);
782 if (
auto *ge = qgraphicsitem_cast<GraphicElement *>(item)) {
783 m_lastId = (std::max)(m_lastId, ge->id());
787 m_scene.setLastId(m_lastId);
788 m_scene.setSceneRect(m_scene.itemsBoundingRect());
792 m_parentWorkspace = parent;
793 m_parentICElementId = icElementId;
799 m_inlineBlobName = elm->blobName();
807 auto *item = m_scene.itemById(icElementId);
813 if (!elm || !elm->isEmbedded()) {
817 const QString targetBlobName = elm->
blobName();
818 const auto targets = m_scene.icRegistry()->findICsByBlobName(targetBlobName);
819 if (targets.isEmpty()) {
828 QByteArray oldBlob = m_scene.icRegistry()->blob(targetBlobName);
833 m_scene.icRegistry()->setBlob(targetBlobName, blob);
834 QList<GraphicElement *> updated;
836 for (
auto *target : targets) {
837 auto *ic =
static_cast<IC *
>(target);
839 updated.append(target);
843 m_scene.icRegistry()->setBlob(targetBlobName, oldBlob);
848 cmd->setOldBlob(oldBlob);
849 m_scene.undoStack()->push(cmd);
854 QList<QGraphicsItem *> toDelete;
856 for (
auto *item : m_scene.items()) {
860 auto *elm = qgraphicsitem_cast<GraphicElement *>(item);
861 if (elm && elm->isEmbedded() && elm->blobName() == blobName) {
862 toDelete.append(item);
864 for (
int i = 0; i < elm->inputSize(); ++i) {
865 for (
auto *conn : elm->inputPort(i)->connections()) {
866 if (!toDelete.contains(conn)) {
867 toDelete.append(conn);
871 for (
int i = 0; i < elm->outputSize(); ++i) {
872 for (
auto *conn : elm->outputPort(i)->connections()) {
873 if (!toDelete.contains(conn)) {
874 toDelete.append(conn);
881 const bool hasBlob = m_scene.icRegistry()->hasBlob(blobName);
882 if (toDelete.isEmpty() && !hasBlob) {
889 m_scene.undoStack()->beginMacro(tr(
"Remove embedded IC \"%1\"").arg(blobName));
890 if (!toDelete.isEmpty()) {
896 m_scene.undoStack()->endMacro();
899void WorkSpace::setCurrentFile(
const QString &filePath)
901 m_fileInfo = QFileInfo(filePath);
907 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.
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
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.