| @@ -46,6 +46,8 @@ struct SVGPanel; | |||
| static const float RACK_GRID_WIDTH = 15; | |||
| static const float RACK_GRID_HEIGHT = 380; | |||
| static const Vec RACK_GRID_SIZE = Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | |||
| static const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm"; | |||
| static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv"; | |||
| struct ModuleWidget : OpaqueWidget { | |||
| @@ -68,6 +70,12 @@ struct ModuleWidget : OpaqueWidget { | |||
| virtual json_t *toJson(); | |||
| virtual void fromJson(json_t *rootJ); | |||
| void copyClipboard(); | |||
| void pasteClipboard(); | |||
| void save(std::string filename); | |||
| void load(std::string filename); | |||
| void loadDialog(); | |||
| void saveDialog(); | |||
| virtual void create(); | |||
| virtual void _delete(); | |||
| @@ -154,17 +162,20 @@ struct RackWidget : OpaqueWidget { | |||
| void clear(); | |||
| /** Clears the rack and loads the template patch */ | |||
| void reset(); | |||
| void openDialog(); | |||
| void loadDialog(); | |||
| void saveDialog(); | |||
| void saveAsDialog(); | |||
| /** If `lastPath` is defined, ask the user to reload it */ | |||
| void revert(); | |||
| /** Disconnects all wires */ | |||
| void disconnect(); | |||
| void savePatch(std::string filename); | |||
| void loadPatch(std::string filename); | |||
| void save(std::string filename); | |||
| void load(std::string filename); | |||
| json_t *toJson(); | |||
| void fromJson(json_t *rootJ); | |||
| /** Creates a module and adds it to the rack */ | |||
| ModuleWidget *moduleFromJson(json_t *moduleJ); | |||
| void pastePresetClipboard(); | |||
| void addModule(ModuleWidget *m); | |||
| /** Removes the module and transfers ownership to the caller */ | |||
| @@ -2,6 +2,8 @@ | |||
| #include "engine.hpp" | |||
| #include "plugin.hpp" | |||
| #include "window.hpp" | |||
| #include "asset.hpp" | |||
| #include "osdialog.h" | |||
| namespace rack { | |||
| @@ -68,10 +70,6 @@ json_t *ModuleWidget::toJson() { | |||
| json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str())); | |||
| // model | |||
| json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); | |||
| // pos | |||
| Vec pos = box.pos.div(RACK_GRID_SIZE).round(); | |||
| json_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | |||
| json_object_set_new(rootJ, "pos", posJ); | |||
| // params | |||
| json_t *paramsJ = json_array(); | |||
| for (ParamWidget *paramWidget : params) { | |||
| @@ -91,24 +89,42 @@ json_t *ModuleWidget::toJson() { | |||
| } | |||
| void ModuleWidget::fromJson(json_t *rootJ) { | |||
| // Check if plugin and model are incorrect | |||
| json_t *pluginJ = json_object_get(rootJ, "plugin"); | |||
| std::string pluginSlug; | |||
| if (pluginJ) { | |||
| pluginSlug = json_string_value(pluginJ); | |||
| if (pluginSlug != model->plugin->slug) { | |||
| warn("Plugin %s does not match ModuleWidget's plugin %s.", pluginSlug.c_str(), model->plugin->slug.c_str()); | |||
| return; | |||
| } | |||
| } | |||
| json_t *modelJ = json_object_get(rootJ, "model"); | |||
| std::string modelSlug; | |||
| if (modelJ) { | |||
| modelSlug = json_string_value(modelJ); | |||
| if (modelSlug != model->slug) { | |||
| warn("Model %s does not match ModuleWidget's model %s.", modelSlug.c_str(), model->slug.c_str()); | |||
| return; | |||
| } | |||
| } | |||
| // Check plugin version | |||
| json_t *versionJ = json_object_get(rootJ, "version"); | |||
| if (versionJ) { | |||
| std::string version = json_string_value(versionJ); | |||
| if (version != model->plugin->version) { | |||
| info("Patch created with %s version %s, using version %s.", pluginSlug.c_str(), version.c_str(), model->plugin->version.c_str()); | |||
| } | |||
| } | |||
| // legacy | |||
| int legacy = 0; | |||
| json_t *legacyJ = json_object_get(rootJ, "legacy"); | |||
| if (legacyJ) | |||
| legacy = json_integer_value(legacyJ); | |||
| // pos | |||
| json_t *posJ = json_object_get(rootJ, "pos"); | |||
| double x, y; | |||
| json_unpack(posJ, "[F, F]", &x, &y); | |||
| Vec pos = Vec(x, y); | |||
| if (legacy && legacy <= 1) { | |||
| box.pos = pos; | |||
| } | |||
| else { | |||
| box.pos = pos.mult(RACK_GRID_SIZE); | |||
| } | |||
| // params | |||
| json_t *paramsJ = json_object_get(rootJ, "params"); | |||
| size_t i; | |||
| @@ -146,6 +162,102 @@ void ModuleWidget::fromJson(json_t *rootJ) { | |||
| } | |||
| } | |||
| void ModuleWidget::copyClipboard() { | |||
| json_t *moduleJ = toJson(); | |||
| char *moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| glfwSetClipboardString(gWindow, moduleJson); | |||
| free(moduleJson); | |||
| json_decref(moduleJ); | |||
| } | |||
| void ModuleWidget::pasteClipboard() { | |||
| const char *moduleJson = glfwGetClipboardString(gWindow); | |||
| if (!moduleJson) { | |||
| warn("Could not get text from clipboard."); | |||
| return; | |||
| } | |||
| json_error_t error; | |||
| json_t *moduleJ = json_loads(moduleJson, 0, &error); | |||
| if (moduleJ) { | |||
| fromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| } | |||
| else { | |||
| warn("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| } | |||
| } | |||
| void ModuleWidget::load(std::string filename) { | |||
| info("Loading preset %s", filename.c_str()); | |||
| FILE *file = fopen(filename.c_str(), "r"); | |||
| if (!file) { | |||
| // Exit silently | |||
| return; | |||
| } | |||
| json_error_t error; | |||
| json_t *moduleJ = json_loadf(file, 0, &error); | |||
| if (moduleJ) { | |||
| fromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| } | |||
| else { | |||
| std::string message = stringf("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | |||
| } | |||
| fclose(file); | |||
| } | |||
| void ModuleWidget::save(std::string filename) { | |||
| info("Saving preset %s", filename.c_str()); | |||
| json_t *moduleJ = toJson(); | |||
| if (!moduleJ) | |||
| return; | |||
| FILE *file = fopen(filename.c_str(), "w"); | |||
| if (file) { | |||
| json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| fclose(file); | |||
| } | |||
| json_decref(moduleJ); | |||
| } | |||
| void ModuleWidget::loadDialog() { | |||
| std::string dir = assetLocal("presets"); | |||
| systemCreateDirectory(dir); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||
| char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); | |||
| if (path) { | |||
| load(path); | |||
| free(path); | |||
| } | |||
| osdialog_filters_free(filters); | |||
| } | |||
| void ModuleWidget::saveDialog() { | |||
| std::string dir = assetLocal("presets"); | |||
| systemCreateDirectory(dir); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||
| char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcvm", filters); | |||
| if (path) { | |||
| std::string pathStr = path; | |||
| free(path); | |||
| std::string extension = stringExtension(pathStr); | |||
| if (extension.empty()) { | |||
| pathStr += ".vcvm"; | |||
| } | |||
| save(pathStr); | |||
| } | |||
| osdialog_filters_free(filters); | |||
| } | |||
| void ModuleWidget::disconnect() { | |||
| for (Port *input : inputs) { | |||
| gRackWidget->wireContainer->removeAllWires(input); | |||
| @@ -268,6 +380,20 @@ void ModuleWidget::onHoverKey(EventHoverKey &e) { | |||
| return; | |||
| } | |||
| } break; | |||
| case GLFW_KEY_C: { | |||
| if (windowIsModPressed() && !windowIsShiftPressed()) { | |||
| copyClipboard(); | |||
| e.consumed = true; | |||
| return; | |||
| } | |||
| } break; | |||
| case GLFW_KEY_V: { | |||
| if (windowIsModPressed() && !windowIsShiftPressed()) { | |||
| pasteClipboard(); | |||
| e.consumed = true; | |||
| return; | |||
| } | |||
| } break; | |||
| case GLFW_KEY_D: { | |||
| if (windowIsModPressed() && !windowIsShiftPressed()) { | |||
| gRackWidget->cloneModule(this); | |||
| @@ -303,35 +429,63 @@ void ModuleWidget::onDragMove(EventDragMove &e) { | |||
| } | |||
| struct DisconnectMenuItem : MenuItem { | |||
| struct ModuleDisconnectItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->disconnect(); | |||
| } | |||
| }; | |||
| struct ResetMenuItem : MenuItem { | |||
| struct ModuleResetItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->reset(); | |||
| } | |||
| }; | |||
| struct RandomizeMenuItem : MenuItem { | |||
| struct ModuleRandomizeItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->randomize(); | |||
| } | |||
| }; | |||
| struct CloneMenuItem : MenuItem { | |||
| struct ModuleCopyItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->copyClipboard(); | |||
| } | |||
| }; | |||
| struct ModulePasteItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->pasteClipboard(); | |||
| } | |||
| }; | |||
| struct ModuleSaveItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->saveDialog(); | |||
| } | |||
| }; | |||
| struct ModuleLoadItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| moduleWidget->loadDialog(); | |||
| } | |||
| }; | |||
| struct ModuleCloneItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| gRackWidget->cloneModule(moduleWidget); | |||
| } | |||
| }; | |||
| struct DeleteMenuItem : MenuItem { | |||
| struct ModuleDeleteItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| void onAction(EventAction &e) override { | |||
| gRackWidget->deleteModule(moduleWidget); | |||
| @@ -347,31 +501,53 @@ Menu *ModuleWidget::createContextMenu() { | |||
| menuLabel->text = model->author + " " + model->name + " " + model->plugin->version; | |||
| menu->addChild(menuLabel); | |||
| ResetMenuItem *resetItem = new ResetMenuItem(); | |||
| ModuleResetItem *resetItem = new ModuleResetItem(); | |||
| resetItem->text = "Initialize"; | |||
| resetItem->rightText = WINDOW_MOD_KEY_NAME "+I"; | |||
| resetItem->moduleWidget = this; | |||
| menu->addChild(resetItem); | |||
| RandomizeMenuItem *randomizeItem = new RandomizeMenuItem(); | |||
| ModuleRandomizeItem *randomizeItem = new ModuleRandomizeItem(); | |||
| randomizeItem->text = "Randomize"; | |||
| randomizeItem->rightText = WINDOW_MOD_KEY_NAME "+R"; | |||
| randomizeItem->moduleWidget = this; | |||
| menu->addChild(randomizeItem); | |||
| DisconnectMenuItem *disconnectItem = new DisconnectMenuItem(); | |||
| ModuleDisconnectItem *disconnectItem = new ModuleDisconnectItem(); | |||
| disconnectItem->text = "Disconnect cables"; | |||
| disconnectItem->rightText = WINDOW_MOD_KEY_NAME "+U"; | |||
| disconnectItem->moduleWidget = this; | |||
| menu->addChild(disconnectItem); | |||
| CloneMenuItem *cloneItem = new CloneMenuItem(); | |||
| ModuleCloneItem *cloneItem = new ModuleCloneItem(); | |||
| cloneItem->text = "Duplicate"; | |||
| cloneItem->rightText = WINDOW_MOD_KEY_NAME "+D"; | |||
| cloneItem->moduleWidget = this; | |||
| menu->addChild(cloneItem); | |||
| DeleteMenuItem *deleteItem = new DeleteMenuItem(); | |||
| ModuleCopyItem *copyItem = new ModuleCopyItem(); | |||
| copyItem->text = "Copy preset"; | |||
| copyItem->rightText = WINDOW_MOD_KEY_NAME "+C"; | |||
| copyItem->moduleWidget = this; | |||
| menu->addChild(copyItem); | |||
| ModulePasteItem *pasteItem = new ModulePasteItem(); | |||
| pasteItem->text = "Paste preset"; | |||
| pasteItem->rightText = WINDOW_MOD_KEY_NAME "+V"; | |||
| pasteItem->moduleWidget = this; | |||
| menu->addChild(pasteItem); | |||
| ModuleLoadItem *loadItem = new ModuleLoadItem(); | |||
| loadItem->text = "Load preset"; | |||
| loadItem->moduleWidget = this; | |||
| menu->addChild(loadItem); | |||
| ModuleSaveItem *saveItem = new ModuleSaveItem(); | |||
| saveItem->text = "Save preset"; | |||
| saveItem->moduleWidget = this; | |||
| menu->addChild(saveItem); | |||
| ModuleDeleteItem *deleteItem = new ModuleDeleteItem(); | |||
| deleteItem->text = "Delete"; | |||
| deleteItem->rightText = "Backspace/Delete"; | |||
| deleteItem->moduleWidget = this; | |||
| @@ -77,7 +77,7 @@ void RackScene::onHoverKey(EventHoverKey &e) { | |||
| } break; | |||
| case GLFW_KEY_O: { | |||
| if (windowIsModPressed() && !windowIsShiftPressed()) { | |||
| gRackWidget->openDialog(); | |||
| gRackWidget->loadDialog(); | |||
| e.consumed = true; | |||
| } | |||
| if (windowIsModPressed() && windowIsShiftPressed()) { | |||
| @@ -95,6 +95,12 @@ void RackScene::onHoverKey(EventHoverKey &e) { | |||
| e.consumed = true; | |||
| } | |||
| } break; | |||
| case GLFW_KEY_V: { | |||
| if (windowIsModPressed() && !windowIsShiftPressed()) { | |||
| gRackWidget->pastePresetClipboard(); | |||
| e.consumed = true; | |||
| } | |||
| } break; | |||
| case GLFW_KEY_ENTER: | |||
| case GLFW_KEY_KP_ENTER: { | |||
| appModuleBrowserCreate(); | |||
| @@ -109,9 +115,9 @@ void RackScene::onHoverKey(EventHoverKey &e) { | |||
| void RackScene::onPathDrop(EventPathDrop &e) { | |||
| if (e.paths.size() >= 1) { | |||
| const std::string& firstPath = e.paths.front(); | |||
| const std::string &firstPath = e.paths.front(); | |||
| if (stringExtension(firstPath) == "vcv") { | |||
| gRackWidget->loadPatch(firstPath); | |||
| gRackWidget->load(firstPath); | |||
| e.consumed = true; | |||
| } | |||
| } | |||
| @@ -12,9 +12,6 @@ | |||
| namespace rack { | |||
| static const char *FILTERS = "VCV Rack patch (.vcv):vcv"; | |||
| struct ModuleContainer : Widget { | |||
| void draw(NVGcontext *vg) override { | |||
| // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front. | |||
| @@ -66,12 +63,12 @@ void RackWidget::reset() { | |||
| if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear patch and start over?")) { | |||
| clear(); | |||
| // Fails silently if file does not exist | |||
| loadPatch(assetLocal("template.vcv")); | |||
| load(assetLocal("template.vcv")); | |||
| lastPath = ""; | |||
| } | |||
| } | |||
| void RackWidget::openDialog() { | |||
| void RackWidget::loadDialog() { | |||
| std::string dir; | |||
| if (lastPath.empty()) { | |||
| dir = assetLocal("patches"); | |||
| @@ -80,10 +77,10 @@ void RackWidget::openDialog() { | |||
| else { | |||
| dir = stringDirectory(lastPath); | |||
| } | |||
| osdialog_filters *filters = osdialog_filters_parse(FILTERS); | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||
| char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); | |||
| if (path) { | |||
| loadPatch(path); | |||
| load(path); | |||
| lastPath = path; | |||
| free(path); | |||
| } | |||
| @@ -92,7 +89,7 @@ void RackWidget::openDialog() { | |||
| void RackWidget::saveDialog() { | |||
| if (!lastPath.empty()) { | |||
| savePatch(lastPath); | |||
| save(lastPath); | |||
| } | |||
| else { | |||
| saveAsDialog(); | |||
| @@ -110,7 +107,7 @@ void RackWidget::saveAsDialog() { | |||
| dir = stringDirectory(lastPath); | |||
| filename = stringFilename(lastPath); | |||
| } | |||
| osdialog_filters *filters = osdialog_filters_parse(FILTERS); | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||
| char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); | |||
| if (path) { | |||
| @@ -121,19 +118,19 @@ void RackWidget::saveAsDialog() { | |||
| pathStr += ".vcv"; | |||
| } | |||
| savePatch(pathStr); | |||
| save(pathStr); | |||
| lastPath = pathStr; | |||
| } | |||
| osdialog_filters_free(filters); | |||
| } | |||
| void RackWidget::savePatch(std::string path) { | |||
| info("Saving patch %s", path.c_str()); | |||
| void RackWidget::save(std::string filename) { | |||
| info("Saving patch %s", filename.c_str()); | |||
| json_t *rootJ = toJson(); | |||
| if (!rootJ) | |||
| return; | |||
| FILE *file = fopen(path.c_str(), "w"); | |||
| FILE *file = fopen(filename.c_str(), "w"); | |||
| if (file) { | |||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
| fclose(file); | |||
| @@ -142,9 +139,9 @@ void RackWidget::savePatch(std::string path) { | |||
| json_decref(rootJ); | |||
| } | |||
| void RackWidget::loadPatch(std::string path) { | |||
| info("Loading patch %s", path.c_str()); | |||
| FILE *file = fopen(path.c_str(), "r"); | |||
| void RackWidget::load(std::string filename) { | |||
| info("Loading patch %s", filename.c_str()); | |||
| FILE *file = fopen(filename.c_str(), "r"); | |||
| if (!file) { | |||
| // Exit silently | |||
| return; | |||
| @@ -158,7 +155,7 @@ void RackWidget::loadPatch(std::string path) { | |||
| json_decref(rootJ); | |||
| } | |||
| else { | |||
| std::string message = stringf("JSON parsing error at %s %d:%d %s\n", error.source, error.line, error.column, error.text); | |||
| std::string message = stringf("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); | |||
| } | |||
| @@ -169,7 +166,7 @@ void RackWidget::revert() { | |||
| if (lastPath.empty()) | |||
| return; | |||
| if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert patch to the last saved state?")) { | |||
| loadPatch(lastPath); | |||
| load(lastPath); | |||
| } | |||
| } | |||
| @@ -203,6 +200,12 @@ json_t *RackWidget::toJson() { | |||
| moduleId++; | |||
| // module | |||
| json_t *moduleJ = moduleWidget->toJson(); | |||
| { | |||
| // pos | |||
| Vec pos = moduleWidget->box.pos.div(RACK_GRID_SIZE).round(); | |||
| json_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | |||
| json_object_set_new(moduleJ, "pos", posJ); | |||
| } | |||
| json_array_append_new(modulesJ, moduleJ); | |||
| } | |||
| json_object_set_new(rootJ, "modules", modulesJ); | |||
| @@ -275,25 +278,30 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| json_object_set(moduleJ, "legacy", json_integer(legacy)); | |||
| } | |||
| json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| if (!pluginSlugJ) continue; | |||
| json_t *modelSlugJ = json_object_get(moduleJ, "model"); | |||
| if (!modelSlugJ) continue; | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | |||
| if (moduleWidget) { | |||
| // pos | |||
| json_t *posJ = json_object_get(moduleJ, "pos"); | |||
| double x, y; | |||
| json_unpack(posJ, "[F, F]", &x, &y); | |||
| Vec pos = Vec(x, y); | |||
| if (legacy && legacy <= 1) { | |||
| moduleWidget->box.pos = pos; | |||
| } | |||
| else { | |||
| moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | |||
| } | |||
| Model *model = pluginGetModel(pluginSlug, modelSlug); | |||
| if (!model) { | |||
| moduleWidgets[moduleId] = moduleWidget; | |||
| } | |||
| else { | |||
| json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| json_t *modelSlugJ = json_object_get(moduleJ, "model"); | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| message += stringf("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str()); | |||
| continue; | |||
| } | |||
| // Create ModuleWidget | |||
| ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
| assert(moduleWidget); | |||
| moduleWidget->fromJson(moduleJ); | |||
| moduleContainer->addChild(moduleWidget); | |||
| moduleWidgets[moduleId] = moduleWidget; | |||
| } | |||
| // wires | |||
| @@ -355,6 +363,53 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
| } | |||
| } | |||
| ModuleWidget *RackWidget::moduleFromJson(json_t *moduleJ) { | |||
| // Get slugs | |||
| json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| if (!pluginSlugJ) | |||
| return NULL; | |||
| json_t *modelSlugJ = json_object_get(moduleJ, "model"); | |||
| if (!modelSlugJ) | |||
| return NULL; | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| // Get Model | |||
| Model *model = pluginGetModel(pluginSlug, modelSlug); | |||
| if (!model) | |||
| return NULL; | |||
| // Create ModuleWidget | |||
| ModuleWidget *moduleWidget = model->createModuleWidget(); | |||
| assert(moduleWidget); | |||
| moduleWidget->fromJson(moduleJ); | |||
| moduleContainer->addChild(moduleWidget); | |||
| return moduleWidget; | |||
| } | |||
| void RackWidget::pastePresetClipboard() { | |||
| const char *moduleJson = glfwGetClipboardString(gWindow); | |||
| if (!moduleJson) { | |||
| warn("Could not get text from clipboard."); | |||
| return; | |||
| } | |||
| json_error_t error; | |||
| json_t *moduleJ = json_loads(moduleJson, 0, &error); | |||
| if (moduleJ) { | |||
| ModuleWidget *moduleWidget = moduleFromJson(moduleJ); | |||
| // Set moduleWidget position | |||
| Rect newBox = moduleWidget->box; | |||
| newBox.pos = lastMousePos.minus(newBox.size.div(2)); | |||
| requestModuleBoxNearest(moduleWidget, newBox); | |||
| json_decref(moduleJ); | |||
| } | |||
| else { | |||
| warn("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| } | |||
| } | |||
| void RackWidget::addModule(ModuleWidget *m) { | |||
| moduleContainer->addChild(m); | |||
| m->create(); | |||
| @@ -366,16 +421,13 @@ void RackWidget::deleteModule(ModuleWidget *m) { | |||
| } | |||
| void RackWidget::cloneModule(ModuleWidget *m) { | |||
| // Create new module from model | |||
| ModuleWidget *clonedModuleWidget = m->model->createModuleWidget(); | |||
| // JSON serialization is the most straightforward way to do this | |||
| json_t *moduleJ = m->toJson(); | |||
| clonedModuleWidget->fromJson(moduleJ); | |||
| ModuleWidget *clonedModuleWidget = moduleFromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| Rect clonedBox = clonedModuleWidget->box; | |||
| clonedBox.pos = m->box.pos; | |||
| requestModuleBoxNearest(clonedModuleWidget, clonedBox); | |||
| addModule(clonedModuleWidget); | |||
| } | |||
| bool RackWidget::requestModuleBox(ModuleWidget *m, Rect box) { | |||
| @@ -438,7 +490,7 @@ void RackWidget::step() { | |||
| // Autosave every 15 seconds | |||
| if (gGuiFrame % (60 * 15) == 0) { | |||
| savePatch(assetLocal("autosave.vcv")); | |||
| save(assetLocal("autosave.vcv")); | |||
| settingsSave(assetLocal("settings.json")); | |||
| } | |||
| @@ -45,7 +45,7 @@ struct OpenButton : TooltipIconButton { | |||
| tooltipText = "Open (" WINDOW_MOD_KEY_NAME "+O)"; | |||
| } | |||
| void onAction(EventAction &e) override { | |||
| gRackWidget->openDialog(); | |||
| gRackWidget->loadDialog(); | |||
| } | |||
| }; | |||
| @@ -295,9 +295,9 @@ void engineSetParamSmooth(Module *module, int paramId, float value) { | |||
| if (smoothModule && !(smoothModule == module && smoothParamId == paramId)) { | |||
| smoothModule->params[smoothParamId].value = smoothValue; | |||
| } | |||
| smoothModule = module; | |||
| smoothParamId = paramId; | |||
| smoothValue = value; | |||
| smoothModule = module; | |||
| } | |||
| void engineSetSampleRate(float newSampleRate) { | |||
| @@ -73,13 +73,13 @@ int main(int argc, char* argv[]) { | |||
| else { | |||
| // Load autosave | |||
| std::string oldLastPath = gRackWidget->lastPath; | |||
| gRackWidget->loadPatch(assetLocal("autosave.vcv")); | |||
| gRackWidget->load(assetLocal("autosave.vcv")); | |||
| gRackWidget->lastPath = oldLastPath; | |||
| } | |||
| } | |||
| else { | |||
| // Load patch | |||
| gRackWidget->loadPatch(patchFile); | |||
| gRackWidget->load(patchFile); | |||
| } | |||
| engineStart(); | |||
| @@ -87,7 +87,7 @@ int main(int argc, char* argv[]) { | |||
| engineStop(); | |||
| // Destroy namespaces | |||
| gRackWidget->savePatch(assetLocal("autosave.vcv")); | |||
| gRackWidget->save(assetLocal("autosave.vcv")); | |||
| settingsSave(assetLocal("settings.json")); | |||
| appDestroy(); | |||
| windowDestroy(); | |||