@@ -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(); | |||
@@ -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; | |||
@@ -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 |
@@ -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 |
@@ -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; | |||
@@ -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()) { | |||
@@ -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]); | |||
@@ -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 | |||
@@ -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(); | |||
} | |||
@@ -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); | |||
} | |||
} | |||
@@ -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(); | |||
} | |||
}; | |||
@@ -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 |
@@ -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 | |||
} | |||
@@ -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); | |||
} | |||
@@ -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"); | |||
@@ -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 |
@@ -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"); | |||
@@ -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()); | |||