| @@ -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 { | |||
| @@ -115,6 +139,10 @@ struct Prototype : Module { | |||
| configParam(KNOB_PARAMS + i, 0.f, 1.f, 0.5f, string::f("Knob %d", i + 1)); | |||
| for (int i = 0; i < NUM_ROWS; i++) | |||
| 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; | |||
| 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() { | |||
| std::string dir = asset::plugin(pluginInstance, "examples"); | |||
| 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. | |||
| 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 { | |||
| module->loadScriptDialog(); | |||
| Menu* menu = createMenu(); | |||
| module->appendContextMenu(menu); | |||
| } | |||
| }; | |||
| @@ -437,6 +596,20 @@ struct MessageChoice : LedDisplayChoice { | |||
| } | |||
| 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 { | |||
| PrototypeWidget(Prototype* module) { | |||
| setModule(module); | |||
| @@ -548,26 +697,17 @@ struct PrototypeWidget : ModuleWidget { | |||
| void appendContextMenu(Menu* menu) override { | |||
| 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 { | |||
| 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 { | |||
| @@ -586,4 +726,5 @@ void init(Plugin* p) { | |||
| pluginInstance = p; | |||
| p->addModel(createModel<Prototype, PrototypeWidget>("Prototype")); | |||
| settingsLoad(); | |||
| } | |||