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