diff --git a/.gitignore b/.gitignore index 03820916..bd9a4f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ /autosave /settings.json /screenshots +/selections diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 3bf75d56..fbe391c1 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -80,7 +80,9 @@ struct RackWidget : widget::OpaqueWidget { int getNumSelected(); std::vector getSelectedModules(); json_t* selectionToJson(); + void loadSelection(std::string path); void loadSelectionDialog(); + void saveSelection(std::string path); void saveSelectionDialog(); void copyClipboardSelection(); void resetSelectionAction(); diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index f4d805c1..eef1b243 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -876,12 +877,87 @@ json_t* RackWidget::selectionToJson() { return rootJ; } +static const char SELECTION_FILTERS[] = "VCV Rack module selection (.vcvs):vcvs"; + +void RackWidget::loadSelection(std::string path) { + FILE* file = std::fopen(path.c_str(), "r"); + if (!file) + throw Exception("Could not load selection file %s", path.c_str()); + DEFER({std::fclose(file);}); + + INFO("Loading selection %s", path.c_str()); + + json_error_t error; + json_t* rootJ = json_loadf(file, 0, &error); + if (!rootJ) + throw Exception("File is not a valid selection file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + DEFER({json_decref(rootJ);}); + + pasteJsonAction(rootJ); +} + void RackWidget::loadSelectionDialog() { - // TODO + std::string selectionDir = asset::user("selections"); + system::createDirectories(selectionDir); + + osdialog_filters* filters = osdialog_filters_parse(SELECTION_FILTERS); + DEFER({osdialog_filters_free(filters);}); + + char* pathC = osdialog_file(OSDIALOG_OPEN, selectionDir.c_str(), NULL, filters); + if (!pathC) { + // No path selected + return; + } + DEFER({std::free(pathC);}); + + try { + loadSelection(pathC); + } + catch (Exception& e) { + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); + } +} + +void RackWidget::saveSelection(std::string path) { + INFO("Saving selection %s", path.c_str()); + + json_t* rootJ = selectionToJson(); + assert(rootJ); + DEFER({json_decref(rootJ);}); + + cleanupModuleJson(rootJ); + + FILE* file = std::fopen(path.c_str(), "w"); + if (!file) { + std::string message = string::f("Could not save selection to file %s", path.c_str()); + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + return; + } + DEFER({std::fclose(file);}); + + json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); } void RackWidget::saveSelectionDialog() { - // TODO + std::string selectionDir = asset::user("selections"); + system::createDirectories(selectionDir); + + osdialog_filters* filters = osdialog_filters_parse(SELECTION_FILTERS); + DEFER({osdialog_filters_free(filters);}); + + char* pathC = osdialog_file(OSDIALOG_SAVE, selectionDir.c_str(), "Untitled.vcvs", filters); + if (!pathC) { + // No path selected + return; + } + DEFER({std::free(pathC);}); + + std::string path = pathC; + // Automatically append .vcvs extension + if (system::getExtension(path) != ".vcvs") + path += ".vcvs"; + + saveSelection(path); } void RackWidget::copyClipboardSelection() { @@ -1072,7 +1148,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { })); // Save - menu->addChild(createMenuItem("Save selection", "", [=]() { + menu->addChild(createMenuItem("Save selection as", "", [=]() { saveSelectionDialog(); }, n == 0));