wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
SceneUiBinder.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QAction>
7#include <QKeySequence>
8#include <QLabel>
9#include <QMenu>
10#include <QShortcut>
11#include <QStatusBar>
12#include <QUndoStack>
13
14#include "App/Core/Common.h"
15#include "App/Element/IC.h"
19#include "App/Scene/Scene.h"
20#include "App/Scene/Workspace.h"
24#include "App/UI/MainWindowUI.h"
25
26SceneUiBinder::SceneUiBinder(MainWindowUi *ui, ElementPalette *palette, ICPreviewPopup *previewPopup, QWidget *shortcutParent, QObject *parent)
27 : QObject(parent)
28 , m_ui(ui)
29 , m_palette(palette)
30 , m_previewPopup(previewPopup)
31{
32 // [ / ] cycle a selected element's primary property (e.g. input size).
33 // { / } cycle a secondary property; < / > morph through element variants.
34 // They are connected to the active scene in bind() so they follow the current tab.
35 m_prevMainPropShortcut = new QShortcut(QKeySequence("["), shortcutParent);
36 m_nextMainPropShortcut = new QShortcut(QKeySequence("]"), shortcutParent);
37 m_prevSecndPropShortcut = new QShortcut(QKeySequence("{"), shortcutParent);
38 m_nextSecndPropShortcut = new QShortcut(QKeySequence("}"), shortcutParent);
39 m_changePrevElmShortcut = new QShortcut(QKeySequence("<"), shortcutParent);
40 m_changeNextElmShortcut = new QShortcut(QKeySequence(">"), shortcutParent);
41
42 // Permanent right-aligned status-bar indicator (zoom % + selection count), refreshed per
43 // bound tab. Transient showMessage() text still appears to its left.
44 m_statusInfo = new QLabel;
45 m_statusInfo->setObjectName("statusInfoLabel");
46 m_ui->statusBar->addPermanentWidget(m_statusInfo);
47}
48
49void SceneUiBinder::updateStatusInfo()
50{
51 if (!m_bound || !m_bound->scene() || !m_bound->view()) {
52 m_statusInfo->clear();
53 return;
54 }
55
56 auto *scene = m_bound->scene();
57 const int zoomPct = qRound(m_bound->view()->transform().m11() * 100.0);
58 const int selected = static_cast<int>(scene->selectedElements().size());
59 const int total = static_cast<int>(scene->elements().size());
60 m_statusInfo->setText(tr("Zoom: %1% Selected: %2 / %3").arg(zoomPct).arg(selected).arg(total));
61}
62
63void SceneUiBinder::addUndoRedoMenu()
64{
65 if (!m_bound) {
66 return;
67 }
68
69 auto *scene = m_bound->scene();
70 if (!scene) {
71 return;
72 }
73
74 const auto actions = m_ui->menuEdit->actions();
75 if (actions.size() < 2) {
76 return;
77 }
78
79 // Insert before position 0 then before the new position 1 so undo appears
80 // first, redo second — above the separator that already exists in menuEdit.
81 m_ui->menuEdit->insertAction(actions.at(0), scene->undoAction());
82 m_ui->menuEdit->insertAction(m_ui->menuEdit->actions().at(1), scene->redoAction());
83
84 // The same per-scene actions also drive the toolbar, in front of the transform group
85 // (a QAction can back several widgets, so both stay in sync with the active tab's stack).
86 m_ui->mainToolBar->insertAction(m_ui->actionRotateLeft, scene->undoAction());
87 m_ui->mainToolBar->insertAction(m_ui->actionRotateLeft, scene->redoAction());
88}
89
90void SceneUiBinder::removeUndoRedoMenu()
91{
92 if (!m_bound) {
93 return;
94 }
95
96 // The undo/redo actions are always inserted at positions 0 and 1 of menuEdit.
97 // Any fewer entries means they were never added, so nothing to remove.
98 const auto actions = m_ui->menuEdit->actions();
99 if (actions.size() < 2) {
100 return;
101 }
102 m_ui->menuEdit->removeAction(actions.at(0));
103 m_ui->menuEdit->removeAction(actions.at(1));
104
105 // Remove the same actions from the toolbar (by identity, so position is irrelevant).
106 if (auto *scene = m_bound->scene()) {
107 m_ui->mainToolBar->removeAction(scene->undoAction());
108 m_ui->mainToolBar->removeAction(scene->redoAction());
109 }
110}
111
112void SceneUiBinder::syncZoomActions()
113{
114 if (!m_bound) {
115 return;
116 }
117
118 m_ui->actionZoomIn->setEnabled(m_bound->view()->canZoomIn());
119 m_ui->actionZoomOut->setEnabled(m_bound->view()->canZoomOut());
120}
121
123{
124 m_bound = tab;
125 auto *scene = tab->scene();
126
127 qCDebug(zero) << "Connecting undo and redo functions to UI menu.";
128 addUndoRedoMenu();
129
130 m_palette->updateEmbeddedICList(scene);
131
132 qCDebug(zero) << "Connecting current tab to element editor menu in UI.";
133 m_ui->elementEditor->setScene(scene);
134
135 connect(tab->view(), &GraphicsView::zoomChanged, this, &SceneUiBinder::syncZoomActions);
136 connect(tab->view(), &GraphicsView::zoomChanged, this, &SceneUiBinder::updateStatusInfo);
137 connect(scene, &QGraphicsScene::selectionChanged, this, &SceneUiBinder::updateStatusInfo);
138 connect(scene, &Scene::circuitHasChanged, this, &SceneUiBinder::updateStatusInfo);
139 connect(tab->simulation(), &Simulation::simulationWarning, this, [this](const QString &msg) {
140 m_ui->statusBar->showMessage(msg, 8000);
141 });
142 connect(scene, &Scene::showStatusMessageRequested, this, [this](const QString &msg) {
143 m_ui->statusBar->showMessage(msg, 5000);
144 });
145 connect(scene->undoStack(), &QUndoStack::indexChanged, this, [this] {
146 if (m_bound) {
147 m_palette->updateEmbeddedICList(m_bound->scene());
148 }
149 });
150
151 connect(m_prevMainPropShortcut, &QShortcut::activated, scene, &Scene::prevMainPropShortcut);
152 connect(m_nextMainPropShortcut, &QShortcut::activated, scene, &Scene::nextMainPropShortcut);
153 connect(m_prevSecndPropShortcut, &QShortcut::activated, scene, &Scene::prevSecndPropShortcut);
154 connect(m_nextSecndPropShortcut, &QShortcut::activated, scene, &Scene::nextSecndPropShortcut);
155 connect(m_changePrevElmShortcut, &QShortcut::activated, scene, &Scene::prevElm);
156 connect(m_changeNextElmShortcut, &QShortcut::activated, scene, &Scene::nextElm);
157 connect(scene, &Scene::openTruthTableRequested, this, [this] {
158 m_ui->elementEditor->truthTable();
159 });
160 connect(scene, &Scene::icOpenRequested, this, [this](int elementId, const QString &blobName, const QString &filePath) {
161 if (!blobName.isEmpty()) {
162 if (m_bound) {
163 emit openICRequested(blobName, elementId, m_bound->scene()->icRegistry()->blob(blobName));
164 }
165 } else if (!filePath.isEmpty()) {
166 emit loadFileRequested(filePath);
167 }
168 });
169 connect(scene, &Scene::fileDropRequested, this, [this](const QString &filePath) {
170 emit loadFileRequested(filePath);
171 });
172 connect(scene, &Scene::icPreviewRequested, this, [this](IC *ic, const QPoint &screenPos) {
173 m_previewPopup->showForIC(ic, screenPos);
174 });
175 connect(scene, &Scene::icPreviewMoved, this, [this](IC *ic, const QPoint &screenPos) {
176 // Keep tracking the cursor while a show is pending, but re-arm the show
177 // when the cursor returns from a port within the same hover.
178 if (m_previewPopup->isShowActiveFor(ic)) {
179 m_previewPopup->updatePendingPos(screenPos);
180 } else {
181 m_previewPopup->showForIC(ic, screenPos);
182 }
183 });
184 connect(scene, &Scene::icPreviewHideRequested, this, [this] {
185 m_previewPopup->scheduleHide();
186 });
187 connect(scene, &Scene::icPreviewCancelRequested, this, [this](IC *ic) {
188 Q_UNUSED(ic)
189 // Unconditional, matching the pre-signal direct-call behavior: double-clicking
190 // any IC hides the one shared popup regardless of which IC it was pending/showing
191 // for (harmless no-op if it wasn't visible). A pendingIC()-gated version would
192 // leave a stale preview on screen if the user switched tabs while a popup from
193 // another tab's IC was still pending/visible (tab switch never touches the popup).
194 m_previewPopup->cancelHide();
195 m_previewPopup->hide();
196 });
197
198 if (m_ui->actionPlay->isChecked()) {
199 qCDebug(zero) << "Restarting simulation.";
200 tab->simulation()->start();
201 // Clear selection so the element editor doesn't show stale data from the
202 // previously active tab.
203 scene->clearSelection();
204 }
205
206 tab->view()->setFastMode(m_ui->actionFastMode->isChecked());
207 // Synchronise zoom button state to the newly visible tab's zoom level.
208 m_ui->actionZoomIn->setEnabled(tab->view()->canZoomIn());
209 m_ui->actionZoomOut->setEnabled(tab->view()->canZoomOut());
210
211 // Synchronise the mute button state to the newly visible tab's mute intent.
212 const bool muted = tab->simulation()->isUserMuted();
213 m_ui->actionMute->setChecked(muted);
214 m_ui->actionMute->setText(muted ? tr("Unmute") : tr("Mute"));
215
216 updateStatusInfo();
217}
218
220{
221 // Tear down all connections that route scene signals into shared UI elements, and
222 // stop the simulation so it doesn't keep running in the background consuming CPU.
223 if (!m_bound) {
224 return;
225 }
226
227 auto *scene = m_bound->scene();
228
229 m_ui->elementEditor->setScene(nullptr);
230
231 qCDebug(zero) << "Stopping simulation.";
232 m_bound->simulation()->stop();
233
234 qCDebug(zero) << "Disconnecting zoom and simulation signals from UI.";
235 disconnect(m_bound->view(), &GraphicsView::zoomChanged, this, &SceneUiBinder::syncZoomActions);
236 disconnect(m_bound->view(), &GraphicsView::zoomChanged, this, &SceneUiBinder::updateStatusInfo);
237 disconnect(scene, &QGraphicsScene::selectionChanged, this, &SceneUiBinder::updateStatusInfo);
238 disconnect(scene, &Scene::circuitHasChanged, this, &SceneUiBinder::updateStatusInfo);
239 disconnect(m_bound->simulation(), &Simulation::simulationWarning, this, nullptr);
240 disconnect(scene, &Scene::showStatusMessageRequested, this, nullptr);
241
242 qCDebug(zero) << "Disconnecting scene shortcuts from previous tab.";
243 disconnect(m_prevMainPropShortcut, nullptr, scene, nullptr);
244 disconnect(m_nextMainPropShortcut, nullptr, scene, nullptr);
245 disconnect(m_prevSecndPropShortcut, nullptr, scene, nullptr);
246 disconnect(m_nextSecndPropShortcut, nullptr, scene, nullptr);
247 disconnect(m_changePrevElmShortcut, nullptr, scene, nullptr);
248 disconnect(m_changeNextElmShortcut, nullptr, scene, nullptr);
249 disconnect(scene, &Scene::openTruthTableRequested, this, nullptr);
250 disconnect(scene, &Scene::icOpenRequested, this, nullptr);
251 disconnect(scene, &Scene::icPreviewRequested, this, nullptr);
252 disconnect(scene, &Scene::icPreviewMoved, this, nullptr);
253 disconnect(scene, &Scene::icPreviewHideRequested, this, nullptr);
254 disconnect(scene, &Scene::icPreviewCancelRequested, this, nullptr);
255 disconnect(scene->undoStack(), &QUndoStack::indexChanged, this, nullptr);
256
257 qCDebug(zero) << "Removing undo and redo actions from UI menu.";
258 removeUndoRedoMenu();
259
260 m_bound = nullptr;
261 m_statusInfo->clear(); // no tab bound → no zoom/selection to report
262}
Common logging utilities, the Pandaception error type, and helper macros.
#define qCDebug(category)
Definition Common.h:29
ElementEditor widget for inspecting and modifying selected circuit element properties.
ElementPalette: manages the left-panel element palette and search UI.
Extended QGraphicsView with zoom, pan, and fast-rendering modes.
Frameless popup widget that shows a preview of an IC's internal circuit.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
MainWindowUi: hand-written UI class for the MainWindow.
SceneUiBinder: wires the active tab's scene into the shared editor chrome.
Main circuit editing scene with undo/redo and user interaction.
Synchronous cycle-based simulation engine with event-driven clock support.
WorkSpace widget: the complete circuit editing environment for one tab.
Controller for the left-panel element palette, IC list, and search tab.
void zoomChanged()
Emitted whenever the zoom level changes.
Frameless tooltip-like widget showing a rendered preview of an IC's sub-circuit.
Graphic element representing an Integrated Circuit (sub-circuit) box.
Definition IC.h:31
SceneUiBinder(MainWindowUi *ui, ElementPalette *palette, ICPreviewPopup *previewPopup, QWidget *shortcutParent, QObject *parent=nullptr)
void bind(WorkSpace *tab)
Connects tab's scene/view/simulation to the chrome and syncs action state.
void unbind()
Tears down the connections established by bind() for the currently bound tab.
void icPreviewRequested(IC *ic, const QPoint &screenPos)
void fileDropRequested(const QString &filePath)
Emitted when a .panda file is dropped onto the canvas from the file manager.
void nextSecndPropShortcut()
Advances the secondary property of selected elements to the next value.
Definition Scene.cpp:693
void showStatusMessageRequested(const QString &message)
void prevElm()
Cycles selection backward to the previous element.
Definition Scene.cpp:697
void icOpenRequested(int elementId, const QString &blobName, const QString &filePath)
Emitted when an IC element is double-clicked to request opening its sub-circuit.
void icPreviewMoved(IC *ic, const QPoint &screenPos)
void icPreviewCancelRequested(IC *ic)
void prevMainPropShortcut()
Retreats the main property of selected elements to the previous value.
Definition Scene.cpp:687
void prevSecndPropShortcut()
Retreats the secondary property of selected elements to the previous value.
Definition Scene.cpp:691
void nextMainPropShortcut()
Advances the main property of selected elements to the next value.
Definition Scene.cpp:689
void nextElm()
Cycles selection forward to the next element.
Definition Scene.cpp:695
void icPreviewHideRequested()
void circuitHasChanged()
Emitted whenever the circuit changes (element added/removed/moved).
void openTruthTableRequested()
Emitted when a TruthTable element is double-clicked to request opening its editor.
void simulationWarning(const QString &message)
Emitted (at most once per initialize()) when a feedback circuit fails to converge.
A widget containing a complete circuit editing environment.
Definition Workspace.h:33
Scene * scene()
Returns the Scene embedded in this workspace.
Simulation * simulation()
Returns the embedded Simulation.
GraphicsView * view()
Returns the GraphicsView embedded in this workspace.