| @@ -62,8 +62,8 @@ struct ModuleWidget : OpaqueWidget { | |||
| /** Serializes/unserializes the module state */ | |||
| void copyClipboard(); | |||
| void pasteClipboard(); | |||
| void load(std::string filename); | |||
| void pasteClipboardAction(); | |||
| void loadAction(std::string filename); | |||
| void save(std::string filename); | |||
| void loadDialog(); | |||
| void saveDialog(); | |||
| @@ -72,18 +72,20 @@ struct ModuleWidget : OpaqueWidget { | |||
| Called when the user clicks Disconnect Cables in the context menu. | |||
| */ | |||
| void disconnect(); | |||
| /** Resets the parameters of the module and calls the Module's randomize(). | |||
| Called when the user clicks Initialize in the context menu. | |||
| */ | |||
| void reset(); | |||
| void resetAction(); | |||
| /** Randomizes the parameters of the module and calls the Module's randomize(). | |||
| Called when the user clicks Randomize in the context menu. | |||
| */ | |||
| void randomize(); | |||
| void removeAction(); | |||
| void bypassAction(); | |||
| void randomizeAction(); | |||
| void disconnectAction(); | |||
| void cloneAction(); | |||
| void bypassAction(); | |||
| /** Deletes `this` */ | |||
| void removeAction(); | |||
| void createContextMenu(); | |||
| /** Override to add context menu entries to your subclass. | |||
| It is recommended to add a blank MenuEntry first for spacing. | |||
| @@ -21,8 +21,10 @@ struct PortWidget : OpaqueWidget { | |||
| PortWidget(); | |||
| ~PortWidget(); | |||
| void step() override; | |||
| void draw(NVGcontext *vg) override; | |||
| void onButton(const event::Button &e) override; | |||
| void onDragStart(const event::DragStart &e) override; | |||
| void onDragEnd(const event::DragEnd &e) override; | |||
| @@ -25,6 +25,7 @@ struct RackWidget : OpaqueWidget { | |||
| void draw(NVGcontext *vg) override; | |||
| void onHover(const event::Hover &e) override; | |||
| void onHoverKey(const event::HoverKey &e) override; | |||
| void onDragHover(const event::DragHover &e) override; | |||
| void onButton(const event::Button &e) override; | |||
| void onZoom(const event::Zoom &e) override; | |||
| @@ -33,7 +34,7 @@ struct RackWidget : OpaqueWidget { | |||
| void clear(); | |||
| json_t *toJson(); | |||
| void fromJson(json_t *rootJ); | |||
| void pastePresetClipboard(); | |||
| void pastePresetClipboardAction(); | |||
| // Module methods | |||
| @@ -67,6 +68,8 @@ struct RackWidget : OpaqueWidget { | |||
| /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack */ | |||
| CableWidget *getTopCable(PortWidget *port); | |||
| CableWidget *getCable(int cableId); | |||
| /** Returns all cables attached to port, complete or not */ | |||
| std::list<CableWidget*> getCablesOnPort(PortWidget *port); | |||
| }; | |||
| @@ -83,6 +83,15 @@ struct ModuleBypass : ModuleAction { | |||
| }; | |||
| struct ModuleChange : ModuleAction { | |||
| json_t *oldModuleJ; | |||
| json_t *newModuleJ; | |||
| ~ModuleChange(); | |||
| void undo() override; | |||
| void redo() override; | |||
| }; | |||
| struct ParamChange : ModuleAction { | |||
| int paramId; | |||
| float oldValue; | |||
| @@ -22,7 +22,7 @@ struct ModuleDisconnectItem : MenuItem { | |||
| rightText = WINDOW_MOD_CTRL_NAME "+U"; | |||
| } | |||
| void onAction(const event::Action &e) override { | |||
| moduleWidget->disconnect(); | |||
| moduleWidget->disconnectAction(); | |||
| } | |||
| }; | |||
| @@ -33,7 +33,7 @@ struct ModuleResetItem : MenuItem { | |||
| rightText = WINDOW_MOD_CTRL_NAME "+I"; | |||
| } | |||
| void onAction(const event::Action &e) override { | |||
| moduleWidget->reset(); | |||
| moduleWidget->resetAction(); | |||
| } | |||
| }; | |||
| @@ -44,7 +44,7 @@ struct ModuleRandomizeItem : MenuItem { | |||
| rightText = WINDOW_MOD_CTRL_NAME "+R"; | |||
| } | |||
| void onAction(const event::Action &e) override { | |||
| moduleWidget->randomize(); | |||
| moduleWidget->randomizeAction(); | |||
| } | |||
| }; | |||
| @@ -66,7 +66,7 @@ struct ModulePasteItem : MenuItem { | |||
| rightText = WINDOW_MOD_CTRL_NAME "+V"; | |||
| } | |||
| void onAction(const event::Action &e) override { | |||
| moduleWidget->pasteClipboard(); | |||
| moduleWidget->pasteClipboardAction(); | |||
| } | |||
| }; | |||
| @@ -214,13 +214,13 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { | |||
| switch (e.key) { | |||
| case GLFW_KEY_I: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| reset(); | |||
| resetAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_R: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| randomize(); | |||
| randomizeAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| @@ -232,18 +232,19 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { | |||
| } break; | |||
| case GLFW_KEY_V: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| pasteClipboard(); | |||
| pasteClipboardAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_D: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| cloneAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_U: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| disconnect(); | |||
| disconnectAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| @@ -267,7 +268,7 @@ void ModuleWidget::onDragStart(const event::DragStart &e) { | |||
| void ModuleWidget::onDragEnd(const event::DragEnd &e) { | |||
| if (!box.pos.isEqual(oldPos)) { | |||
| // Push ModuleMove history action | |||
| // history::ModuleMove | |||
| history::ModuleMove *h = new history::ModuleMove; | |||
| h->moduleId = module->id; | |||
| h->oldPos = oldPos; | |||
| @@ -412,7 +413,7 @@ void ModuleWidget::copyClipboard() { | |||
| glfwSetClipboardString(app()->window->win, moduleJson); | |||
| } | |||
| void ModuleWidget::pasteClipboard() { | |||
| void ModuleWidget::pasteClipboardAction() { | |||
| const char *moduleJson = glfwGetClipboardString(app()->window->win); | |||
| if (!moduleJson) { | |||
| WARN("Could not get text from clipboard."); | |||
| @@ -429,10 +430,18 @@ void ModuleWidget::pasteClipboard() { | |||
| json_decref(moduleJ); | |||
| }); | |||
| // history::ModuleChange | |||
| history::ModuleChange *h = new history::ModuleChange; | |||
| h->moduleId = module->id; | |||
| h->oldModuleJ = toJson(); | |||
| fromJson(moduleJ); | |||
| h->newModuleJ = toJson(); | |||
| app()->history->push(h); | |||
| } | |||
| void ModuleWidget::load(std::string filename) { | |||
| void ModuleWidget::loadAction(std::string filename) { | |||
| INFO("Loading preset %s", filename.c_str()); | |||
| FILE *file = fopen(filename.c_str(), "r"); | |||
| @@ -455,7 +464,15 @@ void ModuleWidget::load(std::string filename) { | |||
| json_decref(moduleJ); | |||
| }); | |||
| // history::ModuleChange | |||
| history::ModuleChange *h = new history::ModuleChange; | |||
| h->moduleId = module->id; | |||
| h->oldModuleJ = toJson(); | |||
| fromJson(moduleJ); | |||
| h->newModuleJ = toJson(); | |||
| app()->history->push(h); | |||
| } | |||
| void ModuleWidget::save(std::string filename) { | |||
| @@ -496,7 +513,7 @@ void ModuleWidget::loadDialog() { | |||
| free(path); | |||
| }); | |||
| load(path); | |||
| loadAction(path); | |||
| } | |||
| void ModuleWidget::saveDialog() { | |||
| @@ -535,39 +552,68 @@ void ModuleWidget::disconnect() { | |||
| } | |||
| } | |||
| void ModuleWidget::reset() { | |||
| if (module) { | |||
| app()->engine->resetModule(module); | |||
| } | |||
| } | |||
| void ModuleWidget::resetAction() { | |||
| assert(module); | |||
| void ModuleWidget::randomize() { | |||
| if (module) { | |||
| app()->engine->randomizeModule(module); | |||
| } | |||
| // history::ModuleChange | |||
| history::ModuleChange *h = new history::ModuleChange; | |||
| h->moduleId = module->id; | |||
| h->oldModuleJ = toJson(); | |||
| app()->engine->resetModule(module); | |||
| h->newModuleJ = toJson(); | |||
| app()->history->push(h); | |||
| } | |||
| void ModuleWidget::removeAction() { | |||
| history::ComplexAction *complexAction = new history::ComplexAction; | |||
| void ModuleWidget::randomizeAction() { | |||
| assert(module); | |||
| // Push ModuleRemove history action | |||
| history::ModuleRemove *moduleRemove = new history::ModuleRemove; | |||
| moduleRemove->setModule(this); | |||
| complexAction->push(moduleRemove); | |||
| // history::ModuleChange | |||
| history::ModuleChange *h = new history::ModuleChange; | |||
| h->moduleId = module->id; | |||
| h->oldModuleJ = toJson(); | |||
| app()->history->push(complexAction); | |||
| app()->engine->randomizeModule(module); | |||
| app()->scene->rackWidget->removeModule(this); | |||
| delete this; | |||
| h->newModuleJ = toJson(); | |||
| app()->history->push(h); | |||
| } | |||
| void ModuleWidget::bypassAction() { | |||
| // Push ModuleBypass history action | |||
| history::ModuleBypass *h = new history::ModuleBypass; | |||
| h->moduleId = module->id; | |||
| h->bypass = !module->bypass; | |||
| app()->history->push(h); | |||
| h->redo(); | |||
| static void disconnectActions(ModuleWidget *mw, history::ComplexAction *complexAction) { | |||
| // Add CableRemove action for all cables attached to outputs | |||
| for (PortWidget* output : mw->outputs) { | |||
| for (CableWidget *cw : app()->scene->rackWidget->getCablesOnPort(output)) { | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| // history::CableRemove | |||
| history::CableRemove *h = new history::CableRemove; | |||
| h->setCable(cw); | |||
| complexAction->push(h); | |||
| } | |||
| } | |||
| // Add CableRemove action for all cables attached to inputs | |||
| for (PortWidget* input : mw->inputs) { | |||
| for (CableWidget *cw : app()->scene->rackWidget->getCablesOnPort(input)) { | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| // Avoid creating duplicate actions for self-patched cables | |||
| if (cw->outputPort->module == mw->module) | |||
| continue; | |||
| // history::CableRemove | |||
| history::CableRemove *h = new history::CableRemove; | |||
| h->setCable(cw); | |||
| complexAction->push(h); | |||
| } | |||
| } | |||
| } | |||
| void ModuleWidget::disconnectAction() { | |||
| history::ComplexAction *complexAction = new history::ComplexAction; | |||
| disconnectActions(this, complexAction); | |||
| app()->history->push(complexAction); | |||
| disconnect(); | |||
| } | |||
| void ModuleWidget::cloneAction() { | |||
| @@ -580,12 +626,38 @@ void ModuleWidget::cloneAction() { | |||
| app()->scene->rackWidget->addModuleAtMouse(clonedModuleWidget); | |||
| // Push ModuleAdd history action | |||
| // history::ModuleAdd | |||
| history::ModuleAdd *h = new history::ModuleAdd; | |||
| h->setModule(clonedModuleWidget); | |||
| app()->history->push(h); | |||
| } | |||
| void ModuleWidget::bypassAction() { | |||
| assert(module); | |||
| // history::ModuleBypass | |||
| history::ModuleBypass *h = new history::ModuleBypass; | |||
| h->moduleId = module->id; | |||
| h->bypass = !module->bypass; | |||
| app()->history->push(h); | |||
| h->redo(); | |||
| } | |||
| void ModuleWidget::removeAction() { | |||
| history::ComplexAction *complexAction = new history::ComplexAction; | |||
| disconnectActions(this, complexAction); | |||
| // history::ModuleRemove | |||
| history::ModuleRemove *moduleRemove = new history::ModuleRemove; | |||
| moduleRemove->setModule(this); | |||
| complexAction->push(moduleRemove); | |||
| app()->history->push(complexAction); | |||
| // This disconnects cables, removes the module, and transfers ownership to caller | |||
| app()->scene->rackWidget->removeModule(this); | |||
| delete this; | |||
| } | |||
| void ModuleWidget::createContextMenu() { | |||
| Menu *menu = createMenu(); | |||
| assert(model); | |||
| @@ -108,8 +108,6 @@ void PortWidget::onDragStart(const event::DragStart &e) { | |||
| } | |||
| void PortWidget::onDragEnd(const event::DragEnd &e) { | |||
| // FIXME | |||
| // If the source PortWidget is deleted, this will be called, removing the cable | |||
| CableWidget *cw = app()->scene->rackWidget->releaseIncompleteCable(); | |||
| if (cw->isComplete()) { | |||
| app()->scene->rackWidget->addCable(cw); | |||
| @@ -148,6 +148,22 @@ void RackWidget::onHover(const event::Hover &e) { | |||
| mousePos = e.pos; | |||
| } | |||
| void RackWidget::onHoverKey(const event::HoverKey &e) { | |||
| OpaqueWidget::onHoverKey(e); | |||
| if (e.getConsumed() != this) | |||
| return; | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| switch (e.key) { | |||
| case GLFW_KEY_V: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| pastePresetClipboardAction(); | |||
| } | |||
| } break; | |||
| } | |||
| } | |||
| } | |||
| void RackWidget::onDragHover(const event::DragHover &e) { | |||
| OpaqueWidget::onDragHover(e); | |||
| mousePos = e.pos; | |||
| @@ -287,7 +303,7 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| } | |||
| } | |||
| void RackWidget::pastePresetClipboard() { | |||
| void RackWidget::pastePresetClipboardAction() { | |||
| const char *moduleJson = glfwGetClipboardString(app()->window->win); | |||
| if (!moduleJson) { | |||
| WARN("Could not get text from clipboard."); | |||
| @@ -297,13 +313,14 @@ void RackWidget::pastePresetClipboard() { | |||
| json_error_t error; | |||
| json_t *moduleJ = json_loads(moduleJson, 0, &error); | |||
| if (moduleJ) { | |||
| ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | |||
| ModuleWidget *mw = moduleFromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| addModule(moduleWidget); | |||
| // Set moduleWidget position | |||
| math::Rect newBox = moduleWidget->box; | |||
| newBox.pos = mousePos.minus(newBox.size.div(2)); | |||
| requestModuleBoxNearest(moduleWidget, newBox); | |||
| addModuleAtMouse(mw); | |||
| // history::ModuleAdd | |||
| history::ModuleAdd *h = new history::ModuleAdd; | |||
| h->setModule(mw); | |||
| app()->history->push(h); | |||
| } | |||
| else { | |||
| WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| @@ -349,7 +366,9 @@ bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect requestedBox) { | |||
| // Check intersection with other modules | |||
| for (Widget *m2 : moduleContainer->children) { | |||
| if (m == m2) continue; | |||
| // Don't intersect with self | |||
| if (m == m2) | |||
| continue; | |||
| if (requestedBox.intersects(m2->box)) { | |||
| return false; | |||
| } | |||
| @@ -401,8 +420,10 @@ void RackWidget::clearCables() { | |||
| for (Widget *w : cableContainer->children) { | |||
| CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (cw != incompleteCable) | |||
| app()->engine->removeCable(cw->cable); | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| app()->engine->removeCable(cw->cable); | |||
| } | |||
| incompleteCable = NULL; | |||
| cableContainer->clearChildren(); | |||
| @@ -415,7 +436,7 @@ void RackWidget::clearCablesAction() { | |||
| for (Widget *w : cableContainer->children) { | |||
| CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (cw == incompleteCable) | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| // history::CableRemove | |||
| @@ -429,23 +450,16 @@ void RackWidget::clearCablesAction() { | |||
| } | |||
| void RackWidget::clearCablesOnPort(PortWidget *port) { | |||
| assert(port); | |||
| std::list<Widget*> childrenCopy = cableContainer->children; | |||
| for (Widget *w : childrenCopy) { | |||
| CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| for (CableWidget *cw : getCablesOnPort(port)) { | |||
| // Check if cable is connected to port | |||
| if (cw->inputPort == port || cw->outputPort == port) { | |||
| if (cw == incompleteCable) { | |||
| incompleteCable = NULL; | |||
| cableContainer->removeChild(cw); | |||
| } | |||
| else { | |||
| removeCable(cw); | |||
| } | |||
| delete cw; | |||
| if (cw == incompleteCable) { | |||
| incompleteCable = NULL; | |||
| cableContainer->removeChild(cw); | |||
| } | |||
| else { | |||
| removeCable(cw); | |||
| } | |||
| delete cw; | |||
| } | |||
| } | |||
| @@ -503,5 +517,18 @@ CableWidget *RackWidget::getCable(int cableId) { | |||
| return NULL; | |||
| } | |||
| std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget *port) { | |||
| assert(port); | |||
| std::list<CableWidget*> cables; | |||
| for (Widget *w : cableContainer->children) { | |||
| CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (cw->inputPort == port || cw->outputPort == port) { | |||
| cables.push_back(cw); | |||
| } | |||
| } | |||
| return cables; | |||
| } | |||
| } // namespace rack | |||
| @@ -124,12 +124,6 @@ void Scene::onHoverKey(const event::HoverKey &e) { | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_V: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| rackWidget->pastePresetClipboard(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| case GLFW_KEY_Z: { | |||
| if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | |||
| app()->history->undo(); | |||
| @@ -50,8 +50,6 @@ struct Engine::Internal { | |||
| float sampleTime; | |||
| float sampleRateRequested; | |||
| Module *resetModule = NULL; | |||
| Module *randomizeModule = NULL; | |||
| int nextModuleId = 1; | |||
| int nextCableId = 1; | |||
| @@ -93,16 +91,6 @@ static void Engine_step(Engine *engine) { | |||
| } | |||
| } | |||
| // Events | |||
| if (engine->internal->resetModule) { | |||
| engine->internal->resetModule->reset(); | |||
| engine->internal->resetModule = NULL; | |||
| } | |||
| if (engine->internal->randomizeModule) { | |||
| engine->internal->randomizeModule->randomize(); | |||
| engine->internal->randomizeModule = NULL; | |||
| } | |||
| // Param smoothing | |||
| { | |||
| Module *smoothModule = engine->internal->smoothModule; | |||
| @@ -273,11 +261,17 @@ void Engine::removeModule(Module *module) { | |||
| } | |||
| void Engine::resetModule(Module *module) { | |||
| internal->resetModule = module; | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::mutex> lock(internal->mutex); | |||
| module->reset(); | |||
| } | |||
| void Engine::randomizeModule(Module *module) { | |||
| internal->randomizeModule = module; | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::mutex> lock(internal->mutex); | |||
| module->randomize(); | |||
| } | |||
| static void Engine_updateActive(Engine *engine) { | |||
| @@ -344,7 +338,7 @@ void Engine::removeCable(Cable *cable) { | |||
| } | |||
| void Engine::setParam(Module *module, int paramId, float value) { | |||
| // TODO Make thread safe | |||
| // TODO Does this need to be thread-safe? | |||
| module->params[paramId].value = value; | |||
| } | |||
| @@ -90,6 +90,24 @@ void ModuleBypass::redo() { | |||
| } | |||
| ModuleChange::~ModuleChange() { | |||
| json_decref(oldModuleJ); | |||
| json_decref(newModuleJ); | |||
| } | |||
| void ModuleChange::undo() { | |||
| ModuleWidget *mw = app()->scene->rackWidget->getModule(moduleId); | |||
| assert(mw); | |||
| mw->fromJson(oldModuleJ); | |||
| } | |||
| void ModuleChange::redo() { | |||
| ModuleWidget *mw = app()->scene->rackWidget->getModule(moduleId); | |||
| assert(mw); | |||
| mw->fromJson(newModuleJ); | |||
| } | |||
| void ParamChange::undo() { | |||
| ModuleWidget *mw = app()->scene->rackWidget->getModule(moduleId); | |||
| assert(mw); | |||