diff --git a/include/app/Knob.hpp b/include/app/Knob.hpp index 4d292a81..8c48561e 100644 --- a/include/app/Knob.hpp +++ b/include/app/Knob.hpp @@ -11,6 +11,7 @@ namespace rack { struct Knob : ParamWidget { /** Multiplier for mouse movement to adjust knob value */ float speed = 1.0; + float oldValue = 0.f; void onButton(const event::Button &e) override; void onDragStart(const event::DragStart &e) override; diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index 216494ff..cbbe4020 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.hpp @@ -22,6 +22,7 @@ struct ModuleWidget : OpaqueWidget { std::vector outputs; /** For RackWidget dragging */ math::Vec dragPos; + math::Vec oldPos; ModuleWidget(Module *module); ~ModuleWidget(); diff --git a/include/app/ParamWidget.hpp b/include/app/ParamWidget.hpp index 34a7bd6f..58de6d64 100644 --- a/include/app/ParamWidget.hpp +++ b/include/app/ParamWidget.hpp @@ -2,14 +2,15 @@ #include "app/common.hpp" #include "widgets/OpaqueWidget.hpp" #include "ui/Tooltip.hpp" -#include "ui/Quantity.hpp" +#include "app/ParamQuantity.hpp" +#include "history.hpp" namespace rack { struct ParamWidget : OpaqueWidget { - Quantity *quantity = NULL; + ParamQuantity *paramQuantity = NULL; float dirtyValue = NAN; Tooltip *tooltip = NULL; diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 40569494..44f11b1c 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -44,12 +44,13 @@ struct RackWidget : OpaqueWidget { void addModule(ModuleWidget *m); void addModuleAtMouse(ModuleWidget *m); /** Removes the module and transfers ownership to the caller */ - void deleteModule(ModuleWidget *m); + void removeModule(ModuleWidget *m); void cloneModule(ModuleWidget *m); /** Sets a module's box if non-colliding. Returns true if set */ bool requestModuleBox(ModuleWidget *m, math::Rect box); /** Moves a module to the closest non-colliding position */ bool requestModuleBoxNearest(ModuleWidget *m, math::Rect box); + ModuleWidget *getModule(int moduleId); void step() override; void draw(NVGcontext *vg) override; diff --git a/include/helpers.hpp b/include/helpers.hpp index 320f90d6..2ee240e4 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -60,7 +60,7 @@ TParamWidget *createParam(math::Vec pos, Module *module, int paramId) { ParamQuantity *q = new ParamQuantity; q->module = module; q->paramId = paramId; - o->quantity = q; + o->paramQuantity = q; return o; } @@ -71,7 +71,7 @@ TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) { ParamQuantity *q = new ParamQuantity; q->module = module; q->paramId = paramId; - o->quantity = q; + o->paramQuantity = q; return o; } diff --git a/include/history.hpp b/include/history.hpp index 579248c0..ffbf1c61 100644 --- a/include/history.hpp +++ b/include/history.hpp @@ -3,6 +3,7 @@ #include "math.hpp" #include "plugin/Model.hpp" #include +#include namespace rack { @@ -16,15 +17,95 @@ struct Action { }; -struct ModuleAdd : Action { - Model *model; +/** An action operating on a module +Subclass this to create your own custom actions for your module. +*/ +struct ModuleAction : Action { int moduleId; +}; + + +struct ModuleAdd : ModuleAction { + Model *model; math::Vec pos; void undo() override; void redo() override; }; +struct ModuleRemove : ModuleAction { + Model *model; + math::Vec pos; + json_t *moduleJ; + + struct WireInfo { + int wireId; + int outputModuleId; + int outputId; + int inputModuleId; + int inputId; + }; + std::vector wireInfos; + + ~ModuleRemove(); + void undo() override; + void redo() override; +}; + + +struct ModuleMove : ModuleAction { + math::Vec oldPos; + math::Vec newPos; + void undo() override; + void redo() override; +}; + + +struct ParamChange : ModuleAction { + int paramId; + float oldValue; + float newValue; + void undo() override; + void redo() override; +}; + + +struct WireAdd : Action { + int wireId; + int outputModuleId; + int outputId; + int inputModuleId; + int inputId; + void undo() override; + void redo() override; +}; + + +struct WireRemove : Action { + int wireId; + int outputModuleId; + int outputId; + int inputModuleId; + int inputId; + void undo() override; + void redo() override; +}; + + +struct WireMove : Action { + int wireId; + int oldOutputModuleId; + int oldOutputId; + int oldInputModuleId; + int oldInputId; + int newOutputModuleId; + int newOutputId; + int newInputModuleId; + int newInputId; + void undo() override; + void redo() override; +}; + struct State { std::vector actions; diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp index 73476c01..e17af8e7 100644 --- a/src/app/Knob.cpp +++ b/src/app/Knob.cpp @@ -1,4 +1,7 @@ #include "app/Knob.hpp" +#include "app.hpp" +#include "app/Scene.hpp" +#include "history.hpp" namespace rack { @@ -17,18 +20,34 @@ void Knob::onButton(const event::Button &e) { } void Knob::onDragStart(const event::DragStart &e) { + if (paramQuantity) + oldValue = paramQuantity->getValue(); + app()->window->cursorLock(); } void Knob::onDragEnd(const event::DragEnd &e) { app()->window->cursorUnlock(); + + if (paramQuantity) { + float newValue = paramQuantity->getValue(); + if (oldValue != newValue) { + // Push ParamChange history action + history::ParamChange *h = new history::ParamChange; + h->moduleId = paramQuantity->module->id; + h->paramId = paramQuantity->paramId; + h->oldValue = oldValue; + h->newValue = newValue; + app()->history->push(h); + } + } } void Knob::onDragMove(const event::DragMove &e) { - if (quantity) { + if (paramQuantity) { float range; - if (quantity->isBounded()) { - range = quantity->getRange(); + if (paramQuantity->isBounded()) { + range = paramQuantity->getRange(); } else { // Continuous encoders scale as if their limits are +/-1 @@ -39,7 +58,7 @@ void Knob::onDragMove(const event::DragMove &e) { // Drag slower if Mod is held if (app()->window->isModPressed()) delta /= 16.f; - quantity->moveValue(delta); + paramQuantity->moveValue(delta); } ParamWidget::onDragMove(e); diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 2713f9b8..c968c5b1 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -8,6 +8,7 @@ #include "app/Scene.hpp" #include "plugin.hpp" #include "app.hpp" +#include "history.hpp" #include #include @@ -78,12 +79,19 @@ struct ModuleBox : OpaqueWidget { // Create module ModuleWidget *moduleWidget = model->createModuleWidget(); assert(moduleWidget); - app()->scene->rackWidget->addModule(moduleWidget); + app()->scene->rackWidget->addModuleAtMouse(moduleWidget); // This is a bit nonstandard/unsupported usage, but pretend the moduleWidget was clicked so it can be dragged in the RackWidget - e.consume(moduleWidget); + // e.consume(moduleWidget); // Close Module Browser ModuleBrowser *moduleBrowser = getAncestorOfType(); moduleBrowser->visible = false; + + // Push ModuleAdd history action + history::ModuleAdd *h = new history::ModuleAdd; + h->model = moduleWidget->model; + h->moduleId = moduleWidget->module->id; + h->pos = moduleWidget->box.pos; + app()->history->push(h); } OpaqueWidget::onButton(e); } diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 2bb9b9be..6f5b5735 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -7,6 +7,7 @@ #include "helpers.hpp" #include "app.hpp" #include "settings.hpp" +#include "history.hpp" #include "osdialog.h" @@ -15,23 +16,12 @@ namespace rack { ModuleWidget::ModuleWidget(Module *module) { - if (module) { - app()->engine->addModule(module); - } this->module = module; } ModuleWidget::~ModuleWidget() { - // HACK - // If we try to disconnect wires in the Module Browser (e.g. when Rack is closed while the Module Browser is open), app()->scene->rackWidget will be an invalid pointer. - // So only attempt to disconnect if the module is not NULL. - if (module) - disconnect(); - // Remove and delete the Module instance if (module) { - app()->engine->removeModule(module); delete module; - module = NULL; } } @@ -327,13 +317,26 @@ void ModuleWidget::drawShadow(NVGcontext *vg) { nvgFill(vg); } +static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { + // Push ModuleRemove history action + history::ModuleRemove *h = new history::ModuleRemove; + h->model = moduleWidget->model; + h->moduleId = moduleWidget->module->id; + h->pos = moduleWidget->box.pos; + h->moduleJ = moduleWidget->toJson(); + app()->history->push(h); + + app()->scene->rackWidget->removeModule(moduleWidget); + delete moduleWidget; +} + void ModuleWidget::onHover(const event::Hover &e) { OpaqueWidget::onHover(e); // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. if (glfwGetKey(app()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(app()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { if (!app()->window->isModPressed() && !app()->window->isShiftPressed()) { - requestedDelete = true; + ModuleWidget_removeAction(this); return; } } @@ -401,10 +404,19 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { } void ModuleWidget::onDragStart(const event::DragStart &e) { + oldPos = box.pos; dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); } void ModuleWidget::onDragEnd(const event::DragEnd &e) { + if (!box.pos.isEqual(oldPos)) { + // Push ModuleMove history action + history::ModuleMove *h = new history::ModuleMove; + h->moduleId = module->id; + h->oldPos = oldPos; + h->newPos = box.pos; + app()->history->push(h); + } } void ModuleWidget::onDragMove(const event::DragMove &e) { @@ -526,8 +538,7 @@ struct ModuleDeleteItem : MenuItem { rightText = "Backspace/Delete"; } void onAction(const event::Action &e) override { - app()->scene->rackWidget->deleteModule(moduleWidget); - delete moduleWidget; + ModuleWidget_removeAction(moduleWidget); } }; diff --git a/src/app/MomentarySwitch.cpp b/src/app/MomentarySwitch.cpp index 7e842903..c3520678 100644 --- a/src/app/MomentarySwitch.cpp +++ b/src/app/MomentarySwitch.cpp @@ -5,14 +5,14 @@ namespace rack { void MomentarySwitch::onDragStart(const event::DragStart &e) { - if (quantity) { - quantity->setMax(); + if (paramQuantity) { + paramQuantity->setMax(); } } void MomentarySwitch::onDragEnd(const event::DragEnd &e) { - if (quantity) { - quantity->setMin(); + if (paramQuantity) { + paramQuantity->setMin(); } } diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index 1c42edc0..8340febb 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -6,6 +6,7 @@ #include "app.hpp" #include "settings.hpp" #include "random.hpp" +#include "history.hpp" namespace rack { @@ -21,15 +22,27 @@ struct ParamField : TextField { void setParamWidget(ParamWidget *paramWidget) { this->paramWidget = paramWidget; - if (paramWidget->quantity) - text = paramWidget->quantity->getDisplayValueString(); + if (paramWidget->paramQuantity) + text = paramWidget->paramQuantity->getDisplayValueString(); selectAll(); } void onSelectKey(const event::SelectKey &e) override { if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) { - if (paramWidget->quantity) - paramWidget->quantity->setDisplayValueString(text); + float oldValue = paramWidget->paramQuantity->getValue(); + if (paramWidget->paramQuantity) + paramWidget->paramQuantity->setDisplayValueString(text); + float newValue = paramWidget->paramQuantity->getValue(); + + if (oldValue != newValue) { + // Push ParamChange history action + history::ParamChange *h = new history::ParamChange; + h->moduleId = paramWidget->paramQuantity->module->id; + h->paramId = paramWidget->paramQuantity->paramId; + h->oldValue = oldValue; + h->newValue = newValue; + app()->history->push(h); + } MenuOverlay *overlay = getAncestorOfType(); overlay->requestedDelete = true; @@ -49,14 +62,14 @@ struct ParamField : TextField { ParamWidget::~ParamWidget() { - if (quantity) - delete quantity; + if (paramQuantity) + delete paramQuantity; } void ParamWidget::step() { - if (quantity) { - float value = quantity->getValue(); - // Trigger change event when quantity value changes + if (paramQuantity) { + float value = paramQuantity->getValue(); + // Trigger change event when paramQuantity value changes if (value != dirtyValue) { dirtyValue = value; event::Change eChange; @@ -65,13 +78,10 @@ void ParamWidget::step() { } if (tooltip) { - // Quantity string - if (quantity) { - tooltip->text = quantity->getString(); - } - // Param description - ParamQuantity *paramQuantity = dynamic_cast(quantity); if (paramQuantity) { + // Quantity string + tooltip->text = paramQuantity->getString(); + // Param description std::string description = paramQuantity->getParam()->description; if (!description.empty()) tooltip->text += "\n" + description; @@ -86,18 +96,31 @@ void ParamWidget::step() { void ParamWidget::fromJson(json_t *rootJ) { json_t *valueJ = json_object_get(rootJ, "value"); if (valueJ) { - if (quantity) - quantity->setValue(json_number_value(valueJ)); + if (paramQuantity) + paramQuantity->setValue(json_number_value(valueJ)); } } void ParamWidget::onButton(const event::Button &e) { // Right click to reset if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT && !(e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { - if (quantity) - quantity->reset(); + if (paramQuantity) { + float oldValue = paramQuantity->getValue(); + paramQuantity->reset(); + float newValue = paramQuantity->getValue(); + + if (oldValue != newValue) { + // Push ParamChange history action + history::ParamChange *h = new history::ParamChange; + h->moduleId = paramQuantity->module->id; + h->paramId = paramQuantity->paramId; + h->oldValue = oldValue; + h->newValue = newValue; + app()->history->push(h); + } + } // Here's another way of doing it, but either works. - // dynamic_cast(quantity)->getParam()->reset(); + // paramQuantity->getParam()->reset(); e.consume(this); } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 1ee915a2..a805215a 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -9,6 +9,7 @@ #include "asset.hpp" #include "system.hpp" #include "plugin.hpp" +#include "engine/Engine.hpp" #include "app.hpp" @@ -53,12 +54,19 @@ RackWidget::RackWidget() { } RackWidget::~RackWidget() { + clear(); } void RackWidget::clear() { wireContainer->activeWire = NULL; wireContainer->clearChildren(); - moduleContainer->clearChildren(); + // Remove ModuleWidgets + std::list widgets = moduleContainer->children; + for (Widget *w : widgets) { + ModuleWidget *moduleWidget = dynamic_cast(w); + assert(moduleWidget); + removeModule(moduleWidget); + } app()->scene->scrollWidget->offset = math::Vec(0, 0); } @@ -455,17 +463,32 @@ void RackWidget::pastePresetClipboard() { } void RackWidget::addModule(ModuleWidget *m) { + // Add module to ModuleContainer + assert(m); + assert(m->module); moduleContainer->addChild(m); + + // Add module to Engine + app()->engine->addModule(m->module); } void RackWidget::addModuleAtMouse(ModuleWidget *m) { - addModule(m); + assert(m); // Move module nearest to the mouse position m->box.pos = lastMousePos.minus(m->box.size.div(2)); requestModuleBoxNearest(m, m->box); + addModule(m); } -void RackWidget::deleteModule(ModuleWidget *m) { +void RackWidget::removeModule(ModuleWidget *m) { + // Disconnect wires + m->disconnect(); + + // Remove module from Engine + assert(m->module); + app()->engine->removeModule(m->module); + + // Remove module from ModuleContainer moduleContainer->removeChild(m); } @@ -496,8 +519,8 @@ bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { // Create possible positions - int x0 = roundf(box.pos.x / RACK_GRID_WIDTH); - int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT); + int x0 = std::round(box.pos.x / RACK_GRID_WIDTH); + int y0 = std::round(box.pos.y / RACK_GRID_HEIGHT); std::vector positions; for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { for (int x = std::max(0, x0 - 400); x < x0 + 400; x++) { @@ -520,6 +543,16 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { return false; } +ModuleWidget *RackWidget::getModule(int moduleId) { + for (Widget *w : moduleContainer->children) { + ModuleWidget *moduleWidget = dynamic_cast(w); + assert(moduleWidget); + if (moduleWidget->module->id == moduleId) + return moduleWidget; + } + return NULL; +} + void RackWidget::step() { // Expand size to fit modules math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); diff --git a/src/app/SVGKnob.cpp b/src/app/SVGKnob.cpp index 2a4bdfb3..76b88198 100644 --- a/src/app/SVGKnob.cpp +++ b/src/app/SVGKnob.cpp @@ -32,14 +32,14 @@ void SVGKnob::step() { void SVGKnob::onChange(const event::Change &e) { // Re-transform the TransformWidget - if (quantity) { + if (paramQuantity) { float angle; - if (quantity->isBounded()) { - angle = math::rescale(quantity->getScaledValue(), 0.f, 1.f, minAngle, maxAngle); + if (paramQuantity->isBounded()) { + angle = math::rescale(paramQuantity->getScaledValue(), 0.f, 1.f, minAngle, maxAngle); angle = std::fmod(angle, 2*M_PI); } else { - angle = math::rescale(quantity->getValue(), 0.f, 1.f, minAngle, maxAngle); + angle = math::rescale(paramQuantity->getValue(), 0.f, 1.f, minAngle, maxAngle); } tw->identity(); // Rotate SVG diff --git a/src/app/SVGSlider.cpp b/src/app/SVGSlider.cpp index 9595b6b7..973fcc90 100644 --- a/src/app/SVGSlider.cpp +++ b/src/app/SVGSlider.cpp @@ -28,9 +28,9 @@ void SVGSlider::step() { } void SVGSlider::onChange(const event::Change &e) { - if (quantity) { + if (paramQuantity) { // Interpolate handle position - float v = quantity->getScaledValue(); + float v = paramQuantity->getScaledValue(); handle->box.pos = math::Vec( math::rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), math::rescale(v, 0.f, 1.f, minHandlePos.y, maxHandlePos.y)); diff --git a/src/app/SVGSwitch.cpp b/src/app/SVGSwitch.cpp index 4e716593..5eecca81 100644 --- a/src/app/SVGSwitch.cpp +++ b/src/app/SVGSwitch.cpp @@ -25,8 +25,8 @@ void SVGSwitch::addFrame(std::shared_ptr svg) { void SVGSwitch::onChange(const event::Change &e) { assert(frames.size() > 0); - if (quantity) { - int index = quantity->getScaledValue() * (frames.size() - 1); + if (paramQuantity) { + int index = paramQuantity->getScaledValue() * (frames.size() - 1); index = math::clamp(index, 0, (int) frames.size() - 1); sw->setSVG(frames[index]); dirty = true; diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index fe34bf7c..52297aa4 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -76,7 +76,7 @@ void Scene::draw(NVGcontext *vg) { } void Scene::onHoverKey(const event::HoverKey &e) { - if (e.action == GLFW_PRESS) { + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { switch (e.key) { case GLFW_KEY_N: { if ((e.mods & WINDOW_MOD) && !(e.mods & GLFW_MOD_SHIFT)) { diff --git a/src/app/ToggleSwitch.cpp b/src/app/ToggleSwitch.cpp index c0f7ed7b..8e040057 100644 --- a/src/app/ToggleSwitch.cpp +++ b/src/app/ToggleSwitch.cpp @@ -1,4 +1,7 @@ #include "app/ToggleSwitch.hpp" +#include "app.hpp" +#include "app/Scene.hpp" +#include "history.hpp" namespace rack { @@ -7,12 +10,24 @@ namespace rack { void ToggleSwitch::onDragStart(const event::DragStart &e) { // Cycle through values // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. - if (quantity) { - if (quantity->isMax()) { - quantity->setMin(); + if (paramQuantity) { + float oldValue = paramQuantity->getValue(); + if (paramQuantity->isMax()) { + paramQuantity->setMin(); } else { - quantity->setValue(std::floor(quantity->getValue() + 1)); + paramQuantity->setValue(std::floor(paramQuantity->getValue() + 1)); + } + float newValue = paramQuantity->getValue(); + + if (oldValue != newValue) { + // Push ParamChange history action + history::ParamChange *h = new history::ParamChange; + h->moduleId = paramQuantity->module->id; + h->paramId = paramQuantity->paramId; + h->oldValue = oldValue; + h->newValue = newValue; + app()->history->push(h); } } } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 69b256eb..49a2a4b0 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -230,12 +230,22 @@ void Engine::addModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); std::lock_guard lock(internal->mutex); - // Set ID - assert(module->id == 0); - module->id = internal->nextModuleId++; // Check that the module is not already added auto it = std::find(modules.begin(), modules.end(), module); assert(it == modules.end()); + // Set ID + if (module->id == 0) { + // Automatically assign ID + module->id = internal->nextModuleId++; + } + else { + // Manual ID + // Check that the ID is not already taken + for (Module *m : modules) { + assert(module->id != m->id); + } + } + // Add module modules.push_back(module); } @@ -299,8 +309,17 @@ void Engine::addWire(Wire *wire) { assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); } // Set ID - assert(wire->id == 0); - wire->id = internal->nextWireId++; + if (wire->id == 0) { + // Automatically assign ID + wire->id = internal->nextWireId++; + } + else { + // Manual ID + // Check that the ID is not already taken + for (Wire *w : wires) { + assert(wire->id != w->id); + } + } // Add the wire wires.push_back(wire); Engine_updateActive(this); diff --git a/src/history.cpp b/src/history.cpp index b3e4c89b..5552ff83 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -1,10 +1,82 @@ #include "history.hpp" +#include "app.hpp" +#include "app/Scene.hpp" namespace rack { namespace history { +void ModuleAdd::undo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + app()->scene->rackWidget->removeModule(moduleWidget); + delete moduleWidget; +} + +void ModuleAdd::redo() { + ModuleWidget *moduleWidget = model->createModuleWidget(); + assert(moduleWidget); + assert(moduleWidget->module); + moduleWidget->module->id = moduleId; + moduleWidget->box.pos = pos; + app()->scene->rackWidget->addModule(moduleWidget); +} + + +ModuleRemove::~ModuleRemove() { + json_decref(moduleJ); +} + +void ModuleRemove::undo() { + ModuleWidget *moduleWidget = model->createModuleWidget(); + assert(moduleWidget); + assert(moduleWidget->module); + moduleWidget->module->id = moduleId; + moduleWidget->box.pos = pos; + moduleWidget->fromJson(moduleJ); + app()->scene->rackWidget->addModule(moduleWidget); + + // Add wires + for (WireInfo &wireInfo : wireInfos) { + // TODO Add wire + } +} + +void ModuleRemove::redo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + app()->scene->rackWidget->removeModule(moduleWidget); + delete moduleWidget; +} + + +void ModuleMove::undo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + moduleWidget->box.pos = oldPos; +} + +void ModuleMove::redo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + moduleWidget->box.pos = newPos; +} + + +void ParamChange::undo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + moduleWidget->module->params[paramId].value = oldValue; +} + +void ParamChange::redo() { + ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); + assert(moduleWidget); + moduleWidget->module->params[paramId].value = newValue; +} + + State::~State() { for (Action *action : actions) { delete action;