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