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.

1035 lines
27KB

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