wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
ElementContextMenu.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 <QComboBox>
8#include <QMenu>
9#include <QPixmap>
10
11#include "App/Core/Common.h"
14#include "App/Scene/Commands.h"
15#include "App/Scene/Scene.h"
17
18// Helper: add a morph target unless the element already IS that type.
19static QAction *addElementAction(QMenu *menu, GraphicElement *selectedElm, ElementType type, const bool hasSameType)
20{
21 if (hasSameType && (selectedElm->elementType() == type)) {
22 return nullptr;
23 }
24
25 auto *action = menu->addAction(QIcon(ElementFactory::pixmap(type)), ElementFactory::translatedName(type));
26 action->setData(static_cast<int>(type));
27 return action;
28}
29
30void ElementContextMenu::exec(QPoint screenPos,
31 QGraphicsItem *itemAtMouse,
32 const SelectionCapabilities &caps,
33 const QList<GraphicElement *> &elements,
34 QComboBox *colorCombo,
35 Scene *scene,
36 const std::function<void(QUndoCommand *)> &sendCommand,
37 const std::function<void()> &onRename,
38 const std::function<void()> &onTriggerChange,
39 const std::function<void()> &onAppearanceChange,
40 const std::function<void()> &onAppearanceRevert,
41 const std::function<void()> &onFrequencyFocus,
42 const std::function<void()> &onEditSubcircuit,
43 const std::function<void()> &onEmbedSubcircuit,
44 const std::function<void()> &onExtractToFile)
45{
46 QMenu menu;
47 const QString changeAppearanceText(QObject::tr("Change appearance to ..."));
48 const QString colorMenuText(QObject::tr("Change color to..."));
49 const QString flipHorizontalText(QObject::tr("Flip horizontally"));
50 const QString flipVerticalText(QObject::tr("Flip vertically"));
51 const QString frequencyText(QObject::tr("Change frequency"));
52 const QString morphMenuText(QObject::tr("Morph to..."));
53 const QString renameText(QObject::tr("Rename"));
54 const QString revertAppearanceText(QObject::tr("Restore default appearance"));
55 const QString rotateLeftText(QObject::tr("Rotate left"));
56 const QString rotateRightText(QObject::tr("Rotate right"));
57 const QString triggerText(QObject::tr("Change trigger"));
58
59 if (caps.hasLabel) {
60 menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/rename.svg")), renameText)->setData(renameText);
61 }
62
63 if (caps.hasTrigger) {
64 menu.addAction(QIcon(ElementFactory::pixmap(ElementType::InputButton)), triggerText)->setData(triggerText);
65 }
66
67 if (caps.canChangeAppearance) {
68 menu.addAction(changeAppearanceText);
69 menu.addAction(revertAppearanceText);
70 }
71
72 menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/rotateL.svg")), rotateLeftText)->setData(rotateLeftText);
73 menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/rotateR.svg")), rotateRightText)->setData(rotateRightText);
74 menu.addAction(flipHorizontalText)->setData(flipHorizontalText);
75 menu.addAction(flipVerticalText)->setData(flipVerticalText);
76
77 if (caps.hasFrequency) {
78 menu.addAction(QIcon(ElementFactory::pixmap(ElementType::Clock)), frequencyText)->setData(frequencyText);
79 }
80
81 QMenu *submenuColors = nullptr;
82
83 if (caps.hasColors) {
84 submenuColors = menu.addMenu(colorMenuText);
85 for (int i = 0; i < colorCombo->count(); ++i) {
86 if (colorCombo->currentIndex() != i) {
87 submenuColors->addAction(colorCombo->itemIcon(i), colorCombo->itemText(i));
88 }
89 }
90 }
91
92 QMenu *submenuMorph = nullptr;
93
94 if (auto *selectedElm = qgraphicsitem_cast<GraphicElement *>(itemAtMouse); selectedElm && caps.canMorph) {
95 submenuMorph = menu.addMenu(morphMenuText);
96
97 switch (selectedElm->elementGroup()) {
98 case ElementGroup::Gate: {
99 if (selectedElm->inputSize() == 1) {
100 addElementAction(submenuMorph, selectedElm, ElementType::Node, caps.hasSameType);
101 addElementAction(submenuMorph, selectedElm, ElementType::Not, caps.hasSameType);
102 } else {
103 addElementAction(submenuMorph, selectedElm, ElementType::And, caps.hasSameType);
104 addElementAction(submenuMorph, selectedElm, ElementType::Nand, caps.hasSameType);
105 addElementAction(submenuMorph, selectedElm, ElementType::Nor, caps.hasSameType);
106 addElementAction(submenuMorph, selectedElm, ElementType::Or, caps.hasSameType);
107 addElementAction(submenuMorph, selectedElm, ElementType::Xnor, caps.hasSameType);
108 addElementAction(submenuMorph, selectedElm, ElementType::Xor, caps.hasSameType);
109 }
110 break;
111 }
112
113 case ElementGroup::StaticInput: [[fallthrough]];
114 case ElementGroup::Input: {
115 addElementAction(submenuMorph, selectedElm, ElementType::Clock, caps.hasSameType);
116 addElementAction(submenuMorph, selectedElm, ElementType::InputButton, caps.hasSameType);
117 addElementAction(submenuMorph, selectedElm, ElementType::InputGnd, caps.hasSameType);
118 addElementAction(submenuMorph, selectedElm, ElementType::InputRotary, caps.hasSameType);
119 addElementAction(submenuMorph, selectedElm, ElementType::InputSwitch, caps.hasSameType);
120 addElementAction(submenuMorph, selectedElm, ElementType::InputVcc, caps.hasSameType);
121 break;
122 }
123
124 case ElementGroup::Memory: {
125 // 2 inputs → DLatch; 4 inputs → DFF/TFF; 5 inputs → JKFF/SRFF.
126 if (selectedElm->inputSize() == 2) {
127 addElementAction(submenuMorph, selectedElm, ElementType::DLatch, caps.hasSameType);
128 break;
129 }
130 if (selectedElm->inputSize() == 4) {
131 addElementAction(submenuMorph, selectedElm, ElementType::DFlipFlop, caps.hasSameType);
132 addElementAction(submenuMorph, selectedElm, ElementType::TFlipFlop, caps.hasSameType);
133 break;
134 }
135 if (selectedElm->inputSize() == 5) {
136 addElementAction(submenuMorph, selectedElm, ElementType::JKFlipFlop, caps.hasSameType);
137 addElementAction(submenuMorph, selectedElm, ElementType::SRFlipFlop, caps.hasSameType);
138 }
139 break;
140 }
141
142 case ElementGroup::Output: {
143 if ((selectedElm->elementType() == ElementType::Display7)
144 || (selectedElm->elementType() == ElementType::Display14)
145 || (selectedElm->elementType() == ElementType::Display16)) {
146 addElementAction(submenuMorph, selectedElm, ElementType::Display7, caps.hasSameType);
147 addElementAction(submenuMorph, selectedElm, ElementType::Display14, caps.hasSameType);
148 addElementAction(submenuMorph, selectedElm, ElementType::Display16, caps.hasSameType);
149 } else {
150 addElementAction(submenuMorph, selectedElm, ElementType::Buzzer, caps.hasSameType);
151 addElementAction(submenuMorph, selectedElm, ElementType::Led, caps.hasSameType);
152 }
153 break;
154 }
155
156 case ElementGroup::IC: [[fallthrough]];
157 case ElementGroup::Mux: [[fallthrough]];
158 case ElementGroup::Other:
159 case ElementGroup::Unknown: break;
160
161 default: break;
162 }
163
164 if (submenuMorph->actions().empty()) {
165 menu.removeAction(submenuMorph->menuAction());
166 }
167 }
168
169 // --- IC sub-circuit actions ---
170 const QString editSubcircuitText(QObject::tr("Edit sub-circuit"));
171 const QString embedSubcircuitText(QObject::tr("Embed sub-circuit"));
172 const QString extractToFileText(QObject::tr("Extract to file"));
173
174 if (onEditSubcircuit && (caps.isEmbedded || caps.isFileBacked)) {
175 menu.addAction(editSubcircuitText)->setData(editSubcircuitText);
176 }
177 if (onEmbedSubcircuit && caps.isFileBacked) {
178 menu.addAction(embedSubcircuitText)->setData(embedSubcircuitText);
179 }
180 if (onExtractToFile && caps.isEmbedded) {
181 menu.addAction(extractToFileText)->setData(extractToFileText);
182 }
183
184 menu.addSeparator();
185
186 if (caps.hasElements) {
187 QAction *copyAction = menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/copy.svg")), QObject::tr("Copy"));
188 QAction *cutAction = menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/cut.svg")), QObject::tr("Cut"));
189 QObject::connect(copyAction, &QAction::triggered, scene, &Scene::copyAction);
190 QObject::connect(cutAction, &QAction::triggered, scene, &Scene::cutAction);
191 }
192
193 QAction *deleteAction = menu.addAction(QIcon(QPixmap(":/Interface/Toolbar/delete.svg")), QObject::tr("Delete"));
194 QObject::connect(deleteAction, &QAction::triggered, scene, &Scene::deleteAction);
195
196 QAction *action = menu.exec(screenPos);
197
198 if (!action) {
199 return;
200 }
201
202 const QString actionData = action->data().toString();
203 const QString actionText = action->text();
204
205 if (actionData == renameText) { onRename(); return; }
206 if (actionData == triggerText) { onTriggerChange(); return; }
207 if (actionText == changeAppearanceText) { onAppearanceChange(); return; }
208 if (actionText == revertAppearanceText) { onAppearanceRevert(); return; }
209 if (actionData == frequencyText) { onFrequencyFocus(); return; }
210 if (actionData == editSubcircuitText) { onEditSubcircuit(); return; }
211 if (actionData == embedSubcircuitText) { onEmbedSubcircuit(); return; }
212 if (actionData == extractToFileText) { onExtractToFile(); return; }
213
214 if (actionData == rotateLeftText) {
215 sendCommand(new RotateCommand(elements, -90.0, scene));
216 return;
217 }
218
219 if (actionData == rotateRightText) {
220 sendCommand(new RotateCommand(elements, 90.0, scene));
221 return;
222 }
223
224 if (actionData == flipHorizontalText) {
225 sendCommand(new FlipCommand(elements, 0, scene));
226 return;
227 }
228
229 if (actionData == flipVerticalText) {
230 sendCommand(new FlipCommand(elements, 1, scene));
231 return;
232 }
233
234 if (submenuMorph && submenuMorph->actions().contains(action)) {
235 if (auto type = static_cast<ElementType>(action->data().toInt()); type != ElementType::Unknown) {
236 sendCommand(new MorphCommand(elements, type, scene));
237 }
238 return;
239 }
240
241 if (submenuColors && submenuColors->actions().contains(action)) {
242 colorCombo->setCurrentText(action->text());
243 return;
244 }
245
246 if ((actionText == QObject::tr("Copy")) || (actionText == QObject::tr("Cut")) || (actionText == QObject::tr("Delete"))) {
247 return;
248 }
249
250 throw PANDACEPTION_WITH_CONTEXT("ElementContextMenu", "Unknown context menu option.");
251}
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
#define PANDACEPTION_WITH_CONTEXT(context, msg,...)
Definition Common.h:102
static QAction * addElementAction(QMenu *menu, GraphicElement *selectedElm, ElementType type, const bool hasSameType)
ElementContextMenu free function for building the element right-click menu.
Singleton factory for all circuit element types.
Enums::ElementType ElementType
Definition Enums.h:107
Abstract base class for all graphical circuit elements.
Main circuit editing scene with undo/redo and user interaction.
SelectionCapabilities struct and computeCapabilities() free function.
static QString translatedName(const ElementType type)
Returns the translated human-readable name for type.
static QPixmap pixmap(const ElementType type)
Returns the pixmap icon for the given element type.
Undo command that flips a selection of elements along a horizontal or vertical axis.
Definition Commands.h:385
Abstract base class for all graphical circuit elements in wiRedPanda.
ElementType elementType() const
Returns the type identifier for this element.
Undo command that changes the type of selected elements while preserving connections.
Definition Commands.h:286
Undo command that rotates a list of elements by a fixed angle.
Definition Commands.h:129
Main circuit editing scene.
Definition Scene.h:56
void copyAction()
Copies the selected items to the internal clipboard.
Definition Scene.cpp:814
void deleteAction()
Deletes the currently selected items.
Definition Scene.cpp:838
void cutAction()
Cuts the selected items to the internal clipboard.
Definition Scene.cpp:820
void exec(QPoint screenPos, QGraphicsItem *itemAtMouse, const SelectionCapabilities &caps, const QList< GraphicElement * > &elements, QComboBox *colorCombo, Scene *scene, const std::function< void(QUndoCommand *)> &sendCommand, const std::function< void()> &onRename, const std::function< void()> &onTriggerChange, const std::function< void()> &onAppearanceChange, const std::function< void()> &onAppearanceRevert, const std::function< void()> &onFrequencyFocus, const std::function< void()> &onEditSubcircuit={}, const std::function< void()> &onEmbedSubcircuit={}, const std::function< void()> &onExtractToFile={})
Builds and runs the right-click context menu for a set of selected elements.
Computed capabilities and consensus state of the current element selection.