Browse Source

Add PatchManager. Clean up and refactor RackWidget and CableContainer.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
8cc4cb3c2b
18 changed files with 345 additions and 283 deletions
  1. +2
    -0
      include/app.hpp
  2. +10
    -26
      include/app/RackWidget.hpp
  3. +2
    -2
      include/app/common.hpp
  4. +35
    -0
      include/patch.hpp
  5. +4
    -0
      src/app.cpp
  6. +13
    -16
      src/app/CableContainer.cpp
  7. +2
    -2
      src/app/CableWidget.cpp
  8. +1
    -1
      src/app/ModuleWidget.cpp
  9. +8
    -205
      src/app/RackWidget.cpp
  10. +16
    -7
      src/app/Scene.cpp
  11. +8
    -8
      src/app/Toolbar.cpp
  12. +0
    -3
      src/app/common.cpp
  13. +1
    -1
      src/asset.cpp
  14. +1
    -1
      src/engine/Module.cpp
  15. +8
    -7
      src/main.cpp
  16. +228
    -0
      src/patch.cpp
  17. +3
    -2
      src/settings.cpp
  18. +3
    -2
      src/window.cpp

+ 2
- 0
include/app.hpp View File

@@ -16,6 +16,7 @@ namespace history {
struct Scene;
struct Engine;
struct Window;
struct PatchManager;


/** Contains the application state */
@@ -25,6 +26,7 @@ struct App {
Engine *engine = NULL;
Window *window = NULL;
history::State *history = NULL;
PatchManager *patch = NULL;

App();
~App();


+ 10
- 26
include/app/RackWidget.hpp View File

@@ -11,46 +11,30 @@ namespace rack {

struct RackWidget : OpaqueWidget {
FramebufferWidget *rails;
// Only put ModuleWidgets in here
Widget *moduleContainer;
// Only put CableWidgets in here
CableContainer *cableContainer;
/** The currently loaded patch file path */
std::string patchPath;
/** The last mouse position in the RackWidget */
math::Vec mousePos;

RackWidget();
~RackWidget();

void addModule(ModuleWidget *mw);
void addModuleAtMouse(ModuleWidget *mw);
/** Removes the module and transfers ownership to the caller */
void removeModule(ModuleWidget *mw);
/** Sets a module's box if non-colliding. Returns true if set */
bool requestModuleBox(ModuleWidget *mw, math::Rect requestedBox);
/** Moves a module to the closest non-colliding position */
bool requestModuleBoxNearest(ModuleWidget *mw, math::Rect requestedBox);
ModuleWidget *getModule(int moduleId);

/** Completely clear the rack's modules and cables */
void clear();
/** Clears the rack and loads the template patch */
void reset();
void loadDialog();
void saveDialog();
void saveAsDialog();
void saveTemplate();
/** If `lastPath` is defined, ask the user to reload it */
void revert();
/** Disconnects all cables */
void disconnect();
void save(std::string filename);
void load(std::string filename);
json_t *toJson();
void fromJson(json_t *rootJ);
void pastePresetClipboard();

void addModule(ModuleWidget *m);
void addModuleAtMouse(ModuleWidget *m);
/** Removes the module and transfers ownership to the caller */
void removeModule(ModuleWidget *m);
/** Sets a module's box if non-colliding. Returns true if set */
bool requestModuleBox(ModuleWidget *m, math::Rect requestedBox);
/** Moves a module to the closest non-colliding position */
bool requestModuleBoxNearest(ModuleWidget *m, math::Rect requestedBox);
ModuleWidget *getModule(int moduleId);

void step() override;
void draw(NVGcontext *vg) override;



+ 2
- 2
include/app/common.hpp View File

@@ -38,8 +38,8 @@ inline math::Vec mm2px(math::Vec mm) {
static const float RACK_GRID_WIDTH = 15;
static const float RACK_GRID_HEIGHT = 380;
static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
extern const std::string PRESET_FILTERS;
extern const std::string PATCH_FILTERS;
static const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm";


} // namespace rack

+ 35
- 0
include/patch.hpp View File

@@ -0,0 +1,35 @@
#pragma once
#include "common.hpp"
#include <jansson.h>


namespace rack {


struct PatchManager {
/** The currently loaded patch file path */
std::string path;
/** Enables certain compatibility behavior based on the value */
int legacy;
std::string warningLog;

void reset();
void resetDialog();
void save(std::string path);
void saveDialog();
void saveAsDialog();
void saveTemplateDialog();
void load(std::string path);
void loadDialog();
/** If `lastPath` is defined, ask the user to reload it */
void revertDialog();
/** Disconnects all cables */
void disconnectDialog();

json_t *toJson();
void fromJson(json_t *rootJ);
bool isLegacy(int level);
};


} // namespace rack

+ 4
- 0
src/app.cpp View File

@@ -1,6 +1,7 @@
#include "app.hpp"
#include "event.hpp"
#include "window.hpp"
#include "patch.hpp"
#include "engine/Engine.hpp"
#include "app/Scene.hpp"
#include "history.hpp"
@@ -14,12 +15,15 @@ App::App() {
history = new history::State;
window = new Window;
engine = new Engine;
patch = new PatchManager;
scene = new Scene;
event->rootWidget = scene;
}

App::~App() {
// Set pointers to NULL so other objects will segfault when attempting to access them
delete scene; scene = NULL;
delete patch; patch = NULL;
delete event; event = NULL;
delete history; history = NULL;
delete engine; engine = NULL;


+ 13
- 16
src/app/CableContainer.cpp View File

@@ -23,26 +23,22 @@ void CableContainer::clear() {

void CableContainer::clearPort(PortWidget *port) {
assert(port);
// Collect cables to remove
std::list<CableWidget*> cables;
for (Widget *w : children) {
std::list<Widget*> childrenCopy = children;
for (Widget *w : childrenCopy) {
CableWidget *cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (cw->inputPort == port || cw->outputPort == port) {
cables.push_back(cw);
}
}

// Remove and delete the cables
for (CableWidget *cw : cables) {
if (cw == incompleteCable) {
incompleteCable = NULL;
removeChild(cw);
}
else {
removeCable(cw);
// Check if cable is connected to port
if (cw->inputPort == port || cw->outputPort == port) {
if (cw == incompleteCable) {
incompleteCable = NULL;
removeChild(cw);
}
else {
removeCable(cw);
}
delete cw;
}
delete cw;
}
}

@@ -109,6 +105,7 @@ void CableContainer::fromJson(json_t *rootJ, const std::map<int, ModuleWidget*>
size_t cableIndex;
json_t *cableJ;
json_array_foreach(rootJ, cableIndex, cableJ) {
// Create a unserialize cable
CableWidget *cw = new CableWidget;
cw->fromJson(cableJ, moduleWidgets);
if (!cw->isComplete()) {


+ 2
- 2
src/app/CableWidget.cpp View File

@@ -4,6 +4,7 @@
#include "window.hpp"
#include "event.hpp"
#include "app.hpp"
#include "patch.hpp"
#include "settings.hpp"


@@ -174,8 +175,7 @@ void CableWidget::fromJson(json_t *rootJ, const std::map<int, ModuleWidget*> &mo
ModuleWidget *inputModule = inputModuleIt->second;

// Set ports
// TODO
if (false /*legacy && legacy <= 1*/) {
if (app()->patch->isLegacy(1)) {
// Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector.
setOutputPort(outputModule->outputs[outputId]);
setInputPort(inputModule->inputs[inputId]);


+ 1
- 1
src/app/ModuleWidget.cpp View File

@@ -328,7 +328,7 @@ json_t *ModuleWidget::toJson() {
// model
json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));

// Other properties
// Merge with module JSON
if (module) {
json_t *moduleJ = module->toJson();
// Merge with rootJ


+ 8
- 205
src/app/RackWidget.cpp View File

@@ -1,16 +1,16 @@
#include <map>
#include <algorithm>
#include "app/RackWidget.hpp"
#include "app/RackRail.hpp"
#include "app/Scene.hpp"
#include "app/ModuleBrowser.hpp"
#include "osdialog.h"
#include "settings.hpp"
#include "asset.hpp"
#include "system.hpp"
#include "plugin.hpp"
#include "engine/Engine.hpp"
#include "app.hpp"
#include "asset.hpp"
#include "patch.hpp"
#include "osdialog.h"
#include <map>
#include <algorithm>


namespace rack {
@@ -92,166 +92,10 @@ void RackWidget::clear() {
assert(cableContainer->children.empty());
}

void RackWidget::reset() {
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear patch and start over?")) {
clear();
app()->scene->scrollWidget->offset = math::Vec(0, 0);
// Fails silently if file does not exist
load(asset::user("template.vcv"));
patchPath = "";
}
}

void RackWidget::loadDialog() {
std::string dir;
if (patchPath.empty()) {
dir = asset::user("patches");
system::createDirectory(dir);
}
else {
dir = string::directory(patchPath);
}

osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str());
DEFER({
osdialog_filters_free(filters);
});

char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
if (!path) {
// Fail silently
return;
}
DEFER({
free(path);
});

load(path);
patchPath = path;
}

void RackWidget::saveDialog() {
if (!patchPath.empty()) {
save(patchPath);
}
else {
saveAsDialog();
}
}

void RackWidget::saveAsDialog() {
std::string dir;
std::string filename;
if (patchPath.empty()) {
dir = asset::user("patches");
system::createDirectory(dir);
}
else {
dir = string::directory(patchPath);
filename = string::filename(patchPath);
}

osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str());
DEFER({
osdialog_filters_free(filters);
});

char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters);
if (!path) {
// Fail silently
return;
}
DEFER({
free(path);
});


std::string pathStr = path;
if (string::extension(pathStr).empty()) {
pathStr += ".vcv";
}

save(pathStr);
patchPath = pathStr;
}

void RackWidget::saveTemplate() {
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) {
save(asset::user("template.vcv"));
}
}

void RackWidget::save(std::string filename) {
INFO("Saving patch %s", filename.c_str());
json_t *rootJ = toJson();
if (!rootJ)
return;
DEFER({
json_decref(rootJ);
});

FILE *file = fopen(filename.c_str(), "w");
if (!file) {
// Fail silently
return;
}
DEFER({
fclose(file);
});

json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
}

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;
}
DEFER({
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);
});

clear();
app()->scene->scrollWidget->offset = math::Vec(0, 0);
fromJson(rootJ);
}

void RackWidget::revert() {
if (patchPath.empty())
return;
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert patch to the last saved state?")) {
load(patchPath);
}
}

void RackWidget::disconnect() {
if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?"))
return;

cableContainer->clear();
}

json_t *RackWidget::toJson() {
// root
json_t *rootJ = json_object();

// version
json_t *versionJ = json_string(APP_VERSION.c_str());
json_object_set_new(rootJ, "version", versionJ);

// modules
json_t *modulesJ = json_array();
for (Widget *w : moduleContainer->children) {
@@ -278,30 +122,6 @@ json_t *RackWidget::toJson() {
}

void RackWidget::fromJson(json_t *rootJ) {
std::string message;

// 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.)
int legacy = 0;
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);
}

// modules
json_t *modulesJ = json_object_get(rootJ, "modules");
if (!modulesJ)
@@ -310,11 +130,6 @@ void RackWidget::fromJson(json_t *rootJ) {
size_t moduleIndex;
json_t *moduleJ;
json_array_foreach(modulesJ, moduleIndex, moduleJ) {
// Add "legacy" property if in legacy mode
if (legacy) {
json_object_set(moduleJ, "legacy", json_integer(legacy));
}

ModuleWidget *moduleWidget = moduleFromJson(moduleJ);

if (moduleWidget) {
@@ -328,7 +143,7 @@ void RackWidget::fromJson(json_t *rootJ) {
double x, y;
json_unpack(posJ, "[F, F]", &x, &y);
math::Vec pos = math::Vec(x, y);
if (legacy && legacy <= 1) {
if (app()->patch->isLegacy(1)) {
// Before 0.6, positions were in pixel units
moduleWidget->box.pos = pos;
}
@@ -336,7 +151,7 @@ void RackWidget::fromJson(json_t *rootJ) {
moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE);
}

if (legacy && legacy <= 2) {
if (app()->patch->isLegacy(2)) {
// Before 1.0, the module ID was the index in the "modules" array
moduleWidgets[moduleIndex] = moduleWidget;
}
@@ -350,7 +165,7 @@ void RackWidget::fromJson(json_t *rootJ) {
json_t *modelSlugJ = json_object_get(moduleJ, "model");
std::string pluginSlug = json_string_value(pluginSlugJ);
std::string modelSlug = json_string_value(modelSlugJ);
message += string::f("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str());
app()->patch->warningLog += string::f("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str());
}
}

@@ -361,11 +176,6 @@ void RackWidget::fromJson(json_t *rootJ) {
cablesJ = json_object_get(rootJ, "wires");
if (cablesJ)
cableContainer->fromJson(cablesJ, moduleWidgets);

// Display a message if we have something to say
if (!message.empty()) {
osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
}
}

void RackWidget::pastePresetClipboard() {
@@ -496,13 +306,6 @@ void RackWidget::step() {
rail->box.size = rails->box.size;
}

// Autosave every 15 seconds
int frame = app()->window->frame;
if (frame > 0 && frame % (60 * 15) == 0) {
save(asset::user("autosave.vcv"));
settings::save(asset::user("settings.json"));
}

Widget::step();
}



+ 16
- 7
src/app/Scene.cpp View File

@@ -1,4 +1,3 @@
#include "osdialog.h"
#include "system.hpp"
#include "network.hpp"
#include "app/Scene.hpp"
@@ -7,6 +6,9 @@
#include "app.hpp"
#include "history.hpp"
#include "settings.hpp"
#include "patch.hpp"
#include "asset.hpp"
#include "osdialog.h"
#include <thread>


@@ -53,6 +55,13 @@ void Scene::step() {
zoomWidget->box.size = rackWidget->box.size.mult(zoomWidget->zoom);
moduleBrowser->box.size = box.size;

// Autosave every 15 seconds
int frame = app()->window->frame;
if (frame > 0 && frame % (60 * 15) == 0) {
app()->patch->save(asset::user("autosave.vcv"));
settings::save(asset::user("settings.json"));
}

// Set zoom every few frames
if (app()->window->frame % 10 == 0)
zoomWidget->setZoom(settings::zoom);
@@ -85,7 +94,7 @@ void Scene::onHoverKey(const event::HoverKey &e) {
switch (e.key) {
case GLFW_KEY_N: {
if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) {
rackWidget->reset();
app()->patch->resetDialog();
e.consume(this);
}
} break;
@@ -97,21 +106,21 @@ void Scene::onHoverKey(const event::HoverKey &e) {
} break;
case GLFW_KEY_O: {
if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) {
rackWidget->loadDialog();
app()->patch->loadDialog();
e.consume(this);
}
if ((e.mods & WINDOW_MOD_MASK) == (WINDOW_MOD_CTRL | GLFW_MOD_SHIFT)) {
rackWidget->revert();
app()->patch->revertDialog();
e.consume(this);
}
} break;
case GLFW_KEY_S: {
if ((e.mods & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) {
rackWidget->saveDialog();
app()->patch->saveDialog();
e.consume(this);
}
if ((e.mods & WINDOW_MOD_MASK) == (WINDOW_MOD_CTRL | GLFW_MOD_SHIFT)) {
rackWidget->saveAsDialog();
app()->patch->saveAsDialog();
e.consume(this);
}
} break;
@@ -151,7 +160,7 @@ void Scene::onPathDrop(const event::PathDrop &e) {
if (e.paths.size() >= 1) {
const std::string &path = e.paths[0];
if (string::extension(path) == "vcv") {
rackWidget->load(path);
app()->patch->load(path);
e.consume(this);
}
}


+ 8
- 8
src/app/Toolbar.cpp View File

@@ -9,12 +9,12 @@
#include "ui/TextField.hpp"
#include "ui/PasswordField.hpp"
#include "ui/ProgressBar.hpp"
#include "app/Scene.hpp"
#include "app.hpp"
#include "settings.hpp"
#include "helpers.hpp"
#include "system.hpp"
#include "plugin.hpp"
#include "patch.hpp"
#include <thread>


@@ -38,7 +38,7 @@ struct NewItem : MenuItem {
rightText = "(" WINDOW_MOD_CTRL_NAME "+N)";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->reset();
app()->patch->resetDialog();
}
};

@@ -49,7 +49,7 @@ struct OpenItem : MenuItem {
rightText = "(" WINDOW_MOD_CTRL_NAME "+O)";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->loadDialog();
app()->patch->loadDialog();
}
};

@@ -60,7 +60,7 @@ struct SaveItem : MenuItem {
rightText = "(" WINDOW_MOD_CTRL_NAME "+S)";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->saveDialog();
app()->patch->saveDialog();
}
};

@@ -71,7 +71,7 @@ struct SaveAsItem : MenuItem {
rightText = "(" WINDOW_MOD_CTRL_NAME "+Shift+S)";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->saveAsDialog();
app()->patch->saveAsDialog();
}
};

@@ -81,7 +81,7 @@ struct SaveTemplateItem : MenuItem {
text = "Save template";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->saveTemplate();
app()->patch->saveTemplateDialog();
}
};

@@ -91,7 +91,7 @@ struct RevertItem : MenuItem {
text = "Revert";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->revert();
app()->patch->revertDialog();
}
};

@@ -101,7 +101,7 @@ struct DisconnectCablesItem : MenuItem {
text = "Disconnect cables";
}
void onAction(const event::Action &e) override {
app()->scene->rackWidget->disconnect();
app()->patch->disconnectDialog();
}
};



+ 0
- 3
src/app/common.cpp View File

@@ -8,8 +8,5 @@ const std::string APP_NAME = "VCV Rack";
const std::string APP_VERSION = TOSTRING(VERSION);
const std::string API_HOST = "https://api.vcvrack.com";

const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm";
const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv";


} // namespace rack

+ 1
- 1
src/asset.cpp View File

@@ -49,7 +49,7 @@ void init(bool devMode) {
systemDir = moduleBuf;
#endif
#if defined ARCH_LIN
// TODO For now, users should launch Rack from their terminal in the system directory
// Users should launch Rack from their terminal in the system directory
systemDir = ".";
#endif
}


+ 1
- 1
src/engine/Module.cpp View File

@@ -50,9 +50,9 @@ void Module::fromJson(json_t *rootJ) {
json_array_foreach(paramsJ, i, paramJ) {
uint32_t paramId = i;
// Get paramId
// Legacy v0.6.0 to <v1.0
json_t *paramIdJ = json_object_get(paramJ, "paramId");
if (paramIdJ) {
// Legacy v0.6.0 to <v1.0
paramId = json_integer_value(paramIdJ);
}



+ 8
- 7
src/main.cpp View File

@@ -11,6 +11,7 @@
#include "app/Scene.hpp"
#include "plugin.hpp"
#include "app.hpp"
#include "patch.hpp"
#include "ui.hpp"

#include <unistd.h>
@@ -93,19 +94,19 @@ int main(int argc, char *argv[]) {
settings::save(asset::user("settings.json"));
settings::skipLoadOnLaunch = false;
if (oldSkipLoadOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Clear your patch and start over?")) {
app()->scene->rackWidget->patchPath = "";
app()->patch->path = "";
}
else {
// Load autosave
std::string oldLastPath = app()->scene->rackWidget->patchPath;
app()->scene->rackWidget->load(asset::user("autosave.vcv"));
app()->scene->rackWidget->patchPath = oldLastPath;
std::string oldLastPath = app()->patch->path;
app()->patch->load(asset::user("autosave.vcv"));
app()->patch->path = oldLastPath;
}
}
else {
// Load patch
app()->scene->rackWidget->load(patchFile);
app()->scene->rackWidget->patchPath = patchFile;
app()->patch->load(patchFile);
app()->patch->path = patchFile;
}
INFO("Initialized app");

@@ -115,7 +116,7 @@ int main(int argc, char *argv[]) {
app()->engine->stop();

// Destroy app
app()->scene->rackWidget->save(asset::user("autosave.vcv"));
app()->patch->save(asset::user("autosave.vcv"));
settings::save(asset::user("settings.json"));
appDestroy();
INFO("Cleaned up app");


+ 228
- 0
src/patch.cpp View File

@@ -0,0 +1,228 @@
#include "patch.hpp"
#include "asset.hpp"
#include "system.hpp"
#include "app.hpp"
#include "app/Scene.hpp"
#include "app/RackWidget.hpp"
#include "osdialog.h"


namespace rack {


static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv";


void PatchManager::reset() {
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()->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->cableContainer->clear();
}

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

+ 3
- 2
src/settings.cpp View File

@@ -5,6 +5,7 @@
#include "app/ModuleBrowser.hpp"
#include "engine/Engine.hpp"
#include "app.hpp"
#include "patch.hpp"
#include <jansson.h>


@@ -53,7 +54,7 @@ static json_t *settingsToJson() {
json_object_set_new(rootJ, "sampleRate", sampleRateJ);

// patchPath
json_t *patchPathJ = json_string(app()->scene->rackWidget->patchPath.c_str());
json_t *patchPathJ = json_string(app()->patch->path.c_str());
json_object_set_new(rootJ, "patchPath", patchPathJ);

// skipLoadOnLaunch
@@ -128,7 +129,7 @@ static void settingsFromJson(json_t *rootJ) {
// patchPath
json_t *patchPathJ = json_object_get(rootJ, "patchPath");
if (patchPathJ)
app()->scene->rackWidget->patchPath = json_string_value(patchPathJ);
app()->patch->path = json_string_value(patchPathJ);

// skipLoadOnLaunch
json_t *skipLoadOnLaunchJ = json_object_get(rootJ, "skipLoadOnLaunch");


+ 3
- 2
src/window.cpp View File

@@ -5,6 +5,7 @@
#include "gamepad.hpp"
#include "event.hpp"
#include "app.hpp"
#include "patch.hpp"

#include <map>
#include <queue>
@@ -316,9 +317,9 @@ void Window::run() {
windowTitle = APP_NAME;
windowTitle += " ";
windowTitle += APP_VERSION;
if (!app()->scene->rackWidget->patchPath.empty()) {
if (!app()->patch->path.empty()) {
windowTitle += " - ";
windowTitle += string::filename(app()->scene->rackWidget->patchPath);
windowTitle += string::filename(app()->patch->path);
}
if (windowTitle != internal->lastWindowTitle) {
glfwSetWindowTitle(win, windowTitle.c_str());


Loading…
Cancel
Save