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.

1180 lines
31KB

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