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.

984 lines
25KB

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