| @@ -25,49 +25,73 @@ ScriptEngine* createScriptEngine(std::string extension) { | |||||
| } | } | ||||
| // json_t *settingsToJson() { | |||||
| // json_t *rootJ = json_object(); | |||||
| // json_object_set_new(rootJ, "securityAccepted", json_boolean(securityAccepted)); | |||||
| // return rootJ; | |||||
| // } | |||||
| // void settingsFromJson(json_t *rootJ) { | |||||
| // json_t *securityAcceptedJ = json_object_get(rootJ, "securityAccepted"); | |||||
| // if (securityAcceptedJ) | |||||
| // securityAccepted = json_boolean_value(securityAcceptedJ); | |||||
| // } | |||||
| // void settingsLoad() { | |||||
| // // Load plugin settings | |||||
| // std::string filename = asset::user("VCV-Prototype.json"); | |||||
| // FILE *file = fopen(filename.c_str(), "r"); | |||||
| // if (!file) { | |||||
| // return; | |||||
| // } | |||||
| // DEFER({ | |||||
| // fclose(file); | |||||
| // }); | |||||
| // json_error_t error; | |||||
| // json_t *rootJ = json_loadf(file, 0, &error); | |||||
| // if (rootJ) { | |||||
| // settingsFromJson(rootJ); | |||||
| // json_decref(rootJ); | |||||
| // } | |||||
| // } | |||||
| // void settingsSave() { | |||||
| // json_t *rootJ = settingsToJson(); | |||||
| // std::string filename = asset::user("VCV-Prototype.json"); | |||||
| // FILE *file = fopen(filename.c_str(), "w"); | |||||
| // if (file) { | |||||
| // json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||||
| // fclose(file); | |||||
| // } | |||||
| // json_decref(rootJ); | |||||
| // } | |||||
| static std::string editorPath; | |||||
| json_t *settingsToJson() { | |||||
| json_t *rootJ = json_object(); | |||||
| json_object_set_new(rootJ, "editorPath", json_string(editorPath.c_str())); | |||||
| return rootJ; | |||||
| } | |||||
| void settingsFromJson(json_t *rootJ) { | |||||
| json_t *editorPathJ = json_object_get(rootJ, "editorPath"); | |||||
| if (editorPathJ) | |||||
| editorPath = json_string_value(editorPathJ); | |||||
| } | |||||
| void settingsLoad() { | |||||
| // Load plugin settings | |||||
| std::string filename = asset::user("VCV-Prototype.json"); | |||||
| FILE *file = std::fopen(filename.c_str(), "r"); | |||||
| if (!file) { | |||||
| return; | |||||
| } | |||||
| DEFER({ | |||||
| std::fclose(file); | |||||
| }); | |||||
| json_error_t error; | |||||
| json_t *rootJ = json_loadf(file, 0, &error); | |||||
| if (rootJ) { | |||||
| settingsFromJson(rootJ); | |||||
| json_decref(rootJ); | |||||
| } | |||||
| } | |||||
| void settingsSave() { | |||||
| json_t *rootJ = settingsToJson(); | |||||
| std::string filename = asset::user("VCV-Prototype.json"); | |||||
| FILE *file = std::fopen(filename.c_str(), "w"); | |||||
| if (file) { | |||||
| json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||||
| std::fclose(file); | |||||
| } | |||||
| json_decref(rootJ); | |||||
| } | |||||
| void setEditorDialog() { | |||||
| char* editorPathC = NULL; | |||||
| #if defined ARCH_LIN | |||||
| editorPathC = osdialog_file(OSDIALOG_OPEN, "/usr/bin/", NULL, NULL); | |||||
| #elif defined ARCH_WIN | |||||
| osdialog_filters* filters = osdialog_filters_parse("Executable:exe"); | |||||
| editorPathC = osdialog_file(OSDIALOG_OPEN, "C:/", NULL, filters); | |||||
| osdialog_filters_free(filters); | |||||
| #elif defined ARCH_MAC | |||||
| osdialog_filters* filters = osdialog_filters_parse("Application:app"); | |||||
| editorPathC = osdialog_file(OSDIALOG_OPEN, "/Applications/", NULL, filters); | |||||
| osdialog_filters_free(filters); | |||||
| #endif | |||||
| if (!editorPathC) | |||||
| return; | |||||
| editorPath = editorPathC; | |||||
| settingsSave(); | |||||
| std::free(editorPathC); | |||||
| } | |||||
| struct Prototype : Module { | struct Prototype : Module { | ||||
| @@ -115,6 +139,10 @@ struct Prototype : Module { | |||||
| configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1)); | configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1)); | ||||
| for (int i = 0; i < NUM_ROWS; i++) | for (int i = 0; i < NUM_ROWS; i++) | ||||
| configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1)); | configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1)); | ||||
| // for (int i = 0; i < NUM_ROWS; i++) | |||||
| // configInput(IN_INPUTS + i, string::f("#%d", i + 1)); | |||||
| // for (int i = 0; i < NUM_ROWS; i++) | |||||
| // configOutput(OUT_OUTPUTS + i, string::f("#%d", i + 1)); | |||||
| block = new ProcessBlock; | block = new ProcessBlock; | ||||
| setPath(""); | setPath(""); | ||||
| @@ -337,6 +365,55 @@ struct Prototype : Module { | |||||
| } | } | ||||
| } | } | ||||
| bool doesPathExist() { | |||||
| if (path == "") | |||||
| return false; | |||||
| // Try to open file | |||||
| std::ifstream file(path); | |||||
| return file.good(); | |||||
| } | |||||
| void newScriptDialog() { | |||||
| std::string ext = "js"; | |||||
| // Get current extension if a script is currently loaded | |||||
| if (!path.empty()) { | |||||
| ext = string::filenameExtension(string::filename(path)); | |||||
| } | |||||
| std::string dir = asset::plugin(pluginInstance, "examples"); | |||||
| std::string filename = "Untitled." + ext; | |||||
| char* newPathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), NULL); | |||||
| if (!newPathC) { | |||||
| return; | |||||
| } | |||||
| std::string newPath = newPathC; | |||||
| std::free(newPathC); | |||||
| // Unload script so the user is guaranteed to see the following error messages if they occur. | |||||
| setPath(""); | |||||
| // Get extension of requested filename | |||||
| ext = string::filenameExtension(string::filename(newPath)); | |||||
| if (ext == "") { | |||||
| message = "File extension required"; | |||||
| return; | |||||
| } | |||||
| auto it = scriptEngineFactories.find(ext); | |||||
| if (it == scriptEngineFactories.end()) { | |||||
| message = "File extension \"" + ext + "\" not recognized"; | |||||
| return; | |||||
| } | |||||
| // Copy template to new script | |||||
| std::string templatePath = asset::plugin(pluginInstance, "examples/template." + ext); | |||||
| { | |||||
| std::ifstream templateFile(templatePath, std::ios::binary); | |||||
| std::ofstream newFile(newPath, std::ios::binary); | |||||
| newFile << templateFile.rdbuf(); | |||||
| } | |||||
| setPath(newPath); | |||||
| editScript(); | |||||
| } | |||||
| void loadScriptDialog() { | void loadScriptDialog() { | ||||
| std::string dir = asset::plugin(pluginInstance, "examples"); | std::string dir = asset::plugin(pluginInstance, "examples"); | ||||
| char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); | char* pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, NULL); | ||||
| @@ -379,6 +456,87 @@ struct Prototype : Module { | |||||
| // Load path so that it reloads and is watched. | // Load path so that it reloads and is watched. | ||||
| setPath(newPath); | setPath(newPath); | ||||
| } | } | ||||
| void editScript() { | |||||
| if (editorPath.empty()) | |||||
| return; | |||||
| if (path.empty()) | |||||
| return; | |||||
| // TODO Check on Mac/Windows | |||||
| std::string command = "\"" + editorPath + "\" \"" + path + "\" &"; | |||||
| std::system(command.c_str()); | |||||
| } | |||||
| void setClipboardMessage() { | |||||
| glfwSetClipboardString(APP->window->win, message.c_str()); | |||||
| } | |||||
| void appendContextMenu(Menu* menu) { | |||||
| struct NewScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->newScriptDialog(); | |||||
| } | |||||
| }; | |||||
| NewScriptItem* newScriptItem = createMenuItem<NewScriptItem>("New script"); | |||||
| newScriptItem->module = this; | |||||
| menu->addChild(newScriptItem); | |||||
| struct LoadScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->loadScriptDialog(); | |||||
| } | |||||
| }; | |||||
| LoadScriptItem* loadScriptItem = createMenuItem<LoadScriptItem>("Load script"); | |||||
| loadScriptItem->module = this; | |||||
| menu->addChild(loadScriptItem); | |||||
| struct ReloadScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->reloadScript(); | |||||
| } | |||||
| }; | |||||
| ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload script"); | |||||
| reloadScriptItem->module = this; | |||||
| menu->addChild(reloadScriptItem); | |||||
| struct SaveScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->saveScriptDialog(); | |||||
| } | |||||
| }; | |||||
| SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("Save script as"); | |||||
| saveScriptItem->module = this; | |||||
| menu->addChild(saveScriptItem); | |||||
| struct EditScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->editScript(); | |||||
| } | |||||
| }; | |||||
| EditScriptItem* editScriptItem = createMenuItem<EditScriptItem>("Edit script"); | |||||
| editScriptItem->module = this; | |||||
| editScriptItem->disabled = !doesPathExist(); | |||||
| menu->addChild(editScriptItem); | |||||
| struct SetEditorItem : MenuItem { | |||||
| void onAction(const event::Action& e) override { | |||||
| setEditorDialog(); | |||||
| } | |||||
| }; | |||||
| SetEditorItem* setEditorItem = createMenuItem<SetEditorItem>("Set editor application"); | |||||
| menu->addChild(setEditorItem); | |||||
| // if (!editorPath.empty()) { | |||||
| // std::string editorBase = string::filenameBase(string::filename(editorPath)); | |||||
| // MenuLabel* editorBaseLabel = createMenuLabel(editorBase); | |||||
| // menu->addChild(editorBaseLabel); | |||||
| // } | |||||
| } | |||||
| }; | }; | ||||
| @@ -412,7 +570,8 @@ struct FileChoice : LedDisplayChoice { | |||||
| } | } | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| module->loadScriptDialog(); | |||||
| Menu* menu = createMenu(); | |||||
| module->appendContextMenu(menu); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -437,6 +596,20 @@ struct MessageChoice : LedDisplayChoice { | |||||
| } | } | ||||
| nvgResetScissor(args.vg); | nvgResetScissor(args.vg); | ||||
| } | } | ||||
| void onAction(const event::Action& e) override { | |||||
| Menu* menu = createMenu(); | |||||
| struct SetClipboardMessageItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->setClipboardMessage(); | |||||
| } | |||||
| }; | |||||
| SetClipboardMessageItem* item = createMenuItem<SetClipboardMessageItem>("Copy"); | |||||
| item->module = module; | |||||
| menu->addChild(item); | |||||
| } | |||||
| }; | }; | ||||
| @@ -466,30 +639,6 @@ struct PrototypeDisplay : LedDisplay { | |||||
| }; | }; | ||||
| struct LoadScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->loadScriptDialog(); | |||||
| } | |||||
| }; | |||||
| struct ReloadScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->reloadScript(); | |||||
| } | |||||
| }; | |||||
| struct SaveScriptItem : MenuItem { | |||||
| Prototype* module; | |||||
| void onAction(const event::Action& e) override { | |||||
| module->saveScriptDialog(); | |||||
| } | |||||
| }; | |||||
| struct PrototypeWidget : ModuleWidget { | struct PrototypeWidget : ModuleWidget { | ||||
| PrototypeWidget(Prototype* module) { | PrototypeWidget(Prototype* module) { | ||||
| setModule(module); | setModule(module); | ||||
| @@ -548,26 +697,17 @@ struct PrototypeWidget : ModuleWidget { | |||||
| void appendContextMenu(Menu* menu) override { | void appendContextMenu(Menu* menu) override { | ||||
| Prototype* module = dynamic_cast<Prototype*>(this->module); | Prototype* module = dynamic_cast<Prototype*>(this->module); | ||||
| menu->addChild(new MenuEntry); | |||||
| LoadScriptItem* loadScriptItem = createMenuItem<LoadScriptItem>("Load script"); | |||||
| loadScriptItem->module = module; | |||||
| menu->addChild(loadScriptItem); | |||||
| ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload script"); | |||||
| reloadScriptItem->module = module; | |||||
| menu->addChild(reloadScriptItem); | |||||
| SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("Save script as"); | |||||
| saveScriptItem->module = module; | |||||
| menu->addChild(saveScriptItem); | |||||
| menu->addChild(new MenuSeparator); | |||||
| module->appendContextMenu(menu); | |||||
| } | } | ||||
| void onPathDrop(const event::PathDrop& e) override { | void onPathDrop(const event::PathDrop& e) override { | ||||
| Prototype* module = dynamic_cast<Prototype*>(this->module); | Prototype* module = dynamic_cast<Prototype*>(this->module); | ||||
| if (module && e.paths.size() >= 1) { | |||||
| module->setPath(e.paths[0]); | |||||
| } | |||||
| if (!module) | |||||
| return; | |||||
| if (e.paths.size() < 1) | |||||
| return; | |||||
| module->setPath(e.paths[0]); | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| @@ -586,4 +726,5 @@ void init(Plugin* p) { | |||||
| pluginInstance = p; | pluginInstance = p; | ||||
| p->addModel(createModel<Prototype, PrototypeWidget>("Prototype")); | p->addModel(createModel<Prototype, PrototypeWidget>("Prototype")); | ||||
| settingsLoad(); | |||||
| } | } | ||||