diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index d46113e2..50fd0e74 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.hpp @@ -79,9 +79,10 @@ struct ModuleWidget : widget::OpaqueWidget { json_t* toJson(); void fromJson(json_t* rootJ); - void pasteJsonAction(json_t* rootJ); + /** Returns whether paste was successful. */ + bool pasteJsonAction(json_t* rootJ); void copyClipboard(); - void pasteClipboardAction(); + bool pasteClipboardAction(); void load(std::string filename); void loadAction(std::string filename); void loadTemplate(); diff --git a/include/engine/Cable.hpp b/include/engine/Cable.hpp index 1c59f63c..0e85d607 100644 --- a/include/engine/Cable.hpp +++ b/include/engine/Cable.hpp @@ -20,6 +20,7 @@ struct Cable { json_t* toJson(); void fromJson(json_t* rootJ); + INTERNAL static void jsonStripIds(json_t* rootJ); }; diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index f7d26e2a..e82c1b78 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -757,7 +757,7 @@ struct HelpButton : MenuButton { APP->scene->addChild(tipWindowCreate()); })); - menu->addChild(createMenuItem("Manual", "F1", [=]() { + menu->addChild(createMenuItem("User manual", "F1", [=]() { system::openBrowser("https://vcvrack.com/manual/"); })); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 3db8f663..87ea7bd4 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -312,8 +312,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { e.consume(this); } if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - pasteClipboardAction(); - e.consume(this); + if (pasteClipboardAction()) { + e.consume(this); + } } if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { cloneAction(); @@ -342,7 +343,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { return; } if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - system::openBrowser(model->getManualUrl()); + std::string manualUrl = model->getManualUrl(); + if (!manualUrl.empty()) + system::openBrowser(manualUrl); e.consume(this); } } @@ -468,7 +471,7 @@ void ModuleWidget::fromJson(json_t* moduleJ) { APP->engine->moduleFromJson(module, moduleJ); } -void ModuleWidget::pasteJsonAction(json_t* moduleJ) { +bool ModuleWidget::pasteJsonAction(json_t* moduleJ) { engine::Module::jsonStripIds(moduleJ); json_t* oldModuleJ = toJson(); @@ -479,7 +482,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { catch (Exception& e) { WARN("%s", e.what()); json_decref(oldModuleJ); - return; + return false; } // history::ModuleChange @@ -489,6 +492,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { h->oldModuleJ = oldModuleJ; h->newModuleJ = moduleJ; APP->history->push(h); + return true; } void ModuleWidget::copyClipboard() { @@ -501,22 +505,22 @@ void ModuleWidget::copyClipboard() { glfwSetClipboardString(APP->window->win, json); } -void ModuleWidget::pasteClipboardAction() { +bool ModuleWidget::pasteClipboardAction() { const char* json = glfwGetClipboardString(APP->window->win); if (!json) { WARN("Could not get text from clipboard."); - return; + return false; } json_error_t error; json_t* moduleJ = json_loads(json, 0, &error); if (!moduleJ) { WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); - return; + return false; } DEFER({json_decref(moduleJ);}); - pasteJsonAction(moduleJ); + return pasteJsonAction(moduleJ); } void ModuleWidget::load(std::string filename) { @@ -758,6 +762,9 @@ void ModuleWidget::cloneAction() { // JSON serialization is the obvious way to do this json_t* moduleJ = toJson(); + DEFER({ + json_decref(moduleJ); + }); engine::Module::jsonStripIds(moduleJ); // Clone Module @@ -769,7 +776,6 @@ void ModuleWidget::cloneAction() { catch (Exception& e) { WARN("%s", e.what()); } - json_decref(moduleJ); APP->engine->addModule(clonedModule); // Clone ModuleWidget diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index ce1297ee..35228630 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -454,7 +454,7 @@ static PasteJsonReturn RackWidget_pasteJson(RackWidget* that, json_t* rootJ, his size_t cableIndex; json_t* cableJ; json_array_foreach(cablesJ, cableIndex, cableJ) { - json_object_del(cableJ, "id"); + engine::Cable::jsonStripIds(cableJ); // Remap old module IDs to new IDs json_t* inputModuleIdJ = json_object_get(cableJ, "inputModuleId"); @@ -600,6 +600,9 @@ void RackWidget::removeModule(ModuleWidget* m) { // Disconnect cables m->disconnect(); + // Deselect module if selected + internal->selectedModules.erase(m); + // Remove module from ModuleContainer internal->moduleContainer->removeChild(m); } @@ -1105,7 +1108,9 @@ void RackWidget::deleteSelectionAction() { history::ComplexAction* complexAction = new history::ComplexAction; complexAction->name = "remove modules"; - for (ModuleWidget* mw : getSelected()) { + // Copy selected set since removing ModuleWidgets modifies it. + std::set selectedModules = getSelected(); + for (ModuleWidget* mw : selectedModules) { mw->appendDisconnectActions(complexAction); // history::ModuleRemove diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index a8288d7d..e77c576f 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -136,6 +136,7 @@ void Scene::onDragHover(const DragHoverEvent& e) { void Scene::onHoverKey(const HoverKeyEvent& e) { + // Key commands that override children 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) { @@ -193,10 +194,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { settings::zoom = 0.f; e.consume(this); } - if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { - browser->show(); - e.consume(this); - } if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { system::openBrowser("https://vcvrack.com/manual/"); e.consume(this); @@ -211,13 +208,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { // menuBar->hide(); e.consume(this); } - // Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. - if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { - if (APP->window->isFullScreen()) { - APP->window->setFullScreen(false); - e.consume(this); - } - } // Module selections if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { @@ -234,10 +224,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { 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->hasSelection()) { rack->resetSelectionAction(); @@ -307,6 +293,27 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { if (e.isConsumed()) return; OpaqueWidget::onHoverKey(e); + if (e.isConsumed()) + return; + + // Key commands that can be overridden by children + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { + // Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. + if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { + if (APP->window->isFullScreen()) { + APP->window->setFullScreen(false); + e.consume(this); + } + } + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + rack->pasteClipboardAction(); + e.consume(this); + } + if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { + browser->show(); + e.consume(this); + } + } } diff --git a/src/engine/Cable.cpp b/src/engine/Cable.cpp index 058f368f..4d21ae11 100644 --- a/src/engine/Cable.cpp +++ b/src/engine/Cable.cpp @@ -60,5 +60,10 @@ void Cable::fromJson(json_t* rootJ) { } +void Cable::jsonStripIds(json_t* rootJ) { + json_object_del(rootJ, "id"); +} + + } // namespace engine } // namespace rack