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.

1162 lines
30KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. /**
  18. * This file is an edited version of VCVRack's ModuleWidget.cpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include "../../CardinalCommon.hpp"
  27. #include <thread>
  28. #include <regex>
  29. #include <osdialog.h>
  30. #include <app/ModuleWidget.hpp>
  31. #include <app/Scene.hpp>
  32. #include <engine/Engine.hpp>
  33. #include <plugin/Plugin.hpp>
  34. #include <app/SvgPanel.hpp>
  35. #include <ui/MenuSeparator.hpp>
  36. #include <system.hpp>
  37. #include <asset.hpp>
  38. #include <helpers.hpp>
  39. #include <context.hpp>
  40. #include <settings.hpp>
  41. #include <history.hpp>
  42. #include <string.hpp>
  43. #include <componentlibrary.hpp>
  44. namespace rack {
  45. namespace app {
  46. static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm";
  47. struct ModuleWidget::Internal {
  48. /** The module position clicked on to start dragging in the rack.
  49. */
  50. math::Vec dragOffset;
  51. /** Global rack position the user clicked on.
  52. */
  53. math::Vec dragRackPos;
  54. bool dragEnabled = true;
  55. widget::Widget* panel = NULL;
  56. };
  57. ModuleWidget::ModuleWidget() {
  58. internal = new Internal;
  59. box.size = math::Vec(0, RACK_GRID_HEIGHT);
  60. }
  61. ModuleWidget::~ModuleWidget() {
  62. clearChildren();
  63. setModule(NULL);
  64. delete internal;
  65. }
  66. plugin::Model* ModuleWidget::getModel() {
  67. return model;
  68. }
  69. void ModuleWidget::setModel(plugin::Model* model) {
  70. assert(!this->model);
  71. this->model = model;
  72. }
  73. engine::Module* ModuleWidget::getModule() {
  74. return module;
  75. }
  76. void ModuleWidget::setModule(engine::Module* module) {
  77. if (this->module) {
  78. APP->engine->removeModule(this->module);
  79. delete this->module;
  80. this->module = NULL;
  81. }
  82. this->module = module;
  83. }
  84. widget::Widget* ModuleWidget::getPanel() {
  85. return internal->panel;
  86. }
  87. void ModuleWidget::setPanel(widget::Widget* panel) {
  88. // Remove existing panel
  89. if (internal->panel) {
  90. removeChild(internal->panel);
  91. delete internal->panel;
  92. internal->panel = NULL;
  93. }
  94. if (panel) {
  95. addChildBottom(panel);
  96. internal->panel = panel;
  97. box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  98. // If width is zero, set it to 12HP for sanity
  99. if (box.size.x == 0.0)
  100. box.size.x = 12 * RACK_GRID_WIDTH;
  101. }
  102. }
  103. void ModuleWidget::setPanel(std::shared_ptr<window::Svg> svg) {
  104. // Create SvgPanel
  105. SvgPanel* panel = new SvgPanel;
  106. panel->setBackground(svg);
  107. setPanel(panel);
  108. }
  109. void ModuleWidget::addParam(ParamWidget* param) {
  110. addChild(param);
  111. }
  112. void ModuleWidget::addInput(PortWidget* input) {
  113. // Check that the port is an input
  114. assert(input->type == engine::Port::INPUT);
  115. // Check that the port doesn't have a duplicate ID
  116. PortWidget* input2 = getInput(input->portId);
  117. assert(!input2);
  118. // Add port
  119. addChild(input);
  120. }
  121. void ModuleWidget::addOutput(PortWidget* output) {
  122. // Check that the port is an output
  123. assert(output->type == engine::Port::OUTPUT);
  124. // Check that the port doesn't have a duplicate ID
  125. PortWidget* output2 = getOutput(output->portId);
  126. assert(!output2);
  127. // Add port
  128. addChild(output);
  129. }
  130. template <class T, typename F>
  131. T* getFirstDescendantOfTypeWithCondition(widget::Widget* w, F f) {
  132. T* t = dynamic_cast<T*>(w);
  133. if (t && f(t))
  134. return t;
  135. for (widget::Widget* child : w->children) {
  136. T* foundT = getFirstDescendantOfTypeWithCondition<T>(child, f);
  137. if (foundT)
  138. return foundT;
  139. }
  140. return NULL;
  141. }
  142. ParamWidget* ModuleWidget::getParam(int paramId) {
  143. return getFirstDescendantOfTypeWithCondition<ParamWidget>(this, [&](ParamWidget* pw) -> bool {
  144. return pw->paramId == paramId;
  145. });
  146. }
  147. PortWidget* ModuleWidget::getInput(int portId) {
  148. return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
  149. return pw->type == engine::Port::INPUT && pw->portId == portId;
  150. });
  151. }
  152. PortWidget* ModuleWidget::getOutput(int portId) {
  153. return getFirstDescendantOfTypeWithCondition<PortWidget>(this, [&](PortWidget* pw) -> bool {
  154. return pw->type == engine::Port::OUTPUT && pw->portId == portId;
  155. });
  156. }
  157. template <class T, typename F>
  158. void doIfTypeRecursive(widget::Widget* w, F f) {
  159. T* t = dynamic_cast<T*>(w);
  160. if (t)
  161. f(t);
  162. for (widget::Widget* child : w->children) {
  163. doIfTypeRecursive<T>(child, f);
  164. }
  165. }
  166. std::vector<ParamWidget*> ModuleWidget::getParams() {
  167. std::vector<ParamWidget*> pws;
  168. doIfTypeRecursive<ParamWidget>(this, [&](ParamWidget* pw) {
  169. pws.push_back(pw);
  170. });
  171. return pws;
  172. }
  173. std::vector<PortWidget*> ModuleWidget::getPorts() {
  174. std::vector<PortWidget*> pws;
  175. doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
  176. pws.push_back(pw);
  177. });
  178. return pws;
  179. }
  180. std::vector<PortWidget*> ModuleWidget::getInputs() {
  181. std::vector<PortWidget*> pws;
  182. doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
  183. if (pw->type == engine::Port::INPUT)
  184. pws.push_back(pw);
  185. });
  186. return pws;
  187. }
  188. std::vector<PortWidget*> ModuleWidget::getOutputs() {
  189. std::vector<PortWidget*> pws;
  190. doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
  191. if (pw->type == engine::Port::OUTPUT)
  192. pws.push_back(pw);
  193. });
  194. return pws;
  195. }
  196. void ModuleWidget::draw(const DrawArgs& args) {
  197. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  198. if (module && module->isBypassed()) {
  199. nvgAlpha(args.vg, 0.33);
  200. }
  201. Widget::draw(args);
  202. // Meter
  203. if (module && settings::cpuMeter) {
  204. float sampleRate = APP->engine->getSampleRate();
  205. const float* meterBuffer = module->meterBuffer();
  206. int meterLength = module->meterLength();
  207. int meterIndex = module->meterIndex();
  208. // // Text background
  209. // nvgBeginPath(args.vg);
  210. // nvgRect(args.vg, 0.0, box.size.y - infoHeight, box.size.x, infoHeight);
  211. // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  212. // nvgFill(args.vg);
  213. // Draw time plot
  214. const float plotHeight = box.size.y - BND_WIDGET_HEIGHT;
  215. nvgBeginPath(args.vg);
  216. nvgMoveTo(args.vg, 0.0, plotHeight);
  217. math::Vec p1;
  218. for (int i = 0; i < meterLength; i++) {
  219. int index = math::eucMod(meterIndex + i + 1, meterLength);
  220. float meter = math::clamp(meterBuffer[index] * sampleRate, 0.f, 1.f);
  221. meter = std::max(0.f, meter);
  222. math::Vec p;
  223. p.x = (float) i / (meterLength - 1) * box.size.x;
  224. p.y = (1.f - meter) * plotHeight;
  225. if (i == 0) {
  226. nvgLineTo(args.vg, VEC_ARGS(p));
  227. }
  228. else {
  229. math::Vec p2 = p;
  230. p2.x -= 0.5f / (meterLength - 1) * box.size.x;
  231. nvgBezierTo(args.vg, VEC_ARGS(p1), VEC_ARGS(p2), VEC_ARGS(p));
  232. }
  233. p1 = p;
  234. p1.x += 0.5f / (meterLength - 1) * box.size.x;
  235. }
  236. nvgLineTo(args.vg, box.size.x, plotHeight);
  237. nvgClosePath(args.vg);
  238. NVGcolor color = componentlibrary::SCHEME_ORANGE;
  239. nvgFillColor(args.vg, color::alpha(color, 0.75));
  240. nvgFill(args.vg);
  241. nvgStrokeWidth(args.vg, 2.0);
  242. nvgStrokeColor(args.vg, color);
  243. nvgStroke(args.vg);
  244. // Text background
  245. bndMenuBackground(args.vg, 0.0, plotHeight, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL);
  246. // Text
  247. float percent = meterBuffer[meterIndex] * sampleRate * 100.f;
  248. // float microseconds = meterBuffer[meterIndex] * 1e6f;
  249. std::string meterText = string::f("%.1f", percent);
  250. // Only append "%" if wider than 2 HP
  251. if (box.getWidth() > RACK_GRID_WIDTH * 2)
  252. meterText += "%";
  253. math::Vec pt;
  254. pt.x = box.size.x - bndLabelWidth(args.vg, -1, meterText.c_str()) + 3;
  255. pt.y = plotHeight + 0.5;
  256. bndMenuLabel(args.vg, VEC_ARGS(pt), INFINITY, BND_WIDGET_HEIGHT, -1, meterText.c_str());
  257. }
  258. // Selection
  259. if (APP->scene->rack->isSelected(this)) {
  260. nvgBeginPath(args.vg);
  261. nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size));
  262. nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25));
  263. nvgFill(args.vg);
  264. nvgStrokeWidth(args.vg, 2.0);
  265. nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5));
  266. nvgStroke(args.vg);
  267. }
  268. nvgResetScissor(args.vg);
  269. }
  270. void ModuleWidget::drawLayer(const DrawArgs& args, int layer) {
  271. if (layer == -1) {
  272. nvgBeginPath(args.vg);
  273. float r = 20; // Blur radius
  274. float c = 20; // Corner radius
  275. math::Rect shadowBox = box.zeroPos().grow(math::Vec(10, -30));
  276. math::Rect shadowOutsideBox = shadowBox.grow(math::Vec(r, r));
  277. nvgRect(args.vg, RECT_ARGS(shadowOutsideBox));
  278. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  279. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  280. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, RECT_ARGS(shadowBox), c, r, shadowColor, transparentColor));
  281. nvgFill(args.vg);
  282. }
  283. else {
  284. Widget::drawLayer(args, layer);
  285. }
  286. }
  287. void ModuleWidget::onHover(const HoverEvent& e) {
  288. if (APP->scene->rack->isSelected(this)) {
  289. e.consume(this);
  290. }
  291. OpaqueWidget::onHover(e);
  292. }
  293. void ModuleWidget::onHoverKey(const HoverKeyEvent& e) {
  294. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  295. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  296. copyClipboard();
  297. e.consume(this);
  298. }
  299. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  300. if (pasteClipboardAction()) {
  301. e.consume(this);
  302. }
  303. }
  304. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  305. cloneAction(false);
  306. e.consume(this);
  307. }
  308. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  309. cloneAction(true);
  310. e.consume(this);
  311. }
  312. if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  313. resetAction();
  314. e.consume(this);
  315. }
  316. if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  317. randomizeAction();
  318. e.consume(this);
  319. }
  320. if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  321. disconnectAction();
  322. e.consume(this);
  323. }
  324. if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  325. bypassAction(!module->isBypassed());
  326. e.consume(this);
  327. }
  328. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  329. // Deletes `this`
  330. removeAction();
  331. e.consume(NULL);
  332. return;
  333. }
  334. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  335. std::string manualUrl = model->getManualUrl();
  336. if (!manualUrl.empty())
  337. system::openBrowser(manualUrl);
  338. e.consume(this);
  339. }
  340. }
  341. if (e.isConsumed())
  342. return;
  343. OpaqueWidget::onHoverKey(e);
  344. }
  345. void ModuleWidget::onButton(const ButtonEvent& e) {
  346. bool selected = APP->scene->rack->isSelected(this);
  347. if (selected) {
  348. if (e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  349. if (e.action == GLFW_PRESS) {
  350. // Open selection context menu on right-click
  351. ui::Menu* menu = createMenu();
  352. patchUtils::appendSelectionContextMenu(menu);
  353. }
  354. e.consume(this);
  355. }
  356. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  357. if (e.action == GLFW_PRESS) {
  358. // Toggle selection on Shift-click
  359. if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  360. APP->scene->rack->select(this, false);
  361. e.consume(NULL);
  362. return;
  363. }
  364. // If module positions are locked, don't consume left-click
  365. if (settings::lockModules) {
  366. return;
  367. }
  368. internal->dragOffset = e.pos;
  369. }
  370. e.consume(this);
  371. }
  372. return;
  373. }
  374. // Dispatch event to children
  375. Widget::onButton(e);
  376. e.stopPropagating();
  377. if (e.isConsumed())
  378. return;
  379. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  380. if (e.action == GLFW_PRESS) {
  381. // Toggle selection on Shift-click
  382. if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  383. APP->scene->rack->select(this, true);
  384. e.consume(NULL);
  385. return;
  386. }
  387. // If module positions are locked, don't consume left-click
  388. if (settings::lockModules) {
  389. return;
  390. }
  391. internal->dragOffset = e.pos;
  392. }
  393. e.consume(this);
  394. }
  395. // Open context menu on right-click
  396. if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
  397. createContextMenu();
  398. e.consume(this);
  399. }
  400. }
  401. void ModuleWidget::onDragStart(const DragStartEvent& e) {
  402. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  403. // HACK Disable FramebufferWidget redrawing subpixels while dragging
  404. APP->window->fbDirtyOnSubpixelChange() = false;
  405. // Clear dragRack so dragging in not enabled until mouse is moved a bit.
  406. internal->dragRackPos = math::Vec(NAN, NAN);
  407. // Prepare initial position of modules for history.
  408. APP->scene->rack->updateModuleOldPositions();
  409. }
  410. }
  411. void ModuleWidget::onDragEnd(const DragEndEvent& e) {
  412. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  413. APP->window->fbDirtyOnSubpixelChange() = true;
  414. // The next time the module is dragged, it should always move immediately
  415. internal->dragEnabled = true;
  416. history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
  417. if (!h->isEmpty())
  418. APP->history->push(h);
  419. else
  420. delete h;
  421. }
  422. }
  423. void ModuleWidget::onDragMove(const DragMoveEvent& e) {
  424. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  425. math::Vec mousePos = APP->scene->rack->getMousePos();
  426. if (!internal->dragEnabled) {
  427. // Set dragRackPos on the first time after dragging
  428. if (!internal->dragRackPos.isFinite())
  429. internal->dragRackPos = mousePos;
  430. // Check if the mouse has moved enough to start dragging the module.
  431. const float minDist = RACK_GRID_WIDTH;
  432. if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2))
  433. internal->dragEnabled = true;
  434. }
  435. // Move module
  436. if (internal->dragEnabled) {
  437. // Round y coordinate to nearest rack height
  438. math::Vec pos = mousePos;
  439. pos.x -= internal->dragOffset.x;
  440. pos.y -= RACK_GRID_HEIGHT / 2;
  441. if (APP->scene->rack->isSelected(this)) {
  442. pos = (pos / RACK_GRID_SIZE).round() * RACK_GRID_SIZE;
  443. math::Vec delta = pos.minus(box.pos);
  444. APP->scene->rack->setSelectionPosNearest(delta);
  445. }
  446. else {
  447. if (settings::squeezeModules) {
  448. APP->scene->rack->setModulePosSqueeze(this, pos);
  449. }
  450. else {
  451. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  452. APP->scene->rack->setModulePosForce(this, pos);
  453. else
  454. APP->scene->rack->setModulePosNearest(this, pos);
  455. }
  456. }
  457. }
  458. }
  459. }
  460. void ModuleWidget::onDragHover(const DragHoverEvent& e) {
  461. if (APP->scene->rack->isSelected(this)) {
  462. e.consume(this);
  463. }
  464. OpaqueWidget::onDragHover(e);
  465. }
  466. json_t* ModuleWidget::toJson() {
  467. json_t* moduleJ = APP->engine->moduleToJson(module);
  468. return moduleJ;
  469. }
  470. void ModuleWidget::fromJson(json_t* moduleJ) {
  471. APP->engine->moduleFromJson(module, moduleJ);
  472. }
  473. bool ModuleWidget::pasteJsonAction(json_t* moduleJ) {
  474. engine::Module::jsonStripIds(moduleJ);
  475. json_t* oldModuleJ = toJson();
  476. DEFER({json_decref(oldModuleJ);});
  477. try {
  478. fromJson(moduleJ);
  479. }
  480. catch (Exception& e) {
  481. WARN("%s", e.what());
  482. return false;
  483. }
  484. // history::ModuleChange
  485. history::ModuleChange* h = new history::ModuleChange;
  486. h->name = "paste module preset";
  487. h->moduleId = module->id;
  488. json_incref(oldModuleJ);
  489. h->oldModuleJ = oldModuleJ;
  490. json_incref(moduleJ);
  491. h->newModuleJ = moduleJ;
  492. APP->history->push(h);
  493. return true;
  494. }
  495. void ModuleWidget::copyClipboard() {
  496. json_t* moduleJ = toJson();
  497. engine::Module::jsonStripIds(moduleJ);
  498. DEFER({json_decref(moduleJ);});
  499. char* json = json_dumps(moduleJ, JSON_INDENT(2));
  500. DEFER({std::free(json);});
  501. glfwSetClipboardString(APP->window->win, json);
  502. }
  503. bool ModuleWidget::pasteClipboardAction() {
  504. const char* json = glfwGetClipboardString(APP->window->win);
  505. if (!json) {
  506. WARN("Could not get text from clipboard.");
  507. return false;
  508. }
  509. json_error_t error;
  510. json_t* moduleJ = json_loads(json, 0, &error);
  511. if (!moduleJ) {
  512. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  513. return false;
  514. }
  515. DEFER({json_decref(moduleJ);});
  516. return pasteJsonAction(moduleJ);
  517. }
  518. void ModuleWidget::load(std::string filename) {
  519. FILE* file = std::fopen(filename.c_str(), "r");
  520. if (!file)
  521. throw Exception("Could not load patch file %s", filename.c_str());
  522. DEFER({std::fclose(file);});
  523. INFO("Loading preset %s", filename.c_str());
  524. json_error_t error;
  525. json_t* moduleJ = json_loadf(file, 0, &error);
  526. if (!moduleJ)
  527. 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);
  528. DEFER({json_decref(moduleJ);});
  529. engine::Module::jsonStripIds(moduleJ);
  530. fromJson(moduleJ);
  531. }
  532. void ModuleWidget::loadAction(std::string filename) {
  533. // history::ModuleChange
  534. history::ModuleChange* h = new history::ModuleChange;
  535. h->name = "load module preset";
  536. h->moduleId = module->id;
  537. h->oldModuleJ = toJson();
  538. try {
  539. load(filename);
  540. }
  541. catch (Exception& e) {
  542. delete h;
  543. throw;
  544. }
  545. // TODO We can use `moduleJ` here instead to save a toJson() call.
  546. h->newModuleJ = toJson();
  547. APP->history->push(h);
  548. }
  549. void ModuleWidget::loadTemplate() {
  550. std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm");
  551. try {
  552. load(templatePath);
  553. }
  554. catch (Exception& e) {
  555. // Do nothing
  556. }
  557. }
  558. void ModuleWidget::loadDialog() {
  559. std::string presetDir = model->getUserPresetDirectory();
  560. system::createDirectories(presetDir);
  561. WeakPtr<ModuleWidget> weakThis = this;
  562. async_dialog_filebrowser(false, nullptr, presetDir.c_str(), "Load preset", [=](char* pathC) {
  563. // Delete directories if empty
  564. DEFER({
  565. try {
  566. system::remove(presetDir);
  567. system::remove(system::getDirectory(presetDir));
  568. }
  569. catch (Exception& e) {
  570. // Ignore exceptions if directory cannot be removed.
  571. }
  572. });
  573. if (!weakThis)
  574. return;
  575. if (!pathC) {
  576. // No path selected
  577. return;
  578. }
  579. DEFER({std::free(pathC);});
  580. try {
  581. weakThis->loadAction(pathC);
  582. }
  583. catch (Exception& e) {
  584. async_dialog_message(e.what());
  585. }
  586. });
  587. }
  588. void ModuleWidget::save(std::string filename) {
  589. INFO("Saving preset %s", filename.c_str());
  590. json_t* moduleJ = toJson();
  591. assert(moduleJ);
  592. DEFER({json_decref(moduleJ);});
  593. engine::Module::jsonStripIds(moduleJ);
  594. FILE* file = std::fopen(filename.c_str(), "w");
  595. if (!file) {
  596. std::string message = string::f("Could not save preset to file %s", filename.c_str());
  597. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  598. return;
  599. }
  600. DEFER({std::fclose(file);});
  601. json_dumpf(moduleJ, file, JSON_INDENT(2));
  602. }
  603. void ModuleWidget::saveTemplate() {
  604. std::string presetDir = model->getUserPresetDirectory();
  605. system::createDirectories(presetDir);
  606. std::string templatePath = system::join(presetDir, "template.vcvm");
  607. save(templatePath);
  608. }
  609. void ModuleWidget::saveTemplateDialog() {
  610. if (hasTemplate()) {
  611. std::string message = string::f("Overwrite default preset for %s?", model->getFullName().c_str());
  612. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  613. return;
  614. }
  615. saveTemplate();
  616. }
  617. bool ModuleWidget::hasTemplate() {
  618. std::string presetDir = model->getUserPresetDirectory();
  619. std::string templatePath = system::join(presetDir, "template.vcvm");
  620. return system::exists(templatePath);;
  621. }
  622. void ModuleWidget::clearTemplate() {
  623. std::string presetDir = model->getUserPresetDirectory();
  624. std::string templatePath = system::join(presetDir, "template.vcvm");
  625. system::remove(templatePath);
  626. }
  627. void ModuleWidget::clearTemplateDialog() {
  628. std::string message = string::f("Delete default preset for %s?", model->getFullName().c_str());
  629. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  630. return;
  631. clearTemplate();
  632. }
  633. void ModuleWidget::saveDialog() {
  634. std::string presetDir = model->getUserPresetDirectory();
  635. system::createDirectories(presetDir);
  636. WeakPtr<ModuleWidget> weakThis = this;
  637. async_dialog_filebrowser(true, "preset.vcvm", presetDir.c_str(), "Save preset", [=](char* pathC) {
  638. // Delete directories if empty
  639. DEFER({
  640. try {
  641. system::remove(presetDir);
  642. system::remove(system::getDirectory(presetDir));
  643. }
  644. catch (Exception& e) {
  645. // Ignore exceptions if directory cannot be removed.
  646. }
  647. });
  648. if (!weakThis)
  649. return;
  650. if (!pathC) {
  651. // No path selected
  652. return;
  653. }
  654. DEFER({std::free(pathC);});
  655. std::string path = pathC;
  656. // Automatically append .vcvm extension
  657. if (system::getExtension(path) != ".vcvm")
  658. path += ".vcvm";
  659. weakThis->save(path);
  660. });
  661. }
  662. void ModuleWidget::disconnect() {
  663. for (PortWidget* pw : getPorts()) {
  664. APP->scene->rack->clearCablesOnPort(pw);
  665. }
  666. }
  667. void ModuleWidget::resetAction() {
  668. assert(module);
  669. // history::ModuleChange
  670. history::ModuleChange* h = new history::ModuleChange;
  671. h->name = "reset module";
  672. h->moduleId = module->id;
  673. h->oldModuleJ = toJson();
  674. APP->engine->resetModule(module);
  675. h->newModuleJ = toJson();
  676. APP->history->push(h);
  677. }
  678. void ModuleWidget::randomizeAction() {
  679. assert(module);
  680. // history::ModuleChange
  681. history::ModuleChange* h = new history::ModuleChange;
  682. h->name = "randomize module";
  683. h->moduleId = module->id;
  684. h->oldModuleJ = toJson();
  685. APP->engine->randomizeModule(module);
  686. h->newModuleJ = toJson();
  687. APP->history->push(h);
  688. }
  689. void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) {
  690. for (PortWidget* pw : getPorts()) {
  691. for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
  692. // history::CableRemove
  693. history::CableRemove* h = new history::CableRemove;
  694. h->setCable(cw);
  695. complexAction->push(h);
  696. // Delete cable
  697. APP->scene->rack->removeCable(cw);
  698. delete cw;
  699. }
  700. };
  701. }
  702. void ModuleWidget::disconnectAction() {
  703. history::ComplexAction* complexAction = new history::ComplexAction;
  704. complexAction->name = "disconnect cables";
  705. appendDisconnectActions(complexAction);
  706. if (!complexAction->isEmpty())
  707. APP->history->push(complexAction);
  708. else
  709. delete complexAction;
  710. }
  711. void ModuleWidget::cloneAction(bool cloneCables) {
  712. // history::ComplexAction
  713. history::ComplexAction* h = new history::ComplexAction;
  714. h->name = "duplicate module";
  715. // Save patch store in this module so we can copy it below
  716. APP->engine->prepareSaveModule(module);
  717. // JSON serialization is the obvious way to do this
  718. json_t* moduleJ = toJson();
  719. DEFER({
  720. json_decref(moduleJ);
  721. });
  722. engine::Module::jsonStripIds(moduleJ);
  723. // Clone Module
  724. INFO("Creating module %s", model->getFullName().c_str());
  725. engine::Module* clonedModule = model->createModule();
  726. // Set ID here so we can copy module storage dir
  727. clonedModule->id = random::u64() % (1ull << 53);
  728. system::copy(module->getPatchStorageDirectory(), clonedModule->getPatchStorageDirectory());
  729. // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
  730. try {
  731. clonedModule->fromJson(moduleJ);
  732. }
  733. catch (Exception& e) {
  734. WARN("%s", e.what());
  735. }
  736. APP->engine->addModule(clonedModule);
  737. // Clone ModuleWidget
  738. INFO("Creating module widget %s", model->getFullName().c_str());
  739. ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
  740. APP->scene->rack->updateModuleOldPositions();
  741. APP->scene->rack->addModule(clonedModuleWidget);
  742. // Place module to the right of `this` module, by forcing it to 1 HP to the right.
  743. math::Vec clonedPos = box.pos;
  744. clonedPos.x += clonedModuleWidget->box.getWidth();
  745. if (settings::squeezeModules)
  746. APP->scene->rack->squeezeModulePos(clonedModuleWidget, clonedPos);
  747. else
  748. APP->scene->rack->setModulePosNearest(clonedModuleWidget, clonedPos);
  749. h->push(APP->scene->rack->getModuleDragAction());
  750. APP->scene->rack->updateExpanders();
  751. // history::ModuleAdd
  752. history::ModuleAdd* hma = new history::ModuleAdd;
  753. hma->setModule(clonedModuleWidget);
  754. h->push(hma);
  755. if (cloneCables) {
  756. // Clone cables attached to input ports
  757. for (PortWidget* pw : getInputs()) {
  758. for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
  759. // Create cable attached to cloned ModuleWidget's input
  760. engine::Cable* clonedCable = new engine::Cable;
  761. clonedCable->inputModule = clonedModule;
  762. clonedCable->inputId = cw->cable->inputId;
  763. // If cable is self-patched, attach to cloned module instead
  764. if (cw->cable->outputModule == module)
  765. clonedCable->outputModule = clonedModule;
  766. else
  767. clonedCable->outputModule = cw->cable->outputModule;
  768. clonedCable->outputId = cw->cable->outputId;
  769. APP->engine->addCable(clonedCable);
  770. app::CableWidget* clonedCw = new app::CableWidget;
  771. clonedCw->setCable(clonedCable);
  772. clonedCw->color = cw->color;
  773. APP->scene->rack->addCable(clonedCw);
  774. // history::CableAdd
  775. history::CableAdd* hca = new history::CableAdd;
  776. hca->setCable(clonedCw);
  777. h->push(hca);
  778. }
  779. }
  780. }
  781. APP->history->push(h);
  782. }
  783. void ModuleWidget::bypassAction(bool bypassed) {
  784. assert(module);
  785. // history::ModuleBypass
  786. history::ModuleBypass* h = new history::ModuleBypass;
  787. h->moduleId = module->id;
  788. h->bypassed = bypassed;
  789. if (!bypassed)
  790. h->name = "un-bypass module";
  791. APP->history->push(h);
  792. APP->engine->bypassModule(module, bypassed);
  793. }
  794. void ModuleWidget::removeAction() {
  795. history::ComplexAction* h = new history::ComplexAction;
  796. h->name = "delete module";
  797. // Disconnect cables
  798. appendDisconnectActions(h);
  799. // Unset module position from rack.
  800. APP->scene->rack->updateModuleOldPositions();
  801. if (settings::squeezeModules)
  802. APP->scene->rack->unsqueezeModulePos(this);
  803. h->push(APP->scene->rack->getModuleDragAction());
  804. // history::ModuleRemove
  805. history::ModuleRemove* moduleRemove = new history::ModuleRemove;
  806. moduleRemove->setModule(this);
  807. h->push(moduleRemove);
  808. APP->history->push(h);
  809. // This removes the module and transfers ownership to caller
  810. APP->scene->rack->removeModule(this);
  811. delete this;
  812. APP->scene->rack->updateExpanders();
  813. }
  814. // Create ModulePresetPathItems for each patch in a directory.
  815. static void appendPresetItems(ui::Menu* menu, WeakPtr<ModuleWidget> moduleWidget, std::string presetDir) {
  816. bool hasPresets = false;
  817. if (system::isDirectory(presetDir)) {
  818. // Note: This is not cached, so opening this menu each time might have a bit of latency.
  819. std::vector<std::string> entries = system::getEntries(presetDir);
  820. std::sort(entries.begin(), entries.end());
  821. for (std::string path : entries) {
  822. std::string name = system::getStem(path);
  823. // Remove "1_", "42_", "001_", etc at the beginning of preset filenames
  824. std::regex r("^\\d+_");
  825. name = std::regex_replace(name, r, "");
  826. if (false) {
  827. }
  828. else if (system::getExtension(path) == ".vcvm" && name != "template") {
  829. if (!hasPresets)
  830. menu->addChild(new ui::MenuSeparator);
  831. hasPresets = true;
  832. menu->addChild(createMenuItem(name, "", [=]() {
  833. if (!moduleWidget)
  834. return;
  835. try {
  836. moduleWidget->loadAction(path);
  837. }
  838. catch (Exception& e) {
  839. async_dialog_message(e.what());
  840. }
  841. }));
  842. }
  843. }
  844. }
  845. };
  846. void ModuleWidget::createContextMenu() {
  847. ui::Menu* menu = createMenu();
  848. assert(model);
  849. WeakPtr<ModuleWidget> weakThis = this;
  850. // Brand and module name
  851. menu->addChild(createMenuLabel(model->name));
  852. menu->addChild(createMenuLabel(model->plugin->brand));
  853. // Info
  854. menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) {
  855. model->appendContextMenu(menu);
  856. }));
  857. // Preset
  858. menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) {
  859. menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() {
  860. if (!weakThis)
  861. return;
  862. weakThis->copyClipboard();
  863. }));
  864. menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() {
  865. if (!weakThis)
  866. return;
  867. weakThis->pasteClipboardAction();
  868. }));
  869. menu->addChild(createMenuItem("Open", "", [=]() {
  870. if (!weakThis)
  871. return;
  872. weakThis->loadDialog();
  873. }));
  874. /* TODO requires setting up user dir
  875. menu->addChild(createMenuItem("Save as", "", [=]() {
  876. if (!weakThis)
  877. return;
  878. weakThis->saveDialog();
  879. }));
  880. menu->addChild(createMenuItem("Save default", "", [=]() {
  881. if (!weakThis)
  882. return;
  883. weakThis->saveTemplateDialog();
  884. }));
  885. menu->addChild(createMenuItem("Clear default", "", [=]() {
  886. if (!weakThis)
  887. return;
  888. weakThis->clearTemplateDialog();
  889. }, !weakThis->hasTemplate()));
  890. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  891. menu->addChild(new ui::MenuSeparator);
  892. menu->addChild(createMenuLabel("User presets"));
  893. appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());
  894. */
  895. // Scan `<plugin dir>/presets/<module slug>` for presets.
  896. /* TODO enable only after setting up user dir
  897. menu->addChild(new ui::MenuSeparator);
  898. menu->addChild(createMenuLabel("Factory presets"));
  899. */
  900. appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
  901. }));
  902. // Initialize
  903. menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() {
  904. if (!weakThis)
  905. return;
  906. weakThis->resetAction();
  907. }));
  908. // Randomize
  909. menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() {
  910. if (!weakThis)
  911. return;
  912. weakThis->randomizeAction();
  913. }));
  914. // Disconnect cables
  915. menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() {
  916. if (!weakThis)
  917. return;
  918. weakThis->disconnectAction();
  919. }));
  920. // Bypass
  921. std::string bypassText = RACK_MOD_CTRL_NAME "+E";
  922. bool bypassed = module && module->isBypassed();
  923. if (bypassed)
  924. bypassText += " " CHECKMARK_STRING;
  925. menu->addChild(createMenuItem("Bypass", bypassText, [=]() {
  926. if (!weakThis)
  927. return;
  928. weakThis->bypassAction(!bypassed);
  929. }));
  930. // Duplicate
  931. menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() {
  932. if (!weakThis)
  933. return;
  934. weakThis->cloneAction(false);
  935. }));
  936. // Duplicate with cables
  937. menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() {
  938. if (!weakThis)
  939. return;
  940. weakThis->cloneAction(true);
  941. }));
  942. // Delete
  943. menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() {
  944. if (!weakThis)
  945. return;
  946. weakThis->removeAction();
  947. }, false, true));
  948. appendContextMenu(menu);
  949. }
  950. math::Vec ModuleWidget::getGridPosition() {
  951. return ((getPosition() - RACK_OFFSET) / RACK_GRID_SIZE).round();
  952. }
  953. void ModuleWidget::setGridPosition(math::Vec pos) {
  954. setPosition(pos * RACK_GRID_SIZE + RACK_OFFSET);
  955. }
  956. math::Vec ModuleWidget::getGridSize() {
  957. return (getSize() / RACK_GRID_SIZE).round();
  958. }
  959. math::Rect ModuleWidget::getGridBox() {
  960. return math::Rect(getGridPosition(), getGridSize());
  961. }
  962. math::Vec& ModuleWidget::dragOffset() {
  963. return internal->dragOffset;
  964. }
  965. bool& ModuleWidget::dragEnabled() {
  966. return internal->dragEnabled;
  967. }
  968. engine::Module* ModuleWidget::releaseModule() {
  969. engine::Module* module = this->module;
  970. this->module = NULL;
  971. return module;
  972. }
  973. } // namespace app
  974. } // namespace rack