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.

1077 lines
28KB

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