| @@ -6,6 +6,8 @@ | |||
| #include <app/ParamWidget.hpp> | |||
| #include <plugin/Model.hpp> | |||
| #include <engine/Module.hpp> | |||
| #include <history.hpp> | |||
| #include <list> | |||
| namespace rack { | |||
| @@ -54,12 +56,17 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
| ParamWidget* getParam(int paramId); | |||
| PortWidget* getInput(int portId); | |||
| PortWidget* getOutput(int portId); | |||
| /** Scans children widgets recursively for all ParamWidgets. */ | |||
| std::list<ParamWidget*> getParams(); | |||
| std::list<PortWidget*> getPorts(); | |||
| std::list<PortWidget*> getInputs(); | |||
| std::list<PortWidget*> getOutputs(); | |||
| void draw(const DrawArgs& args) override; | |||
| void drawShadow(const DrawArgs& args); | |||
| /** Override to add context menu entries to your subclass. | |||
| It is recommended to add a blank ui::MenuEntry first for spacing. | |||
| It is recommended to add a blank `ui::MenuSeparator` first for spacing. | |||
| */ | |||
| virtual void appendContextMenu(ui::Menu* menu) {} | |||
| @@ -98,12 +105,14 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
| Called when the user clicks Randomize in the context menu. | |||
| */ | |||
| void randomizeAction(); | |||
| void appendDisconnectActions(history::ComplexAction* complexAction); | |||
| void disconnectAction(); | |||
| void cloneAction(); | |||
| void bypassAction(); | |||
| /** Deletes `this` */ | |||
| void removeAction(); | |||
| void createContextMenu(); | |||
| void createSelectionContextMenu(); | |||
| INTERNAL math::Vec& dragOffset(); | |||
| INTERNAL bool& dragEnabled(); | |||
| @@ -52,12 +52,12 @@ struct RackWidget : widget::OpaqueWidget { | |||
| // Module methods | |||
| /** Adds a module and adds it to the Engine | |||
| Ownership rules work like add/removeChild() | |||
| /** Adds a module and adds it to the Engine, adopting ownership. | |||
| */ | |||
| void addModule(ModuleWidget* mw); | |||
| void addModuleAtMouse(ModuleWidget* mw); | |||
| /** Removes the module and transfers ownership to the caller */ | |||
| /** Removes the module and transfers ownership to the caller. | |||
| */ | |||
| void removeModule(ModuleWidget* mw); | |||
| /** Sets a module's box if non-colliding. Returns true if set */ | |||
| bool requestModulePos(ModuleWidget* mw, math::Vec pos); | |||
| @@ -65,21 +65,31 @@ struct RackWidget : widget::OpaqueWidget { | |||
| void setModulePosNearest(ModuleWidget* mw, math::Vec pos); | |||
| void setModulePosForce(ModuleWidget* mw, math::Vec pos); | |||
| ModuleWidget* getModule(int64_t moduleId); | |||
| bool isEmpty(); | |||
| std::list<ModuleWidget*> getModules(); | |||
| bool hasModules(); | |||
| void updateModuleOldPositions(); | |||
| history::ComplexAction* getModuleDragAction(); | |||
| void updateModuleSelections(); | |||
| int getNumSelectedModules(); | |||
| std::list<ModuleWidget*> getSelectedModules(); | |||
| void resetSelectedModulesAction(); | |||
| void randomizeSelectedModulesAction(); | |||
| void disconnectSelectedModulesAction(); | |||
| void bypassSelectedModulesAction(); | |||
| void deleteSelectedModulesAction(); | |||
| // Cable methods | |||
| void clearCables(); | |||
| void clearCablesAction(); | |||
| /** Removes all complete cables connected to the port */ | |||
| /** Removes all cables connected to the port */ | |||
| void clearCablesOnPort(PortWidget* port); | |||
| /** Adds a complete cable. | |||
| Ownership rules work like add/removeChild() | |||
| /** Adds a complete cable and adopts ownership. | |||
| */ | |||
| void addCable(CableWidget* cw); | |||
| /** Removes cable and releases ownership to caller. | |||
| */ | |||
| void removeCable(CableWidget* cw); | |||
| /** Takes ownership of `cw` and adds it as a child if it isn't already. */ | |||
| void setIncompleteCable(CableWidget* cw); | |||
| @@ -87,6 +97,7 @@ struct RackWidget : widget::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(int64_t cableId); | |||
| std::list<CableWidget*> getCompleteCables(); | |||
| /** Returns all cables attached to port, complete or not. */ | |||
| std::list<CableWidget*> getCablesOnPort(PortWidget* port); | |||
| }; | |||
| @@ -159,6 +159,51 @@ PortWidget* ModuleWidget::getOutput(int portId) { | |||
| }); | |||
| } | |||
| template <class T, typename F> | |||
| void doIfTypeRecursive(widget::Widget* w, F f) { | |||
| T* t = dynamic_cast<T*>(w); | |||
| if (t) | |||
| f(t); | |||
| for (widget::Widget* child : w->children) { | |||
| doIfTypeRecursive<T>(child, f); | |||
| } | |||
| } | |||
| std::list<ParamWidget*> ModuleWidget::getParams() { | |||
| std::list<ParamWidget*> pws; | |||
| doIfTypeRecursive<ParamWidget>(this, [&](ParamWidget* pw) { | |||
| pws.push_back(pw); | |||
| }); | |||
| return pws; | |||
| } | |||
| std::list<PortWidget*> ModuleWidget::getPorts() { | |||
| std::list<PortWidget*> pws; | |||
| doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) { | |||
| pws.push_back(pw); | |||
| }); | |||
| return pws; | |||
| } | |||
| std::list<PortWidget*> ModuleWidget::getInputs() { | |||
| std::list<PortWidget*> pws; | |||
| doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) { | |||
| if (pw->type == engine::Port::INPUT) | |||
| pws.push_back(pw); | |||
| }); | |||
| return pws; | |||
| } | |||
| std::list<PortWidget*> ModuleWidget::getOutputs() { | |||
| std::list<PortWidget*> pws; | |||
| doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) { | |||
| if (pw->type == engine::Port::OUTPUT) | |||
| pws.push_back(pw); | |||
| }); | |||
| return pws; | |||
| } | |||
| void ModuleWidget::draw(const DrawArgs& args) { | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| @@ -325,7 +370,12 @@ void ModuleWidget::onButton(const ButtonEvent& e) { | |||
| if (!e.isConsumed()) { | |||
| // Open context menu on right-click | |||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
| createContextMenu(); | |||
| if (internal->selected) { | |||
| createSelectionContextMenu(); | |||
| } | |||
| else { | |||
| createContextMenu(); | |||
| } | |||
| e.consume(this); | |||
| } | |||
| } | |||
| @@ -615,21 +665,10 @@ void ModuleWidget::saveDialog() { | |||
| save(path); | |||
| } | |||
| template <class T, typename F> | |||
| void doOfType(widget::Widget* w, F f) { | |||
| T* t = dynamic_cast<T*>(w); | |||
| if (t) | |||
| f(t); | |||
| for (widget::Widget* child : w->children) { | |||
| doOfType<T>(child, f); | |||
| } | |||
| } | |||
| void ModuleWidget::disconnect() { | |||
| doOfType<PortWidget>(this, [&](PortWidget* pw) { | |||
| for (PortWidget* pw : getPorts()) { | |||
| APP->scene->rack->clearCablesOnPort(pw); | |||
| }); | |||
| } | |||
| } | |||
| void ModuleWidget::resetAction() { | |||
| @@ -662,30 +701,31 @@ void ModuleWidget::randomizeAction() { | |||
| APP->history->push(h); | |||
| } | |||
| static void disconnectActions(ModuleWidget* mw, history::ComplexAction* complexAction) { | |||
| // Add CableRemove action for all cables | |||
| doOfType<PortWidget>(mw, [&](PortWidget* pw) { | |||
| void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) { | |||
| for (PortWidget* pw : getPorts()) { | |||
| for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) { | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| // Avoid creating duplicate actions for self-patched cables | |||
| if (pw->type == engine::Port::INPUT && cw->outputPort->module == mw->module) | |||
| continue; | |||
| // history::CableRemove | |||
| history::CableRemove* h = new history::CableRemove; | |||
| h->setCable(cw); | |||
| complexAction->push(h); | |||
| // Delete cable | |||
| APP->scene->rack->removeCable(cw); | |||
| delete cw; | |||
| } | |||
| }); | |||
| }; | |||
| } | |||
| void ModuleWidget::disconnectAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "disconnect cables"; | |||
| disconnectActions(this, complexAction); | |||
| APP->history->push(complexAction); | |||
| appendDisconnectActions(complexAction); | |||
| disconnect(); | |||
| if (!complexAction->isEmpty()) | |||
| APP->history->push(complexAction); | |||
| else | |||
| delete complexAction; | |||
| } | |||
| void ModuleWidget::cloneAction() { | |||
| @@ -722,11 +762,8 @@ void ModuleWidget::cloneAction() { | |||
| h->push(hma); | |||
| // Clone cables attached to input ports | |||
| doOfType<PortWidget>(this, [&](PortWidget* pw) { | |||
| if (pw->type != engine::Port::INPUT) | |||
| return; | |||
| std::list<CableWidget*> cables = APP->scene->rack->getCablesOnPort(pw); | |||
| for (CableWidget* cw : cables) { | |||
| for (PortWidget* pw : getInputs()) { | |||
| for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) { | |||
| // Create cable attached to cloned ModuleWidget's input | |||
| engine::Cable* clonedCable = new engine::Cable; | |||
| clonedCable->id = -1; | |||
| @@ -750,7 +787,7 @@ void ModuleWidget::cloneAction() { | |||
| hca->setCable(clonedCw); | |||
| h->push(hca); | |||
| } | |||
| }); | |||
| } | |||
| APP->history->push(h); | |||
| } | |||
| @@ -769,7 +806,7 @@ void ModuleWidget::bypassAction() { | |||
| void ModuleWidget::removeAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "remove module"; | |||
| disconnectActions(this, complexAction); | |||
| appendDisconnectActions(complexAction); | |||
| // history::ModuleRemove | |||
| history::ModuleRemove* moduleRemove = new history::ModuleRemove; | |||
| @@ -778,7 +815,7 @@ void ModuleWidget::removeAction() { | |||
| APP->history->push(complexAction); | |||
| // This disconnects cables, removes the module, and transfers ownership to caller | |||
| // This removes the module and transfers ownership to caller | |||
| APP->scene->rack->removeModule(this); | |||
| delete this; | |||
| } | |||
| @@ -1018,6 +1055,38 @@ void ModuleWidget::createContextMenu() { | |||
| appendContextMenu(menu); | |||
| } | |||
| void ModuleWidget::createSelectionContextMenu() { | |||
| ui::Menu* menu = createMenu(); | |||
| int n = APP->scene->rack->getNumSelectedModules(); | |||
| menu->addChild(createMenuLabel(string::f("%d selected %s", n, n == 1 ? "module" : "modules"))); | |||
| // Initialize | |||
| menu->addChild(createMenuItem("Initialize", "", [=]() { | |||
| APP->scene->rack->resetSelectedModulesAction(); | |||
| })); | |||
| // Randomize | |||
| menu->addChild(createMenuItem("Randomize", "", [=]() { | |||
| APP->scene->rack->randomizeSelectedModulesAction(); | |||
| })); | |||
| // Disconnect cables | |||
| menu->addChild(createMenuItem("Disconnect cables", "", [=]() { | |||
| APP->scene->rack->disconnectSelectedModulesAction(); | |||
| })); | |||
| // Bypass | |||
| menu->addChild(createMenuItem("Bypass", "", [=]() { | |||
| APP->scene->rack->bypassSelectedModulesAction(); | |||
| })); | |||
| // Delete | |||
| menu->addChild(createMenuItem("Delete", "", [=]() { | |||
| APP->scene->rack->deleteSelectedModulesAction(); | |||
| })); | |||
| } | |||
| math::Vec& ModuleWidget::dragOffset() { | |||
| return internal->dragOffset; | |||
| } | |||
| @@ -612,7 +612,17 @@ ModuleWidget* RackWidget::getModule(int64_t moduleId) { | |||
| return NULL; | |||
| } | |||
| bool RackWidget::isEmpty() { | |||
| std::list<ModuleWidget*> RackWidget::getModules() { | |||
| std::list<ModuleWidget*> mws; | |||
| for (widget::Widget* w : internal->moduleContainer->children) { | |||
| ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||
| assert(mw); | |||
| mws.push_back(mw); | |||
| } | |||
| return mws; | |||
| } | |||
| bool RackWidget::hasModules() { | |||
| return internal->moduleContainer->children.empty(); | |||
| } | |||
| @@ -659,6 +669,107 @@ void RackWidget::updateModuleSelections() { | |||
| } | |||
| } | |||
| int RackWidget::getNumSelectedModules() { | |||
| int count = 0; | |||
| for (widget::Widget* w : internal->moduleContainer->children) { | |||
| ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||
| assert(mw); | |||
| if (mw->selected()) | |||
| count++; | |||
| } | |||
| return count; | |||
| } | |||
| std::list<ModuleWidget*> RackWidget::getSelectedModules() { | |||
| std::list<ModuleWidget*> mws; | |||
| for (widget::Widget* w : internal->moduleContainer->children) { | |||
| ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||
| assert(mw); | |||
| if (mw->selected()) | |||
| mws.push_back(mw); | |||
| } | |||
| return mws; | |||
| } | |||
| void RackWidget::resetSelectedModulesAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "reset modules"; | |||
| for (ModuleWidget* mw : getSelectedModules()) { | |||
| assert(mw->module); | |||
| // history::ModuleChange | |||
| history::ModuleChange* h = new history::ModuleChange; | |||
| h->moduleId = mw->module->id; | |||
| h->oldModuleJ = mw->toJson(); | |||
| APP->engine->resetModule(mw->module); | |||
| h->newModuleJ = mw->toJson(); | |||
| complexAction->push(h); | |||
| } | |||
| APP->history->push(complexAction); | |||
| } | |||
| void RackWidget::randomizeSelectedModulesAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "randomize modules"; | |||
| for (ModuleWidget* mw : getSelectedModules()) { | |||
| assert(mw->module); | |||
| // history::ModuleChange | |||
| history::ModuleChange* h = new history::ModuleChange; | |||
| h->moduleId = mw->module->id; | |||
| h->oldModuleJ = mw->toJson(); | |||
| APP->engine->randomizeModule(mw->module); | |||
| h->newModuleJ = mw->toJson(); | |||
| complexAction->push(h); | |||
| } | |||
| APP->history->push(complexAction); | |||
| } | |||
| void RackWidget::disconnectSelectedModulesAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "disconnect cables"; | |||
| for (ModuleWidget* mw : getSelectedModules()) { | |||
| mw->appendDisconnectActions(complexAction); | |||
| } | |||
| if (!complexAction->isEmpty()) | |||
| APP->history->push(complexAction); | |||
| else | |||
| delete complexAction; | |||
| } | |||
| void RackWidget::bypassSelectedModulesAction() { | |||
| // TODO | |||
| } | |||
| void RackWidget::deleteSelectedModulesAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "remove modules"; | |||
| for (ModuleWidget* mw : getSelectedModules()) { | |||
| mw->appendDisconnectActions(complexAction); | |||
| // history::ModuleRemove | |||
| history::ModuleRemove* moduleRemove = new history::ModuleRemove; | |||
| moduleRemove->setModule(mw); | |||
| complexAction->push(moduleRemove); | |||
| removeModule(mw); | |||
| delete mw; | |||
| } | |||
| APP->history->push(complexAction); | |||
| } | |||
| void RackWidget::clearCables() { | |||
| incompleteCable = NULL; | |||
| internal->cableContainer->clearChildren(); | |||
| @@ -669,12 +780,7 @@ void RackWidget::clearCablesAction() { | |||
| history::ComplexAction* complexAction = new history::ComplexAction; | |||
| complexAction->name = "clear cables"; | |||
| for (widget::Widget* w : internal->cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| for (CableWidget* cw : getCompleteCables()) { | |||
| // history::CableRemove | |||
| history::CableRemove* h = new history::CableRemove; | |||
| h->setCable(cw); | |||
| @@ -685,6 +791,7 @@ void RackWidget::clearCablesAction() { | |||
| APP->history->push(complexAction); | |||
| else | |||
| delete complexAction; | |||
| clearCables(); | |||
| } | |||
| @@ -756,6 +863,17 @@ CableWidget* RackWidget::getCable(int64_t cableId) { | |||
| return NULL; | |||
| } | |||
| std::list<CableWidget*> RackWidget::getCompleteCables() { | |||
| std::list<CableWidget*> cws; | |||
| for (widget::Widget* w : internal->cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (cw->isComplete()) | |||
| cws.push_back(cw); | |||
| } | |||
| return cws; | |||
| } | |||
| std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | |||
| assert(port); | |||
| std::list<CableWidget*> cws; | |||
| @@ -79,7 +79,7 @@ void PatchManager::clear() { | |||
| static bool promptClear(std::string text) { | |||
| if (APP->history->isSaved()) | |||
| return true; | |||
| if (APP->scene->rack->isEmpty()) | |||
| if (APP->scene->rack->hasModules()) | |||
| return true; | |||
| return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str()); | |||
| } | |||