You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1017 lines
26KB

  1. #include <thread>
  2. #include <regex>
  3. #include <osdialog.h>
  4. #include <app/ModuleWidget.hpp>
  5. #include <app/Scene.hpp>
  6. #include <engine/Engine.hpp>
  7. #include <plugin/Plugin.hpp>
  8. #include <app/SvgPanel.hpp>
  9. #include <ui/MenuSeparator.hpp>
  10. #include <system.hpp>
  11. #include <asset.hpp>
  12. #include <helpers.hpp>
  13. #include <context.hpp>
  14. #include <settings.hpp>
  15. #include <history.hpp>
  16. #include <string.hpp>
  17. #include <tag.hpp>
  18. namespace rack {
  19. namespace app {
  20. static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm";
  21. struct ModuleUrlItem : ui::MenuItem {
  22. std::string url;
  23. void onAction(const event::Action& e) override {
  24. std::thread t(system::openBrowser, url);
  25. t.detach();
  26. }
  27. };
  28. struct ModuleFolderItem : ui::MenuItem {
  29. std::string path;
  30. void onAction(const event::Action& e) override {
  31. std::thread t(system::openFolder, path);
  32. t.detach();
  33. }
  34. };
  35. struct ModuleInfoItem : ui::MenuItem {
  36. plugin::Model* model;
  37. ui::Menu* createChildMenu() override {
  38. ui::Menu* menu = new ui::Menu;
  39. // plugin
  40. ModuleUrlItem* pluginItem = new ModuleUrlItem;
  41. pluginItem->text = "Plugin: " + model->plugin->name + " v" + model->plugin->version;
  42. if (model->plugin->pluginUrl != "") {
  43. pluginItem->url = model->plugin->pluginUrl;
  44. }
  45. else {
  46. pluginItem->disabled = true;
  47. }
  48. menu->addChild(pluginItem);
  49. // ui::MenuLabel* versionLabel = new ui::MenuLabel;
  50. // versionLabel->text = "v" + model->plugin->version;
  51. // menu->addChild(versionLabel);
  52. // author
  53. if (model->plugin->author != "") {
  54. ModuleUrlItem* authorItem = new ModuleUrlItem;
  55. authorItem->text = "Author: " + model->plugin->author;
  56. if (model->plugin->authorUrl != "") {
  57. authorItem->url = model->plugin->authorUrl;
  58. }
  59. else {
  60. authorItem->disabled = true;
  61. }
  62. menu->addChild(authorItem);
  63. }
  64. // license
  65. if (model->plugin->license != "") {
  66. ui::MenuLabel* licenseLabel = new ui::MenuLabel;
  67. licenseLabel->text = "License: " + model->plugin->license;
  68. menu->addChild(licenseLabel);
  69. }
  70. // tags
  71. if (!model->tags.empty()) {
  72. ui::MenuLabel* tagsLabel = new ui::MenuLabel;
  73. tagsLabel->text = "Tags:";
  74. menu->addChild(tagsLabel);
  75. for (int tagId : model->tags) {
  76. ui::MenuLabel* tagLabel = new ui::MenuLabel;
  77. tagLabel->text = "• " + tag::getTag(tagId);
  78. menu->addChild(tagLabel);
  79. }
  80. }
  81. menu->addChild(new ui::MenuSeparator);
  82. // library
  83. ModuleUrlItem* libraryItem = new ModuleUrlItem;
  84. libraryItem->text = "VCV Library entry";
  85. libraryItem->url = "https://library.vcvrack.com/" + model->plugin->slug + "/" + model->slug;
  86. menu->addChild(libraryItem);
  87. // manual
  88. std::string manualUrl = (model->manualUrl != "") ? model->manualUrl : model->plugin->manualUrl;
  89. if (manualUrl != "") {
  90. ModuleUrlItem* manualItem = new ModuleUrlItem;
  91. manualItem->text = "User manual";
  92. manualItem->url = manualUrl;
  93. menu->addChild(manualItem);
  94. }
  95. // donate
  96. if (model->plugin->donateUrl != "") {
  97. ModuleUrlItem* donateItem = new ModuleUrlItem;
  98. donateItem->text = "Donate";
  99. donateItem->url = model->plugin->donateUrl;
  100. menu->addChild(donateItem);
  101. }
  102. // changelog
  103. if (model->plugin->changelogUrl != "") {
  104. ModuleUrlItem* changelogItem = new ModuleUrlItem;
  105. changelogItem->text = "Changelog";
  106. changelogItem->url = model->plugin->changelogUrl;
  107. menu->addChild(changelogItem);
  108. }
  109. // source code
  110. if (model->plugin->sourceUrl != "") {
  111. ModuleUrlItem* sourceItem = new ModuleUrlItem;
  112. sourceItem->text = "Source code";
  113. sourceItem->url = model->plugin->sourceUrl;
  114. menu->addChild(sourceItem);
  115. }
  116. // plugin folder
  117. if (model->plugin->path != "") {
  118. ModuleFolderItem* pathItem = new ModuleFolderItem;
  119. pathItem->text = "Open plugin folder";
  120. pathItem->path = model->plugin->path;
  121. menu->addChild(pathItem);
  122. }
  123. return menu;
  124. }
  125. };
  126. struct ModuleDisconnectItem : ui::MenuItem {
  127. ModuleWidget* moduleWidget;
  128. void onAction(const event::Action& e) override {
  129. moduleWidget->disconnectAction();
  130. }
  131. };
  132. struct ModuleResetItem : ui::MenuItem {
  133. ModuleWidget* moduleWidget;
  134. void onAction(const event::Action& e) override {
  135. moduleWidget->resetAction();
  136. }
  137. };
  138. struct ModuleRandomizeItem : ui::MenuItem {
  139. ModuleWidget* moduleWidget;
  140. void onAction(const event::Action& e) override {
  141. moduleWidget->randomizeAction();
  142. }
  143. };
  144. struct ModuleCopyItem : ui::MenuItem {
  145. ModuleWidget* moduleWidget;
  146. void onAction(const event::Action& e) override {
  147. moduleWidget->copyClipboard();
  148. }
  149. };
  150. struct ModulePasteItem : ui::MenuItem {
  151. ModuleWidget* moduleWidget;
  152. void onAction(const event::Action& e) override {
  153. moduleWidget->pasteClipboardAction();
  154. }
  155. };
  156. struct ModuleSaveItem : ui::MenuItem {
  157. ModuleWidget* moduleWidget;
  158. void onAction(const event::Action& e) override {
  159. moduleWidget->saveDialog();
  160. }
  161. };
  162. struct ModuleSaveTemplateItem : ui::MenuItem {
  163. ModuleWidget* moduleWidget;
  164. void onAction(const event::Action& e) override {
  165. moduleWidget->saveTemplate();
  166. }
  167. };
  168. struct ModuleLoadItem : ui::MenuItem {
  169. ModuleWidget* moduleWidget;
  170. void onAction(const event::Action& e) override {
  171. moduleWidget->loadDialog();
  172. }
  173. };
  174. struct ModulePresetPathItem : ui::MenuItem {
  175. ModuleWidget* moduleWidget;
  176. std::string presetPath;
  177. void onAction(const event::Action& e) override {
  178. try {
  179. moduleWidget->loadAction(presetPath);
  180. }
  181. catch (Exception& e) {
  182. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  183. }
  184. }
  185. };
  186. struct ModulePresetItem : ui::MenuItem {
  187. ModuleWidget* moduleWidget;
  188. ui::Menu* createChildMenu() override {
  189. ui::Menu* menu = new ui::Menu;
  190. ModuleCopyItem* copyItem = new ModuleCopyItem;
  191. copyItem->text = "Copy";
  192. copyItem->rightText = RACK_MOD_CTRL_NAME "+C";
  193. copyItem->moduleWidget = moduleWidget;
  194. menu->addChild(copyItem);
  195. ModulePasteItem* pasteItem = new ModulePasteItem;
  196. pasteItem->text = "Paste";
  197. pasteItem->rightText = RACK_MOD_CTRL_NAME "+V";
  198. pasteItem->moduleWidget = moduleWidget;
  199. menu->addChild(pasteItem);
  200. ModuleLoadItem* loadItem = new ModuleLoadItem;
  201. loadItem->text = "Open";
  202. loadItem->moduleWidget = moduleWidget;
  203. menu->addChild(loadItem);
  204. ModuleSaveItem* saveItem = new ModuleSaveItem;
  205. saveItem->text = "Save as";
  206. saveItem->moduleWidget = moduleWidget;
  207. menu->addChild(saveItem);
  208. ModuleSaveTemplateItem* saveTemplateItem = new ModuleSaveTemplateItem;
  209. saveTemplateItem->text = "Save template";
  210. saveTemplateItem->moduleWidget = moduleWidget;
  211. menu->addChild(saveTemplateItem);
  212. // Create ModulePresetPathItems for each patch in a directory.
  213. auto createPresetItems = [&](std::string presetDir) {
  214. bool hasPresets = false;
  215. // Note: This is not cached, so opening this menu each time might have a bit of latency.
  216. for (const std::string& presetPath : system::getEntries(presetDir)) {
  217. std::string presetFilename = string::filename(presetPath);
  218. if (string::filenameExtension(presetFilename) != "vcvm")
  219. continue;
  220. hasPresets = true;
  221. std::string presetName = string::filenameBase(presetFilename);
  222. // Remove "1_", "42_", "001_", etc at the beginning of preset filenames
  223. std::regex r("^\\d*_");
  224. presetName = std::regex_replace(presetName, r, "");
  225. ModulePresetPathItem* presetItem = new ModulePresetPathItem;
  226. presetItem->text = presetName;
  227. presetItem->presetPath = presetPath;
  228. presetItem->moduleWidget = moduleWidget;
  229. menu->addChild(presetItem);
  230. }
  231. if (!hasPresets) {
  232. menu->addChild(createMenuLabel("(None)"));
  233. }
  234. };
  235. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  236. menu->addChild(new ui::MenuSeparator);
  237. menu->addChild(createMenuLabel("User presets"));
  238. createPresetItems(moduleWidget->model->getUserPresetDir());
  239. // Scan `<plugin dir>/presets/<module slug>` for presets.
  240. menu->addChild(new ui::MenuSeparator);
  241. menu->addChild(createMenuLabel("Factory presets"));
  242. createPresetItems(moduleWidget->model->getFactoryPresetDir());
  243. return menu;
  244. }
  245. };
  246. struct ModuleCloneItem : ui::MenuItem {
  247. ModuleWidget* moduleWidget;
  248. void onAction(const event::Action& e) override {
  249. moduleWidget->cloneAction();
  250. }
  251. };
  252. struct ModuleBypassItem : ui::MenuItem {
  253. ModuleWidget* moduleWidget;
  254. void onAction(const event::Action& e) override {
  255. moduleWidget->bypassAction();
  256. }
  257. };
  258. struct ModuleDeleteItem : ui::MenuItem {
  259. ModuleWidget* moduleWidget;
  260. void onAction(const event::Action& e) override {
  261. moduleWidget->removeAction();
  262. }
  263. };
  264. struct ModuleWidget::Internal {
  265. /** The position the user clicked on the module to start dragging in the RackWidget.
  266. */
  267. math::Vec dragPos;
  268. /** The position in the RackWidget when dragging began.
  269. Used for history::ModuleMove.
  270. Set by RackWidget::updateModuleOldPositions() when *any* module begins dragging, since force-dragging can move other modules around.
  271. */
  272. math::Vec oldPos;
  273. widget::Widget* panel = NULL;
  274. };
  275. ModuleWidget::ModuleWidget() {
  276. internal = new Internal;
  277. box.size = math::Vec(0, RACK_GRID_HEIGHT);
  278. }
  279. ModuleWidget::~ModuleWidget() {
  280. clearChildren();
  281. setModule(NULL);
  282. delete internal;
  283. }
  284. void ModuleWidget::draw(const DrawArgs& args) {
  285. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  286. if (module && module->bypassed()) {
  287. nvgGlobalAlpha(args.vg, 0.33);
  288. }
  289. Widget::draw(args);
  290. // Power meter
  291. if (module && settings::cpuMeter) {
  292. nvgBeginPath(args.vg);
  293. nvgRect(args.vg,
  294. 0, box.size.y - 35,
  295. 65, 35);
  296. nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  297. nvgFill(args.vg);
  298. float percent = module->cpuTime() * APP->engine->getSampleRate() * 100;
  299. float microseconds = module->cpuTime() * 1e6f;
  300. std::string cpuText = string::f("%.1f%%\n%.2f μs", percent, microseconds);
  301. bndLabel(args.vg, 2.0, box.size.y - 34.0, INFINITY, INFINITY, -1, cpuText.c_str());
  302. float p = math::clamp(module->cpuTime() / APP->engine->getSampleTime(), 0.f, 1.f);
  303. nvgBeginPath(args.vg);
  304. nvgRect(args.vg,
  305. 0, (1.f - p) * box.size.y,
  306. 5, p * box.size.y);
  307. nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 1.0));
  308. nvgFill(args.vg);
  309. }
  310. // if (module) {
  311. // nvgBeginPath(args.vg);
  312. // nvgRect(args.vg, 0, 0, 20, 20);
  313. // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  314. // nvgFill(args.vg);
  315. // std::string debugText = string::f("%d", module->id);
  316. // bndLabel(args.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str());
  317. // }
  318. nvgResetScissor(args.vg);
  319. }
  320. void ModuleWidget::drawShadow(const DrawArgs& args) {
  321. nvgBeginPath(args.vg);
  322. float r = 20; // Blur radius
  323. float c = 20; // Corner radius
  324. math::Vec b = math::Vec(-10, 30); // Offset from each corner
  325. nvgRect(args.vg, b.x - r, b.y - r, box.size.x - 2 * b.x + 2 * r, box.size.y - 2 * b.y + 2 * r);
  326. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  327. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  328. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, b.x, b.y, box.size.x - 2 * b.x, box.size.y - 2 * b.y, c, r, shadowColor, transparentColor));
  329. nvgFill(args.vg);
  330. }
  331. void ModuleWidget::onButton(const event::Button& e) {
  332. // Don't consume left button if `lockModules` is enabled.
  333. if (settings::lockModules)
  334. Widget::onButton(e);
  335. else
  336. OpaqueWidget::onButton(e);
  337. if (e.isConsumed())
  338. return;
  339. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  340. createContextMenu();
  341. e.consume(this);
  342. }
  343. }
  344. void ModuleWidget::onHoverKey(const event::HoverKey& e) {
  345. OpaqueWidget::onHoverKey(e);
  346. if (e.isConsumed())
  347. return;
  348. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  349. if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  350. resetAction();
  351. e.consume(this);
  352. }
  353. if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  354. randomizeAction();
  355. e.consume(this);
  356. }
  357. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  358. copyClipboard();
  359. e.consume(this);
  360. }
  361. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  362. pasteClipboardAction();
  363. e.consume(this);
  364. }
  365. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  366. cloneAction();
  367. e.consume(this);
  368. }
  369. if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  370. disconnectAction();
  371. e.consume(this);
  372. }
  373. if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  374. bypassAction();
  375. e.consume(this);
  376. }
  377. }
  378. if (e.action == RACK_HELD) {
  379. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  380. removeAction();
  381. e.consume(NULL);
  382. }
  383. }
  384. }
  385. void ModuleWidget::onDragStart(const event::DragStart& e) {
  386. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  387. internal->dragPos = APP->scene->rack->mousePos.minus(box.pos);
  388. APP->scene->rack->updateModuleOldPositions();
  389. }
  390. }
  391. void ModuleWidget::onDragEnd(const event::DragEnd& e) {
  392. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  393. history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
  394. if (!h)
  395. return;
  396. APP->history->push(h);
  397. }
  398. }
  399. void ModuleWidget::onDragMove(const event::DragMove& e) {
  400. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  401. if (!settings::lockModules) {
  402. math::Vec pos = APP->scene->rack->mousePos.minus(internal->dragPos);
  403. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  404. APP->scene->rack->setModulePosForce(this, pos);
  405. else
  406. APP->scene->rack->setModulePosNearest(this, pos);
  407. }
  408. }
  409. }
  410. void ModuleWidget::setModule(engine::Module* module) {
  411. if (this->module) {
  412. APP->engine->removeModule(this->module);
  413. delete this->module;
  414. this->module = NULL;
  415. }
  416. this->module = module;
  417. }
  418. void ModuleWidget::setPanel(widget::Widget* panel) {
  419. // Remove existing panel
  420. if (internal->panel) {
  421. removeChild(internal->panel);
  422. delete internal->panel;
  423. internal->panel = NULL;
  424. }
  425. if (panel) {
  426. addChildBottom(panel);
  427. internal->panel = panel;
  428. box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  429. }
  430. }
  431. void ModuleWidget::setPanel(std::shared_ptr<Svg> svg) {
  432. // Create SvgPanel
  433. SvgPanel* panel = new SvgPanel;
  434. panel->setBackground(svg);
  435. setPanel(panel);
  436. }
  437. void ModuleWidget::addParam(ParamWidget* param) {
  438. addChild(param);
  439. }
  440. void ModuleWidget::addInput(PortWidget* input) {
  441. // Check that the port is an input
  442. assert(input->type == engine::Port::INPUT);
  443. // Check that the port doesn't have a duplicate ID
  444. PortWidget* input2 = getInput(input->portId);
  445. assert(!input2);
  446. // Add port
  447. addChild(input);
  448. }
  449. void ModuleWidget::addOutput(PortWidget* output) {
  450. // Check that the port is an output
  451. assert(output->type == engine::Port::OUTPUT);
  452. // Check that the port doesn't have a duplicate ID
  453. PortWidget* output2 = getOutput(output->portId);
  454. assert(!output2);
  455. // Add port
  456. addChild(output);
  457. }
  458. template <class T, typename F>
  459. T* getFirstDescendantOfTypeWithCondition(widget::Widget* w, F f) {
  460. T* t = dynamic_cast<T*>(w);
  461. if (t && f(t))
  462. return t;
  463. for (widget::Widget* child : w->children) {
  464. T* foundT = getFirstDescendantOfTypeWithCondition<T>(child, f);
  465. if (foundT)
  466. return foundT;
  467. }
  468. return NULL;
  469. }
  470. ParamWidget* ModuleWidget::getParam(int paramId) {
  471. return getFirstDescendantOfTypeWithCondition<ParamWidget>(this, [&](ParamWidget* pw) -> bool {
  472. return pw->paramId == paramId;
  473. });
  474. }
  475. PortWidget* ModuleWidget::getInput(int portId) {
  476. return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
  477. return pw->type == engine::Port::INPUT && pw->portId == portId;
  478. });
  479. }
  480. PortWidget* ModuleWidget::getOutput(int portId) {
  481. return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
  482. return pw->type == engine::Port::OUTPUT && pw->portId == portId;
  483. });
  484. }
  485. json_t* ModuleWidget::toJson() {
  486. json_t* moduleJ = APP->engine->moduleToJson(module);
  487. // When serializing ModuleWidget, don't include the ID. This ID is only meaningful when serializing the entire rack.
  488. json_object_del(moduleJ, "id");
  489. return moduleJ;
  490. }
  491. void ModuleWidget::fromJson(json_t* rootJ) {
  492. APP->engine->moduleFromJson(module, rootJ);
  493. }
  494. void ModuleWidget::copyClipboard() {
  495. json_t* moduleJ = toJson();
  496. DEFER({
  497. json_decref(moduleJ);
  498. });
  499. char* moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  500. DEFER({
  501. free(moduleJson);
  502. });
  503. glfwSetClipboardString(APP->window->win, moduleJson);
  504. }
  505. void ModuleWidget::pasteClipboardAction() {
  506. const char* moduleJson = glfwGetClipboardString(APP->window->win);
  507. if (!moduleJson) {
  508. WARN("Could not get text from clipboard.");
  509. return;
  510. }
  511. json_error_t error;
  512. json_t* moduleJ = json_loads(moduleJson, 0, &error);
  513. if (!moduleJ) {
  514. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  515. return;
  516. }
  517. DEFER({
  518. json_decref(moduleJ);
  519. });
  520. // history::ModuleChange
  521. history::ModuleChange* h = new history::ModuleChange;
  522. h->name = "paste module preset";
  523. h->moduleId = module->id;
  524. h->oldModuleJ = toJson();
  525. fromJson(moduleJ);
  526. h->newModuleJ = toJson();
  527. APP->history->push(h);
  528. }
  529. void ModuleWidget::load(std::string filename) {
  530. FILE* file = std::fopen(filename.c_str(), "r");
  531. if (!file)
  532. throw Exception(string::f("Could not load patch file %s", filename.c_str()));
  533. DEFER({
  534. std::fclose(file);
  535. });
  536. INFO("Loading preset %s", filename.c_str());
  537. json_error_t error;
  538. json_t* moduleJ = json_loadf(file, 0, &error);
  539. if (!moduleJ)
  540. throw Exception(string::f("File is not a valid patch file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text));
  541. DEFER({
  542. json_decref(moduleJ);
  543. });
  544. fromJson(moduleJ);
  545. }
  546. void ModuleWidget::loadAction(std::string filename) {
  547. // history::ModuleChange
  548. history::ModuleChange* h = new history::ModuleChange;
  549. h->name = "load module preset";
  550. h->moduleId = module->id;
  551. h->oldModuleJ = toJson();
  552. try {
  553. load(filename);
  554. }
  555. catch (Exception& e) {
  556. delete h;
  557. throw;
  558. }
  559. h->newModuleJ = toJson();
  560. APP->history->push(h);
  561. }
  562. void ModuleWidget::loadTemplate() {
  563. std::string templatePath = model->getUserPresetDir() + "/" + "template.vcvm";
  564. try {
  565. load(templatePath);
  566. }
  567. catch (Exception& e) {
  568. // Do nothing
  569. }
  570. }
  571. void ModuleWidget::loadDialog() {
  572. std::string presetDir = model->getUserPresetDir();
  573. system::createDirectories(presetDir);
  574. // Delete directories if empty
  575. DEFER({
  576. system::removeDirectories(presetDir);
  577. });
  578. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  579. DEFER({
  580. osdialog_filters_free(filters);
  581. });
  582. char* path = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters);
  583. if (!path) {
  584. // No path selected
  585. return;
  586. }
  587. DEFER({
  588. free(path);
  589. });
  590. try {
  591. loadAction(path);
  592. }
  593. catch (Exception& e) {
  594. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  595. }
  596. }
  597. void ModuleWidget::save(std::string filename) {
  598. INFO("Saving preset %s", filename.c_str());
  599. json_t* moduleJ = toJson();
  600. assert(moduleJ);
  601. DEFER({
  602. json_decref(moduleJ);
  603. });
  604. FILE* file = fopen(filename.c_str(), "w");
  605. if (!file) {
  606. WARN("Could not write to patch file %s", filename.c_str());
  607. }
  608. DEFER({
  609. fclose(file);
  610. });
  611. json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  612. }
  613. void ModuleWidget::saveTemplate() {
  614. std::string presetDir = model->getUserPresetDir();
  615. system::createDirectories(presetDir);
  616. std::string templatePath = presetDir + "/" + "template.vcvm";
  617. save(templatePath);
  618. }
  619. void ModuleWidget::saveDialog() {
  620. std::string presetDir = model->getUserPresetDir();
  621. system::createDirectories(presetDir);
  622. // Delete directories if empty
  623. DEFER({
  624. system::removeDirectories(presetDir);
  625. });
  626. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  627. DEFER({
  628. osdialog_filters_free(filters);
  629. });
  630. char* path = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters);
  631. if (!path) {
  632. // No path selected
  633. return;
  634. }
  635. DEFER({
  636. free(path);
  637. });
  638. std::string pathStr = path;
  639. std::string extension = string::filenameExtension(string::filename(pathStr));
  640. if (extension == "") {
  641. pathStr += ".vcvm";
  642. }
  643. save(pathStr);
  644. }
  645. template <class T, typename F>
  646. void doOfType(widget::Widget* w, F f) {
  647. T* t = dynamic_cast<T*>(w);
  648. if (t)
  649. f(t);
  650. for (widget::Widget* child : w->children) {
  651. doOfType<T>(child, f);
  652. }
  653. }
  654. void ModuleWidget::disconnect() {
  655. doOfType<PortWidget>(this, [&](PortWidget* pw) {
  656. APP->scene->rack->clearCablesOnPort(pw);
  657. });
  658. }
  659. void ModuleWidget::resetAction() {
  660. assert(module);
  661. // history::ModuleChange
  662. history::ModuleChange* h = new history::ModuleChange;
  663. h->name = "reset module";
  664. h->moduleId = module->id;
  665. h->oldModuleJ = toJson();
  666. APP->engine->resetModule(module);
  667. h->newModuleJ = toJson();
  668. APP->history->push(h);
  669. }
  670. void ModuleWidget::randomizeAction() {
  671. assert(module);
  672. // history::ModuleChange
  673. history::ModuleChange* h = new history::ModuleChange;
  674. h->name = "randomize module";
  675. h->moduleId = module->id;
  676. h->oldModuleJ = toJson();
  677. APP->engine->randomizeModule(module);
  678. h->newModuleJ = toJson();
  679. APP->history->push(h);
  680. }
  681. static void disconnectActions(ModuleWidget* mw, history::ComplexAction* complexAction) {
  682. // Add CableRemove action for all cables
  683. doOfType<PortWidget>(mw, [&](PortWidget* pw) {
  684. for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) {
  685. if (!cw->isComplete())
  686. continue;
  687. // Avoid creating duplicate actions for self-patched cables
  688. if (pw->type == engine::Port::INPUT && cw->outputPort->module == mw->module)
  689. continue;
  690. // history::CableRemove
  691. history::CableRemove* h = new history::CableRemove;
  692. h->setCable(cw);
  693. complexAction->push(h);
  694. }
  695. });
  696. }
  697. void ModuleWidget::disconnectAction() {
  698. history::ComplexAction* complexAction = new history::ComplexAction;
  699. complexAction->name = "disconnect cables";
  700. disconnectActions(this, complexAction);
  701. APP->history->push(complexAction);
  702. disconnect();
  703. }
  704. void ModuleWidget::cloneAction() {
  705. // history::ComplexAction
  706. history::ComplexAction* h = new history::ComplexAction;
  707. // Clone Module
  708. engine::Module* clonedModule = model->createModule();
  709. // JSON serialization is the obvious way to do this
  710. json_t* moduleJ = toJson();
  711. // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
  712. clonedModule->fromJson(moduleJ);
  713. json_decref(moduleJ);
  714. // Reset ID so the Engine automatically assigns a new one
  715. clonedModule->id = -1;
  716. APP->engine->addModule(clonedModule);
  717. // Clone ModuleWidget
  718. ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
  719. APP->scene->rack->addModuleAtMouse(clonedModuleWidget);
  720. // history::ModuleAdd
  721. history::ModuleAdd* hma = new history::ModuleAdd;
  722. hma->name = "clone modules";
  723. hma->setModule(clonedModuleWidget);
  724. h->push(hma);
  725. // Clone cables attached to input ports
  726. doOfType<PortWidget>(this, [&](PortWidget* pw) {
  727. if (pw->type != engine::Port::INPUT)
  728. return;
  729. std::list<CableWidget*> cables = APP->scene->rack->getCablesOnPort(pw);
  730. for (CableWidget* cw : cables) {
  731. // Create cable attached to cloned ModuleWidget's input
  732. engine::Cable* clonedCable = new engine::Cable;
  733. clonedCable->id = -1;
  734. clonedCable->inputModule = clonedModule;
  735. clonedCable->inputId = cw->cable->inputId;
  736. // If cable is self-patched, attach to cloned module instead
  737. if (cw->cable->outputModule == module)
  738. clonedCable->outputModule = clonedModule;
  739. else
  740. clonedCable->outputModule = cw->cable->outputModule;
  741. clonedCable->outputId = cw->cable->outputId;
  742. APP->engine->addCable(clonedCable);
  743. app::CableWidget* clonedCw = new app::CableWidget;
  744. clonedCw->setCable(clonedCable);
  745. clonedCw->color = cw->color;
  746. APP->scene->rack->addCable(clonedCw);
  747. // history::CableAdd
  748. history::CableAdd* hca = new history::CableAdd;
  749. hca->setCable(clonedCw);
  750. h->push(hca);
  751. }
  752. });
  753. APP->history->push(h);
  754. }
  755. void ModuleWidget::bypassAction() {
  756. assert(module);
  757. APP->engine->bypassModule(module, !module->bypassed());
  758. // history::ModuleBypass
  759. history::ModuleBypass* h = new history::ModuleBypass;
  760. h->moduleId = module->id;
  761. h->bypassed = module->bypassed();
  762. APP->history->push(h);
  763. }
  764. void ModuleWidget::removeAction() {
  765. history::ComplexAction* complexAction = new history::ComplexAction;
  766. complexAction->name = "remove module";
  767. disconnectActions(this, complexAction);
  768. // history::ModuleRemove
  769. history::ModuleRemove* moduleRemove = new history::ModuleRemove;
  770. moduleRemove->setModule(this);
  771. complexAction->push(moduleRemove);
  772. APP->history->push(complexAction);
  773. // This disconnects cables, removes the module, and transfers ownership to caller
  774. APP->scene->rack->removeModule(this);
  775. delete this;
  776. }
  777. void ModuleWidget::createContextMenu() {
  778. ui::Menu* menu = createMenu();
  779. assert(model);
  780. ui::MenuLabel* modelLabel = new ui::MenuLabel;
  781. modelLabel->text = model->plugin->brand + " " + model->name;
  782. menu->addChild(modelLabel);
  783. ModuleInfoItem* infoItem = new ModuleInfoItem;
  784. infoItem->text = "Info";
  785. infoItem->rightText = RIGHT_ARROW;
  786. infoItem->model = model;
  787. menu->addChild(infoItem);
  788. ModulePresetItem* presetsItem = new ModulePresetItem;
  789. presetsItem->text = "Preset";
  790. presetsItem->rightText = RIGHT_ARROW;
  791. presetsItem->moduleWidget = this;
  792. menu->addChild(presetsItem);
  793. ModuleResetItem* resetItem = new ModuleResetItem;
  794. resetItem->text = "Initialize";
  795. resetItem->rightText = RACK_MOD_CTRL_NAME "+I";
  796. resetItem->moduleWidget = this;
  797. menu->addChild(resetItem);
  798. ModuleRandomizeItem* randomizeItem = new ModuleRandomizeItem;
  799. randomizeItem->text = "Randomize";
  800. randomizeItem->rightText = RACK_MOD_CTRL_NAME "+R";
  801. randomizeItem->moduleWidget = this;
  802. menu->addChild(randomizeItem);
  803. ModuleDisconnectItem* disconnectItem = new ModuleDisconnectItem;
  804. disconnectItem->text = "Disconnect cables";
  805. disconnectItem->rightText = RACK_MOD_CTRL_NAME "+U";
  806. disconnectItem->moduleWidget = this;
  807. menu->addChild(disconnectItem);
  808. ModuleCloneItem* cloneItem = new ModuleCloneItem;
  809. cloneItem->text = "Duplicate";
  810. cloneItem->rightText = RACK_MOD_CTRL_NAME "+D";
  811. cloneItem->moduleWidget = this;
  812. menu->addChild(cloneItem);
  813. ModuleBypassItem* bypassItem = new ModuleBypassItem;
  814. bypassItem->text = "Bypass";
  815. bypassItem->rightText = RACK_MOD_CTRL_NAME "+E";
  816. if (module && module->bypassed())
  817. bypassItem->rightText = CHECKMARK_STRING " " + bypassItem->rightText;
  818. bypassItem->moduleWidget = this;
  819. menu->addChild(bypassItem);
  820. ModuleDeleteItem* deleteItem = new ModuleDeleteItem;
  821. deleteItem->text = "Delete";
  822. deleteItem->rightText = "Backspace/Delete";
  823. deleteItem->moduleWidget = this;
  824. menu->addChild(deleteItem);
  825. appendContextMenu(menu);
  826. }
  827. math::Vec& ModuleWidget::dragPos() {
  828. return internal->dragPos;
  829. }
  830. math::Vec& ModuleWidget::oldPos() {
  831. return internal->oldPos;
  832. }
  833. engine::Module* ModuleWidget::releaseModule() {
  834. engine::Module* module = this->module;
  835. this->module = NULL;
  836. return module;
  837. }
  838. } // namespace app
  839. } // namespace rack