| @@ -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(); | |||
| @@ -20,6 +20,7 @@ struct Cable { | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| INTERNAL static void jsonStripIds(json_t* rootJ); | |||
| }; | |||
| @@ -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/"); | |||
| })); | |||
| @@ -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 | |||
| @@ -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<ModuleWidget*> selectedModules = getSelected(); | |||
| for (ModuleWidget* mw : selectedModules) { | |||
| mw->appendDisconnectActions(complexAction); | |||
| // history::ModuleRemove | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| @@ -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 | |||