| @@ -82,7 +82,7 @@ struct RackWidget : widget::OpaqueWidget { | |||||
| bool hasSelection(); | bool hasSelection(); | ||||
| const std::set<ModuleWidget*>& getSelected(); | const std::set<ModuleWidget*>& getSelected(); | ||||
| bool isSelected(ModuleWidget* mw); | bool isSelected(ModuleWidget* mw); | ||||
| json_t* selectionToJson(); | |||||
| json_t* selectionToJson(bool cables = true); | |||||
| void loadSelection(std::string path); | void loadSelection(std::string path); | ||||
| void loadSelectionDialog(); | void loadSelectionDialog(); | ||||
| void saveSelection(std::string path); | void saveSelection(std::string path); | ||||
| @@ -322,11 +322,11 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
| } | } | ||||
| } | } | ||||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| cloneAction(); | |||||
| cloneAction(false); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | ||||
| cloneAction(false); | |||||
| cloneAction(true); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| @@ -1004,7 +1004,14 @@ void ModuleWidget::createContextMenu() { | |||||
| menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | ||||
| if (!weakThis) | if (!weakThis) | ||||
| return; | return; | ||||
| weakThis->cloneAction(); | |||||
| weakThis->cloneAction(false); | |||||
| })); | |||||
| // Duplicate with cables | |||||
| menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { | |||||
| if (!weakThis) | |||||
| return; | |||||
| weakThis->cloneAction(true); | |||||
| })); | })); | ||||
| // Delete | // Delete | ||||
| @@ -863,7 +863,7 @@ bool RackWidget::isSelected(ModuleWidget* mw) { | |||||
| return (it != internal->selectedModules.end()); | return (it != internal->selectedModules.end()); | ||||
| } | } | ||||
| json_t* RackWidget::selectionToJson() { | |||||
| json_t* RackWidget::selectionToJson(bool cables) { | |||||
| json_t* rootJ = json_object(); | json_t* rootJ = json_object(); | ||||
| std::set<engine::Module*> modules; | std::set<engine::Module*> modules; | ||||
| @@ -884,26 +884,28 @@ json_t* RackWidget::selectionToJson() { | |||||
| } | } | ||||
| json_object_set_new(rootJ, "modules", modulesJ); | json_object_set_new(rootJ, "modules", modulesJ); | ||||
| // cables | |||||
| json_t* cablesJ = json_array(); | |||||
| for (CableWidget* cw : getCompleteCables()) { | |||||
| // Only add cables attached on both ends to selected modules | |||||
| engine::Cable* cable = cw->getCable(); | |||||
| if (!cable || !cable->inputModule || !cable->outputModule) | |||||
| continue; | |||||
| const auto inputIt = modules.find(cable->inputModule); | |||||
| if (inputIt == modules.end()) | |||||
| continue; | |||||
| const auto outputIt = modules.find(cable->outputModule); | |||||
| if (outputIt == modules.end()) | |||||
| continue; | |||||
| if (cables) { | |||||
| // cables | |||||
| json_t* cablesJ = json_array(); | |||||
| for (CableWidget* cw : getCompleteCables()) { | |||||
| // Only add cables attached on both ends to selected modules | |||||
| engine::Cable* cable = cw->getCable(); | |||||
| if (!cable || !cable->inputModule || !cable->outputModule) | |||||
| continue; | |||||
| const auto inputIt = modules.find(cable->inputModule); | |||||
| if (inputIt == modules.end()) | |||||
| continue; | |||||
| const auto outputIt = modules.find(cable->outputModule); | |||||
| if (outputIt == modules.end()) | |||||
| continue; | |||||
| json_t* cableJ = cable->toJson(); | |||||
| cw->mergeJson(cableJ); | |||||
| json_t* cableJ = cable->toJson(); | |||||
| cw->mergeJson(cableJ); | |||||
| json_array_append_new(cablesJ, cableJ); | |||||
| json_array_append_new(cablesJ, cableJ); | |||||
| } | |||||
| json_object_set_new(rootJ, "cables", cablesJ); | |||||
| } | } | ||||
| json_object_set_new(rootJ, "cables", cablesJ); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| @@ -1056,7 +1058,7 @@ void RackWidget::disconnectSelectionAction() { | |||||
| } | } | ||||
| void RackWidget::cloneSelectionAction(bool cloneCables) { | void RackWidget::cloneSelectionAction(bool cloneCables) { | ||||
| json_t* rootJ = selectionToJson(); | |||||
| json_t* rootJ = selectionToJson(cloneCables); | |||||
| DEFER({json_decref(rootJ);}); | DEFER({json_decref(rootJ);}); | ||||
| history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
| @@ -1257,7 +1259,12 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||||
| // Duplicate | // Duplicate | ||||
| menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | ||||
| cloneSelectionAction(); | |||||
| cloneSelectionAction(false); | |||||
| }, n == 0)); | |||||
| // Duplicate with cables | |||||
| menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { | |||||
| cloneSelectionAction(true); | |||||
| }, n == 0)); | }, n == 0)); | ||||
| // Delete | // Delete | ||||
| @@ -278,13 +278,13 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
| } | } | ||||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| if (rack->hasSelection()) { | if (rack->hasSelection()) { | ||||
| rack->cloneSelectionAction(); | |||||
| rack->cloneSelectionAction(false); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| } | } | ||||
| if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { | ||||
| if (rack->hasSelection()) { | if (rack->hasSelection()) { | ||||
| rack->cloneSelectionAction(false); | |||||
| rack->cloneSelectionAction(true); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| } | } | ||||
| @@ -34,7 +34,9 @@ void MenuItem::step() { | |||||
| const float rightPadding = 10.0; | const float rightPadding = 10.0; | ||||
| // HACK use APP->window->vg from the window. | // HACK use APP->window->vg from the window. | ||||
| // All this does is inspect the font, so it shouldn't modify APP->window->vg and should work when called from a widget::FramebufferWidget for example. | // All this does is inspect the font, so it shouldn't modify APP->window->vg and should work when called from a widget::FramebufferWidget for example. | ||||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + bndLabelWidth(APP->window->vg, -1, rightText.c_str()) + rightPadding; | |||||
| box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + rightPadding; | |||||
| if (!rightText.empty()) | |||||
| box.size.x += bndLabelWidth(APP->window->vg, -1, rightText.c_str()) - 10.0; | |||||
| Widget::step(); | Widget::step(); | ||||
| } | } | ||||