#include "patch.hpp" #include "asset.hpp" #include "system.hpp" #include "app.hpp" #include "app/Scene.hpp" #include "app/RackWidget.hpp" #include "history.hpp" #include "osdialog.h" namespace rack { static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv"; void PatchManager::reset() { app()->history->clear(); app()->scene->rackWidget->clear(); app()->scene->scrollWidget->offset = math::Vec(0, 0); // Fails silently if file does not exist load(asset::user("template.vcv")); legacy = 0; path = ""; } void PatchManager::resetDialog() { if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear patch and start over?")) { reset(); } } void PatchManager::save(std::string path) { INFO("Saving patch %s", path.c_str()); json_t *rootJ = toJson(); if (!rootJ) return; DEFER({ json_decref(rootJ); }); FILE *file = std::fopen(path.c_str(), "w"); if (!file) { // Fail silently return; } DEFER({ std::fclose(file); }); json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); } void PatchManager::saveDialog() { if (!path.empty()) { save(path); } else { saveAsDialog(); } } void PatchManager::saveAsDialog() { std::string dir; std::string filename; if (path.empty()) { dir = asset::user("patches"); system::createDirectory(dir); } else { dir = string::directory(path); filename = string::filename(path); } osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); DEFER({ osdialog_filters_free(filters); }); char *pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); if (!pathC) { // Fail silently return; } DEFER({ free(pathC); }); std::string pathStr = pathC; if (string::extension(pathStr).empty()) { pathStr += ".vcv"; } save(pathStr); path = pathStr; } void PatchManager::saveTemplateDialog() { if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) { save(asset::user("template.vcv")); } } void PatchManager::load(std::string path) { INFO("Loading patch %s", path.c_str()); FILE *file = std::fopen(path.c_str(), "r"); if (!file) { // Exit silently return; } DEFER({ std::fclose(file); }); json_error_t error; json_t *rootJ = json_loadf(file, 0, &error); if (!rootJ) { std::string message = string::f("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()); return; } DEFER({ json_decref(rootJ); }); app()->history->clear(); app()->scene->rackWidget->clear(); app()->scene->scrollWidget->offset = math::Vec(0, 0); fromJson(rootJ); } void PatchManager::loadDialog() { std::string dir; if (path.empty()) { dir = asset::user("patches"); system::createDirectory(dir); } else { dir = string::directory(path); } osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); DEFER({ osdialog_filters_free(filters); }); char *pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); if (!pathC) { // Fail silently return; } DEFER({ free(pathC); }); load(pathC); path = pathC; } void PatchManager::revertDialog() { if (path.empty()) return; if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert patch to the last saved state?")) { load(path); } } void PatchManager::disconnectDialog() { if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) return; app()->scene->rackWidget->clearCablesAction(); } json_t *PatchManager::toJson() { // root json_t *rootJ = json_object(); // version json_t *versionJ = json_string(APP_VERSION.c_str()); json_object_set_new(rootJ, "version", versionJ); // Merge with RackWidget JSON json_t *rackJ = app()->scene->rackWidget->toJson(); // Merge with rootJ json_object_update(rootJ, rackJ); json_decref(rackJ); return rootJ; } void PatchManager::fromJson(json_t *rootJ) { legacy = 0; // version std::string version; json_t *versionJ = json_object_get(rootJ, "version"); if (versionJ) version = json_string_value(versionJ); if (version != APP_VERSION) { INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str()); } // Detect old patches with ModuleWidget::params/inputs/outputs indices. // (We now use Module::params/inputs/outputs indices.) if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") { legacy = 1; } else if (string::startsWith(version, "0.6.")) { legacy = 2; } if (legacy) { INFO("Loading patch using legacy mode %d", legacy); } app()->scene->rackWidget->fromJson(rootJ); // Display a message if we have something to say if (!warningLog.empty()) { osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, warningLog.c_str()); } warningLog = ""; } bool PatchManager::isLegacy(int level) { return legacy && legacy <= level; } } // namespace rack