@@ -39,6 +39,7 @@ struct CableWidget : widget::Widget { | |||
Adopts ownership. | |||
*/ | |||
void setCable(engine::Cable* cable); | |||
engine::Cable* getCable(); | |||
math::Vec getInputPos(); | |||
math::Vec getOutputPos(); | |||
json_t* toJson(); | |||
@@ -1,6 +1,4 @@ | |||
#pragma once | |||
#include <map> | |||
#include <app/common.hpp> | |||
#include <widget/OpaqueWidget.hpp> | |||
#include <widget/FramebufferWidget.hpp> | |||
@@ -49,7 +47,7 @@ struct RackWidget : widget::OpaqueWidget { | |||
void clear(); | |||
void mergeJson(json_t* rootJ); | |||
void fromJson(json_t* rootJ); | |||
void pastePresetClipboardAction(); | |||
void pasteClipboardAction(); | |||
// Module methods | |||
@@ -66,7 +64,7 @@ struct RackWidget : widget::OpaqueWidget { | |||
void setModulePosNearest(ModuleWidget* mw, math::Vec pos); | |||
void setModulePosForce(ModuleWidget* mw, math::Vec pos); | |||
ModuleWidget* getModule(int64_t moduleId); | |||
std::list<ModuleWidget*> getModules(); | |||
std::vector<ModuleWidget*> getModules(); | |||
bool hasModules(); | |||
void updateModuleOldPositions(); | |||
history::ComplexAction* getModuleDragAction(); | |||
@@ -78,13 +76,15 @@ struct RackWidget : widget::OpaqueWidget { | |||
void selectAllModules(); | |||
bool hasSelectedModules(); | |||
int getNumSelectedModules(); | |||
std::list<ModuleWidget*> getSelectedModules(); | |||
std::vector<ModuleWidget*> getSelectedModules(); | |||
json_t* selectedModulesToJson(); | |||
void resetSelectedModulesAction(); | |||
void randomizeSelectedModulesAction(); | |||
void disconnectSelectedModulesAction(); | |||
void cloneSelectedModulesAction(); | |||
void bypassSelectedModulesAction(bool bypassed); | |||
bool areSelectedModulesBypassed(); | |||
void copyClipboardSelectedModules(); | |||
void deleteSelectedModulesAction(); | |||
bool requestSelectedModulePos(math::Vec delta); | |||
void setSelectedModulesPosNearest(math::Vec delta); | |||
@@ -108,9 +108,9 @@ 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(); | |||
std::vector<CableWidget*> getCompleteCables(); | |||
/** Returns all cables attached to port, complete or not. */ | |||
std::list<CableWidget*> getCablesOnPort(PortWidget* port); | |||
std::vector<CableWidget*> getCablesOnPort(PortWidget* port); | |||
}; | |||
@@ -185,6 +185,10 @@ void CableWidget::setCable(engine::Cable* cable) { | |||
} | |||
} | |||
engine::Cable* CableWidget::getCable() { | |||
return cable; | |||
} | |||
math::Vec CableWidget::getInputPos() { | |||
if (inputPort) { | |||
return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
@@ -318,24 +318,7 @@ void ModuleWidget::onHover(const HoverEvent& e) { | |||
} | |||
void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.isConsumed()) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
// Don't handle key commands if modules are selected, since it will interfere with Scene's module selection key commands | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
resetAction(); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
randomizeAction(); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
copyClipboard(); | |||
e.consume(this); | |||
@@ -345,40 +328,42 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||
e.consume(this); | |||
} | |||
if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
cloneAction(); | |||
e.consume(this); | |||
} | |||
cloneAction(); | |||
e.consume(this); | |||
} | |||
if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { | |||
removeAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
resetAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
randomizeAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
disconnectAction(); | |||
e.consume(this); | |||
} | |||
disconnectAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
bypassAction(!module->isBypassed()); | |||
e.consume(this); | |||
} | |||
} | |||
if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
removeAction(); | |||
e.consume(this); | |||
} | |||
bypassAction(!module->isBypassed()); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.action == RACK_HELD) { | |||
// Also handle Delete/Backspace when holding the key while hovering | |||
if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { | |||
if (!APP->scene->rack->hasSelectedModules()) { | |||
removeAction(); | |||
e.consume(NULL); | |||
} | |||
removeAction(); | |||
e.consume(NULL); | |||
} | |||
} | |||
if (e.isConsumed()) | |||
return; | |||
OpaqueWidget::onHoverKey(e); | |||
} | |||
void ModuleWidget::onButton(const ButtonEvent& e) { | |||
@@ -1077,18 +1062,14 @@ void ModuleWidget::createContextMenu() { | |||
weakThis->randomizeAction(); | |||
})); | |||
// Disconnect cables | |||
menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() { | |||
if (!weakThis) | |||
return; | |||
weakThis->disconnectAction(); | |||
})); | |||
menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
if (!weakThis) | |||
return; | |||
weakThis->cloneAction(); | |||
})); | |||
// Bypass | |||
std::string bypassText = RACK_MOD_CTRL_NAME "+E"; | |||
bool bypassed = module && module->isBypassed(); | |||
if (bypassed) | |||
@@ -1099,6 +1080,14 @@ void ModuleWidget::createContextMenu() { | |||
weakThis->bypassAction(!bypassed); | |||
})); | |||
// Duplicate | |||
menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
if (!weakThis) | |||
return; | |||
weakThis->cloneAction(); | |||
})); | |||
// Delete | |||
menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | |||
if (!weakThis) | |||
return; | |||
@@ -39,8 +39,7 @@ struct PortTooltip : ui::Tooltip { | |||
text += string::f("% .3fV", math::normalizeZero(v)); | |||
} | |||
// Connected to | |||
std::list<CableWidget*> cables = APP->scene->rack->getCablesOnPort(portWidget); | |||
for (CableWidget* cable : cables) { | |||
for (CableWidget* cable : APP->scene->rack->getCablesOnPort(portWidget)) { | |||
PortWidget* otherPw = (portWidget->type == engine::Port::INPUT) ? cable->outputPort : cable->inputPort; | |||
if (!otherPw) | |||
continue; | |||
@@ -138,15 +138,6 @@ void RackWidget::onHover(const HoverEvent& e) { | |||
void RackWidget::onHoverKey(const HoverKeyEvent& e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.isConsumed()) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
pastePresetClipboardAction(); | |||
e.consume(this); | |||
} | |||
} | |||
} | |||
void RackWidget::onButton(const ButtonEvent& e) { | |||
@@ -268,8 +259,8 @@ void RackWidget::mergeJson(json_t* rootJ) { | |||
continue; | |||
} | |||
// Merge CableWidget JSON | |||
json_t* cwJ = cw->toJson(); | |||
// Merge cable JSON object | |||
json_object_update(cableJ, cwJ); | |||
json_decref(cwJ); | |||
} | |||
@@ -368,7 +359,7 @@ void RackWidget::fromJson(json_t* rootJ) { | |||
} | |||
} | |||
void RackWidget::pastePresetClipboardAction() { | |||
void RackWidget::pasteClipboardAction() { | |||
const char* moduleJson = glfwGetClipboardString(APP->window->win); | |||
if (!moduleJson) { | |||
WARN("Could not get text from clipboard."); | |||
@@ -614,8 +605,8 @@ ModuleWidget* RackWidget::getModule(int64_t moduleId) { | |||
return NULL; | |||
} | |||
std::list<ModuleWidget*> RackWidget::getModules() { | |||
std::list<ModuleWidget*> mws; | |||
std::vector<ModuleWidget*> RackWidget::getModules() { | |||
std::vector<ModuleWidget*> mws; | |||
for (widget::Widget* w : internal->moduleContainer->children) { | |||
ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||
assert(mw); | |||
@@ -694,8 +685,8 @@ int RackWidget::getNumSelectedModules() { | |||
return count; | |||
} | |||
std::list<ModuleWidget*> RackWidget::getSelectedModules() { | |||
std::list<ModuleWidget*> mws; | |||
std::vector<ModuleWidget*> RackWidget::getSelectedModules() { | |||
std::vector<ModuleWidget*> mws; | |||
for (widget::Widget* w : internal->moduleContainer->children) { | |||
ModuleWidget* mw = dynamic_cast<ModuleWidget*>(w); | |||
assert(mw); | |||
@@ -705,6 +696,49 @@ std::list<ModuleWidget*> RackWidget::getSelectedModules() { | |||
return mws; | |||
} | |||
json_t* RackWidget::selectedModulesToJson() { | |||
json_t* rootJ = json_object(); | |||
std::set<engine::Module*> modules; | |||
// modules | |||
json_t* modulesJ = json_array(); | |||
for (ModuleWidget* mw : getSelectedModules()) { | |||
json_t* moduleJ = mw->toJson(); | |||
json_array_append_new(modulesJ, moduleJ); | |||
modules.insert(mw->getModule()); | |||
} | |||
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; | |||
json_t* cableJ = cable->toJson(); | |||
// Merge CableWidget JSON | |||
json_t* cwJ = cw->toJson(); | |||
json_object_update(cableJ, cwJ); | |||
json_decref(cwJ); | |||
json_array_append_new(cablesJ, cableJ); | |||
} | |||
json_object_set_new(rootJ, "cables", cablesJ); | |||
return rootJ; | |||
} | |||
void RackWidget::resetSelectedModulesAction() { | |||
history::ComplexAction* complexAction = new history::ComplexAction; | |||
complexAction->name = "reset modules"; | |||
@@ -797,6 +831,14 @@ bool RackWidget::areSelectedModulesBypassed() { | |||
return true; | |||
} | |||
void RackWidget::copyClipboardSelectedModules() { | |||
json_t* rootJ = selectedModulesToJson(); | |||
DEFER({json_decref(rootJ);}); | |||
char* moduleJson = json_dumps(rootJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
DEFER({std::free(moduleJson);}); | |||
glfwSetClipboardString(APP->window->win, moduleJson); | |||
} | |||
void RackWidget::deleteSelectedModulesAction() { | |||
history::ComplexAction* complexAction = new history::ComplexAction; | |||
complexAction->name = "remove modules"; | |||
@@ -866,6 +908,26 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
deselectModules(); | |||
}, n == 0)); | |||
// Copy | |||
menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { | |||
copyClipboardSelectedModules(); | |||
}, n == 0)); | |||
// Paste | |||
menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { | |||
pasteClipboardAction(); | |||
})); | |||
// Duplicate | |||
menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
cloneSelectedModulesAction(); | |||
}, n == 0)); | |||
// Delete | |||
menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | |||
deleteSelectedModulesAction(); | |||
}, n == 0)); | |||
// Initialize | |||
menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() { | |||
resetSelectedModulesAction(); | |||
@@ -881,11 +943,6 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
disconnectSelectedModulesAction(); | |||
}, n == 0)); | |||
// Duplicate | |||
menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { | |||
cloneSelectedModulesAction(); | |||
}, n == 0)); | |||
// Bypass | |||
std::string bypassText = RACK_MOD_CTRL_NAME "+E"; | |||
bool bypassed = (n > 0) && areSelectedModulesBypassed(); | |||
@@ -894,11 +951,6 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { | |||
menu->addChild(createMenuItem("Bypass", bypassText, [=]() { | |||
bypassSelectedModulesAction(!bypassed); | |||
}, n == 0)); | |||
// Delete | |||
menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { | |||
deleteSelectedModulesAction(); | |||
}, n == 0)); | |||
} | |||
void RackWidget::clearCables() { | |||
@@ -994,8 +1046,8 @@ CableWidget* RackWidget::getCable(int64_t cableId) { | |||
return NULL; | |||
} | |||
std::list<CableWidget*> RackWidget::getCompleteCables() { | |||
std::list<CableWidget*> cws; | |||
std::vector<CableWidget*> RackWidget::getCompleteCables() { | |||
std::vector<CableWidget*> cws; | |||
for (widget::Widget* w : internal->cableContainer->children) { | |||
CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
assert(cw); | |||
@@ -1005,9 +1057,9 @@ std::list<CableWidget*> RackWidget::getCompleteCables() { | |||
return cws; | |||
} | |||
std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | |||
std::vector<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | |||
assert(port); | |||
std::list<CableWidget*> cws; | |||
std::vector<CableWidget*> cws; | |||
for (widget::Widget* w : internal->cableContainer->children) { | |||
CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
assert(cw); | |||
@@ -125,10 +125,6 @@ void Scene::onDragHover(const DragHoverEvent& e) { | |||
} | |||
void Scene::onHoverKey(const HoverKeyEvent& e) { | |||
OpaqueWidget::onHoverKey(e); | |||
if (e.isConsumed()) | |||
return; | |||
if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
// DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str()); | |||
if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
@@ -220,35 +216,51 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||
rack->deselectModules(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) { | |||
rack->copyClipboardSelectedModules(); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
rack->pasteClipboardAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) | |||
if (rack->hasSelectedModules()) { | |||
rack->resetSelectedModulesAction(); | |||
e.consume(this); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) | |||
if (rack->hasSelectedModules()) { | |||
rack->randomizeSelectedModulesAction(); | |||
e.consume(this); | |||
} | |||
if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) | |||
rack->cloneSelectedModulesAction(); | |||
e.consume(this); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) | |||
if (rack->hasSelectedModules()) { | |||
rack->disconnectSelectedModulesAction(); | |||
e.consume(this); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) | |||
if (rack->hasSelectedModules()) { | |||
rack->bypassSelectedModulesAction(!rack->areSelectedModulesBypassed()); | |||
e.consume(this); | |||
e.consume(this); | |||
} | |||
} | |||
if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
if (rack->hasSelectedModules()) { | |||
rack->cloneSelectedModulesAction(); | |||
e.consume(this); | |||
} | |||
} | |||
if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { | |||
if (rack->hasSelectedModules()) | |||
if (rack->hasSelectedModules()) { | |||
rack->deleteSelectedModulesAction(); | |||
e.consume(this); | |||
e.consume(this); | |||
} | |||
} | |||
} | |||
@@ -279,6 +291,10 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||
e.consume(this); | |||
} | |||
} | |||
if (e.isConsumed()) | |||
return; | |||
OpaqueWidget::onHoverKey(e); | |||
} | |||
void Scene::onPathDrop(const PathDropEvent& e) { | |||
@@ -1171,7 +1171,6 @@ json_t* Engine::toJson() { | |||
// modules | |||
json_t* modulesJ = json_array(); | |||
for (Module* module : internal->modules) { | |||
// module | |||
json_t* moduleJ = module->toJson(); | |||
json_array_append_new(modulesJ, moduleJ); | |||
} | |||
@@ -1180,7 +1179,6 @@ json_t* Engine::toJson() { | |||
// cables | |||
json_t* cablesJ = json_array(); | |||
for (Cable* cable : internal->cables) { | |||
// cable | |||
json_t* cableJ = cable->toJson(); | |||
json_array_append_new(cablesJ, cableJ); | |||
} | |||