10#include <QInputDialog>
37IC *ICController::selectedIC()
const
45 if (selected.isEmpty()) {
49 if (selected.first()->elementType() != ElementType::IC) {
53 return static_cast<IC *
>(selected.first());
60 auto *tab = m_host.currentTab();
67 if (!ensureProjectSaved(tab->scene())) {
73 if (selectedFile.isEmpty()) {
77 const QStringList files = {selectedFile};
79 QMessageBox::information(m_host.widget(), tr(
"Info"), tr(
"Selected files (and their dependencies) will be copied to the current project folder."));
83 for (
const auto &file : files) {
84 const QFileInfo srcInfo(file);
85 QFileInfo destPath(m_host.currentDir().absolutePath() +
"/" + srcInfo.fileName());
91 QMessageBox box(QMessageBox::Warning, tr(
"File name conflict"),
92 tr(
"A different file named \"%1\" already exists in the project folder.").arg(srcInfo.fileName()),
93 QMessageBox::NoButton, m_host.widget());
94 auto *replaceButton = box.addButton(tr(
"Replace"), QMessageBox::AcceptRole);
95 auto *keepButton = box.addButton(tr(
"Keep Existing"), QMessageBox::RejectRole);
96 box.addButton(QMessageBox::Cancel);
99 if (box.clickedButton() == replaceButton) {
100 QFile::remove(destPath.absoluteFilePath());
101 }
else if (box.clickedButton() == keepButton) {
111 m_host.palette()->updateICList(m_host.icListFile());
119 QMessageBox::information(m_host.widget(), tr(
"Info"), tr(
"Drag here to remove."));
125 auto *tab = m_host.currentTab();
130 QList<QGraphicsItem *> toDelete;
131 for (
auto *element : tab->scene()->elements()) {
132 if (element->elementType() != ElementType::IC) {
135 const auto *ic = qobject_cast<IC *>(element);
136 if (ic && !ic->isEmbedded() && QFileInfo(ic->file()).fileName().toLower() == icFileName.toLower()) {
137 toDelete.append(element);
144 QFile file(m_host.currentDir().absolutePath() +
"/" + icFileName);
145 if (file.exists() && !file.moveToTrash()) {
146 throw PANDACEPTION(
"Error moving file to trash: %1", file.errorString());
149 if (!toDelete.isEmpty()) {
151 tab->simulation()->restart();
154 m_host.palette()->updateICList(m_host.icListFile());
156 m_host.requestSave();
159QString ICController::resolveUniqueBlobName(
const QString &initialName,
Scene *scene)
165 if (blobName != initialName.trimmed()) {
167 blobName = QInputDialog::getText(m_host.
widget(),
168 tr(
"Name Collision"),
169 tr(
"An embedded IC named \"%1\" already exists.\nSuggested name:").arg(initialName.trimmed()),
170 QLineEdit::Normal, blobName, &ok);
171 blobName = blobName.trimmed();
172 if (!ok || blobName.isEmpty()) {
176 if (reg->hasBlob(blobName)) {
177 blobName = reg->uniqueBlobName(blobName);
183bool ICController::ensureProjectSaved(
Scene *scene)
185 if (scene && !scene->
contextDir().isEmpty()) {
189 const auto choice = QMessageBox::question(m_host.widget(), tr(
"Save required"),
190 tr(
"This action needs the project saved to a file first, so IC paths can be resolved.\n\nSave it now?"),
191 QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Save);
192 if (choice != QMessageBox::Save) {
196 m_host.requestSave();
197 return scene && !scene->
contextDir().isEmpty();
203 auto *firstIC = selectedIC();
204 if (!firstIC || firstIC->file().isEmpty()) {
208 auto *scene = m_host.currentTab()->scene();
210 if (!ensureProjectSaved(scene)) {
213 const QString contextDir = scene->
contextDir();
215 QString blobName = resolveUniqueBlobName(QFileInfo(firstIC->file()).baseName(), scene);
216 if (blobName.isEmpty()) {
220 QFile file(QDir(contextDir).absoluteFilePath(firstIC->file()));
221 if (!file.open(QIODevice::ReadOnly)) {
222 QMessageBox::warning(m_host.widget(), tr(
"Error"), tr(
"Could not read IC file: %1").arg(file.errorString()));
225 QByteArray fileBytes = file.readAll();
229 m_host.palette()->updateEmbeddedICList(scene);
230 m_host.showStatusMessage(tr(
"IC embedded successfully."), 4000);
236 auto *firstIC = selectedIC();
237 if (!firstIC || !firstIC->isEmbedded()) {
241 auto *scene = m_host.currentTab()->scene();
242 const QString blobName = firstIC->blobName();
243 if (!ensureProjectSaved(scene)) {
246 const QString contextDir = scene->
contextDir();
248 const QString suggestion = QDir(contextDir).absoluteFilePath(blobName +
".panda");
251 if (fileName.isEmpty()) {
255 if (!fileName.endsWith(
".panda")) {
256 fileName.append(
".panda");
260 m_host.palette()->updateICList(m_host.icListFile());
261 m_host.palette()->updateEmbeddedICList(scene);
262 m_host.showStatusMessage(tr(
"IC extracted to %1").arg(fileName), 4000);
267 auto *tab = m_host.currentTab();
272 auto *scene = tab->scene();
273 if (!ensureProjectSaved(scene)) {
276 const QString contextDir = scene->
contextDir();
278 const QString absolutePath = QDir(contextDir).absoluteFilePath(fileName);
279 QFile file(absolutePath);
280 if (!file.open(QIODevice::ReadOnly)) {
281 QMessageBox::warning(m_host.widget(), tr(
"Error"), tr(
"Could not read IC file: %1").arg(file.errorString()));
284 QByteArray fileBytes = file.readAll();
287 QString blobName = resolveUniqueBlobName(QFileInfo(absolutePath).baseName(), scene);
288 if (blobName.isEmpty()) {
293 if (reg->embedICsByFile(absolutePath, fileBytes, blobName) == 0) {
298 m_host.palette()->updateEmbeddedICList(scene);
299 m_host.showStatusMessage(tr(
"IC embedded successfully."), 4000);
304 auto *tab = m_host.currentTab();
309 auto *scene = tab->scene();
310 if (!ensureProjectSaved(scene)) {
313 const QString contextDir = scene->
contextDir();
317 if (!reg->hasBlob(blobName)) {
321 const QString suggestion = QDir(contextDir).absoluteFilePath(blobName +
".panda");
324 if (fileName.isEmpty()) {
328 if (!fileName.endsWith(
".panda")) {
329 fileName.append(
".panda");
332 reg->extractToFile(blobName, fileName);
333 m_host.palette()->updateICList(m_host.icListFile());
334 m_host.palette()->updateEmbeddedICList(scene);
335 m_host.showStatusMessage(tr(
"IC extracted to %1").arg(fileName), 4000);
341 auto *tab = m_host.currentTab();
346 auto *scene = tab->scene();
347 if (!ensureProjectSaved(scene)) {
350 const QString contextDir = scene->
contextDir();
353 QStringList uniqueFiles;
354 for (
auto *elm : scene->
elements()) {
355 if (elm->elementType() != ElementType::IC || elm->isEmbedded()) {
358 const QString icFile =
static_cast<IC *
>(elm)->file();
359 if (!icFile.isEmpty() && !uniqueFiles.contains(icFile)) {
360 uniqueFiles.append(icFile);
364 if (uniqueFiles.isEmpty()) {
365 m_host.showStatusMessage(tr(
"No file-based ICs to embed."), 4000);
369 int totalEmbedded = 0;
370 bool completed =
true;
373 for (
const QString &icFile : std::as_const(uniqueFiles)) {
374 const QString fullPath = QDir(contextDir).absoluteFilePath(icFile);
375 QFile file(fullPath);
376 if (!file.open(QIODevice::ReadOnly)) {
377 QMessageBox::warning(m_host.widget(), tr(
"Error"), tr(
"Could not read IC file: %1").arg(file.errorString()));
381 QByteArray fileBytes = file.readAll();
384 QString blobName = resolveUniqueBlobName(QFileInfo(icFile).baseName(), scene);
385 if (blobName.isEmpty()) {
390 totalEmbedded += reg->embedICsByFile(fullPath, fileBytes, blobName);
393 m_host.palette()->updateEmbeddedICList(scene);
398 m_host.showStatusMessage(tr(
"Embedded %1 IC(s). Circuit is now self-contained.").arg(totalEmbedded), 4000);
399 }
else if (totalEmbedded > 0) {
400 m_host.showStatusMessage(tr(
"Embedded %1 IC(s); some file-based ICs remain.").arg(totalEmbedded), 4000);
406 auto *tab = m_host.currentTab();
410 QString fileName =
FileDialogs::provider()->
getOpenFileName(m_host.widget(), tr(
"Select IC file to embed"), m_host.currentDir().absolutePath(), tr(
"Panda files") +
" (*.panda)");
411 if (fileName.isEmpty()) {
415 QFile file(fileName);
416 if (!file.open(QIODevice::ReadOnly)) {
417 QMessageBox::warning(m_host.widget(), tr(
"Error"), tr(
"Could not read file: %1").arg(file.errorString()));
420 QByteArray fileBytes = file.readAll();
423 auto *scene = tab->scene();
424 QString blobName = resolveUniqueBlobName(QFileInfo(fileName).baseName(), scene);
425 if (blobName.isEmpty()) {
430 m_host.palette()->updateEmbeddedICList(scene);
435 auto *tab = m_host.currentTab();
437 tab->removeEmbeddedIC(blobName);
438 m_host.palette()->updateEmbeddedICList(tab->scene());
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,...)
ElementPalette: manages the left-panel element palette and search UI.
Central enumeration types for element types, groups, and signal status.
Abstract file dialog interface for testability.
ICController: integrated-circuit embed / extract / import / removal operations.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
MainWindowHost: the application context that MainWindow's extracted controllers depend on.
Main circuit editing scene with undo/redo and user interaction.
Lightweight Sentry helpers gated behind HAVE_SENTRY.
void sentryBreadcrumb(const char *category, const QString &message)
Circuit and waveform file serialization/deserialization utilities.
Synchronous cycle-based simulation engine with event-driven clock support.
WorkSpace widget: the complete circuit editing environment for one tab.
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...
Undo command that removes a list of items from the scene.
virtual FileDialogResult getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter)=0
virtual QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter)=0
void makeSelfContained()
Embeds every file-based IC in the circuit so it becomes self-contained.
void embedICByFile(const QString &fileName)
Embeds the file-based IC named fileName (drag-and-drop target).
void extractICByBlobName(const QString &blobName)
Extracts the embedded IC blob blobName to a user-chosen .panda file.
void addEmbeddedICFromFile()
Prompts for a .panda file and registers it as an embedded IC blob.
void removeICFile(const QString &icFileName)
Deletes icFileName from the project directory and any instances of it in the scene.
void addICFromFile()
Copies a chosen .panda file (and its dependencies) into the project's IC directory.
void embedSelectedIC()
Embeds the currently selected file-backed IC as a blob in the circuit.
void removeEmbeddedIC(const QString &blobName)
Removes the embedded IC blob blobName from the current circuit.
void showRemoveICHint()
Shows the "drag here to remove" hint for the file-based IC trash button.
void extractSelectedIC()
Extracts the currently selected embedded IC back out to a .panda file.
ICController(MainWindowHost &host, QObject *parent=nullptr)
int embedICsByFile(const QString &fileName, const QByteArray &fileBytes, const QString &blobName)
Converts all file-backed IC elements referencing fileName to embedded ICs using blobName.
QString uniqueBlobName(const QString &baseName) const
Returns baseName if available, or appends a numeric suffix to avoid collision.
int extractToFile(const QString &blobName, const QString &filePath)
Writes the blob to disk and converts all embedded ICs with blobName to file-backed.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Interface MainWindow provides to its extracted controllers (export, IC, …).
virtual WorkSpace * currentTab() const =0
Currently visible WorkSpace tab, or nullptr when none is open.
virtual QWidget * widget()=0
Widget used to parent modal dialogs spawned by a controller.
Undo command that registers/unregisters a blob in the IC registry.
Main circuit editing scene.
const QVector< GraphicElement * > elements() const
Returns all graphic elements in the scene.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
const QList< GraphicElement * > selectedElements() const
Returns the list of currently selected graphic elements.
QString contextDir() const override
Returns the directory of the .panda file associated with this scene.
ICRegistry * icRegistry()
Returns the IC definition registry for this scene.
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.
Scene * scene()
Returns the Scene embedded in this workspace.
FileDialogProvider * provider()
Returns the active provider. Never null.
bool filesHaveSameContent(const QFileInfo &a, const QFileInfo &b)
QString fileName
Selected file path, empty if cancelled.