wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
PropertyShortcutHandler.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 <algorithm>
7
8#include "App/Core/Enums.h"
12#include "App/Scene/Commands.h"
13#include "App/Scene/Scene.h"
14
16 : m_scene(scene)
17{
18}
19
20void PropertyShortcutHandler::applyWithUndo(GraphicElement *element, const std::function<void()> &mutate)
21{
22 // Wraps a direct property mutation in an UpdateCommand (F37) so the
23 // [ / ] shortcuts are undoable and mark the circuit dirty, exactly like
24 // the port-size path below and the property editor.
25 QByteArray oldData;
26 {
27 QDataStream stream(&oldData, QIODevice::WriteOnly);
29 element->save(stream, {.purpose = SerializationPurpose::InMemorySnapshot});
30 }
31 mutate();
32 m_scene->receiveCommand(new UpdateCommand({element}, oldData, m_scene));
33}
34
35void PropertyShortcutHandler::adjustMainProperty(int dir)
36{
37 // dir = -1 for prev, +1 for next
38 // Cycles the "primary" configurable property of each selected element:
39 // input count for logic gates, output count for rotary inputs,
40 // clock frequency (0.5 Hz step), buzzer frequency (100 Hz step), or display color.
41 const auto selected = m_scene->selectedElements();
42 const bool needsMacro = selected.size() > 1;
43 if (needsMacro) {
44 m_scene->undoStack()->beginMacro(tr("Cycle element properties"));
45 }
46
47 for (auto *element : selected) {
48 switch (element->elementType()) {
49 // Logic Elements
50 case ElementType::And:
51 case ElementType::Or:
52 case ElementType::Nand:
53 case ElementType::Nor:
54 case ElementType::Xor:
55 case ElementType::Xnor:
56 // Output and truthtable
57 case ElementType::Led:
58 case ElementType::TruthTable: {
59 const int newSize = element->inputSize() + dir;
60 if (newSize >= element->minInputSize() && newSize <= element->maxInputSize())
61 m_scene->receiveCommand(new ChangePortSizeCommand(QList<GraphicElement *>{element},
62 newSize, m_scene, true));
63 break;
64 }
65 // Input ports
66 case ElementType::InputRotary: {
67 const int newSize = element->outputSize() + dir;
68 if (newSize >= element->minOutputSize() && newSize <= element->maxOutputSize())
69 m_scene->receiveCommand(new ChangePortSizeCommand(QList<GraphicElement *>{element},
70 newSize, m_scene, false));
71 break;
72 }
73 case ElementType::Clock:
74 if (element->hasFrequency())
75 applyWithUndo(element, [element, dir] { element->setFrequency(element->frequency() + dir * 0.5); });
76 break;
77
78 case ElementType::Buzzer:
79 if (element->hasFrequency())
80 applyWithUndo(element, [element, dir] { element->setFrequency(std::clamp(element->frequency() + dir * 100.0, 20.0, 20000.0)); });
81 break;
82
83 case ElementType::Display16:
84 // Only previous-color is handled for Display16 (no next-color cycling)
85 if (dir < 0 && element->hasColors())
86 applyWithUndo(element, [element] { element->setColor(element->previousColor()); });
87 break;
88
89 case ElementType::Display14:
90 case ElementType::Display7:
91 if (element->hasColors())
92 applyWithUndo(element, [element, dir] { element->setColor(dir < 0 ? element->previousColor() : element->nextColor()); });
93 break;
94
95 // Not implemented for these types
96 case ElementType::AudioBox:
97 case ElementType::DFlipFlop:
98 case ElementType::DLatch:
99 case ElementType::Demux:
100 case ElementType::IC:
101 case ElementType::InputButton:
102 case ElementType::InputGnd:
103 case ElementType::InputSwitch:
104 case ElementType::InputVcc:
105 case ElementType::JKFlipFlop:
106 case ElementType::JKLatch:
107 case ElementType::Line:
108 case ElementType::Mux:
109 case ElementType::Node:
110 case ElementType::Not:
111 case ElementType::SRFlipFlop:
112 case ElementType::SRLatch:
113 case ElementType::TFlipFlop:
114 case ElementType::Text:
115 case ElementType::Unknown:
116 break;
117 }
118
119 // Toggling selection off and on forces the property inspector to refresh
120 element->setSelected(false);
121 element->setSelected(true);
122 }
123
124 if (needsMacro) {
125 m_scene->undoStack()->endMacro();
126 }
127}
128
130{
131 adjustMainProperty(-1);
132}
133
135{
136 adjustMainProperty(1);
137}
138
139void PropertyShortcutHandler::adjustSecondaryProperty(int dir)
140{
141 // dir = -1 for prev, +1 for next
142 const auto selected = m_scene->selectedElements();
143 const bool needsMacro = selected.size() > 1;
144 if (needsMacro) {
145 m_scene->undoStack()->beginMacro(tr("Cycle element properties"));
146 }
147
148 for (auto *element : selected) {
149 switch (element->elementType()) {
150 case ElementType::TruthTable: {
151 const int newSize = element->outputSize() + dir;
152 if (newSize >= element->minOutputSize() && newSize <= element->maxOutputSize())
153 m_scene->receiveCommand(new ChangePortSizeCommand(QList<GraphicElement *>{element},
154 newSize, m_scene, false));
155 break;
156 }
157 case ElementType::Led:
158 if (element->hasColors())
159 applyWithUndo(element, [element, dir] { element->setColor(dir < 0 ? element->previousColor() : element->nextColor()); });
160 break;
161
162 case ElementType::And:
163 case ElementType::AudioBox:
164 case ElementType::Buzzer:
165 case ElementType::Clock:
166 case ElementType::DFlipFlop:
167 case ElementType::DLatch:
168 case ElementType::Demux:
169 case ElementType::Display14:
170 case ElementType::Display16:
171 case ElementType::Display7:
172 case ElementType::IC:
173 case ElementType::InputButton:
174 case ElementType::InputGnd:
175 case ElementType::InputRotary:
176 case ElementType::InputSwitch:
177 case ElementType::InputVcc:
178 case ElementType::JKFlipFlop:
179 case ElementType::JKLatch:
180 case ElementType::Line:
181 case ElementType::Mux:
182 case ElementType::Nand:
183 case ElementType::Node:
184 case ElementType::Nor:
185 case ElementType::Not:
186 case ElementType::Or:
187 case ElementType::SRFlipFlop:
188 case ElementType::SRLatch:
189 case ElementType::TFlipFlop:
190 case ElementType::Text:
191 case ElementType::Unknown:
192 case ElementType::Xnor:
193 case ElementType::Xor:
194 break;
195 }
196
197 element->setSelected(false);
198 element->setSelected(true);
199 }
200
201 if (needsMacro) {
202 m_scene->undoStack()->endMacro();
203 }
204}
205
207{
208 adjustSecondaryProperty(-1);
209}
210
212{
213 adjustSecondaryProperty(1);
214}
215
217{
218 const auto selected = m_scene->selectedElements();
219 const bool needsMacro = selected.size() > 1;
220 if (needsMacro) {
221 m_scene->undoStack()->beginMacro(tr("Morph elements"));
222 }
223
224 for (auto *element : selected) {
225 const QPointF elmPosition = element->scenePos();
226 auto nextType = Enums::nextElmType(element->elementType());
227
228 // ElementType::Unknown signals there is no "next" in the cycle for this type
229 if (nextType == ElementType::Unknown) { continue; }
230
231 m_scene->receiveCommand(new MorphCommand(QList<GraphicElement *>{element},
232 Enums::nextElmType(element->elementType()), m_scene));
233
234 // MorphCommand replaces the element in-place; re-select via position because
235 // the old pointer is now invalid after the command's redo()
236 auto *item = m_scene->itemAt(elmPosition);
237 if (item) {
238 item->setSelected(true);
239 }
240 }
241
242 if (needsMacro) {
243 m_scene->undoStack()->endMacro();
244 }
245}
246
248{
249 const auto selected = m_scene->selectedElements();
250 const bool needsMacro = selected.size() > 1;
251 if (needsMacro) {
252 m_scene->undoStack()->beginMacro(tr("Morph elements"));
253 }
254
255 for (auto *element : selected) {
256 const QPointF elmPosition = element->scenePos();
257 auto prevType = Enums::prevElmType(element->elementType());
258
259 if (prevType == ElementType::Unknown) { continue; }
260
261 m_scene->receiveCommand(new MorphCommand(QList<GraphicElement *>{element},
262 Enums::prevElmType(element->elementType()), m_scene));
263
264 auto *item = m_scene->itemAt(elmPosition);
265 if (item) {
266 item->setSelected(true);
267 }
268 }
269
270 if (needsMacro) {
271 m_scene->undoStack()->endMacro();
272 }
273}
All QUndoCommand subclasses and the CommandUtils helper namespace.
Central enumeration types for element types, groups, and signal status.
Abstract base class for all graphical circuit elements.
PropertyShortcutHandler: dispatches keyboard shortcuts that cycle element properties.
Main circuit editing scene with undo/redo and user interaction.
Deserialization/serialization context structs passed through load()/save() call chains.
Circuit and waveform file serialization/deserialization utilities.
static ElementType nextElmType(ElementType type)
Returns the ElementType that follows type in the defined sequence.
Definition Enums.cpp:49
static ElementType prevElmType(ElementType type)
Returns the ElementType that precedes type in the defined sequence.
Definition Enums.cpp:57
Abstract base class for all graphical circuit elements in wiRedPanda.
virtual void setFrequency(const double freq)
Sets the clock frequency to freq (overridden by clock elements).
ElementType elementType() const
Returns the type identifier for this element.
virtual void setColor(const QString &color)
Sets the element color to color and refreshes the pixmap.
int inputSize() const
Returns the current number of input ports.
int minInputSize() const
Returns the minimum allowed number of input ports.
virtual double frequency() const
Returns the clock frequency in Hz (overridden by Clock; returns 0 for other elements).
int outputSize() const
Returns the current number of output ports.
bool hasFrequency() const
Returns true if this element type exposes a configurable clock frequency.
QString nextColor() const
Returns the name of the next color in the element's color list.
bool hasColors() const
Returns true if this element type supports color selection.
virtual void save(QDataStream &stream, SerializationOptions options) const
int minOutputSize() const
Returns the minimum allowed number of output ports.
QString previousColor() const
Returns the name of the previous color in the element's color list.
Undo command that changes the type of selected elements while preserving connections.
Definition Commands.h:286
void prevElement()
Morphs each selected element to the previous type in the cycle.
void nextMainProperty()
Increments the primary property of each selected element.
void prevMainProperty()
Decrements the primary property of each selected element.
void nextElement()
Morphs each selected element to the next type in the cycle.
void nextSecondaryProperty()
Increments the secondary property of each selected element.
void prevSecondaryProperty()
Decrements the secondary property of each selected element.
Main circuit editing scene.
Definition Scene.h:56
const QList< GraphicElement * > selectedElements() const
Returns the list of currently selected graphic elements.
Definition Scene.cpp:427
QUndoStack * undoStack()
Returns the scene's undo stack.
Definition Scene.cpp:672
static void writePandaHeader(QDataStream &stream)
Writes the .panda circuit file header to stream.