@@ -6,6 +6,8 @@ | |||||
#include <app/ParamWidget.hpp> | #include <app/ParamWidget.hpp> | ||||
#include <plugin/Model.hpp> | #include <plugin/Model.hpp> | ||||
#include <engine/Module.hpp> | #include <engine/Module.hpp> | ||||
#include <history.hpp> | |||||
#include <list> | |||||
namespace rack { | namespace rack { | ||||
@@ -54,12 +56,17 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
ParamWidget* getParam(int paramId); | ParamWidget* getParam(int paramId); | ||||
PortWidget* getInput(int portId); | PortWidget* getInput(int portId); | ||||
PortWidget* getOutput(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 draw(const DrawArgs& args) override; | ||||
void drawShadow(const DrawArgs& args); | void drawShadow(const DrawArgs& args); | ||||
/** Override to add context menu entries to your subclass. | /** 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) {} | virtual void appendContextMenu(ui::Menu* menu) {} | ||||
@@ -98,12 +105,14 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
Called when the user clicks Randomize in the context menu. | Called when the user clicks Randomize in the context menu. | ||||
*/ | */ | ||||
void randomizeAction(); | void randomizeAction(); | ||||
void appendDisconnectActions(history::ComplexAction* complexAction); | |||||
void disconnectAction(); | void disconnectAction(); | ||||
void cloneAction(); | void cloneAction(); | ||||
void bypassAction(); | void bypassAction(); | ||||
/** Deletes `this` */ | /** Deletes `this` */ | ||||
void removeAction(); | void removeAction(); | ||||
void createContextMenu(); | void createContextMenu(); | ||||
void createSelectionContextMenu(); | |||||
INTERNAL math::Vec& dragOffset(); | INTERNAL math::Vec& dragOffset(); | ||||
INTERNAL bool& dragEnabled(); | INTERNAL bool& dragEnabled(); | ||||
@@ -52,12 +52,12 @@ struct RackWidget : widget::OpaqueWidget { | |||||
// Module methods | // 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 addModule(ModuleWidget* mw); | ||||
void addModuleAtMouse(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); | void removeModule(ModuleWidget* mw); | ||||
/** Sets a module's box if non-colliding. Returns true if set */ | /** Sets a module's box if non-colliding. Returns true if set */ | ||||
bool requestModulePos(ModuleWidget* mw, math::Vec pos); | bool requestModulePos(ModuleWidget* mw, math::Vec pos); | ||||
@@ -65,21 +65,31 @@ struct RackWidget : widget::OpaqueWidget { | |||||
void setModulePosNearest(ModuleWidget* mw, math::Vec pos); | void setModulePosNearest(ModuleWidget* mw, math::Vec pos); | ||||
void setModulePosForce(ModuleWidget* mw, math::Vec pos); | void setModulePosForce(ModuleWidget* mw, math::Vec pos); | ||||
ModuleWidget* getModule(int64_t moduleId); | ModuleWidget* getModule(int64_t moduleId); | ||||
bool isEmpty(); | |||||
std::list<ModuleWidget*> getModules(); | |||||
bool hasModules(); | |||||
void updateModuleOldPositions(); | void updateModuleOldPositions(); | ||||
history::ComplexAction* getModuleDragAction(); | history::ComplexAction* getModuleDragAction(); | ||||
void updateModuleSelections(); | void updateModuleSelections(); | ||||
int getNumSelectedModules(); | |||||
std::list<ModuleWidget*> getSelectedModules(); | |||||
void resetSelectedModulesAction(); | |||||
void randomizeSelectedModulesAction(); | |||||
void disconnectSelectedModulesAction(); | |||||
void bypassSelectedModulesAction(); | |||||
void deleteSelectedModulesAction(); | |||||
// Cable methods | // Cable methods | ||||
void clearCables(); | void clearCables(); | ||||
void clearCablesAction(); | void clearCablesAction(); | ||||
/** Removes all complete cables connected to the port */ | |||||
/** Removes all cables connected to the port */ | |||||
void clearCablesOnPort(PortWidget* 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); | void addCable(CableWidget* cw); | ||||
/** Removes cable and releases ownership to caller. | |||||
*/ | |||||
void removeCable(CableWidget* cw); | void removeCable(CableWidget* cw); | ||||
/** Takes ownership of `cw` and adds it as a child if it isn't already. */ | /** Takes ownership of `cw` and adds it as a child if it isn't already. */ | ||||
void setIncompleteCable(CableWidget* cw); | 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. */ | /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */ | ||||
CableWidget* getTopCable(PortWidget* port); | CableWidget* getTopCable(PortWidget* port); | ||||
CableWidget* getCable(int64_t cableId); | CableWidget* getCable(int64_t cableId); | ||||
std::list<CableWidget*> getCompleteCables(); | |||||
/** Returns all cables attached to port, complete or not. */ | /** Returns all cables attached to port, complete or not. */ | ||||
std::list<CableWidget*> getCablesOnPort(PortWidget* port); | 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) { | void ModuleWidget::draw(const DrawArgs& args) { | ||||
nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | ||||
@@ -325,7 +370,12 @@ void ModuleWidget::onButton(const ButtonEvent& e) { | |||||
if (!e.isConsumed()) { | if (!e.isConsumed()) { | ||||
// Open context menu on right-click | // Open context menu on right-click | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | ||||
createContextMenu(); | |||||
if (internal->selected) { | |||||
createSelectionContextMenu(); | |||||
} | |||||
else { | |||||
createContextMenu(); | |||||
} | |||||
e.consume(this); | e.consume(this); | ||||
} | } | ||||
} | } | ||||
@@ -615,21 +665,10 @@ void ModuleWidget::saveDialog() { | |||||
save(path); | 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() { | void ModuleWidget::disconnect() { | ||||
doOfType<PortWidget>(this, [&](PortWidget* pw) { | |||||
for (PortWidget* pw : getPorts()) { | |||||
APP->scene->rack->clearCablesOnPort(pw); | APP->scene->rack->clearCablesOnPort(pw); | ||||
}); | |||||
} | |||||
} | } | ||||
void ModuleWidget::resetAction() { | void ModuleWidget::resetAction() { | ||||
@@ -662,30 +701,31 @@ void ModuleWidget::randomizeAction() { | |||||
APP->history->push(h); | 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)) { | for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) { | ||||
if (!cw->isComplete()) | if (!cw->isComplete()) | ||||
continue; | 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 | ||||
history::CableRemove* h = new history::CableRemove; | history::CableRemove* h = new history::CableRemove; | ||||
h->setCable(cw); | h->setCable(cw); | ||||
complexAction->push(h); | complexAction->push(h); | ||||
// Delete cable | |||||
APP->scene->rack->removeCable(cw); | |||||
delete cw; | |||||
} | } | ||||
}); | |||||
}; | |||||
} | } | ||||
void ModuleWidget::disconnectAction() { | void ModuleWidget::disconnectAction() { | ||||
history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
complexAction->name = "disconnect cables"; | 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() { | void ModuleWidget::cloneAction() { | ||||
@@ -722,11 +762,8 @@ void ModuleWidget::cloneAction() { | |||||
h->push(hma); | h->push(hma); | ||||
// Clone cables attached to input ports | // 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 | // Create cable attached to cloned ModuleWidget's input | ||||
engine::Cable* clonedCable = new engine::Cable; | engine::Cable* clonedCable = new engine::Cable; | ||||
clonedCable->id = -1; | clonedCable->id = -1; | ||||
@@ -750,7 +787,7 @@ void ModuleWidget::cloneAction() { | |||||
hca->setCable(clonedCw); | hca->setCable(clonedCw); | ||||
h->push(hca); | h->push(hca); | ||||
} | } | ||||
}); | |||||
} | |||||
APP->history->push(h); | APP->history->push(h); | ||||
} | } | ||||
@@ -769,7 +806,7 @@ void ModuleWidget::bypassAction() { | |||||
void ModuleWidget::removeAction() { | void ModuleWidget::removeAction() { | ||||
history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
complexAction->name = "remove module"; | complexAction->name = "remove module"; | ||||
disconnectActions(this, complexAction); | |||||
appendDisconnectActions(complexAction); | |||||
// history::ModuleRemove | // history::ModuleRemove | ||||
history::ModuleRemove* moduleRemove = new history::ModuleRemove; | history::ModuleRemove* moduleRemove = new history::ModuleRemove; | ||||
@@ -778,7 +815,7 @@ void ModuleWidget::removeAction() { | |||||
APP->history->push(complexAction); | 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); | APP->scene->rack->removeModule(this); | ||||
delete this; | delete this; | ||||
} | } | ||||
@@ -1018,6 +1055,38 @@ void ModuleWidget::createContextMenu() { | |||||
appendContextMenu(menu); | 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() { | math::Vec& ModuleWidget::dragOffset() { | ||||
return internal->dragOffset; | return internal->dragOffset; | ||||
} | } | ||||
@@ -612,7 +612,17 @@ ModuleWidget* RackWidget::getModule(int64_t moduleId) { | |||||
return NULL; | 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(); | 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() { | void RackWidget::clearCables() { | ||||
incompleteCable = NULL; | incompleteCable = NULL; | ||||
internal->cableContainer->clearChildren(); | internal->cableContainer->clearChildren(); | ||||
@@ -669,12 +780,7 @@ void RackWidget::clearCablesAction() { | |||||
history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
complexAction->name = "clear cables"; | 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 | ||||
history::CableRemove* h = new history::CableRemove; | history::CableRemove* h = new history::CableRemove; | ||||
h->setCable(cw); | h->setCable(cw); | ||||
@@ -685,6 +791,7 @@ void RackWidget::clearCablesAction() { | |||||
APP->history->push(complexAction); | APP->history->push(complexAction); | ||||
else | else | ||||
delete complexAction; | delete complexAction; | ||||
clearCables(); | clearCables(); | ||||
} | } | ||||
@@ -756,6 +863,17 @@ CableWidget* RackWidget::getCable(int64_t cableId) { | |||||
return NULL; | 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) { | std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | ||||
assert(port); | assert(port); | ||||
std::list<CableWidget*> cws; | std::list<CableWidget*> cws; | ||||
@@ -79,7 +79,7 @@ void PatchManager::clear() { | |||||
static bool promptClear(std::string text) { | static bool promptClear(std::string text) { | ||||
if (APP->history->isSaved()) | if (APP->history->isSaved()) | ||||
return true; | return true; | ||||
if (APP->scene->rack->isEmpty()) | |||||
if (APP->scene->rack->hasModules()) | |||||
return true; | return true; | ||||
return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str()); | return osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, text.c_str()); | ||||
} | } | ||||