@@ -80,6 +80,7 @@ struct ModuleWidget : OpaqueWidget { | |||||
void removeAction(); | void removeAction(); | ||||
void bypassAction(); | void bypassAction(); | ||||
void cloneAction(); | |||||
void createContextMenu(); | void createContextMenu(); | ||||
/** 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 MenuEntry first for spacing. | It is recommended to add a blank MenuEntry first for spacing. | ||||
@@ -45,7 +45,6 @@ struct RackWidget : OpaqueWidget { | |||||
void addModuleAtMouse(ModuleWidget *m); | void addModuleAtMouse(ModuleWidget *m); | ||||
/** Removes the module and transfers ownership to the caller */ | /** Removes the module and transfers ownership to the caller */ | ||||
void removeModule(ModuleWidget *m); | void removeModule(ModuleWidget *m); | ||||
void cloneModule(ModuleWidget *m); | |||||
/** 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 requestModuleBox(ModuleWidget *m, math::Rect box); | bool requestModuleBox(ModuleWidget *m, math::Rect box); | ||||
/** Moves a module to the closest non-colliding position */ | /** Moves a module to the closest non-colliding position */ | ||||
@@ -97,7 +97,7 @@ struct ModuleCloneItem : MenuItem { | |||||
rightText = WINDOW_MOD_CTRL_NAME "+D"; | rightText = WINDOW_MOD_CTRL_NAME "+D"; | ||||
} | } | ||||
void onAction(const event::Action &e) override { | void onAction(const event::Action &e) override { | ||||
app()->scene->rackWidget->cloneModule(moduleWidget); | |||||
moduleWidget->cloneAction(); | |||||
} | } | ||||
}; | }; | ||||
@@ -229,8 +229,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { | |||||
} break; | } break; | ||||
case GLFW_KEY_D: { | case GLFW_KEY_D: { | ||||
if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { | ||||
app()->scene->rackWidget->cloneModule(this); | |||||
e.consume(this); | |||||
cloneAction(); | |||||
} | } | ||||
} break; | } break; | ||||
case GLFW_KEY_U: { | case GLFW_KEY_U: { | ||||
@@ -548,6 +547,24 @@ void ModuleWidget::bypassAction() { | |||||
h->redo(); | h->redo(); | ||||
} | } | ||||
void ModuleWidget::cloneAction() { | |||||
ModuleWidget *clonedModuleWidget = model->createModuleWidget(); | |||||
assert(clonedModuleWidget); | |||||
// JSON serialization is the obvious way to do this | |||||
json_t *moduleJ = toJson(); | |||||
clonedModuleWidget->fromJson(moduleJ); | |||||
json_decref(moduleJ); | |||||
app()->scene->rackWidget->addModuleAtMouse(clonedModuleWidget); | |||||
// Push ModuleAdd history action | |||||
history::ModuleAdd *h = new history::ModuleAdd; | |||||
h->model = clonedModuleWidget->model; | |||||
h->moduleId = clonedModuleWidget->module->id; | |||||
h->pos = clonedModuleWidget->box.pos; | |||||
app()->history->push(h); | |||||
} | |||||
void ModuleWidget::createContextMenu() { | void ModuleWidget::createContextMenu() { | ||||
Menu *menu = createMenu(); | Menu *menu = createMenu(); | ||||
assert(model); | assert(model); | ||||
@@ -495,17 +495,6 @@ void RackWidget::removeModule(ModuleWidget *m) { | |||||
moduleContainer->removeChild(m); | moduleContainer->removeChild(m); | ||||
} | } | ||||
void RackWidget::cloneModule(ModuleWidget *m) { | |||||
// JSON serialization is the obvious way to do this | |||||
json_t *moduleJ = m->toJson(); | |||||
ModuleWidget *clonedModuleWidget = moduleFromJson(moduleJ); | |||||
json_decref(moduleJ); | |||||
addModule(clonedModuleWidget); | |||||
math::Rect clonedBox = clonedModuleWidget->box; | |||||
clonedBox.pos = m->box.pos; | |||||
requestModuleBoxNearest(clonedModuleWidget, clonedBox); | |||||
} | |||||
bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { | bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { | ||||
if (box.pos.x < 0 || box.pos.y < 0) | if (box.pos.x < 0 || box.pos.y < 0) | ||||
return false; | return false; | ||||