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.

1106 lines
29KB

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