@@ -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(); | ||||
} | } | ||||