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;
107 }
catch (
const std::exception &e) {
108 return createErrorResponse(QString(
"Failed to create element of type %1: %2").arg(typeStr, e.what()),
122 element->setPos(x, y);
123 if (!label.isEmpty()) {
134 scene->clearSelection();
140 }
catch (
const std::exception &e) {
153 result[
"element_id"] = element->
id();
154 result[
"width"] = bounds.width();
155 result[
"height"] = bounds.height();
160QJsonObject ElementHandler::handleDeleteElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
182 },
"delete element", requestId);
185QJsonObject ElementHandler::handleListElements(
const QJsonObject &,
const QJsonValue &requestId)
193 const auto sceneElements = scene->
elements();
195 for (
const auto *element : sceneElements) {
197 QJsonObject elementObj;
198 elementObj[
"element_id"] = element->
id();
200 elementObj[
"x"] = element->pos().x();
201 elementObj[
"y"] = element->pos().y();
202 elementObj[
"width"] = br.width();
203 elementObj[
"height"] = br.height();
204 elementObj[
"label"] = element->
label();
205 elementObj[
"rotation"] = element->
rotation();
206 elementObj[
"input_size"] = element->
inputSize();
207 elementObj[
"output_size"] = element->
outputSize();
210 elementObj[
"color"] = element->
color();
213 elementObj[
"frequency"] = element->
frequency();
216 elementObj[
"delay"] = element->
delay();
219 elementObj[
"trigger"] = element->
trigger().toString();
222 elementObj[
"audio"] = element->
audio();
225 elementObj[
"volume"] =
static_cast<double>(element->
volume());
227 if (
const auto *inputElm = qobject_cast<const GraphicElementInput *>(element)) {
228 elementObj[
"locked"] = inputElm->isLocked();
231 elements.append(elementObj);
235 result[
"elements"] = elements;
240QJsonObject ElementHandler::handleMoveElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
255 const int x = qRound(params.value(
"x").toDouble() / snap) * snap;
256 const int y = qRound(params.value(
"y").toDouble() / snap) * snap;
270 QPointF oldPos = element->pos();
271 QPointF newPos(x, y);
273 element->setPos(newPos);
275 QList<GraphicElement *> elements = {element};
276 QList<QPointF> oldPositions = {oldPos};
281 scene->
receiveCommand(
new MoveCommand(elements, oldPositions, scene));
282 }
catch (
const std::exception &e) {
283 element->setPos(oldPos);
287 element->setPos(oldPos);
293 result[
"old_position"] = QJsonObject{{
"x", oldPos.x()}, {
"y", oldPos.y()}};
294 result[
"new_position"] = QJsonObject{{
"x", newPos.x()}, {
"y", newPos.y()}};
305QString statusName(
const Status status)
308 case Status::Active:
return QStringLiteral(
"high");
309 case Status::Inactive:
return QStringLiteral(
"low");
310 case Status::Error:
return QStringLiteral(
"error");
311 default:
return QStringLiteral(
"unknown");
317QJsonObject ElementHandler::handleSetElementProperties(
const QJsonObject ¶ms,
const QJsonValue &requestId)
330 if (params.contains(
"input_size") || params.contains(
"output_size")) {
331 return createErrorResponse(
"Use change_input_size / change_output_size commands to change port counts",
338 QDataStream stream(&oldData, QIODevice::WriteOnly);
344 QJsonObject oldProperties;
345 QJsonObject newProperties;
347 if (params.contains(
"label")) {
348 QString oldLabel = element->
label();
349 QString newLabel = params.value(
"label").toString();
351 oldProperties[
"label"] = oldLabel;
352 newProperties[
"label"] = newLabel;
357 if (params.contains(
"color") && element->
hasColors()) {
358 QString oldColor = element->
color();
359 QString newColor = params.value(
"color").toString();
361 oldProperties[
"color"] = oldColor;
362 newProperties[
"color"] = newColor;
367 if (params.contains(
"frequency") && element->
hasFrequency()) {
368 if (!
validateNumeric(params.value(
"frequency"),
"frequency", errorMsg)) {
372 double newFreq = params.value(
"frequency").toDouble();
377 oldProperties[
"frequency"] = oldFreq;
378 newProperties[
"frequency"] = newFreq;
383 if (params.contains(
"rotation")) {
384 qreal oldRotation = element->
rotation();
385 qreal newRotation = params.value(
"rotation").toDouble();
387 oldProperties[
"rotation"] = oldRotation;
388 newProperties[
"rotation"] = newRotation;
393 if (params.contains(
"delay") && element->
hasDelay()) {
397 double oldDelay = element->
delay();
398 double newDelay = params.value(
"delay").toDouble();
399 if (newDelay < -1.0 || newDelay > 1.0) {
400 return createErrorResponse(
"Parameter 'delay' must be in [-1, 1] (fraction of period; negative advances the clock)",
404 oldProperties[
"delay"] = oldDelay;
405 newProperties[
"delay"] = newDelay;
410 if (params.contains(
"trigger") && element->
hasTrigger()) {
411 QString oldTrigger = element->
trigger().toString();
412 QString newTrigger = params.value(
"trigger").toString();
414 oldProperties[
"trigger"] = oldTrigger;
415 newProperties[
"trigger"] = newTrigger;
417 element->
setTrigger(QKeySequence(newTrigger));
421 if (params.contains(
"audio") && element->
hasAudio()) {
422 QString oldAudio = element->
audio();
423 QString newAudio = params.value(
"audio").toString();
425 oldProperties[
"audio"] = oldAudio;
426 newProperties[
"audio"] = newAudio;
431 if (params.contains(
"locked")) {
432 auto *inputElm = qobject_cast<GraphicElementInput *>(element);
434 bool oldLocked = inputElm->isLocked();
435 bool newLocked = params.value(
"locked").toBool();
437 oldProperties[
"locked"] = oldLocked;
438 newProperties[
"locked"] = newLocked;
440 inputElm->setLocked(newLocked);
444 if (params.contains(
"volume") && element->
hasVolume()) {
448 float oldVolume = element->
volume();
449 float newVolume =
static_cast<float>(params.value(
"volume").toDouble());
450 if (newVolume < 0.0f || newVolume > 1.0f) {
454 oldProperties[
"volume"] =
static_cast<double>(oldVolume);
455 newProperties[
"volume"] =
static_cast<double>(newVolume);
461 QString appearancePath = params.value(
"appearance").toString();
462 bool useDefault = appearancePath.isEmpty();
463 int appearanceIndex = params.contains(
"appearance_index") ? params.value(
"appearance_index").toInt() : -1;
465 newProperties[
"appearance"] = appearancePath;
466 newProperties[
"appearance_default"] = useDefault;
468 if (appearanceIndex >= 0) {
471 newProperties[
"appearance_index"] = appearanceIndex;
478 QList<QGraphicsItem *> wirelessConnsToDelete;
479 if (params.contains(
"wireless_mode")) {
480 if (
auto *node = qobject_cast<Node *>(element)) {
481 int modeInt = params.value(
"wireless_mode").toInt();
482 if (modeInt < 0 || modeInt > 2) {
486 auto oldMode =
static_cast<int>(node->wirelessMode());
489 oldProperties[
"wireless_mode"] = oldMode;
490 newProperties[
"wireless_mode"] = modeInt;
494 Port *port = (newMode == WirelessMode::Rx) ?
static_cast<Port *
>(node->inputPort())
495 : (newMode == WirelessMode::Tx) ?
static_cast<Port *
>(node->outputPort())
499 wirelessConnsToDelete.append(
static_cast<QGraphicsItem *
>(conn));
503 node->setWirelessMode(newMode);
510 if (!newProperties.isEmpty()) {
511 const bool needsMacro = !wirelessConnsToDelete.isEmpty();
515 scene->
undoStack()->beginMacro(QStringLiteral(
"Change wireless mode"));
517 scene->
receiveCommand(
new UpdateCommand({element}, oldData, scene));
519 scene->
receiveCommand(
new DeleteItemsCommand(wirelessConnsToDelete, scene));
525 result[
"old_properties"] = oldProperties;
526 result[
"new_properties"] = newProperties;
531QJsonObject ElementHandler::handleSetInputValue(
const QJsonObject ¶ms,
const QJsonValue &requestId)
542 const bool value = params.value(
"value").toBool();
544 auto *inputElement =
dynamic_cast<GraphicElementInput *
>(element);
546 inputElement->setOn(value);
559QJsonObject ElementHandler::handleGetOutputValue(
const QJsonObject ¶ms,
const QJsonValue &requestId)
573 if (params.contains(
"port")) {
577 portIndex = params.value(
"port").toInt();
582 auto *inputElement =
dynamic_cast<GraphicElementInput *
>(element);
584 result[
"value"] = inputElement->isOn();
588 if (group == ElementGroup::Output) {
592 InputPort *inPort = element->
inputPort(portIndex);
593 result[
"value"] = inPort ? (inPort->
status() == Status::Active) : false;
594 result[
"status"] = statusName(inPort ? inPort->
status() : Status::Unknown);
599 OutputPort *outPort = element->
outputPort(portIndex);
600 result[
"value"] = outPort ? (outPort->
status() == Status::Active) : false;
601 result[
"status"] = statusName(outPort ? outPort->
status() : Status::Unknown);
608QJsonObject ElementHandler::handleRotateElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
618 int angle = params.value(
"angle").toInt();
635 angle = ((angle % 360) + 360) % 360;
638 scene->
receiveCommand(
new RotateCommand({element}, angle, scene));
640 result[
"element_id"] = element->
id();
641 result[
"angle"] = angle;
643 },
"rotate element", requestId);
646QJsonObject ElementHandler::handleFlipElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
656 const int axis = params.value(
"axis").toInt();
657 if (axis != 0 && axis != 1) {
677 result[
"element_id"] = element->
id();
678 result[
"axis"] = (axis == 0 ?
"horizontal" :
"vertical");
680 },
"flip element", requestId);
683QJsonObject ElementHandler::handleUpdateElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
686 QJsonObject response = handleSetElementProperties(params, requestId);
689 if (response.contains(
"error")) {
695 result[
"element_id"] = params.value(
"element_id").toInt();
700QJsonObject ElementHandler::handleChangeInputSize(
const QJsonObject ¶ms,
const QJsonValue &requestId)
710 const int newSize = params.value(
"size").toInt();
717 if (newSize < element->minInputSize() || newSize > element->
maxInputSize()) {
731 scene->
receiveCommand(
new ChangePortSizeCommand({element}, newSize, scene,
true));
734 result[
"element_id"] = element->
id();
735 result[
"new_size"] = newSize;
736 result[
"width"] = bounds.width();
737 result[
"height"] = bounds.height();
739 },
"change input size", requestId);
742QJsonObject ElementHandler::handleChangeOutputSize(
const QJsonObject ¶ms,
const QJsonValue &requestId)
752 const int newSize = params.value(
"size").toInt();
759 if (newSize < element->minOutputSize() || newSize > element->
maxOutputSize()) {
773 scene->
receiveCommand(
new ChangePortSizeCommand({element}, newSize, scene,
false));
776 result[
"element_id"] = element->
id();
777 result[
"new_size"] = newSize;
778 result[
"width"] = bounds.width();
779 result[
"height"] = bounds.height();
781 },
"change output size", requestId);
784QJsonObject ElementHandler::handleToggleTruthTableOutput(
const QJsonObject ¶ms,
const QJsonValue &requestId)
794 const int position = params.value(
"position").toInt();
801 auto *truthTable =
dynamic_cast<TruthTable *
>(element);
810 const int maxPosition = 256 * truthTable->outputSize();
811 if (position >= maxPosition) {
812 return createErrorResponse(QString(
"position %1 out of range: TruthTable %2 has %3 outputs (valid positions 0..%4)")
813 .arg(position).arg(element->
id()).arg(truthTable->outputSize()).arg(maxPosition - 1),
826 scene->
receiveCommand(
new ToggleTruthTableOutputCommand(truthTable, position, scene));
828 result[
"element_id"] = element->
id();
829 result[
"position"] = position;
831 },
"toggle truth table output", requestId);
834QJsonObject ElementHandler::handleMorphElement(
const QJsonObject ¶ms,
const QJsonValue &requestId)
843 if (!params.value(
"element_ids").isArray()) {
847 QJsonArray elementIdsArray = params.value(
"element_ids").toArray();
848 if (elementIdsArray.empty()) {
857 QString typeStr = params.value(
"target_type").toString();
859 if (targetType == ElementType::Unknown) {
865 QList<GraphicElement *> elementsToMorph;
866 for (
const QJsonValue &idValue : std::as_const(elementIdsArray)) {
867 if (!idValue.isDouble()) {
871 int elementId = idValue.toInt();
877 auto *element =
dynamic_cast<GraphicElement *
>(item);
883 elementsToMorph.append(element);
894 QJsonArray morphedIds;
895 for (
const auto *element : elementsToMorph) {
896 morphedIds.append(element->
id());
900 scene->
receiveCommand(
new MorphCommand(elementsToMorph, targetType, scene));
904 result[
"morphed_elements"] = morphedIds;
905 result[
"target_type"] = typeStr;
907 },
"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.).