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>
72void ensureSvgUsage() {
78const char *MainWindow::onBeforeUnload(
int ,
const void * ,
void *userData)
80 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 QString systemLang = s_testSystemLocaleNameOverride.value_or(QLocale::system().name());
177 const QString baseLang = systemLang.split(
'_').first();
178 qCDebug(zero) <<
"Auto-detected system locale:" << systemLang;
181 if (available.contains(systemLang)) {
182 language = systemLang;
183 }
else if (available.contains(baseLang)) {
186 qCDebug(zero) <<
"No translation for" << systemLang <<
"or" << baseLang <<
", falling back to English";
189 qCDebug(zero) <<
"Selected language:" << language;
192 m_languageManager->loadTranslation(language);
196void MainWindow::setupGeometry()
198 qCDebug(zero) <<
"Restoring geometry and setting zoom controls.";
205void MainWindow::setupTheme()
207 qCDebug(zero) <<
"Preparing theme and UI modes.";
208 auto *themeGroup =
new QActionGroup(
this);
209 for (
auto *action : m_ui->menuTheme->actions()) {
210 themeGroup->addAction(action);
212 themeGroup->setExclusive(
true);
227void MainWindow::setupRecentFiles()
229 m_recentFiles =
new RecentFiles(
this);
232 createRecentFileActions();
235void MainWindow::setupExamplesMenu()
239 if (!examplesPath.isEmpty()) {
240 const auto entryList = QDir(examplesPath).entryList({
"*.panda"}, QDir::Files);
242 for (
const auto &entry : entryList) {
246 QString title = QFileInfo(entry).completeBaseName();
247 title.replace(QLatin1Char(
'-'), QLatin1Char(
' '));
248 title.replace(QLatin1Char(
'_'), QLatin1Char(
' '));
249 QStringList words = title.split(QLatin1Char(
' '), Qt::SkipEmptyParts);
250 for (QString &word : words) {
251 word[0] = word[0].toUpper();
254 auto *action =
new QAction(words.join(QLatin1Char(
' ')),
this);
255 action->setData(examplesPath +
"/" + entry);
257 connect(action, &QAction::triggered,
this, [
this] {
258 if (
auto *senderAction = qobject_cast<QAction *>(sender())) {
263 m_ui->menuExamples->addAction(action);
267 if (m_ui->menuExamples->isEmpty()) {
268 m_ui->menuExamples->menuAction()->setVisible(
false);
272void MainWindow::populateContentMenu(QMenu *menu,
const QString &categoryKey,
273 const QString &openFolderText,
274 const QString &openFolderFailureText,
275 const QStringList &completed,
276 const std::function<
void(
const QString &)> &onSelect)
280 auto *openFolderAction =
new QAction(openFolderText, menu);
281 connect(openFolderAction, &QAction::triggered,
this, [
this, categoryKey, openFolderFailureText] {
282 const QString dir = m_preferredContentDirForTesting(categoryKey);
284 QMessageBox::warning(
this, tr(
"Error"), openFolderFailureText);
287 QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
289 menu->addAction(openFolderAction);
291 const QIcon checkIcon = style()->standardIcon(QStyle::SP_DialogApplyButton);
292 const QIcon circleIcon = style()->standardIcon(QStyle::SP_ArrowRight);
295 if (!entries.isEmpty()) {
296 menu->addSeparator();
298 for (
const ExerciseTourResourceEntry &entry : entries) {
300 title.replace(QLatin1Char(
'&'), QStringLiteral(
"&&"));
301 auto *action =
new QAction(title, menu);
302 action->setIcon(completed.contains(entry.id) ? checkIcon : circleIcon);
304 connect(action, &QAction::triggered,
this, [onSelect, path = entry.path] { onSelect(path); });
305 menu->addAction(action);
309void MainWindow::setupExercisesMenu()
311 connect(m_ui->menuExercises, &QMenu::aboutToShow,
this, [
this] {
312 populateContentMenu(m_ui->menuExercises,
"Exercises",
313 tr(
"Open My Exercises Folder"),
314 tr(
"Could not create or access a folder for custom exercises."),
315 Settings::completedExercises(),
316 [this](const QString &path) { startExercise(path); });
320void MainWindow::setupToursMenu()
322 connect(m_ui->menuTours, &QMenu::aboutToShow,
this, [
this] {
323 populateContentMenu(m_ui->menuTours,
"Tours",
324 tr(
"Open My Tours Folder"),
325 tr(
"Could not create or access a folder for custom tours."),
326 Settings::completedTours(),
327 [this](const QString &path) { startTour(path); });
331void MainWindow::setupShortcuts()
335 auto *searchShortcut =
new QShortcut(QKeySequence(
"Ctrl+F"),
this);
336 connect(searchShortcut, &QShortcut::activated, m_ui->lineEditSearch, qOverload<>(&QWidget::setFocus));
339void MainWindow::setupConnections()
344 connect(m_ui->actionAbout, &QAction::triggered,
this, &MainWindow::on_actionAbout_triggered);
345 connect(m_ui->actionAboutQt, &QAction::triggered,
this, &MainWindow::on_actionAboutQt_triggered);
346 connect(m_ui->actionAboutThisVersion, &QAction::triggered,
this, &MainWindow::aboutThisVersion);
347 connect(m_ui->actionReportTranslationError,&QAction::triggered,
this, &MainWindow::on_actionReportTranslationError_triggered);
349 connect(m_ui->actionDarkTheme, &QAction::triggered,
this, &MainWindow::on_actionDarkTheme_triggered);
350 connect(m_ui->actionSystemTheme, &QAction::triggered,
this, &MainWindow::on_actionSystemTheme_triggered);
351 connect(m_ui->actionExit, &QAction::triggered,
this, &MainWindow::on_actionExit_triggered);
356 connect(m_ui->actionFastMode, &QAction::triggered,
this, &MainWindow::on_actionFastMode_triggered);
357 connect(m_ui->actionFlipHorizontally, &QAction::triggered,
this, &MainWindow::on_actionFlipHorizontally_triggered);
358 connect(m_ui->actionFlipVertically, &QAction::triggered,
this, &MainWindow::on_actionFlipVertically_triggered);
359 connect(m_ui->actionAlignLeft, &QAction::triggered,
this, &MainWindow::on_actionAlignLeft_triggered);
360 connect(m_ui->actionAlignRight, &QAction::triggered,
this, &MainWindow::on_actionAlignRight_triggered);
361 connect(m_ui->actionAlignTop, &QAction::triggered,
this, &MainWindow::on_actionAlignTop_triggered);
362 connect(m_ui->actionAlignBottom, &QAction::triggered,
this, &MainWindow::on_actionAlignBottom_triggered);
363 connect(m_ui->actionAlignHorizontalCenter, &QAction::triggered,
this, &MainWindow::on_actionAlignHorizontalCenter_triggered);
364 connect(m_ui->actionAlignVerticalCenter, &QAction::triggered,
this, &MainWindow::on_actionAlignVerticalCenter_triggered);
365 connect(m_ui->actionDistributeHorizontally, &QAction::triggered,
this, &MainWindow::on_actionDistributeHorizontally_triggered);
366 connect(m_ui->actionDistributeVertically, &QAction::triggered,
this, &MainWindow::on_actionDistributeVertically_triggered);
367 connect(m_ui->actionFullscreen, &QAction::triggered,
this, &MainWindow::on_actionFullscreen_triggered);
368 connect(m_ui->actionGates, &QAction::triggered,
this, &MainWindow::on_actionGates_triggered);
369 connect(m_ui->actionLabelsUnderIcons, &QAction::triggered,
this, &MainWindow::on_actionLabelsUnderIcons_triggered);
370 connect(m_ui->actionICPreview, &QAction::triggered,
this, &MainWindow::on_actionICPreview_triggered);
371 connect(m_ui->actionCheckForUpdates, &QAction::triggered,
this, &MainWindow::on_actionCheckForUpdates_triggered);
372 connect(m_ui->actionShowMinimap, &QAction::triggered,
this, &MainWindow::on_actionShowMinimap_triggered);
373 connect(m_ui->actionLightTheme, &QAction::triggered,
this, &MainWindow::on_actionLightTheme_triggered);
374 connect(m_ui->actionMute, &QAction::triggered,
this, &MainWindow::on_actionMute_triggered);
377 connect(m_ui->actionPlay, &QAction::toggled,
this, &MainWindow::on_actionPlay_toggled);
384 openICInTab(blobName, icElementId, currentTab()->scene()->icRegistry()->blob(blobName));
390 connect(m_ui->actionResetZoom, &QAction::triggered,
this, &MainWindow::on_actionResetZoom_triggered);
391 connect(m_ui->actionZoomToFit, &QAction::triggered,
this, &MainWindow::on_actionZoomToFit_triggered);
392 connect(m_ui->actionRestart, &QAction::triggered,
this, &MainWindow::on_actionRestart_triggered);
393 connect(m_ui->actionRotateLeft, &QAction::triggered,
this, &MainWindow::on_actionRotateLeft_triggered);
394 connect(m_ui->actionRotateRight, &QAction::triggered,
this, &MainWindow::on_actionRotateRight_triggered);
397 connect(m_ui->actionSelectAll, &QAction::triggered,
this, &MainWindow::on_actionSelectAll_triggered);
398 connect(m_ui->actionShortcutsAndTips, &QAction::triggered,
this, &MainWindow::on_actionShortcuts_and_Tips_triggered);
399 connect(m_ui->actionWaveform, &QAction::triggered,
this, &MainWindow::on_actionWaveform_triggered);
400 connect(m_ui->actionWires, &QAction::triggered,
this, &MainWindow::on_actionWires_triggered);
401 connect(m_ui->actionZoomIn, &QAction::triggered,
this, &MainWindow::on_actionZoomIn_triggered);
402 connect(m_ui->actionZoomOut, &QAction::triggered,
this, &MainWindow::on_actionZoomOut_triggered);
404 auto *tab = currentTab();
411 auto *view = tab->view();
412 const QPointF center = view->mapToScene(view->viewport()->rect().center());
413 tab->scene()->addItem(mimeData, center);
422 [
this](
const QString &icFileName) {
423 Application::guardedSlot(m_icController, [this, &icFileName] {
424 m_icController->removeICFile(icFileName);
441 connectSceneAction(m_ui->actionClearSelection, &Scene::clearSelection);
449void MainWindow::connectSceneAction(QAction *action,
void (
Scene::*method)())
451 connect(action, &QAction::triggered,
this, [
this, method] {
466 disconnect(m_bwdDestroyed);
471 m_workspaceManager->createNewTab();
476 m_ui->actionFastMode->setChecked(fastMode);
483void MainWindow::on_actionExit_triggered()
492 m_workspaceManager->save(fileName);
499 qCDebug(zero) <<
"Checking for autosave file recovery.";
500 m_workspaceManager->loadAutosaveFiles();
503 updateController->checkForUpdates();
509 startTour(QStringLiteral(
":/Tours/ui-overview.json"));
513void MainWindow::aboutThisVersion()
516 msgBox.setParent(
this);
517 msgBox.setStandardButtons(QMessageBox::Ok);
518 msgBox.setIcon(QMessageBox::Icon::Information);
519 msgBox.setWindowTitle(
"wiRedPanda " APP_VERSION);
521 tr(
"wiRedPanda %1\n\n"
522 "This version includes automatic migration of older project files.\n"
523 "When you open a project file older than the current version, it will be automatically "
524 "upgraded to the current format and a versioned backup will be created.\n\n"
525 "To open projects containing ICs (or boxes), appearances, and/or beWavedDolphin simulations, "
526 "their files must be in the same directory as the main project file.\n"
527 "wiRedPanda %1 will automatically list all other .panda files located "
528 "in the same directory as the current project as ICs in the editor tab.\n"
529 "You have to save new projects before accessing ICs and appearances, or running "
530 "beWavedDolphin simulations.").arg(APP_VERSION));
531 msgBox.setWindowModality(Qt::WindowModal);
532 msgBox.setDefaultButton(QMessageBox::Ok);
537void MainWindow::on_actionWires_triggered(
const bool checked)
547void MainWindow::on_actionRotateRight_triggered()
556void MainWindow::on_actionRotateLeft_triggered()
567 m_workspaceManager->loadPandaFile(fileName);
572 m_workspaceManager->openICInTab(blobName, icElementId, blob);
575void MainWindow::on_actionAbout_triggered()
581 tr(
"<p>wiRedPanda is software developed by students of the Federal University of São Paulo"
582 " to help students learn about logic circuits.</p>"
583 "<p>Software version: %1</p>"
584 "<p><strong>Creators:</strong></p>"
586 "<li> Davi Morales </li>"
587 "<li> Lucas Lellis </li>"
588 "<li> Rodrigo Torres </li>"
589 "<li> Prof. Fábio Cappabianco, Ph.D. </li>"
591 "<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>"
592 "<p> Please file a report at our GitHub page if you find a bug or want to request a new feature.</p>"
593 "<p><a href=\"https://gibis-unifesp.github.io/wiRedPanda/\">Visit our website!</a></p>")
594 .arg(QApplication::applicationVersion()));
601 QMap<QString, QString> byLabel;
605 byLabel.insert(tr(
"Redo"), QKeySequence(QKeySequence::Redo).toString(QKeySequence::NativeText));
606 byLabel.insert(tr(
"Undo"), QKeySequence(QKeySequence::Undo).toString(QKeySequence::NativeText));
608 const auto actions = findChildren<QAction *>();
609 for (
const auto *action : actions) {
610 const QKeySequence seq = action->shortcut();
614 QString label = action->text();
615 label.remove(QLatin1Char(
'&'));
616 if (label.endsWith(QLatin1String(
"..."))) {
619 label = label.trimmed();
620 if (!label.isEmpty()) {
621 byLabel.insert(label, seq.toString(QKeySequence::NativeText));
626 for (
auto it = byLabel.cbegin(); it != byLabel.cend(); ++it) {
627 rows += QStringLiteral(
"<tr><td><b>%1</b> </td><td>%2</td></tr>")
628 .arg(it.value().toHtmlEscaped(), it.key().toHtmlEscaped());
631 return tr(
"<h1>Keyboard Shortcuts</h1>"
633 "<h1>Element Property Navigation</h1>"
634 "<ul style=\"list-style:none;\">"
635 "<li> [ / ] : Previous / next primary property </li>"
636 "<li> { / } : Previous / next secondary property </li>"
637 "<li> < / > : Morph to previous / next element </li>"
639 "<h1>General Tips</h1>"
640 "<ul style=\"list-style:none;\">"
641 "<li> Double-click a wire to create a node </li>"
642 "<li> Drag an element from the left panel onto the canvas to add it </li>"
643 "<li> Nudge the selection with the arrow keys (hold Shift for larger steps) </li>"
644 "<li> Drop a .panda file onto the canvas to open it </li>"
649void MainWindow::on_actionShortcuts_and_Tips_triggered()
656void MainWindow::on_actionAboutQt_triggered()
659 QMessageBox::aboutQt(
this);
663void MainWindow::on_actionReportTranslationError_triggered()
666 QDesktopServices::openUrl(QUrl(
"https://hosted.weblate.org/projects/wiredpanda/wiredpanda"));
672 bool closeWindow =
false;
677 if (!m_workspaceManager->hasModifiedFiles()) {
679 QMessageBox::question(
681 tr(
"Exit") +
" " + QApplication::applicationName(),
683 QMessageBox::Cancel | QMessageBox::Yes,
686 if (reply == QMessageBox::Yes) {
689 }
else if (m_workspaceManager->closeFiles()) {
702void MainWindow::updateSettings()
712 m_ui->pushButtonAddIC->setVisible(visible);
713 m_ui->pushButtonRemoveIC->setVisible(visible);
714 m_ui->pushButtonMakeSelfContained->setVisible(visible);
723 m_ui->pushButtonAddIC->setEnabled(hasFile);
728 return m_workspaceManager->currentFile();
733 return m_workspaceManager->hasModifiedFiles();
738 return m_workspaceManager->currentDir();
758 m_workspaceManager->saveFile();
763 m_ui->statusBar->showMessage(message, timeout);
771void MainWindow::on_actionSelectAll_triggered()
785 return m_workspaceManager->currentTab();
788void MainWindow::onCurrentTabChanged(
WorkSpace *newTab)
796 m_previousTab = newTab;
801 m_ui->elementEditor->hide();
807 if (m_exerciseOverlay && m_exerciseEngine && m_exerciseEngine->isActive()) {
808 m_exerciseOverlay->hide();
809 m_exerciseOverlay->setParent(
nullptr);
814 m_palette->updateICList(QFileInfo());
815 m_palette->updateEmbeddedICList(
nullptr);
820 m_binder->bind(newTab);
833 if (m_exerciseEngine && m_exerciseEngine->isActive() && m_exerciseOverlay) {
834 m_exerciseEngine->setScene(newTab->
scene());
835 m_exerciseOverlay->setParent(newTab);
837 m_exerciseOverlay->repositionToParent();
838 m_exerciseOverlay->show();
839 m_exerciseOverlay->raise();
845void MainWindow::updateWindowTitle()
849 setWindowTitle(QStringLiteral(
"wiRedPanda " APP_VERSION));
850 setWindowModified(
false);
856 setWindowTitle(tr(
"%1[*] — wiRedPanda %2")
857 .arg(m_workspaceManager->currentTabName(), QStringLiteral(APP_VERSION)));
858 setWindowModified(!tab->scene()->undoStack()->isClean());
861void MainWindow::on_actionGates_triggered(
const bool checked)
872 m_ui->actionWires->setEnabled(checked);
880 m_exportController->exportToArduino(std::move(fileName));
885 m_exportController->exportToSystemVerilog(std::move(fileName));
890 m_exportController->exportToWaveFormFile(fileName);
895 m_exportController->exportToWaveFormTerminal();
898void MainWindow::on_actionZoomIn_triggered()
const
909void MainWindow::on_actionZoomOut_triggered()
const
920void MainWindow::on_actionResetZoom_triggered()
const
931void MainWindow::on_actionZoomToFit_triggered()
const
942void MainWindow::updateRecentFileActions()
944 const auto files = m_recentFiles->recentFiles();
947 if (numRecentFiles > 0) {
948 m_ui->menuRecentFiles->setEnabled(
true);
951 auto actions = m_ui->menuRecentFiles->actions();
956 for (
int i = 0; i < numRecentFiles; ++i) {
957 const QString text =
"&" + QString::number(i + 1) +
" " + QFileInfo(files.at(i)).fileName();
958 actions.at(i)->setText(text);
959 actions.at(i)->setData(files.at(i));
960 actions.at(i)->setVisible(
true);
964 actions.at(i)->setVisible(
false);
968void MainWindow::openRecentFile()
970 if (
auto *action = qobject_cast<QAction *>(sender())) {
976void MainWindow::createRecentFileActions()
978 m_ui->menuRecentFiles->clear();
981 auto *action =
new QAction(
this);
982 action->setVisible(
false);
983 connect(action, &QAction::triggered,
this, &MainWindow::openRecentFile);
984 m_ui->menuRecentFiles->addAction(action);
987 updateRecentFileActions();
992 m_ui->retranslateUi();
993 m_ui->elementEditor->retranslateUi();
994 m_palette->retranslateLabels();
996 for (
int index = 0; index < m_ui->tab->count(); ++index) {
997 auto *workspace = qobject_cast<WorkSpace *>(m_ui->tab->widget(index));
1001 auto *scene = workspace->scene();
1005 auto *undoStack = scene->undoStack();
1010 if (workspace->isInlineIC()) {
1011 text =
"[" + workspace->inlineBlobName() +
"]";
1013 auto fileInfo = workspace->fileInfo();
1014 text = fileInfo.exists() ? fileInfo.fileName() : tr(
"New Project");
1017 if (!undoStack->isClean()) {
1021 m_ui->tab->setTabText(index, text);
1023 scene->retranslateUi();
1025 for (
auto *elm : workspace->scene()->elements()) {
1030 if (m_exerciseEngine->isActive()) {
1031 m_exerciseEngine->retranslate();
1033 if (m_tourEngine->isActive()) {
1034 m_tourEngine->retranslate();
1040 m_languageManager->loadTranslation(language);
1045 m_ui->menuLanguage->clear();
1047 auto *languageGroup =
new QActionGroup(
this);
1048 languageGroup->setExclusive(
true);
1050 for (
const QString &langCode : m_languageManager->availableLanguages()) {
1051 auto *action =
new QAction(m_languageManager->displayName(langCode),
this);
1052 action->setCheckable(
true);
1053 action->setData(langCode);
1054 action->setIcon(QIcon(m_languageManager->flagIcon(langCode)));
1057 action->setChecked(
true);
1060 languageGroup->addAction(action);
1061 m_ui->menuLanguage->addAction(action);
1063 connect(action, &QAction::triggered,
this, [
this, langCode]() {
1064 m_languageManager->loadTranslation(langCode);
1069void MainWindow::on_actionPlay_toggled(
const bool checked)
1071 sentryBreadcrumb(
"simulation", QStringLiteral(
"Play toggled: %1").arg(checked));
1079 checked ? simulation->
start() : simulation->stop();
1082void MainWindow::on_actionRestart_triggered()
1094void MainWindow::on_actionFastMode_triggered(
const bool checked)
1103void MainWindow::on_actionWaveform_triggered()
1108 m_bwd->activateWindow();
1117 auto *bwd =
new BewavedDolphin(
currentTab()->scene(),
true,
this,
this);
1120 m_bwdDestroyed = connect(bwd, &QObject::destroyed,
this, [
this] {
1121 if (m_exerciseOverlay && m_exerciseEngine && m_exerciseEngine->isActive()) {
1122 m_exerciseOverlay->hide();
1123 m_exerciseOverlay->setParent(
nullptr);
1130void MainWindow::on_actionLightTheme_triggered()
1138void MainWindow::on_actionDarkTheme_triggered()
1146void MainWindow::on_actionSystemTheme_triggered()
1154void MainWindow::updateTheme()
1157 case Theme::Dark: m_ui->actionDarkTheme->setChecked(
true);
break;
1158 case Theme::Light: m_ui->actionLightTheme->setChecked(
true);
break;
1159 case Theme::System: m_ui->actionSystemTheme->setChecked(
true);
break;
1162 m_palette->updateTheme();
1163 m_ui->elementEditor->updateTheme();
1166void MainWindow::on_actionFlipHorizontally_triggered()
1177void MainWindow::on_actionFlipVertically_triggered()
1188void MainWindow::on_actionAlignLeft_triggered()
1199void MainWindow::on_actionAlignRight_triggered()
1210void MainWindow::on_actionAlignTop_triggered()
1221void MainWindow::on_actionAlignBottom_triggered()
1232void MainWindow::on_actionAlignHorizontalCenter_triggered()
1243void MainWindow::on_actionAlignVerticalCenter_triggered()
1254void MainWindow::on_actionDistributeHorizontally_triggered()
1265void MainWindow::on_actionDistributeVertically_triggered()
1278 return m_workspaceManager->dolphinFileName();
1283 m_workspaceManager->setDolphinFileName(fileName);
1286void MainWindow::on_actionFullscreen_triggered()
1290 isFullScreen() ? showNormal() : showFullScreen();
1294void MainWindow::on_actionMute_triggered(
const bool checked)
1303 m_ui->actionMute->setText(checked ? tr(
"Unmute") : tr(
"Mute"));
1307void MainWindow::on_actionLabelsUnderIcons_triggered(
const bool checked)
1310 sentryBreadcrumb(
"ui", QStringLiteral(
"Labels under icons: %1").arg(checked));
1311 m_ui->mainToolBar->setToolButtonStyle(checked ? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly);
1316void MainWindow::on_actionICPreview_triggered(
const bool checked)
1324void MainWindow::on_actionCheckForUpdates_triggered(
const bool checked)
1327 sentryBreadcrumb(
"ui", QStringLiteral(
"Auto update checks: %1").arg(checked));
1332void MainWindow::on_actionShowMinimap_triggered(
const bool checked)
1343 switch (
event->type()) {
1344 case QEvent::WindowActivate: {
1346 if (m_ui->actionPlay->isChecked()) {
1347 on_actionPlay_toggled(
true);
1352 case QEvent::WindowDeactivate: {
1355 if (!m_ui->actionBackground_Simulation->isChecked()) {
1356 on_actionPlay_toggled(
false);
1364 return QMainWindow::event(
event);
1367void MainWindow::startExercise(
const QString &resourcePath)
1369 if (!
currentTab() || resourcePath.isEmpty()) {
1372 if (!m_exerciseEngine->loadFromResource(resourcePath)) {
1373 qWarning() <<
"ExerciseEngine: failed to load" << resourcePath;
1380 delete m_exerciseOverlay;
1381 m_exerciseOverlay =
new ExerciseOverlay(m_exerciseEngine,
currentTab());
1390 [
this](
int,
int,
const ExerciseStep &step) {
1391 for (
const QString &
id : step.
click) clickTarget(
id);
1393 if (!m_exerciseOverlay) {
1396 const bool wantBwd = (step.
context ==
"bwd");
1397 if (wantBwd && m_bwd) {
1398 m_exerciseOverlay->setParent(m_bwd->centralWidget());
1399 m_bwd->setExerciseOverlay(m_exerciseOverlay);
1407 m_bwd->setExerciseOverlay(
nullptr);
1410 m_exerciseOverlay->repositionToParent();
1411 m_exerciseOverlay->show();
1412 m_exerciseOverlay->raise();
1421 if (!m_exerciseOverlay)
return;
1422 ExerciseOverlay *overlay = m_exerciseOverlay;
1423 m_exerciseOverlay =
nullptr;
1424 m_exerciseEngine->stop();
1429 m_bwd->setExerciseOverlay(
nullptr);
1431 overlay->deleteLater();
1434 auto *esc =
new QShortcut(QKeySequence(Qt::Key_Escape), m_exerciseOverlay);
1438 m_exerciseEngine->setScene(
currentTab()->scene());
1439 m_exerciseEngine->start();
1441 m_exerciseOverlay->repositionToParent();
1442 m_exerciseOverlay->show();
1443 m_exerciseOverlay->raise();
1446void MainWindow::startTour(
const QString &resourcePath)
1448 if (resourcePath.isEmpty()) {
1451 if (!m_tourEngine->loadFromResource(resourcePath)) {
1452 qWarning() <<
"TourEngine: failed to load" << resourcePath;
1456 delete m_tourOverlay;
1457 m_tourOverlay =
new TourOverlay(m_tourEngine,
this);
1458 m_tourOverlay->setTargetResolver([
this](
const QString &
id) {
1459 return resolveTourTarget(
id);
1470 [
this](
int,
int,
const TourStep &step) {
1471 for (
const QString &
id : step.
click) clickTarget(
id);
1473 TourOverlay *overlay = m_tourOverlay;
1477 const bool wantBwd = step.
target.startsWith(
"bwd:");
1478 if (wantBwd && m_bwd) {
1479 if (overlay->parentWidget() != m_bwd) {
1484 }
else if (!wantBwd && overlay->parentWidget() !=
this) {
1499 if (!m_tourOverlay)
return;
1500 TourOverlay *overlay = m_tourOverlay;
1501 m_tourOverlay =
nullptr;
1502 m_tourEngine->stop();
1503 overlay->deleteLater();
1506 auto *esc =
new QShortcut(QKeySequence(Qt::Key_Escape), m_tourOverlay);
1509 m_tourEngine->start();
1511 m_tourOverlay->show();
1512 m_tourOverlay->raise();
1515QRect MainWindow::resolveTourTarget(
const QString &
id)
const
1517 if (!m_tourOverlay ||
id.isEmpty() ||
id ==
"none") {
1521 auto mapWidget = [
this](QWidget *w) -> QRect {
1523 const QPoint topLeft = m_tourOverlay->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)));
1524 return QRect(topLeft, w->size());
1527 if (
id ==
"toolbar")
return mapWidget(m_ui->mainToolBar->widgetForAction(m_ui->actionWaveform));
1528 if (
id ==
"elementPalette")
return mapWidget(m_ui->tabElements);
1529 if (
id ==
"gatesTab") {
1530 QTabBar *bar = m_ui->tabElements->tabBar();
1531 const QRect tabRect = bar->tabRect(1);
1532 const QPoint topLeft = m_tourOverlay->mapFromGlobal(bar->mapToGlobal(tabRect.topLeft()));
1533 return QRect(topLeft, tabRect.size());
1535 if (
id ==
"ioTab") {
1536 QTabBar *bar = m_ui->tabElements->tabBar();
1537 const QRect tabRect = bar->tabRect(0);
1538 const QPoint topLeft = m_tourOverlay->mapFromGlobal(bar->mapToGlobal(tabRect.topLeft()));
1539 return QRect(topLeft, tabRect.size());
1542 if (
id ==
"elementEditor")
return mapWidget(m_ui->elementEditor);
1543 if (
id ==
"searchBar")
return mapWidget(m_ui->lineEditSearch);
1545 BewavedDolphin *bwd = m_bwd;
1546 if (
id.startsWith(
"bwd:") && bwd) {
1547 auto mapBwd = [
this](QWidget *w) -> QRect {
1548 if (!w || !m_tourOverlay) {
1551 const QPoint tl = m_tourOverlay->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)));
1552 return QRect(tl, w->size());
1554 const QString sub =
id.sliced(4);
1557 if (sub ==
"menuBar")
return mapBwd(bwd->menuBar());
1564void MainWindow::clickTarget(
const QString &
id)
1566 if (
id ==
"ioTab") m_ui->tabElements->setCurrentIndex(0);
1567 else if (
id ==
"gatesTab") m_ui->tabElements->setCurrentIndex(1);
1568 else if (
id ==
"combinational") m_ui->tabElements->setCurrentIndex(2);
1569 else if (
id ==
"memoryTab") m_ui->tabElements->setCurrentIndex(3);
1570 else if (
id ==
"actionPlay") m_ui->actionPlay->trigger();
1571 else if (
id ==
"actionWaveform") m_ui->actionWaveform->trigger();
1572 else if (
id ==
"bwd:actionCombinational" && m_bwd) m_bwd->triggerCombinational();
1573 else if (
id ==
"setupElementEditorDemo") {
1578 scene->clearSelection();
1585 else if (
id ==
"setupWaveformDemo") {
1590 scene->clearSelection();
1595 clock1->setPos(-160, -60);
1596 clock2->setPos(-160, 60);
1598 led->setPos(160, 0);
1600 auto *conn1 =
new Connection();
1601 conn1->setStartPort(clock1->outputPort(0));
1602 conn1->setEndPort(gate->inputPort(0));
1603 auto *conn2 =
new Connection();
1604 conn2->setStartPort(clock2->outputPort(0));
1605 conn2->setEndPort(gate->inputPort(1));
1606 auto *conn3 =
new Connection();
1607 conn3->setStartPort(gate->outputPort(0));
1608 conn3->setEndPort(led->inputPort(0));
1613 scene->
receiveCommand(
new AddItemsCommand({clock1, clock2, gate, led}, scene));
1615 conn1->updatePath();
1616 conn2->updatePath();
1617 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.
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 GraphicElement * buildElement(const ElementType type)
Constructs and returns a new graphic element of the given type.
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 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 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.