Browse Source

Add PatchManager. Clean up and refactor RackWidget and CableContainer.

tags/v1.0.0
Andrew Belt 6 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 Scene;
struct Engine; struct Engine;
struct Window; struct Window;
struct PatchManager;




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


App(); App();
~App(); ~App();


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

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


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


RackWidget(); RackWidget();
~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 */ /** Completely clear the rack's modules and cables */
void clear(); 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(); json_t *toJson();
void fromJson(json_t *rootJ); void fromJson(json_t *rootJ);
void pastePresetClipboard(); 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 step() override;
void draw(NVGcontext *vg) 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_WIDTH = 15;
static const float RACK_GRID_HEIGHT = 380; static const float RACK_GRID_HEIGHT = 380;
static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 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 } // 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 "app.hpp"
#include "event.hpp" #include "event.hpp"
#include "window.hpp" #include "window.hpp"
#include "patch.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "app/Scene.hpp" #include "app/Scene.hpp"
#include "history.hpp" #include "history.hpp"
@@ -14,12 +15,15 @@ App::App() {
history = new history::State; history = new history::State;
window = new Window; window = new Window;
engine = new Engine; engine = new Engine;
patch = new PatchManager;
scene = new Scene; scene = new Scene;
event->rootWidget = scene; event->rootWidget = scene;
} }


App::~App() { App::~App() {
// Set pointers to NULL so other objects will segfault when attempting to access them
delete scene; scene = NULL; delete scene; scene = NULL;
delete patch; patch = NULL;
delete event; event = NULL; delete event; event = NULL;
delete history; history = NULL; delete history; history = NULL;
delete engine; engine = 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) { void CableContainer::clearPort(PortWidget *port) {
assert(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); CableWidget *cw = dynamic_cast<CableWidget*>(w);
assert(cw); 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; size_t cableIndex;
json_t *cableJ; json_t *cableJ;
json_array_foreach(rootJ, cableIndex, cableJ) { json_array_foreach(rootJ, cableIndex, cableJ) {
// Create a unserialize cable
CableWidget *cw = new CableWidget; CableWidget *cw = new CableWidget;
cw->fromJson(cableJ, moduleWidgets); cw->fromJson(cableJ, moduleWidgets);
if (!cw->isComplete()) { if (!cw->isComplete()) {


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

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




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


// Set ports // 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. // 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]); setOutputPort(outputModule->outputs[outputId]);
setInputPort(inputModule->inputs[inputId]); setInputPort(inputModule->inputs[inputId]);


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

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


// Other properties
// Merge with module JSON
if (module) { if (module) {
json_t *moduleJ = module->toJson(); json_t *moduleJ = module->toJson();
// Merge with rootJ // 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/RackWidget.hpp"
#include "app/RackRail.hpp" #include "app/RackRail.hpp"
#include "app/Scene.hpp" #include "app/Scene.hpp"
#include "app/ModuleBrowser.hpp" #include "app/ModuleBrowser.hpp"
#include "osdialog.h"
#include "settings.hpp" #include "settings.hpp"
#include "asset.hpp"
#include "system.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "app.hpp" #include "app.hpp"
#include "asset.hpp"
#include "patch.hpp"
#include "osdialog.h"
#include <map>
#include <algorithm>




namespace rack { namespace rack {
@@ -92,166 +92,10 @@ void RackWidget::clear() {
assert(cableContainer->children.empty()); 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() { json_t *RackWidget::toJson() {
// root // root
json_t *rootJ = json_object(); json_t *rootJ = json_object();


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

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


void RackWidget::fromJson(json_t *rootJ) { 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 // modules
json_t *modulesJ = json_object_get(rootJ, "modules"); json_t *modulesJ = json_object_get(rootJ, "modules");
if (!modulesJ) if (!modulesJ)
@@ -310,11 +130,6 @@ void RackWidget::fromJson(json_t *rootJ) {
size_t moduleIndex; size_t moduleIndex;
json_t *moduleJ; json_t *moduleJ;
json_array_foreach(modulesJ, moduleIndex, 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); ModuleWidget *moduleWidget = moduleFromJson(moduleJ);


if (moduleWidget) { if (moduleWidget) {
@@ -328,7 +143,7 @@ void RackWidget::fromJson(json_t *rootJ) {
double x, y; double x, y;
json_unpack(posJ, "[F, F]", &x, &y); json_unpack(posJ, "[F, F]", &x, &y);
math::Vec pos = math::Vec(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 // Before 0.6, positions were in pixel units
moduleWidget->box.pos = pos; moduleWidget->box.pos = pos;
} }
@@ -336,7 +151,7 @@ void RackWidget::fromJson(json_t *rootJ) {
moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); 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 // Before 1.0, the module ID was the index in the "modules" array
moduleWidgets[moduleIndex] = moduleWidget; moduleWidgets[moduleIndex] = moduleWidget;
} }
@@ -350,7 +165,7 @@ void RackWidget::fromJson(json_t *rootJ) {
json_t *modelSlugJ = json_object_get(moduleJ, "model"); json_t *modelSlugJ = json_object_get(moduleJ, "model");
std::string pluginSlug = json_string_value(pluginSlugJ); std::string pluginSlug = json_string_value(pluginSlugJ);
std::string modelSlug = json_string_value(modelSlugJ); 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"); cablesJ = json_object_get(rootJ, "wires");
if (cablesJ) if (cablesJ)
cableContainer->fromJson(cablesJ, moduleWidgets); 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() { void RackWidget::pastePresetClipboard() {
@@ -496,13 +306,6 @@ void RackWidget::step() {
rail->box.size = rails->box.size; 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(); Widget::step();
} }




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

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




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


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

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




@@ -38,7 +38,7 @@ struct NewItem : MenuItem {
rightText = "(" WINDOW_MOD_CTRL_NAME "+N)"; rightText = "(" WINDOW_MOD_CTRL_NAME "+N)";
} }
void onAction(const event::Action &e) override { 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)"; rightText = "(" WINDOW_MOD_CTRL_NAME "+O)";
} }
void onAction(const event::Action &e) override { 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)"; rightText = "(" WINDOW_MOD_CTRL_NAME "+S)";
} }
void onAction(const event::Action &e) override { 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)"; rightText = "(" WINDOW_MOD_CTRL_NAME "+Shift+S)";
} }
void onAction(const event::Action &e) override { void onAction(const event::Action &e) override {
app()->scene->rackWidget->saveAsDialog();
app()->patch->saveAsDialog();
} }
}; };


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


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


@@ -101,7 +101,7 @@ struct DisconnectCablesItem : MenuItem {
text = "Disconnect cables"; text = "Disconnect cables";
} }
void onAction(const event::Action &e) override { 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 APP_VERSION = TOSTRING(VERSION);
const std::string API_HOST = "https://api.vcvrack.com"; 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 } // namespace rack

+ 1
- 1
src/asset.cpp View File

@@ -49,7 +49,7 @@ void init(bool devMode) {
systemDir = moduleBuf; systemDir = moduleBuf;
#endif #endif
#if defined ARCH_LIN #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 = "."; systemDir = ".";
#endif #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) { json_array_foreach(paramsJ, i, paramJ) {
uint32_t paramId = i; uint32_t paramId = i;
// Get paramId // Get paramId
// Legacy v0.6.0 to <v1.0
json_t *paramIdJ = json_object_get(paramJ, "paramId"); json_t *paramIdJ = json_object_get(paramJ, "paramId");
if (paramIdJ) { if (paramIdJ) {
// Legacy v0.6.0 to <v1.0
paramId = json_integer_value(paramIdJ); paramId = json_integer_value(paramIdJ);
} }




+ 8
- 7
src/main.cpp View File

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


#include <unistd.h> #include <unistd.h>
@@ -93,19 +94,19 @@ int main(int argc, char *argv[]) {
settings::save(asset::user("settings.json")); settings::save(asset::user("settings.json"));
settings::skipLoadOnLaunch = false; 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?")) { 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 { else {
// Load autosave // 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 { else {
// Load patch // Load patch
app()->scene->rackWidget->load(patchFile);
app()->scene->rackWidget->patchPath = patchFile;
app()->patch->load(patchFile);
app()->patch->path = patchFile;
} }
INFO("Initialized app"); INFO("Initialized app");


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


// Destroy app // Destroy app
app()->scene->rackWidget->save(asset::user("autosave.vcv"));
app()->patch->save(asset::user("autosave.vcv"));
settings::save(asset::user("settings.json")); settings::save(asset::user("settings.json"));
appDestroy(); appDestroy();
INFO("Cleaned up app"); 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 "app/ModuleBrowser.hpp"
#include "engine/Engine.hpp" #include "engine/Engine.hpp"
#include "app.hpp" #include "app.hpp"
#include "patch.hpp"
#include <jansson.h> #include <jansson.h>




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


// patchPath // 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); json_object_set_new(rootJ, "patchPath", patchPathJ);


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


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


+ 3
- 2
src/window.cpp View File

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


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


Loading…
Cancel
Save