32 if (command ==
"create_element") {
33 return handleCreateElement(params, requestId);
34 }
else if (command ==
"delete_element") {
35 return handleDeleteElement(params, requestId);
36 }
else if (command ==
"list_elements") {
37 return handleListElements(params, requestId);
38 }
else if (command ==
"move_element") {
39 return handleMoveElement(params, requestId);
40 }
else if (command ==
"set_element_properties") {
41 return handleSetElementProperties(params, requestId);
42 }
else if (command ==
"set_input_value") {
43 return handleSetInputValue(params, requestId);
44 }
else if (command ==
"get_output_value") {
45 return handleGetOutputValue(params, requestId);
46 }
else if (command ==
"rotate_element") {
47 return handleRotateElement(params, requestId);
48 }
else if (command ==
"flip_element") {
49 return handleFlipElement(params, requestId);
50 }
else if (command ==
"update_element") {
51 return handleUpdateElement(params, requestId);
52 }
else if (command ==
"change_input_size") {
53 return handleChangeInputSize(params, requestId);
54 }
else if (command ==
"change_output_size") {
55 return handleChangeOutputSize(params, requestId);
56 }
else if (command ==
"toggle_truth_table_output") {
57 return handleToggleTruthTableOutput(params, requestId);
58 }
else if (command ==
"morph_element") {
59 return handleMorphElement(params, requestId);
66QJsonObject ElementHandler::handleCreateElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
85 QString typeStr = params.value(
"type").toString();
86 double x = params.value(
"x").toDouble();
87 double y = params.value(
"y").toDouble();
88 QString label = params.value(
"label").toString();
91 if (type == ElementType::Unknown) {
101 GraphicElement *element =
nullptr;
104 }
catch (
const std::exception &e) {
105 return createErrorResponse(QString(
"Failed to create element of type %1: %2").arg(typeStr, e.what()),
117 element->setPos(x, y);
118 if (!label.isEmpty()) {
129 scene->clearSelection();
133 }
catch (
const std::exception &e) {
146 result[
"element_id"] = element->
id();
147 result[
"width"] = bounds.width();
148 result[
"height"] = bounds.height();
153QJsonObject ElementHandler::handleDeleteElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
173 },
"delete element", requestId);
176QJsonObject ElementHandler::handleListElements(
const QJsonObject &,
const QJsonValue &requestId)
184 const auto sceneElements = scene->
elements();
186 for (
const auto *element : sceneElements) {
188 QJsonObject elementObj;
189 elementObj[
"element_id"] = element->
id();
191 elementObj[
"x"] = element->pos().x();
192 elementObj[
"y"] = element->pos().y();
193 elementObj[
"width"] = br.width();
194 elementObj[
"height"] = br.height();
195 elementObj[
"label"] = element->
label();
196 elementObj[
"rotation"] = element->
rotation();
197 elementObj[
"input_size"] = element->
inputSize();
198 elementObj[
"output_size"] = element->
outputSize();
201 elementObj[
"color"] = element->
color();
204 elementObj[
"frequency"] = element->
frequency();
207 elementObj[
"delay"] = element->
delay();
210 elementObj[
"trigger"] = element->
trigger().toString();
213 elementObj[
"audio"] = element->
audio();
216 elementObj[
"volume"] =
static_cast<double>(element->
volume());
218 if (
const auto *inputElm = qobject_cast<const GraphicElementInput *>(element)) {
219 elementObj[
"locked"] = inputElm->isLocked();
222 elements.append(elementObj);
226 result[
"elements"] = elements;
231QJsonObject ElementHandler::handleMoveElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
246 const int x = qRound(params.value(
"x").toDouble() / snap) * snap;
247 const int y = qRound(params.value(
"y").toDouble() / snap) * snap;
259 QPointF oldPos = element->pos();
260 QPointF newPos(x, y);
262 element->setPos(newPos);
264 QList<GraphicElement *> elements = {element};
265 QList<QPointF> oldPositions = {oldPos};
268 scene->
receiveCommand(
new MoveCommand(elements, oldPositions, scene));
269 }
catch (
const std::exception &e) {
270 element->setPos(oldPos);
274 element->setPos(oldPos);
280 result[
"old_position"] = QJsonObject{{
"x", oldPos.x()}, {
"y", oldPos.y()}};
281 result[
"new_position"] = QJsonObject{{
"x", newPos.x()}, {
"y", newPos.y()}};
286QJsonObject ElementHandler::handleSetElementProperties(
const QJsonObject ¶ms,
const QJsonValue &requestId)
299 if (params.contains(
"input_size") || params.contains(
"output_size")) {
300 return createErrorResponse(
"Use change_input_size / change_output_size commands to change port counts",
307 QDataStream stream(&oldData, QIODevice::WriteOnly);
313 QJsonObject oldProperties;
314 QJsonObject newProperties;
316 if (params.contains(
"label")) {
317 QString oldLabel = element->
label();
318 QString newLabel = params.value(
"label").toString();
320 oldProperties[
"label"] = oldLabel;
321 newProperties[
"label"] = newLabel;
326 if (params.contains(
"color") && element->
hasColors()) {
327 QString oldColor = element->
color();
328 QString newColor = params.value(
"color").toString();
330 oldProperties[
"color"] = oldColor;
331 newProperties[
"color"] = newColor;
336 if (params.contains(
"frequency") && element->
hasFrequency()) {
337 if (!
validateNumeric(params.value(
"frequency"),
"frequency", errorMsg)) {
341 double newFreq = params.value(
"frequency").toDouble();
346 oldProperties[
"frequency"] = oldFreq;
347 newProperties[
"frequency"] = newFreq;
352 if (params.contains(
"rotation")) {
353 qreal oldRotation = element->
rotation();
354 qreal newRotation = params.value(
"rotation").toDouble();
356 oldProperties[
"rotation"] = oldRotation;
357 newProperties[
"rotation"] = newRotation;
362 if (params.contains(
"delay") && element->
hasDelay()) {
366 double oldDelay = element->
delay();
367 double newDelay = params.value(
"delay").toDouble();
368 if (newDelay < -1.0 || newDelay > 1.0) {
369 return createErrorResponse(
"Parameter 'delay' must be in [-1, 1] (fraction of period; negative advances the clock)",
373 oldProperties[
"delay"] = oldDelay;
374 newProperties[
"delay"] = newDelay;
379 if (params.contains(
"trigger") && element->
hasTrigger()) {
380 QString oldTrigger = element->
trigger().toString();
381 QString newTrigger = params.value(
"trigger").toString();
383 oldProperties[
"trigger"] = oldTrigger;
384 newProperties[
"trigger"] = newTrigger;
386 element->
setTrigger(QKeySequence(newTrigger));
389 if (params.contains(
"audio") && element->
hasAudio()) {
390 QString oldAudio = element->
audio();
391 QString newAudio = params.value(
"audio").toString();
393 oldProperties[
"audio"] = oldAudio;
394 newProperties[
"audio"] = newAudio;
399 if (params.contains(
"locked")) {
400 auto *inputElm = qobject_cast<GraphicElementInput *>(element);
402 bool oldLocked = inputElm->isLocked();
403 bool newLocked = params.value(
"locked").toBool();
405 oldProperties[
"locked"] = oldLocked;
406 newProperties[
"locked"] = newLocked;
408 inputElm->setLocked(newLocked);
412 if (params.contains(
"volume") && element->
hasVolume()) {
416 float oldVolume = element->
volume();
417 float newVolume =
static_cast<float>(params.value(
"volume").toDouble());
418 if (newVolume < 0.0f || newVolume > 1.0f) {
422 oldProperties[
"volume"] =
static_cast<double>(oldVolume);
423 newProperties[
"volume"] =
static_cast<double>(newVolume);
429 QString appearancePath = params.value(
"appearance").toString();
430 bool useDefault = appearancePath.isEmpty();
431 int appearanceIndex = params.contains(
"appearance_index") ? params.value(
"appearance_index").toInt() : -1;
433 newProperties[
"appearance"] = appearancePath;
434 newProperties[
"appearance_default"] = useDefault;
436 if (appearanceIndex >= 0) {
439 newProperties[
"appearance_index"] = appearanceIndex;
446 QList<QGraphicsItem *> wirelessConnsToDelete;
447 if (params.contains(
"wireless_mode")) {
448 if (
auto *node = qobject_cast<Node *>(element)) {
449 int modeInt = params.value(
"wireless_mode").toInt();
450 if (modeInt < 0 || modeInt > 2) {
454 auto oldMode =
static_cast<int>(node->wirelessMode());
457 oldProperties[
"wireless_mode"] = oldMode;
458 newProperties[
"wireless_mode"] = modeInt;
462 Port *port = (newMode == WirelessMode::Rx) ?
static_cast<Port *
>(node->inputPort())
463 : (newMode == WirelessMode::Tx) ?
static_cast<Port *
>(node->outputPort())
467 wirelessConnsToDelete.append(
static_cast<QGraphicsItem *
>(conn));
471 node->setWirelessMode(newMode);
478 if (!newProperties.isEmpty()) {
479 const bool needsMacro = !wirelessConnsToDelete.isEmpty();
483 scene->
undoStack()->beginMacro(QStringLiteral(
"Change wireless mode"));
485 scene->
receiveCommand(
new UpdateCommand({element}, oldData, scene));
487 scene->
receiveCommand(
new DeleteItemsCommand(wirelessConnsToDelete, scene));
493 result[
"old_properties"] = oldProperties;
494 result[
"new_properties"] = newProperties;
499QJsonObject ElementHandler::handleSetInputValue(
const QJsonObject ¶ms,
const QJsonValue &requestId)
510 const bool value = params.value(
"value").toBool();
512 auto *inputElement =
dynamic_cast<GraphicElementInput *
>(element);
514 inputElement->setOn(value);
527QJsonObject ElementHandler::handleGetOutputValue(
const QJsonObject ¶ms,
const QJsonValue &requestId)
541 if (params.contains(
"port")) {
545 portIndex = params.value(
"port").toInt();
550 auto *inputElement =
dynamic_cast<GraphicElementInput *
>(element);
552 result[
"value"] = inputElement->isOn();
556 if (group == ElementGroup::Output) {
560 InputPort *inPort = element->
inputPort(portIndex);
561 result[
"value"] = inPort ? (inPort->
status() == Status::Active) : false;
566 OutputPort *outPort = element->
outputPort(portIndex);
567 result[
"value"] = outPort ? (outPort->
status() == Status::Active) : false;
574QJsonObject ElementHandler::handleRotateElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
584 int angle = params.value(
"angle").toInt();
599 angle = ((angle % 360) + 360) % 360;
602 scene->
receiveCommand(
new RotateCommand({element}, angle, scene));
604 result[
"element_id"] = element->
id();
605 result[
"angle"] = angle;
607 },
"rotate element", requestId);
610QJsonObject ElementHandler::handleFlipElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
620 const int axis = params.value(
"axis").toInt();
621 if (axis != 0 && axis != 1) {
639 result[
"element_id"] = element->
id();
640 result[
"axis"] = (axis == 0 ?
"horizontal" :
"vertical");
642 },
"flip element", requestId);
645QJsonObject ElementHandler::handleUpdateElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
648 QJsonObject response = handleSetElementProperties(params, requestId);
651 if (response.contains(
"error")) {
657 result[
"element_id"] = params.value(
"element_id").toInt();
662QJsonObject ElementHandler::handleChangeInputSize(
const QJsonObject ¶ms,
const QJsonValue &requestId)
672 const int newSize = params.value(
"size").toInt();
679 if (newSize < element->minInputSize() || newSize > element->
maxInputSize()) {
691 scene->
receiveCommand(
new ChangePortSizeCommand({element}, newSize, scene,
true));
694 result[
"element_id"] = element->
id();
695 result[
"new_size"] = newSize;
696 result[
"width"] = bounds.width();
697 result[
"height"] = bounds.height();
699 },
"change input size", requestId);
702QJsonObject ElementHandler::handleChangeOutputSize(
const QJsonObject ¶ms,
const QJsonValue &requestId)
712 const int newSize = params.value(
"size").toInt();
719 if (newSize < element->minOutputSize() || newSize > element->
maxOutputSize()) {
731 scene->
receiveCommand(
new ChangePortSizeCommand({element}, newSize, scene,
false));
734 result[
"element_id"] = element->
id();
735 result[
"new_size"] = newSize;
736 result[
"width"] = bounds.width();
737 result[
"height"] = bounds.height();
739 },
"change output size", requestId);
742QJsonObject ElementHandler::handleToggleTruthTableOutput(
const QJsonObject ¶ms,
const QJsonValue &requestId)
752 const int position = params.value(
"position").toInt();
759 auto *truthTable =
dynamic_cast<TruthTable *
>(element);
768 const int maxPosition = 256 * truthTable->outputSize();
769 if (position >= maxPosition) {
770 return createErrorResponse(QString(
"position %1 out of range: TruthTable %2 has %3 outputs (valid positions 0..%4)")
771 .arg(position).arg(element->
id()).arg(truthTable->outputSize()).arg(maxPosition - 1),
782 scene->
receiveCommand(
new ToggleTruthTableOutputCommand(truthTable, position, scene));
784 result[
"element_id"] = element->
id();
785 result[
"position"] = position;
787 },
"toggle truth table output", requestId);
790QJsonObject ElementHandler::handleMorphElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
799 if (!params.value(
"element_ids").isArray()) {
803 QJsonArray elementIdsArray = params.value(
"element_ids").toArray();
804 if (elementIdsArray.empty()) {
813 QString typeStr = params.value(
"target_type").toString();
815 if (targetType == ElementType::Unknown) {
821 QList<GraphicElement *> elementsToMorph;
822 for (
const QJsonValue &idValue : std::as_const(elementIdsArray)) {
823 if (!idValue.isDouble()) {
827 int elementId = idValue.toInt();
833 auto *element =
dynamic_cast<GraphicElement *
>(item);
839 elementsToMorph.append(element);
848 QJsonArray morphedIds;
849 for (
const auto *element : elementsToMorph) {
850 morphedIds.append(element->
id());
854 scene->
receiveCommand(
new MorphCommand(elementsToMorph, targetType, scene));
858 result[
"morphed_elements"] = morphedIds;
859 result[
"target_type"] = typeStr;
861 },
"morph elements", requestId);
All QUndoCommand subclasses and the CommandUtils helper namespace.
Common logging utilities, the Pandaception error type, and helper macros.
Connection: a wire that connects an output port to an input port in the circuit scene.
Shared numeric constants used across layers.
Singleton factory for all circuit element types.
Central enumeration types for element types, groups, and signal status.
Enums::ElementType ElementType
Enums::WirelessMode WirelessMode
Enums::ElementGroup ElementGroup
Abstract base class for all graphical circuit elements.
Graphic element for a wire junction node.
Port classes: Port (base), InputPort, and OutputPort.
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.
Synchronous cycle-based simulation engine with event-driven clock support.
Graphic element for a user-programmable truth table.
bool validatePortRange(GraphicElement *element, int portIndex, bool isOutput, const QString ¶mName, QString &errorMsg) const
QJsonObject createSuccessResponse(const QJsonObject &result={}, const QJsonValue &requestId=QJsonValue()) const
QJsonObject tryCommand(Fn &&fn, const QString &action, const QJsonValue &requestId=QJsonValue())
Wraps fn in a try/catch, returning an error response on exception.
bool validateElementId(int elementId, const QString ¶mName, QString &errorMsg) const
GraphicElement * validatedElement(const QJsonObject ¶ms, const QString ¶mName, QString &errorMsg)
Validates paramName in params, looks up the element, and returns it.
bool validateNonNegativeInteger(const QJsonValue &value, const QString ¶mName, QString &errorMsg) const
BaseHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject createErrorResponse(const QString &error, const QJsonValue &requestId=QJsonValue(), int code=JsonRpcError::InternalError) const
bool validateParameters(const QJsonObject ¶ms, const QStringList &required) const
bool validatePositiveInteger(const QJsonValue &value, const QString ¶mName, QString &errorMsg) const
bool validateNonEmptyString(const QJsonValue &value, const QString ¶mName, QString &errorMsg) const
bool validateNumeric(const QJsonValue &value, const QString ¶mName, QString &errorMsg) const
static QString typeToText(const ElementType type)
Converts type to its internal name string.
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.
ElementHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject handleCommand(const QString &command, const QJsonObject ¶ms, const QJsonValue &requestId) override
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.
bool hasDelay() const
Returns true if this element type exposes a configurable clock phase delay.
virtual void setColor(const QString &color)
Sets the element color to color and refreshes the pixmap.
bool hasVolume() const
Returns true if this element type supports volume control.
virtual void setAudio(const QString &audio)
Sets the audio file associated with this element to audio.
int inputSize() const
Returns the current number of input ports.
qreal rotation() const
Returns the current rotation angle of this element in degrees.
void setRotation(const qreal angle)
Rotates the element to angle degrees and updates port positions.
QString label() const
Returns the user-visible label text for this element.
int maxOutputSize() const
Returns the maximum allowed number of output ports.
void setTrigger(const QKeySequence &trigger)
Sets the keyboard shortcut to trigger and updates the label.
InputPort * inputPort(const int index=0) const
Returns the input port at index (default 0).
void setAppearanceAt(const int index, const QString &fileName)
Sets a custom appearance at a specific index in the appearance list.
int minInputSize() const
Returns the minimum allowed number of input ports.
virtual QString audio() const
Returns the name of the audio file currently associated with this element.
virtual double delay() const
Returns the clock phase delay in seconds (overridden by Clock; returns 0 for other elements).
virtual double frequency() const
Returns the clock frequency in Hz (overridden by Clock; returns 0 for other elements).
QKeySequence trigger() const
Returns the keyboard shortcut that activates this element.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
virtual QString color() const
Returns the name of the color currently applied to this element.
int outputSize() const
Returns the current number of output ports.
OutputPort * outputPort(const int index=0) const
Returns the output port at index (default 0).
bool hasFrequency() const
Returns true if this element type exposes a configurable clock frequency.
bool hasColors() const
Returns true if this element type supports color selection.
ElementGroup elementGroup() const
Returns the group this element belongs to.
int maxInputSize() const
Returns the maximum allowed number of input ports.
virtual void save(QDataStream &stream, SerializationOptions options) const
virtual void setDelay(const double delay)
Sets the clock phase delay to delay (overridden by clock elements).
bool canChangeAppearance() const
Returns true if the user is allowed to choose a custom appearance for this element.
virtual void setAppearance(const bool defaultAppearance, const QString &fileName)
Switches the element's appearance.
int minOutputSize() const
Returns the minimum allowed number of output ports.
virtual void setVolume(float vol)
Sets the audio playback volume to vol (0.0–1.0).
QRectF boundingRect() const override
Returns the bounding rectangle of this element in local coordinates.
virtual float volume() const
Returns the audio playback volume (0.0–1.0).
bool hasAudio() const
Returns true if this element type supports audio output.
bool hasTrigger() const
Returns true if this element type supports a keyboard trigger.
int id() const
Returns the unique integer identifier of this item, or -1 if unassigned.
JSON Schema validator for MCP commands and responses using native json-schema-validator.
The top-level application window hosting the tab bar, menus, element palette, and editor.
Status status() const
Returns the current logical status (Active/Inactive/Unknown/Error).
const QList< Connection * > & connections() const
Returns the list of wires attached to this port.
const QVector< GraphicElement * > elements() const
Returns all graphic elements in the scene.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
ItemWithId * itemById(int id) const
Returns the item registered under id, or nullptr if not found.
Simulation * simulation()
Returns the simulation engine associated with this scene.
QUndoStack * undoStack()
Returns the scene's undo stack.
static void writePandaHeader(QDataStream &stream)
Writes the .panda circuit file header to stream.
void update()
Executes one simulation step (used by tests to advance the simulation manually).
constexpr int gridSize
Scene grid unit in pixels (elements snap to gridSize/2).
constexpr int SceneNotAvailable
No active circuit scene to operate on.
constexpr int MethodNotFound
The requested method does not exist or is unavailable.
constexpr int OperationFailed
Generic Qt API / handler operation failure (catch-all for tryCommand).
constexpr int ElementNotFound
Referenced element id does not exist in the scene.
constexpr int ValidationError
Semantic validation failure (e.g. port index out of range, enum value not allowed).
constexpr int InvalidParams
Invalid method parameters (missing required, wrong type, etc.).