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.

997 lines
25KB

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