| @@ -79,9 +79,10 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
| json_t* toJson(); | json_t* toJson(); | ||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| void pasteJsonAction(json_t* rootJ); | |||||
| /** Returns whether paste was successful. */ | |||||
| bool pasteJsonAction(json_t* rootJ); | |||||
| void copyClipboard(); | void copyClipboard(); | ||||
| void pasteClipboardAction(); | |||||
| bool pasteClipboardAction(); | |||||
| void load(std::string filename); | void load(std::string filename); | ||||
| void loadAction(std::string filename); | void loadAction(std::string filename); | ||||
| void loadTemplate(); | void loadTemplate(); | ||||
| @@ -20,6 +20,7 @@ struct Cable { | |||||
| json_t* toJson(); | json_t* toJson(); | ||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| INTERNAL static void jsonStripIds(json_t* rootJ); | |||||
| }; | }; | ||||
| @@ -757,7 +757,7 @@ struct HelpButton : MenuButton { | |||||
| APP->scene->addChild(tipWindowCreate()); | APP->scene->addChild(tipWindowCreate()); | ||||
| })); | })); | ||||
| menu->addChild(createMenuItem("Manual", "F1", [=]() { | |||||
| menu->addChild(createMenuItem("User manual", "F1", [=]() { | |||||
| system::openBrowser("https://vcvrack.com/manual/"); | system::openBrowser("https://vcvrack.com/manual/"); | ||||
| })); | })); | ||||
| @@ -312,8 +312,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | 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) { | if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| cloneAction(); | cloneAction(); | ||||
| @@ -342,7 +343,9 @@ void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { | |||||
| return; | return; | ||||
| } | } | ||||
| if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | 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); | e.consume(this); | ||||
| } | } | ||||
| } | } | ||||
| @@ -468,7 +471,7 @@ void ModuleWidget::fromJson(json_t* moduleJ) { | |||||
| APP->engine->moduleFromJson(module, moduleJ); | APP->engine->moduleFromJson(module, moduleJ); | ||||
| } | } | ||||
| void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
| bool ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
| engine::Module::jsonStripIds(moduleJ); | engine::Module::jsonStripIds(moduleJ); | ||||
| json_t* oldModuleJ = toJson(); | json_t* oldModuleJ = toJson(); | ||||
| @@ -479,7 +482,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
| catch (Exception& e) { | catch (Exception& e) { | ||||
| WARN("%s", e.what()); | WARN("%s", e.what()); | ||||
| json_decref(oldModuleJ); | json_decref(oldModuleJ); | ||||
| return; | |||||
| return false; | |||||
| } | } | ||||
| // history::ModuleChange | // history::ModuleChange | ||||
| @@ -489,6 +492,7 @@ void ModuleWidget::pasteJsonAction(json_t* moduleJ) { | |||||
| h->oldModuleJ = oldModuleJ; | h->oldModuleJ = oldModuleJ; | ||||
| h->newModuleJ = moduleJ; | h->newModuleJ = moduleJ; | ||||
| APP->history->push(h); | APP->history->push(h); | ||||
| return true; | |||||
| } | } | ||||
| void ModuleWidget::copyClipboard() { | void ModuleWidget::copyClipboard() { | ||||
| @@ -501,22 +505,22 @@ void ModuleWidget::copyClipboard() { | |||||
| glfwSetClipboardString(APP->window->win, json); | glfwSetClipboardString(APP->window->win, json); | ||||
| } | } | ||||
| void ModuleWidget::pasteClipboardAction() { | |||||
| bool ModuleWidget::pasteClipboardAction() { | |||||
| const char* json = glfwGetClipboardString(APP->window->win); | const char* json = glfwGetClipboardString(APP->window->win); | ||||
| if (!json) { | if (!json) { | ||||
| WARN("Could not get text from clipboard."); | WARN("Could not get text from clipboard."); | ||||
| return; | |||||
| return false; | |||||
| } | } | ||||
| json_error_t error; | json_error_t error; | ||||
| json_t* moduleJ = json_loads(json, 0, &error); | json_t* moduleJ = json_loads(json, 0, &error); | ||||
| if (!moduleJ) { | if (!moduleJ) { | ||||
| WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | ||||
| return; | |||||
| return false; | |||||
| } | } | ||||
| DEFER({json_decref(moduleJ);}); | DEFER({json_decref(moduleJ);}); | ||||
| pasteJsonAction(moduleJ); | |||||
| return pasteJsonAction(moduleJ); | |||||
| } | } | ||||
| void ModuleWidget::load(std::string filename) { | void ModuleWidget::load(std::string filename) { | ||||
| @@ -758,6 +762,9 @@ void ModuleWidget::cloneAction() { | |||||
| // JSON serialization is the obvious way to do this | // JSON serialization is the obvious way to do this | ||||
| json_t* moduleJ = toJson(); | json_t* moduleJ = toJson(); | ||||
| DEFER({ | |||||
| json_decref(moduleJ); | |||||
| }); | |||||
| engine::Module::jsonStripIds(moduleJ); | engine::Module::jsonStripIds(moduleJ); | ||||
| // Clone Module | // Clone Module | ||||
| @@ -769,7 +776,6 @@ void ModuleWidget::cloneAction() { | |||||
| catch (Exception& e) { | catch (Exception& e) { | ||||
| WARN("%s", e.what()); | WARN("%s", e.what()); | ||||
| } | } | ||||
| json_decref(moduleJ); | |||||
| APP->engine->addModule(clonedModule); | APP->engine->addModule(clonedModule); | ||||
| // Clone ModuleWidget | // Clone ModuleWidget | ||||
| @@ -454,7 +454,7 @@ static PasteJsonReturn RackWidget_pasteJson(RackWidget* that, json_t* rootJ, his | |||||
| size_t cableIndex; | size_t cableIndex; | ||||
| json_t* cableJ; | json_t* cableJ; | ||||
| json_array_foreach(cablesJ, cableIndex, cableJ) { | json_array_foreach(cablesJ, cableIndex, cableJ) { | ||||
| json_object_del(cableJ, "id"); | |||||
| engine::Cable::jsonStripIds(cableJ); | |||||
| // Remap old module IDs to new IDs | // Remap old module IDs to new IDs | ||||
| json_t* inputModuleIdJ = json_object_get(cableJ, "inputModuleId"); | json_t* inputModuleIdJ = json_object_get(cableJ, "inputModuleId"); | ||||
| @@ -600,6 +600,9 @@ void RackWidget::removeModule(ModuleWidget* m) { | |||||
| // Disconnect cables | // Disconnect cables | ||||
| m->disconnect(); | m->disconnect(); | ||||
| // Deselect module if selected | |||||
| internal->selectedModules.erase(m); | |||||
| // Remove module from ModuleContainer | // Remove module from ModuleContainer | ||||
| internal->moduleContainer->removeChild(m); | internal->moduleContainer->removeChild(m); | ||||
| } | } | ||||
| @@ -1105,7 +1108,9 @@ void RackWidget::deleteSelectionAction() { | |||||
| history::ComplexAction* complexAction = new history::ComplexAction; | history::ComplexAction* complexAction = new history::ComplexAction; | ||||
| complexAction->name = "remove modules"; | complexAction->name = "remove modules"; | ||||
| for (ModuleWidget* mw : getSelected()) { | |||||
| // Copy selected set since removing ModuleWidgets modifies it. | |||||
| std::set<ModuleWidget*> selectedModules = getSelected(); | |||||
| for (ModuleWidget* mw : selectedModules) { | |||||
| mw->appendDisconnectActions(complexAction); | mw->appendDisconnectActions(complexAction); | ||||
| // history::ModuleRemove | // history::ModuleRemove | ||||
| @@ -136,6 +136,7 @@ void Scene::onDragHover(const DragHoverEvent& e) { | |||||
| void Scene::onHoverKey(const HoverKeyEvent& e) { | void Scene::onHoverKey(const HoverKeyEvent& e) { | ||||
| // Key commands that override children | |||||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | 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()); | // 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) { | 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; | settings::zoom = 0.f; | ||||
| e.consume(this); | 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) { | if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | ||||
| system::openBrowser("https://vcvrack.com/manual/"); | system::openBrowser("https://vcvrack.com/manual/"); | ||||
| e.consume(this); | e.consume(this); | ||||
| @@ -211,13 +208,6 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
| // menuBar->hide(); | // menuBar->hide(); | ||||
| e.consume(this); | 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 | // Module selections | ||||
| if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | 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); | 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 (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| if (rack->hasSelection()) { | if (rack->hasSelection()) { | ||||
| rack->resetSelectionAction(); | rack->resetSelectionAction(); | ||||
| @@ -307,6 +293,27 @@ void Scene::onHoverKey(const HoverKeyEvent& e) { | |||||
| if (e.isConsumed()) | if (e.isConsumed()) | ||||
| return; | return; | ||||
| OpaqueWidget::onHoverKey(e); | 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); | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -60,5 +60,10 @@ void Cable::fromJson(json_t* rootJ) { | |||||
| } | } | ||||
| void Cable::jsonStripIds(json_t* rootJ) { | |||||
| json_object_del(rootJ, "id"); | |||||
| } | |||||
| } // namespace engine | } // namespace engine | ||||
| } // namespace rack | } // namespace rack | ||||