From aaf98bb16d560850a482d841e69a81bfbc07f5fe Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 11 Aug 2021 02:08:04 -0400 Subject: [PATCH] Implement RackWidget::copyClipboardSelectedModules(). Clean up RackWidget API. --- include/app/CableWidget.hpp | 1 + include/app/RackWidget.hpp | 14 ++--- src/app/CableWidget.cpp | 4 ++ src/app/ModuleWidget.cpp | 79 +++++++++++--------------- src/app/PortWidget.cpp | 3 +- src/app/RackWidget.cpp | 110 ++++++++++++++++++++++++++---------- src/app/Scene.cpp | 54 +++++++++++------- src/engine/Engine.cpp | 2 - 8 files changed, 163 insertions(+), 104 deletions(-) diff --git a/include/app/CableWidget.hpp b/include/app/CableWidget.hpp index b4bf4ea5..2667a068 100644 --- a/include/app/CableWidget.hpp +++ b/include/app/CableWidget.hpp @@ -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(); diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 45960105..f10e4ac2 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -1,6 +1,4 @@ #pragma once -#include - #include #include #include @@ -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 getModules(); + std::vector getModules(); bool hasModules(); void updateModuleOldPositions(); history::ComplexAction* getModuleDragAction(); @@ -78,13 +76,15 @@ struct RackWidget : widget::OpaqueWidget { void selectAllModules(); bool hasSelectedModules(); int getNumSelectedModules(); - std::list getSelectedModules(); + std::vector 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 getCompleteCables(); + std::vector getCompleteCables(); /** Returns all cables attached to port, complete or not. */ - std::list getCablesOnPort(PortWidget* port); + std::vector getCablesOnPort(PortWidget* port); }; diff --git a/src/app/CableWidget.cpp b/src/app/CableWidget.cpp index 7efbe39d..5f693fcf 100644 --- a/src/app/CableWidget.cpp +++ b/src/app/CableWidget.cpp @@ -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); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 424455d4..6e8ea18e 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -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; diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index 5caed307..7e2d3ce6 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -39,8 +39,7 @@ struct PortTooltip : ui::Tooltip { text += string::f("% .3fV", math::normalizeZero(v)); } // Connected to - std::list 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; diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 4cb1a144..9135afbf 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -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 RackWidget::getModules() { - std::list mws; +std::vector RackWidget::getModules() { + std::vector mws; for (widget::Widget* w : internal->moduleContainer->children) { ModuleWidget* mw = dynamic_cast(w); assert(mw); @@ -694,8 +685,8 @@ int RackWidget::getNumSelectedModules() { return count; } -std::list RackWidget::getSelectedModules() { - std::list mws; +std::vector RackWidget::getSelectedModules() { + std::vector mws; for (widget::Widget* w : internal->moduleContainer->children) { ModuleWidget* mw = dynamic_cast(w); assert(mw); @@ -705,6 +696,49 @@ std::list RackWidget::getSelectedModules() { return mws; } +json_t* RackWidget::selectedModulesToJson() { + json_t* rootJ = json_object(); + + std::set 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 RackWidget::getCompleteCables() { - std::list cws; +std::vector RackWidget::getCompleteCables() { + std::vector cws; for (widget::Widget* w : internal->cableContainer->children) { CableWidget* cw = dynamic_cast(w); assert(cw); @@ -1005,9 +1057,9 @@ std::list RackWidget::getCompleteCables() { return cws; } -std::list RackWidget::getCablesOnPort(PortWidget* port) { +std::vector RackWidget::getCablesOnPort(PortWidget* port) { assert(port); - std::list cws; + std::vector cws; for (widget::Widget* w : internal->cableContainer->children) { CableWidget* cw = dynamic_cast(w); assert(cw); diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index e607342a..72f2c253 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -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) { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index b24c3a72..d15b9366 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -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); }