11#include <emscripten/emscripten.h>
12#include <emscripten/html5.h>
15#include <QActionGroup>
18#include <QDesktopServices>
23#include <QKeySequence>
25#include <QLoggingCategory>
28#include <QPixmapCache>
36#include <QSvgRenderer>
73void ensureSvgUsage() {
79const char *MainWindow::onBeforeUnload(
int ,
const void * ,
void *userData)
81 static_cast<MainWindow *
>(userData)->updateSettings();
102 m_binder =
new SceneUiBinder(m_ui.get(), m_palette, m_icPreviewPopup,
this,
this);
124 emscripten_set_beforeunload_callback(
this, &MainWindow::onBeforeUnload);
127 qCDebug(zero) <<
"Setting left side menus.";
128 m_palette->populate();
130 qCDebug(zero) <<
"Loading recent file list.";
133 qCDebug(zero) <<
"Setting connections";
136 qCDebug(zero) <<
"Checking playing simulation.";
138 m_ui->actionPlay->setChecked(
true);
140 qCDebug(zero) <<
"Window title.";
141 setWindowTitle(
"wiRedPanda " APP_VERSION);
146 qCDebug(zero) <<
"Building a new tab.";
155 qCDebug(zero) <<
"Opening file if not empty.";
156 if (!fileName.isEmpty()) {
161 QPixmapCache::setCacheLimit(100000);
163 qCDebug(zero) <<
"Adding examples to menu";
165 setupExercisesMenu();
169void MainWindow::setupLanguage()
175 if (language.isEmpty()) {
176 const QLocale systemLocale = QLocale::system();
177 const QString systemLang = systemLocale.name();
178 const QString baseLang = systemLang.split(
'_').first();
179 qCDebug(zero) <<
"Auto-detected system locale:" << systemLang;
182 if (available.contains(systemLang)) {
183 language = systemLang;
184 }
else if (available.contains(baseLang)) {
187 qCDebug(zero) <<
"No translation for" << systemLang <<
"or" << baseLang <<
", falling back to English";
190 qCDebug(zero) <<
"Selected language:" << language;
193 m_languageManager->loadTranslation(language);
197void MainWindow::setupGeometry()
199 qCDebug(zero) <<
"Restoring geometry and setting zoom controls.";
206void MainWindow::setupTheme()
208 qCDebug(zero) <<
"Preparing theme and UI modes.";
209 auto *themeGroup =
new QActionGroup(
this);
210 for (
auto *action : m_ui->menuTheme->actions()) {
211 themeGroup->addAction(action);
213 themeGroup->setExclusive(
true);
228void MainWindow::setupRecentFiles()
230 m_recentFiles =
new RecentFiles(
this);
233 createRecentFileActions();
236void MainWindow::setupExamplesMenu()
240 if (!examplesPath.isEmpty()) {
241 const auto entryList = QDir(examplesPath).entryList({
"*.panda"}, QDir::Files);
243 for (
const auto &entry : entryList) {
247 QString title = QFileInfo(entry).completeBaseName();
248 title.replace(QLatin1Char(
'-'), QLatin1Char(
' '));
249 title.replace(QLatin1Char(
'_'), QLatin1Char(
' '));
250 QStringList words = title.split(QLatin1Char(
' '), Qt::SkipEmptyParts);
251 for (QString &word : words) {
252 word[0] = word[0].toUpper();
255 auto *action =
new QAction(words.join(QLatin1Char(
' ')),
this);
256 action->setData(examplesPath +
"/" + entry);
258 connect(action, &QAction::triggered,
this, [
this] {
259 if (
auto *senderAction = qobject_cast<QAction *>(sender())) {
264 m_ui->menuExamples->addAction(action);
268 if (m_ui->menuExamples->isEmpty()) {
269 m_ui->menuExamples->menuAction()->setVisible(
false);
273void MainWindow::populateContentMenu(QMenu *menu,
const QString &categoryKey,
274 const QString &openFolderText,
275 const QString &openFolderFailureText,
276 const QStringList &completed,
277 const std::function<
void(
const QString &)> &onSelect)
281 auto *openFolderAction =
new QAction(openFolderText, menu);
282 connect(openFolderAction, &QAction::triggered,
this, [
this, categoryKey, openFolderFailureText] {
285 QMessageBox::warning(
this, tr(
"Error"), openFolderFailureText);
288 QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
290 menu->addAction(openFolderAction);
292 const QIcon checkIcon = style()->standardIcon(QStyle::SP_DialogApplyButton);
293 const QIcon circleIcon = style()->standardIcon(QStyle::SP_ArrowRight);
296 if (!entries.isEmpty()) {
297 menu->addSeparator();
299 for (
const ExerciseTourResourceEntry &entry : entries) {
301 title.replace(QLatin1Char(
'&'), QStringLiteral(
"&&"));
302 auto *action =
new QAction(title, menu);
303 action->setIcon(completed.contains(entry.id) ? checkIcon : circleIcon);
305 connect(action, &QAction::triggered,
this, [onSelect, path = entry.path] { onSelect(path); });
306 menu->addAction(action);
310void MainWindow::setupExercisesMenu()
312 connect(m_ui->menuExercises, &QMenu::aboutToShow,
this, [
this] {
313 populateContentMenu(m_ui->menuExercises,
"Exercises",
314 tr(
"Open My Exercises Folder"),
315 tr(
"Could not create or access a folder for custom exercises."),
316 Settings::completedExercises(),
317 [this](const QString &path) { startExercise(path); });
321void MainWindow::setupToursMenu()
323 connect(m_ui->menuTours, &QMenu::aboutToShow,
this, [
this] {
324 populateContentMenu(m_ui->menuTours,
"Tours",
325 tr(
"Open My Tours Folder"),
326 tr(
"Could not create or access a folder for custom tours."),
327 Settings::completedTours(),
328 [this](const QString &path) { startTour(path); });
332void MainWindow::setupShortcuts()
336 auto *searchShortcut =
new QShortcut(QKeySequence(
"Ctrl+F"),
this);
337 connect(searchShortcut, &QShortcut::activated, m_ui->lineEditSearch, qOverload<>(&QWidget::setFocus));
340void MainWindow::setupConnections()
345 connect(m_ui->actionAbout, &QAction::triggered,
this, &MainWindow::on_actionAbout_triggered);
346 connect(m_ui->actionAboutQt, &QAction::triggered,
this, &MainWindow::on_actionAboutQt_triggered);
347 connect(m_ui->actionAboutThisVersion, &QAction::triggered,
this, &MainWindow::aboutThisVersion);
348 connect(m_ui->actionReportTranslationError,&QAction::triggered,
this, &MainWindow::on_actionReportTranslationError_triggered);
350 connect(m_ui->actionDarkTheme, &QAction::triggered,
this, &MainWindow::on_actionDarkTheme_triggered);
351 connect(m_ui->actionSystemTheme, &QAction::triggered,
this, &MainWindow::on_actionSystemTheme_triggered);
352 connect(m_ui->actionExit, &QAction::triggered,
this, &MainWindow::on_actionExit_triggered);
357 connect(m_ui->actionFastMode, &QAction::triggered,
this, &MainWindow::on_actionFastMode_triggered);
358 connect(m_ui->actionFlipHorizontally, &QAction::triggered,
this, &MainWindow::on_actionFlipHorizontally_triggered);
359 connect(m_ui->actionFlipVertically, &QAction::triggered,
this, &MainWindow::on_actionFlipVertically_triggered);
360 connect(m_ui->actionAlignLeft, &QAction::triggered,
this, &MainWindow::on_actionAlignLeft_triggered);
361 connect(m_ui->actionAlignRight, &QAction::triggered,
this, &MainWindow::on_actionAlignRight_triggered);
362 connect(m_ui->actionAlignTop, &QAction::triggered,
this, &MainWindow::on_actionAlignTop_triggered);
363 connect(m_ui->actionAlignBottom, &QAction::triggered,
this, &MainWindow::on_actionAlignBottom_triggered);
364 connect(m_ui->actionAlignHorizontalCenter, &QAction::triggered,
this, &MainWindow::on_actionAlignHorizontalCenter_triggered);
365 connect(m_ui->actionAlignVerticalCenter, &QAction::triggered,
this, &MainWindow::on_actionAlignVerticalCenter_triggered);
366 connect(m_ui->actionDistributeHorizontally, &QAction::triggered,
this, &MainWindow::on_actionDistributeHorizontally_triggered);
367 connect(m_ui->actionDistributeVertically, &QAction::triggered,
this, &MainWindow::on_actionDistributeVertically_triggered);
368 connect(m_ui->actionFullscreen, &QAction::triggered,
this, &MainWindow::on_actionFullscreen_triggered);
369 connect(m_ui->actionGates, &QAction::triggered,
this, &MainWindow::on_actionGates_triggered);
370 connect(m_ui->actionLabelsUnderIcons, &QAction::triggered,
this, &MainWindow::on_actionLabelsUnderIcons_triggered);
371 connect(m_ui->actionICPreview, &QAction::triggered,
this, &MainWindow::on_actionICPreview_triggered);
372 connect(m_ui->actionCheckForUpdates, &QAction::triggered,
this, &MainWindow::on_actionCheckForUpdates_triggered);
373 connect(m_ui->actionShowMinimap, &QAction::triggered,
this, &MainWindow::on_actionShowMinimap_triggered);
374 connect(m_ui->actionLightTheme, &QAction::triggered,
this, &MainWindow::on_actionLightTheme_triggered);
375 connect(m_ui->actionMute, &QAction::triggered,
this, &MainWindow::on_actionMute_triggered);
378 connect(m_ui->actionPlay, &QAction::toggled,
this, &MainWindow::on_actionPlay_toggled);
385 openICInTab(blobName, icElementId, currentTab()->scene()->icRegistry()->blob(blobName));
391 connect(m_ui->actionResetZoom, &QAction::triggered,
this, &MainWindow::on_actionResetZoom_triggered);
392 connect(m_ui->actionZoomToFit, &QAction::triggered,
this, &MainWindow::on_actionZoomToFit_triggered);
393 connect(m_ui->actionRestart, &QAction::triggered,
this, &MainWindow::on_actionRestart_triggered);
394 connect(m_ui->actionRotateLeft, &QAction::triggered,
this, &MainWindow::on_actionRotateLeft_triggered);
395 connect(m_ui->actionRotateRight, &QAction::triggered,
this, &MainWindow::on_actionRotateRight_triggered);
398 connect(m_ui->actionSelectAll, &QAction::triggered,
this, &MainWindow::on_actionSelectAll_triggered);
399 connect(m_ui->actionShortcutsAndTips, &QAction::triggered,
this, &MainWindow::on_actionShortcuts_and_Tips_triggered);
400 connect(m_ui->actionWaveform, &QAction::triggered,
this, &MainWindow::on_actionWaveform_triggered);
401 connect(m_ui->actionWires, &QAction::triggered,
this, &MainWindow::on_actionWires_triggered);
402 connect(m_ui->actionZoomIn, &QAction::triggered,
this, &MainWindow::on_actionZoomIn_triggered);
403 connect(m_ui->actionZoomOut, &QAction::triggered,
this, &MainWindow::on_actionZoomOut_triggered);
405 auto *tab = currentTab();
412 auto *view = tab->view();
413 const QPointF center = view->mapToScene(view->viewport()->rect().center());
414 tab->scene()->addItem(mimeData, center);
433 connectSceneAction(m_ui->actionClearSelection, &Scene::clearSelection);
441void MainWindow::connectSceneAction(QAction *action,
void (
Scene::*method)())
443 connect(action, &QAction::triggered,
this, [
this, method] {
458 m_workspaceManager->createNewTab();
463 m_ui->actionFastMode->setChecked(fastMode);
470void MainWindow::on_actionExit_triggered()
479 m_workspaceManager->save(fileName);
486 qCDebug(zero) <<
"Checking for autosave file recovery.";
487 m_workspaceManager->loadAutosaveFiles();
490 updateController->checkForUpdates();
496 startTour(QStringLiteral(
":/Tours/ui-overview.json"));
500void MainWindow::aboutThisVersion()
503 msgBox.setParent(
this);
504 msgBox.setStandardButtons(QMessageBox::Ok);
505 msgBox.setIcon(QMessageBox::Icon::Information);
506 msgBox.setWindowTitle(
"wiRedPanda " APP_VERSION);
508 tr(
"wiRedPanda %1\n\n"
509 "This version includes automatic migration of older project files.\n"
510 "When you open a project file older than the current version, it will be automatically "
511 "upgraded to the current format and a versioned backup will be created.\n\n"
512 "To open projects containing ICs (or boxes), appearances, and/or beWavedDolphin simulations, "
513 "their files must be in the same directory as the main project file.\n"
514 "wiRedPanda %1 will automatically list all other .panda files located "
515 "in the same directory as the current project as ICs in the editor tab.\n"
516 "You have to save new projects before accessing ICs and appearances, or running "
517 "beWavedDolphin simulations.").arg(APP_VERSION));
518 msgBox.setWindowModality(Qt::WindowModal);
519 msgBox.setDefaultButton(QMessageBox::Ok);
524void MainWindow::on_actionWires_triggered(
const bool checked)
534void MainWindow::on_actionRotateRight_triggered()
543void MainWindow::on_actionRotateLeft_triggered()
554 m_workspaceManager->loadPandaFile(fileName);
559 m_workspaceManager->openICInTab(blobName, icElementId, blob);
562void MainWindow::on_actionAbout_triggered()
568 tr(
"<p>wiRedPanda is software developed by students of the Federal University of São Paulo"
569 " to help students learn about logic circuits.</p>"
570 "<p>Software version: %1</p>"
571 "<p><strong>Creators:</strong></p>"
573 "<li> Davi Morales </li>"
574 "<li> Lucas Lellis </li>"
575 "<li> Rodrigo Torres </li>"
576 "<li> Prof. Fábio Cappabianco, Ph.D. </li>"
578 "<p> wiRedPanda is currently maintained by Prof. Fábio Cappabianco, Ph.D., João Pedro M. Oliveira, Matheus R. Esteves and Maycon A. Santana.</p>"
579 "<p> Please file a report at our GitHub page if you find a bug or want to request a new feature.</p>"
580 "<p><a href=\"https://gibis-unifesp.github.io/wiRedPanda/\">Visit our website!</a></p>")
581 .arg(QApplication::applicationVersion()));
588 QMap<QString, QString> byLabel;
592 byLabel.insert(tr(
"Redo"), QKeySequence(QKeySequence::Redo).toString(QKeySequence::NativeText));
593 byLabel.insert(tr(
"Undo"), QKeySequence(QKeySequence::Undo).toString(QKeySequence::NativeText));
595 const auto actions = findChildren<QAction *>();
596 for (
const auto *action : actions) {
597 const QKeySequence seq = action->shortcut();
601 QString label = action->text();
602 label.remove(QLatin1Char(
'&'));
603 if (label.endsWith(QLatin1String(
"..."))) {
606 label = label.trimmed();
607 if (!label.isEmpty()) {
608 byLabel.insert(label, seq.toString(QKeySequence::NativeText));
613 for (
auto it = byLabel.cbegin(); it != byLabel.cend(); ++it) {
614 rows += QStringLiteral(
"<tr><td><b>%1</b> </td><td>%2</td></tr>")
615 .arg(it.value().toHtmlEscaped(), it.key().toHtmlEscaped());
618 return tr(
"<h1>Keyboard Shortcuts</h1>"
620 "<h1>Element Property Navigation</h1>"
621 "<ul style=\"list-style:none;\">"
622 "<li> [ / ] : Previous / next primary property </li>"
623 "<li> { / } : Previous / next secondary property </li>"
624 "<li> < / > : Morph to previous / next element </li>"
626 "<h1>General Tips</h1>"
627 "<ul style=\"list-style:none;\">"
628 "<li> Double-click a wire to create a node </li>"
629 "<li> Drag an element from the left panel onto the canvas to add it </li>"
630 "<li> Nudge the selection with the arrow keys (hold Shift for larger steps) </li>"
631 "<li> Drop a .panda file onto the canvas to open it </li>"
636void MainWindow::on_actionShortcuts_and_Tips_triggered()
643void MainWindow::on_actionAboutQt_triggered()
646 QMessageBox::aboutQt(
this);
650void MainWindow::on_actionReportTranslationError_triggered()
653 QDesktopServices::openUrl(QUrl(
"https://hosted.weblate.org/projects/wiredpanda/wiredpanda"));
659 bool closeWindow =
false;
664 if (!m_workspaceManager->hasModifiedFiles()) {
666 QMessageBox::question(
668 tr(
"Exit") +
" " + QApplication::applicationName(),
670 QMessageBox::Cancel | QMessageBox::Yes,
673 if (reply == QMessageBox::Yes) {
676 }
else if (m_workspaceManager->closeFiles()) {
689void MainWindow::updateSettings()
699 m_ui->pushButtonAddIC->setVisible(visible);
700 m_ui->pushButtonRemoveIC->setVisible(visible);
701 m_ui->pushButtonMakeSelfContained->setVisible(visible);
710 m_ui->pushButtonAddIC->setEnabled(hasFile);
715 return m_workspaceManager->currentFile();
720 return m_workspaceManager->hasModifiedFiles();
725 return m_workspaceManager->currentDir();
745 m_workspaceManager->saveFile();
750 m_ui->statusBar->showMessage(message, timeout);
758void MainWindow::on_actionSelectAll_triggered()
772 return m_workspaceManager->currentTab();
775void MainWindow::onCurrentTabChanged(
WorkSpace *newTab)
783 m_previousTab = newTab;
788 m_ui->elementEditor->hide();
794 if (m_exerciseOverlay && m_exerciseEngine && m_exerciseEngine->isActive()) {
795 m_exerciseOverlay->hide();
796 m_exerciseOverlay->setParent(
nullptr);
801 m_palette->updateICList(QFileInfo());
802 m_palette->updateEmbeddedICList(
nullptr);
807 m_binder->bind(newTab);
820 if (m_exerciseEngine && m_exerciseEngine->isActive() && m_exerciseOverlay) {
821 m_exerciseEngine->setScene(newTab->
scene());
822 m_exerciseOverlay->setParent(newTab);
824 m_exerciseOverlay->repositionToParent();
825 m_exerciseOverlay->show();
826 m_exerciseOverlay->raise();
832void MainWindow::updateWindowTitle()
836 setWindowTitle(QStringLiteral(
"wiRedPanda " APP_VERSION));
837 setWindowModified(
false);
843 setWindowTitle(tr(
"%1[*] — wiRedPanda %2")
844 .arg(m_workspaceManager->currentTabName(), QStringLiteral(APP_VERSION)));
845 setWindowModified(!tab->scene()->undoStack()->isClean());
848void MainWindow::on_actionGates_triggered(
const bool checked)
859 m_ui->actionWires->setEnabled(checked);
867 m_exportController->exportToArduino(std::move(fileName));
872 m_exportController->exportToSystemVerilog(std::move(fileName));
877 m_exportController->exportToWaveFormFile(fileName);
882 m_exportController->exportToWaveFormTerminal();
885void MainWindow::on_actionZoomIn_triggered()
const
896void MainWindow::on_actionZoomOut_triggered()
const
907void MainWindow::on_actionResetZoom_triggered()
const
918void MainWindow::on_actionZoomToFit_triggered()
const
929void MainWindow::updateRecentFileActions()
931 const auto files = m_recentFiles->recentFiles();
934 if (numRecentFiles > 0) {
935 m_ui->menuRecentFiles->setEnabled(
true);
938 auto actions = m_ui->menuRecentFiles->actions();
943 for (
int i = 0; i < numRecentFiles; ++i) {
944 const QString text =
"&" + QString::number(i + 1) +
" " + QFileInfo(files.at(i)).fileName();
945 actions.at(i)->setText(text);
946 actions.at(i)->setData(files.at(i));
947 actions.at(i)->setVisible(
true);
951 actions.at(i)->setVisible(
false);
955void MainWindow::openRecentFile()
957 if (
auto *action = qobject_cast<QAction *>(sender())) {
963void MainWindow::createRecentFileActions()
965 m_ui->menuRecentFiles->clear();
968 auto *action =
new QAction(
this);
969 action->setVisible(
false);
970 connect(action, &QAction::triggered,
this, &MainWindow::openRecentFile);
971 m_ui->menuRecentFiles->addAction(action);
974 updateRecentFileActions();
979 m_ui->retranslateUi();
980 m_ui->elementEditor->retranslateUi();
981 m_palette->retranslateLabels();
983 for (
int index = 0; index < m_ui->tab->count(); ++index) {
984 auto *workspace = qobject_cast<WorkSpace *>(m_ui->tab->widget(index));
988 auto *scene = workspace->scene();
992 auto *undoStack = scene->undoStack();
997 if (workspace->isInlineIC()) {
998 text =
"[" + workspace->inlineBlobName() +
"]";
1000 auto fileInfo = workspace->fileInfo();
1001 text = fileInfo.exists() ? fileInfo.fileName() : tr(
"New Project");
1004 if (!undoStack->isClean()) {
1008 m_ui->tab->setTabText(index, text);
1010 scene->retranslateUi();
1012 for (
auto *elm : workspace->scene()->elements()) {
1017 if (m_exerciseEngine->isActive()) {
1018 m_exerciseEngine->retranslate();
1020 if (m_tourEngine->isActive()) {
1021 m_tourEngine->retranslate();
1027 m_languageManager->loadTranslation(language);
1032 m_ui->menuLanguage->clear();
1034 auto *languageGroup =
new QActionGroup(
this);
1035 languageGroup->setExclusive(
true);
1037 for (
const QString &langCode : m_languageManager->availableLanguages()) {
1038 auto *action =
new QAction(m_languageManager->displayName(langCode),
this);
1039 action->setCheckable(
true);
1040 action->setData(langCode);
1041 action->setIcon(QIcon(m_languageManager->flagIcon(langCode)));
1044 action->setChecked(
true);
1047 languageGroup->addAction(action);
1048 m_ui->menuLanguage->addAction(action);
1050 connect(action, &QAction::triggered,
this, [
this, langCode]() {
1051 m_languageManager->loadTranslation(langCode);
1056void MainWindow::on_actionPlay_toggled(
const bool checked)
1058 sentryBreadcrumb(
"simulation", QStringLiteral(
"Play toggled: %1").arg(checked));
1066 checked ? simulation->
start() : simulation->stop();
1069void MainWindow::on_actionRestart_triggered()
1085 layout->removeItem(spacer);
1089 for (
const auto &name : names) {
1092 layout->addWidget(
new ElementLabel(pixmap, type, name,
this));
1093 m_ui->scrollAreaWidgetContents_Search->layout()->addWidget(
new ElementLabel(pixmap, type, name,
this));
1096 layout->addItem(spacer);
1099void MainWindow::on_actionFastMode_triggered(
const bool checked)
1108void MainWindow::on_actionWaveform_triggered()
1113 m_bwd->activateWindow();
1122 auto *bwd =
new BewavedDolphin(
currentTab()->scene(),
true,
this,
this);
1125 connect(bwd, &QObject::destroyed,
this, [
this] {
1126 if (m_exerciseOverlay && m_exerciseEngine && m_exerciseEngine->isActive()) {
1127 m_exerciseOverlay->hide();
1128 m_exerciseOverlay->setParent(
nullptr);
1135void MainWindow::on_actionLightTheme_triggered()
1143void MainWindow::on_actionDarkTheme_triggered()
1151void MainWindow::on_actionSystemTheme_triggered()
1159void MainWindow::updateTheme()
1162 case Theme::Dark: m_ui->actionDarkTheme->setChecked(
true);
break;
1163 case Theme::Light: m_ui->actionLightTheme->setChecked(
true);
break;
1164 case Theme::System: m_ui->actionSystemTheme->setChecked(
true);
break;
1167 m_palette->updateTheme();
1168 m_ui->elementEditor->updateTheme();
1171void MainWindow::on_actionFlipHorizontally_triggered()
1182void MainWindow::on_actionFlipVertically_triggered()
1193void MainWindow::on_actionAlignLeft_triggered()
1204void MainWindow::on_actionAlignRight_triggered()
1215void MainWindow::on_actionAlignTop_triggered()
1226void MainWindow::on_actionAlignBottom_triggered()
1237void MainWindow::on_actionAlignHorizontalCenter_triggered()
1248void MainWindow::on_actionAlignVerticalCenter_triggered()
1259void MainWindow::on_actionDistributeHorizontally_triggered()
1270void MainWindow::on_actionDistributeVertically_triggered()
1283 return m_workspaceManager->dolphinFileName();
1288 m_workspaceManager->setDolphinFileName(fileName);
1291void MainWindow::on_actionFullscreen_triggered()
1295 isFullScreen() ? showNormal() : showFullScreen();
1299void MainWindow::on_actionMute_triggered(
const bool checked)
1308 m_ui->actionMute->setText(checked ? tr(
"Unmute") : tr(
"Mute"));
1312void MainWindow::on_actionLabelsUnderIcons_triggered(
const bool checked)
1315 sentryBreadcrumb(
"ui", QStringLiteral(
"Labels under icons: %1").arg(checked));
1316 m_ui->mainToolBar->setToolButtonStyle(checked ? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly);
1321void MainWindow::on_actionICPreview_triggered(
const bool checked)
1329void MainWindow::on_actionCheckForUpdates_triggered(
const bool checked)
1332 sentryBreadcrumb(
"ui", QStringLiteral(
"Auto update checks: %1").arg(checked));
1337void MainWindow::on_actionShowMinimap_triggered(
const bool checked)
1348 switch (
event->type()) {
1349 case QEvent::WindowActivate: {
1351 if (m_ui->actionPlay->isChecked()) {
1352 on_actionPlay_toggled(
true);
1357 case QEvent::WindowDeactivate: {
1360 if (!m_ui->actionBackground_Simulation->isChecked()) {
1361 on_actionPlay_toggled(
false);
1369 return QMainWindow::event(
event);
1372void MainWindow::startExercise(
const QString &resourcePath)
1374 if (!
currentTab() || resourcePath.isEmpty()) {
1377 if (!m_exerciseEngine->loadFromResource(resourcePath)) {
1378 qWarning() <<
"ExerciseEngine: failed to load" << resourcePath;
1385 delete m_exerciseOverlay;
1386 m_exerciseOverlay =
new ExerciseOverlay(m_exerciseEngine,
currentTab());
1395 [
this](
int,
int,
const ExerciseStep &step) {
1396 for (
const QString &
id : step.
click) clickTarget(
id);
1398 if (!m_exerciseOverlay) {
1401 const bool wantBwd = (step.
context ==
"bwd");
1402 if (wantBwd && m_bwd) {
1403 m_exerciseOverlay->setParent(m_bwd->centralWidget());
1404 m_bwd->setExerciseOverlay(m_exerciseOverlay);
1412 m_bwd->setExerciseOverlay(
nullptr);
1415 m_exerciseOverlay->repositionToParent();
1416 m_exerciseOverlay->show();
1417 m_exerciseOverlay->raise();
1426 if (!m_exerciseOverlay)
return;
1427 ExerciseOverlay *overlay = m_exerciseOverlay;
1428 m_exerciseOverlay =
nullptr;
1429 m_exerciseEngine->stop();
1434 m_bwd->setExerciseOverlay(
nullptr);
1436 overlay->deleteLater();
1439 auto *esc =
new QShortcut(QKeySequence(Qt::Key_Escape), m_exerciseOverlay);
1443 m_exerciseEngine->setScene(
currentTab()->scene());
1444 m_exerciseEngine->start();
1446 m_exerciseOverlay->repositionToParent();
1447 m_exerciseOverlay->show();
1448 m_exerciseOverlay->raise();
1451void MainWindow::startTour(
const QString &resourcePath)
1453 if (resourcePath.isEmpty()) {
1456 if (!m_tourEngine->loadFromResource(resourcePath)) {
1457 qWarning() <<
"TourEngine: failed to load" << resourcePath;
1461 delete m_tourOverlay;
1462 m_tourOverlay =
new TourOverlay(m_tourEngine,
this);
1463 m_tourOverlay->setTargetResolver([
this](
const QString &
id) {
1464 return resolveTourTarget(
id);
1475 [
this](
int,
int,
const TourStep &step) {
1476 for (
const QString &
id : step.
click) clickTarget(
id);
1478 TourOverlay *overlay = m_tourOverlay;
1482 const bool wantBwd = step.
target.startsWith(
"bwd:");
1483 if (wantBwd && m_bwd) {
1484 if (overlay->parentWidget() != m_bwd) {
1489 }
else if (!wantBwd && overlay->parentWidget() !=
this) {
1504 if (!m_tourOverlay)
return;
1505 TourOverlay *overlay = m_tourOverlay;
1506 m_tourOverlay =
nullptr;
1507 m_tourEngine->stop();
1508 overlay->deleteLater();
1511 auto *esc =
new QShortcut(QKeySequence(Qt::Key_Escape), m_tourOverlay);
1514 m_tourEngine->start();
1516 m_tourOverlay->show();
1517 m_tourOverlay->raise();
1520QRect MainWindow::resolveTourTarget(
const QString &
id)
const
1522 if (!m_tourOverlay ||
id.isEmpty() ||
id ==
"none") {
1526 auto mapWidget = [
this](QWidget *w) -> QRect {
1528 const QPoint topLeft = m_tourOverlay->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)));
1529 return QRect(topLeft, w->size());
1532 if (
id ==
"toolbar")
return mapWidget(m_ui->mainToolBar->widgetForAction(m_ui->actionWaveform));
1533 if (
id ==
"elementPalette")
return mapWidget(m_ui->tabElements);
1534 if (
id ==
"gatesTab") {
1535 QTabBar *bar = m_ui->tabElements->tabBar();
1536 const QRect tabRect = bar->tabRect(1);
1537 const QPoint topLeft = m_tourOverlay->mapFromGlobal(bar->mapToGlobal(tabRect.topLeft()));
1538 return QRect(topLeft, tabRect.size());
1540 if (
id ==
"ioTab") {
1541 QTabBar *bar = m_ui->tabElements->tabBar();
1542 const QRect tabRect = bar->tabRect(0);
1543 const QPoint topLeft = m_tourOverlay->mapFromGlobal(bar->mapToGlobal(tabRect.topLeft()));
1544 return QRect(topLeft, tabRect.size());
1547 if (
id ==
"elementEditor")
return mapWidget(m_ui->elementEditor);
1548 if (
id ==
"searchBar")
return mapWidget(m_ui->lineEditSearch);
1550 BewavedDolphin *bwd = m_bwd;
1551 if (
id.startsWith(
"bwd:") && bwd) {
1552 auto mapBwd = [
this](QWidget *w) -> QRect {
1553 if (!w || !m_tourOverlay) {
1556 const QPoint tl = m_tourOverlay->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)));
1557 return QRect(tl, w->size());
1559 const QString sub =
id.sliced(4);
1562 if (sub ==
"menuBar")
return mapBwd(bwd->menuBar());
1569void MainWindow::clickTarget(
const QString &
id)
1571 if (
id ==
"ioTab") m_ui->tabElements->setCurrentIndex(0);
1572 else if (
id ==
"gatesTab") m_ui->tabElements->setCurrentIndex(1);
1573 else if (
id ==
"combinational") m_ui->tabElements->setCurrentIndex(2);
1574 else if (
id ==
"memoryTab") m_ui->tabElements->setCurrentIndex(3);
1575 else if (
id ==
"actionPlay") m_ui->actionPlay->trigger();
1576 else if (
id ==
"actionWaveform") m_ui->actionWaveform->trigger();
1577 else if (
id ==
"bwd:actionCombinational" && m_bwd) m_bwd->triggerCombinational();
1578 else if (
id ==
"setupElementEditorDemo") {
1583 scene->clearSelection();
1590 else if (
id ==
"setupWaveformDemo") {
1595 scene->clearSelection();
1600 clock1->setPos(-160, -60);
1601 clock2->setPos(-160, 60);
1603 led->setPos(160, 0);
1605 auto *conn1 =
new Connection();
1606 conn1->setStartPort(clock1->outputPort(0));
1607 conn1->setEndPort(gate->inputPort(0));
1608 auto *conn2 =
new Connection();
1609 conn2->setStartPort(clock2->outputPort(0));
1610 conn2->setEndPort(gate->inputPort(1));
1611 auto *conn3 =
new Connection();
1612 conn3->setStartPort(gate->outputPort(0));
1613 conn3->setEndPort(led->inputPort(0));
1618 scene->
receiveCommand(
new AddItemsCommand({clock1, clock2, gate, led}, scene));
1620 conn1->updatePath();
1621 conn2->updatePath();
1622 conn3->updatePath();
Custom QApplication subclass with exception handling and main-window access.
BewavedDolphin waveform editor: digital signal creation, display, and export.
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Connection: a wire that connects an output port to an input port in the circuit scene.
Singleton factory for all circuit element types.
Draggable element icon+label widget used in the element palette.
ElementPalette: manages the left-panel element palette and search UI.
Discovery and translation lookup for Exercise/Tour step content.
ExportController: orchestrates exporting the current circuit to its output formats.
Extended QGraphicsView with zoom, pan, and fast-rendering modes.
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.
Per-platform resolution of install-relative content directories.
LanguageManager: Qt translation loading and language metadata.
MainWindowUi: hand-written UI class for the MainWindow.
Main application window providing menus, toolbars, and tab management.
Recent-files list management with filesystem watching.
SceneUiBinder: wires the active tab's scene into the shared editor chrome.
Lightweight Sentry helpers gated behind HAVE_SENTRY.
void sentryBreadcrumb(const char *category, const QString &message)
Typed wrappers around QSettings for all application preferences.
Synchronous cycle-based simulation engine with event-driven clock support.
Theme management types and singleton ThemeManager.
UpdateController: drives the application's check-for-updates workflow.
File-format version constants and application version accessor.
WorkspaceManager: owns the circuit tabs and their file lifecycle.
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...
static bool interactiveMode
QTableView * signalTableView() const
Returns the waveform table view (for tour target resolution).
QAction * actionCombinational() const
Returns the combinational action (for tour button spotlighting).
QToolBar * mainToolBar() const
Returns the main toolbar (for tour target resolution).
Interface the host application (MainWindow) provides to BewavedDolphin.
void openSubcircuitFileRequested(const QString &filePath)
Emitted when the user requests editing a file-backed IC's sub-circuit file.
void editSubcircuitRequested(const QString &blobName, int icElementId)
Emitted when the user requests editing an embedded IC sub-circuit.
void renameAction()
Opens an inline editor to rename the selected element(s).
void extractToFileRequested()
Emitted when the user requests extracting an embedded IC to file.
void changeTriggerAction()
Opens a dialog to change the trigger key for the selected element(s).
void embedSubcircuitRequested()
Emitted when the user requests embedding a file-backed IC.
static QPixmap pixmap(const ElementType type)
Returns the pixmap icon for the given element type.
static ElementType textToType(const QString &text)
Converts an element type name string to its ElementType enum value.
static GraphicElement * buildElement(const ElementType type)
Constructs and returns a new graphic element of the given type.
Icon and name widget shown in the left-panel element palette.
Controller for the left-panel element palette, IC list, and search tab.
void addElementRequested(QMimeData *mimeData)
Emitted when the user presses Enter in the search box.
void stepChanged(int step, int total, const ExerciseStep &data)
void onExerciseCompleted()
void onStepChanged(int step, int total, const ExerciseStep &stepData)
static QVector< ExerciseTourResourceEntry > discover(const QString &category)
static QString preferredContentDir(const QString &category)
static QString translate(const QString &key, const QString &fallbackEnglish)
Owns the circuit-export workflow: Arduino / SystemVerilog code, PDF / PNG images, and beWavedDolphin ...
void exportArduinoDialog()
void exportSystemVerilogDialog()
void zoomOut()
Decreases the view magnification by one zoom step.
void zoomIn()
Increases the view magnification by one zoom step.
void resetZoom()
Resets the view scale to 1:1.
void setFastMode(const bool fastMode)
Enables or disables fast rendering mode (disables antialiasing).
Owns the IC management workflow: importing file-based ICs into the project directory,...
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.
void extractByBlobNameRequested(const QString &blobName)
Emitted when an embedded IC is dropped onto the file-based section.
void embedByFileRequested(const QString &fileName)
Emitted when a file-based IC is dropped onto the embedded section.
static QString resolve(const QString &category)
Owns the application QTranslator instances and provides language metadata.
void translationChanged()
Emitted after a translation has been successfully loaded (or reset to English).
QStringList availableLanguages() const
Returns all available translation language codes, sorted, with "en" first.
virtual QFileInfo icListFile() const =0
File info used to populate the file-based IC palette (parent workspace for inline IC tabs).
void closeEvent(QCloseEvent *event) override
void exportToSystemVerilog(QString fileName)
Generates SystemVerilog code for the current circuit.
QFileInfo currentFile() const override
Returns the QFileInfo of the currently active .panda file.
QWidget * widget() override
void loadPandaFile(const QString &fileName)
Loads a .panda circuit file into the current or a new tab.
void retranslateUi()
Re-applies all translatable strings to the UI after a language change.
void setDolphinFileName(const QString &fileName) override
Associates fileName as the BeWavedDolphin file for the current tab.
ElementPalette * palette() const override
QString shortcutsHelpHtml() const
void createNewTab()
Creates a new empty circuit tab.
QDir currentDir() const override
Returns the directory of the currently active .panda file.
void save(const QString &fileName={}) override
Saves the current circuit to fileName (or prompts if empty).
void exportToArduino(QString fileName)
Generates Arduino sketch code for the current circuit.
QString dolphinFileName() override
Returns the BeWavedDolphin file name associated with the current tab.
WorkSpace * currentTab() const override
Returns the currently visible WorkSpace tab, or nullptr.
void populateMenu(QSpacerItem *spacer, const QStringList &names, QLayout *layout)
Fills layout with ElementLabel buttons from names, preceded by spacer.
void populateLanguageMenu()
Populates the Language submenu with available translations.
void showStatusMessage(const QString &message, int timeout) override
void setFastMode(const bool fastMode)
Enables or disables fast (non-antialiased) rendering for all views.
void requestSave() override
void openICInTab(const QString &blobName, int icElementId, const QByteArray &blob)
Opens an embedded IC blob for editing in a new tab.
MainWindow(const QString &fileName={}, QWidget *parent=nullptr)
Constructs the MainWindow and optionally opens fileName on start.
void refreshICButtonsEnabled() override
void loadTranslation(const QString &language)
Loads and installs the Qt translation for language.
void exportToWaveFormTerminal()
Exports the current waveform simulation to standard output (terminal).
bool event(QEvent *event) override
bool hasModifiedFiles()
Returns true if any open workspace has unsaved changes or is a recovered autosave.
void exportToWaveFormFile(const QString &fileName)
Saves the BeWavedDolphin waveform session to fileName.
void show()
Shows the window and initializes child widget state.
DolphinHost * dolphinHost() override
void setICButtonsVisible(bool visible) override
static constexpr int maxFiles
void recentFilesUpdated()
Emitted whenever the recent-files list changes (add or file deleted).
void addRecentFile(const QString &filePath)
Prepends filePath to the recent-files list and saves it.
Binds and unbinds the active WorkSpace's scene to the single-instance editor chrome (element editor,...
void unbind()
Tears down the connections established by bind() for the currently bound tab.
void loadFileRequested(const QString &filePath)
The bound scene asked to open a file-based IC by path (forwarded to the tab owner).
void openICRequested(const QString &blobName, int icElementId, const QByteArray &blob)
The bound scene asked to open an embedded IC in a tab (forwarded to the tab owner).
Main circuit editing scene.
void flipHorizontally()
Flips selected elements horizontally.
void alignBottom()
Aligns selected elements' bottom edges to the bottommost selected edge. No-op below 2 elements.
void pasteAction()
Pastes items from the internal clipboard into the scene.
void alignRight()
Aligns selected elements' right edges to the rightmost selected edge. No-op below 2 elements.
void copyAction()
Copies the selected items to the internal clipboard.
void rotateLeft()
Rotates selected elements 90 degrees counter-clockwise.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
void distributeVertically()
void duplicateAction()
Duplicates the selection in place (leaves the system clipboard untouched).
void selectAll()
Selects all items in the scene.
void rotateRight()
Rotates selected elements 90 degrees clockwise.
void alignVerticalCenter()
void flipVertically()
Flips selected elements vertically.
void showWires(bool checked)
Shows or hides connection wires. Delegates to VisibilityManager.
void deleteAction()
Deletes the currently selected items.
void alignTop()
Aligns selected elements' top edges to the topmost selected edge. No-op below 2 elements.
void alignLeft()
Aligns selected elements' left edges to the leftmost selected edge. No-op below 2 elements.
void distributeHorizontally()
void showGates(bool checked)
Shows or hides gate elements. Delegates to VisibilityManager.
void alignHorizontalCenter()
void cutAction()
Cuts the selected items to the internal clipboard.
static QByteArray splitterState()
static QByteArray splitterGeometry()
static void setMainWindowState(const QByteArray &state)
static void setMinimapVisible(bool visible)
static void setSplitterGeometry(const QByteArray &geometry)
static void setMainWindowGeometry(const QByteArray &geometry)
static bool updateChecksDisabled()
Global opt-out of update checks (for offline/managed installs); default false (enabled).
static bool minimapVisible()
static void setIcPreviewDisabled(bool disabled)
static bool labelsUnderIcons()
static QString fileName()
Returns the path to the settings file on disk.
static bool welcomeTourShown()
static QByteArray mainWindowState()
static void setSplitterState(const QByteArray &state)
static void setWelcomeTourShown(bool shown)
static QByteArray mainWindowGeometry()
static void setUpdateChecksDisabled(bool disabled)
static QString language()
static void setFastMode(bool enabled)
static bool icPreviewDisabled()
static void setLabelsUnderIcons(bool enabled)
void setUserMuted(bool muted)
Sets whether the user has explicitly muted audio; persists across stop/start cycles.
void start()
Starts the 1 ms simulation timer.
static Theme theme()
Returns the currently active theme.
static ThemeManager & instance()
Returns the singleton ThemeManager instance.
void themeChanged()
Emitted whenever the active theme changes.
static void setTheme(const Theme theme)
Switches the application to theme and emits themeChanged().
void stepChanged(int step, int total, const TourStep &data)
void onStepChanged(int step, int total, const TourStep &stepData)
void setParentWindow(QWidget *newParent)
Re-parents the overlay to newParent and reinstalls the resize event filter.
Owns the update-check lifecycle: querying for a newer release and presenting the resulting notificati...
A widget containing a complete circuit editing environment.
bool isInlineIC() const
Returns true if this workspace is editing an embedded IC blob (not a file).
Scene * scene()
Returns the Scene embedded in this workspace.
void setMinimapVisible(bool visible)
QString dolphinFileName() const
Returns the path of the associated BeWavedDolphin waveform file.
void setExerciseOverlay(ExerciseOverlay *overlay)
Simulation * simulation()
Returns the embedded Simulation.
GraphicsView * view()
Returns the GraphicsView embedded in this workspace.
QFileInfo fileInfo() const
Returns the file info for the currently open circuit file.
Owns the document/tab model: the current tab, tab creation/closing/switching, and the file open/save/...
void recentFileAdded(const QString &filePath)
Emitted when a file-backed tab is (re)named, to feed the recent-files list.
bool closeTab(int tabIndex)
Closes the tab at tabIndex (prompting to save if needed). Returns false if cancelled.
void openICInTab(const QString &blobName, int icElementId, const QByteArray &blob)
void onCurrentIndexChanged(int newIndex)
Reacts to QTabWidget::currentChanged: updates the current tab and emits currentTabChanged.
void currentTabChanged(WorkSpace *tab)
Emitted when the active tab changes (or becomes null); the shell rebinds the chrome.
QFileInfo icListFile() const
void loadPandaFile(const QString &fileName)
const QVersionNumber current
QStringList click
Widget/action IDs to activate on step enter.
QString context
"" or "circuit" → overlay on WorkSpace; "bwd" → overlay on BeWavedDolphin.
QStringList click
Widget/action IDs to activate on step enter.