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.

1021 lines
26KB

  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<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. pasteClipboardAction();
  266. e.consume(this);
  267. }
  268. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  269. cloneAction();
  270. e.consume(this);
  271. }
  272. if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  273. resetAction();
  274. e.consume(this);
  275. }
  276. if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  277. randomizeAction();
  278. e.consume(this);
  279. }
  280. if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  281. disconnectAction();
  282. e.consume(this);
  283. }
  284. if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  285. bypassAction(!module->isBypassed());
  286. e.consume(this);
  287. }
  288. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  289. // Deletes `this`
  290. removeAction();
  291. e.consume(NULL);
  292. return;
  293. }
  294. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  295. system::openBrowser(model->getManualUrl());
  296. e.consume(this);
  297. }
  298. }
  299. if (e.isConsumed())
  300. return;
  301. OpaqueWidget::onHoverKey(e);
  302. }
  303. void ModuleWidget::onButton(const ButtonEvent& e) {
  304. bool selected = APP->scene->rack->isSelected(this);
  305. if (selected) {
  306. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  307. ui::Menu* menu = createMenu();
  308. APP->scene->rack->appendSelectionContextMenu(menu);
  309. }
  310. e.consume(this);
  311. }
  312. OpaqueWidget::onButton(e);
  313. if (e.getTarget() == this) {
  314. // Set starting drag position
  315. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  316. internal->dragOffset = e.pos;
  317. }
  318. // Toggle selection on Shift-click
  319. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  320. APP->scene->rack->select(this, !selected);
  321. }
  322. }
  323. if (!e.isConsumed() && !selected) {
  324. // Open context menu on right-click
  325. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  326. createContextMenu();
  327. e.consume(this);
  328. }
  329. }
  330. }
  331. void ModuleWidget::onDragStart(const DragStartEvent& e) {
  332. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  333. // HACK Disable FramebufferWidget redrawing subpixels while dragging
  334. APP->window->fbDirtyOnSubpixelChange() = false;
  335. // Clear dragRack so dragging in not enabled until mouse is moved a bit.
  336. internal->dragRackPos = math::Vec(NAN, NAN);
  337. // Prepare initial position of modules for history.
  338. APP->scene->rack->updateModuleOldPositions();
  339. }
  340. }
  341. void ModuleWidget::onDragEnd(const DragEndEvent& e) {
  342. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  343. APP->window->fbDirtyOnSubpixelChange() = true;
  344. // The next time the module is dragged, it should always move immediately
  345. internal->dragEnabled = true;
  346. history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
  347. if (!h->isEmpty())
  348. APP->history->push(h);
  349. else
  350. delete h;
  351. }
  352. }
  353. void ModuleWidget::onDragMove(const DragMoveEvent& e) {
  354. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  355. if (!settings::lockModules) {
  356. math::Vec mousePos = APP->scene->rack->getMousePos();
  357. if (!internal->dragEnabled) {
  358. // Set dragRackPos on the first time after dragging
  359. if (!internal->dragRackPos.isFinite())
  360. internal->dragRackPos = mousePos;
  361. // Check if the mouse has moved enough to start dragging the module.
  362. const float minDist = RACK_GRID_WIDTH;
  363. if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2))
  364. internal->dragEnabled = true;
  365. }
  366. // Move module
  367. if (internal->dragEnabled) {
  368. // Round y coordinate to nearest rack height
  369. math::Vec pos = mousePos;
  370. pos.x -= internal->dragOffset.x;
  371. pos.y -= RACK_GRID_HEIGHT / 2;
  372. if (APP->scene->rack->isSelected(this)) {
  373. pos = (pos / RACK_GRID_SIZE).round() * RACK_GRID_SIZE;
  374. math::Vec delta = pos.minus(box.pos);
  375. APP->scene->rack->setSelectionPosNearest(delta);
  376. }
  377. else {
  378. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  379. APP->scene->rack->setModulePosForce(this, pos);
  380. else
  381. APP->scene->rack->setModulePosNearest(this, pos);
  382. }
  383. }
  384. }
  385. }
  386. }
  387. void ModuleWidget::onDragHover(const DragHoverEvent& e) {
  388. if (APP->scene->rack->isSelected(this)) {
  389. e.consume(this);
  390. }
  391. OpaqueWidget::onDragHover(e);
  392. }
  393. json_t* ModuleWidget::toJson() {
  394. json_t* moduleJ = APP->engine->moduleToJson(module);
  395. return moduleJ;
  396. }
  397. void ModuleWidget::fromJson(json_t* moduleJ) {
  398. APP->engine->moduleFromJson(module, moduleJ);
  399. }
  400. void ModuleWidget::pasteJsonAction(json_t* moduleJ) {
  401. engine::Module::jsonStripIds(moduleJ);
  402. json_t* oldModuleJ = toJson();
  403. try {
  404. fromJson(moduleJ);
  405. }
  406. catch (Exception& e) {
  407. WARN("%s", e.what());
  408. json_decref(oldModuleJ);
  409. return;
  410. }
  411. // history::ModuleChange
  412. history::ModuleChange* h = new history::ModuleChange;
  413. h->name = "paste module preset";
  414. h->moduleId = module->id;
  415. h->oldModuleJ = oldModuleJ;
  416. h->newModuleJ = moduleJ;
  417. APP->history->push(h);
  418. }
  419. void ModuleWidget::copyClipboard() {
  420. json_t* moduleJ = toJson();
  421. engine::Module::jsonStripIds(moduleJ);
  422. DEFER({json_decref(moduleJ);});
  423. char* json = json_dumps(moduleJ, JSON_INDENT(2));
  424. DEFER({std::free(json);});
  425. glfwSetClipboardString(APP->window->win, json);
  426. }
  427. void ModuleWidget::pasteClipboardAction() {
  428. const char* json = glfwGetClipboardString(APP->window->win);
  429. if (!json) {
  430. WARN("Could not get text from clipboard.");
  431. return;
  432. }
  433. json_error_t error;
  434. json_t* moduleJ = json_loads(json, 0, &error);
  435. if (!moduleJ) {
  436. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  437. return;
  438. }
  439. DEFER({json_decref(moduleJ);});
  440. pasteJsonAction(moduleJ);
  441. }
  442. void ModuleWidget::load(std::string filename) {
  443. FILE* file = std::fopen(filename.c_str(), "r");
  444. if (!file)
  445. throw Exception("Could not load patch file %s", filename.c_str());
  446. DEFER({std::fclose(file);});
  447. INFO("Loading preset %s", filename.c_str());
  448. json_error_t error;
  449. json_t* moduleJ = json_loadf(file, 0, &error);
  450. if (!moduleJ)
  451. 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);
  452. DEFER({json_decref(moduleJ);});
  453. engine::Module::jsonStripIds(moduleJ);
  454. fromJson(moduleJ);
  455. }
  456. void ModuleWidget::loadAction(std::string filename) {
  457. // history::ModuleChange
  458. history::ModuleChange* h = new history::ModuleChange;
  459. h->name = "load module preset";
  460. h->moduleId = module->id;
  461. h->oldModuleJ = toJson();
  462. try {
  463. load(filename);
  464. }
  465. catch (Exception& e) {
  466. delete h;
  467. throw;
  468. }
  469. // TODO We can use `moduleJ` here instead to save a toJson() call.
  470. h->newModuleJ = toJson();
  471. APP->history->push(h);
  472. }
  473. void ModuleWidget::loadTemplate() {
  474. std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm");
  475. try {
  476. load(templatePath);
  477. }
  478. catch (Exception& e) {
  479. // Do nothing
  480. }
  481. }
  482. void ModuleWidget::loadDialog() {
  483. std::string presetDir = model->getUserPresetDirectory();
  484. system::createDirectories(presetDir);
  485. // Delete directories if empty
  486. DEFER({
  487. try {
  488. system::remove(presetDir);
  489. system::remove(system::getDirectory(presetDir));
  490. }
  491. catch (Exception& e) {
  492. // Ignore exceptions if directory cannot be removed.
  493. }
  494. });
  495. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  496. DEFER({osdialog_filters_free(filters);});
  497. char* pathC = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters);
  498. if (!pathC) {
  499. // No path selected
  500. return;
  501. }
  502. DEFER({std::free(pathC);});
  503. try {
  504. loadAction(pathC);
  505. }
  506. catch (Exception& e) {
  507. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  508. }
  509. }
  510. void ModuleWidget::save(std::string filename) {
  511. INFO("Saving preset %s", filename.c_str());
  512. json_t* moduleJ = toJson();
  513. assert(moduleJ);
  514. DEFER({json_decref(moduleJ);});
  515. engine::Module::jsonStripIds(moduleJ);
  516. FILE* file = std::fopen(filename.c_str(), "w");
  517. if (!file) {
  518. std::string message = string::f("Could not save preset to file %s", filename.c_str());
  519. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  520. return;
  521. }
  522. DEFER({std::fclose(file);});
  523. json_dumpf(moduleJ, file, JSON_INDENT(2));
  524. }
  525. void ModuleWidget::saveTemplate() {
  526. std::string presetDir = model->getUserPresetDirectory();
  527. system::createDirectories(presetDir);
  528. std::string templatePath = system::join(presetDir, "template.vcvm");
  529. save(templatePath);
  530. }
  531. void ModuleWidget::saveTemplateDialog() {
  532. if (hasTemplate()) {
  533. std::string message = string::f("Overwrite template preset for %s?", model->getFullName().c_str());
  534. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  535. return;
  536. }
  537. saveTemplate();
  538. }
  539. bool ModuleWidget::hasTemplate() {
  540. std::string presetDir = model->getUserPresetDirectory();
  541. std::string templatePath = system::join(presetDir, "template.vcvm");
  542. return system::exists(templatePath);;
  543. }
  544. void ModuleWidget::clearTemplate() {
  545. std::string presetDir = model->getUserPresetDirectory();
  546. std::string templatePath = system::join(presetDir, "template.vcvm");
  547. system::remove(templatePath);
  548. }
  549. void ModuleWidget::clearTemplateDialog() {
  550. std::string message = string::f("Delete template preset for %s?", model->getFullName().c_str());
  551. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  552. return;
  553. clearTemplate();
  554. }
  555. void ModuleWidget::saveDialog() {
  556. std::string presetDir = model->getUserPresetDirectory();
  557. system::createDirectories(presetDir);
  558. // Delete directories if empty
  559. DEFER({
  560. try {
  561. system::remove(presetDir);
  562. system::remove(system::getDirectory(presetDir));
  563. }
  564. catch (Exception& e) {
  565. // Ignore exceptions if directory cannot be removed.
  566. }
  567. });
  568. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  569. DEFER({osdialog_filters_free(filters);});
  570. char* pathC = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters);
  571. if (!pathC) {
  572. // No path selected
  573. return;
  574. }
  575. DEFER({std::free(pathC);});
  576. std::string path = pathC;
  577. // Automatically append .vcvm extension
  578. if (system::getExtension(path) != ".vcvm")
  579. path += ".vcvm";
  580. save(path);
  581. }
  582. void ModuleWidget::disconnect() {
  583. for (PortWidget* pw : getPorts()) {
  584. APP->scene->rack->clearCablesOnPort(pw);
  585. }
  586. }
  587. void ModuleWidget::resetAction() {
  588. assert(module);
  589. // history::ModuleChange
  590. history::ModuleChange* h = new history::ModuleChange;
  591. h->name = "reset module";
  592. h->moduleId = module->id;
  593. h->oldModuleJ = toJson();
  594. APP->engine->resetModule(module);
  595. h->newModuleJ = toJson();
  596. APP->history->push(h);
  597. }
  598. void ModuleWidget::randomizeAction() {
  599. assert(module);
  600. // history::ModuleChange
  601. history::ModuleChange* h = new history::ModuleChange;
  602. h->name = "randomize module";
  603. h->moduleId = module->id;
  604. h->oldModuleJ = toJson();
  605. APP->engine->randomizeModule(module);
  606. h->newModuleJ = toJson();
  607. APP->history->push(h);
  608. }
  609. void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) {
  610. for (PortWidget* pw : getPorts()) {
  611. for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) {
  612. if (!cw->isComplete())
  613. continue;
  614. // history::CableRemove
  615. history::CableRemove* h = new history::CableRemove;
  616. h->setCable(cw);
  617. complexAction->push(h);
  618. // Delete cable
  619. APP->scene->rack->removeCable(cw);
  620. delete cw;
  621. }
  622. };
  623. }
  624. void ModuleWidget::disconnectAction() {
  625. history::ComplexAction* complexAction = new history::ComplexAction;
  626. complexAction->name = "disconnect cables";
  627. appendDisconnectActions(complexAction);
  628. if (!complexAction->isEmpty())
  629. APP->history->push(complexAction);
  630. else
  631. delete complexAction;
  632. }
  633. void ModuleWidget::cloneAction() {
  634. // history::ComplexAction
  635. history::ComplexAction* h = new history::ComplexAction;
  636. h->name = "duplicate module";
  637. // JSON serialization is the obvious way to do this
  638. json_t* moduleJ = toJson();
  639. engine::Module::jsonStripIds(moduleJ);
  640. // Clone Module
  641. engine::Module* clonedModule = model->createModule();
  642. // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
  643. try {
  644. clonedModule->fromJson(moduleJ);
  645. }
  646. catch (Exception& e) {
  647. WARN("%s", e.what());
  648. }
  649. json_decref(moduleJ);
  650. APP->engine->addModule(clonedModule);
  651. // Clone ModuleWidget
  652. ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
  653. APP->scene->rack->addModuleAtMouse(clonedModuleWidget);
  654. // history::ModuleAdd
  655. history::ModuleAdd* hma = new history::ModuleAdd;
  656. hma->setModule(clonedModuleWidget);
  657. h->push(hma);
  658. // Clone cables attached to input ports
  659. for (PortWidget* pw : getInputs()) {
  660. for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) {
  661. // Create cable attached to cloned ModuleWidget's input
  662. engine::Cable* clonedCable = new engine::Cable;
  663. clonedCable->inputModule = clonedModule;
  664. clonedCable->inputId = cw->cable->inputId;
  665. // If cable is self-patched, attach to cloned module instead
  666. if (cw->cable->outputModule == module)
  667. clonedCable->outputModule = clonedModule;
  668. else
  669. clonedCable->outputModule = cw->cable->outputModule;
  670. clonedCable->outputId = cw->cable->outputId;
  671. APP->engine->addCable(clonedCable);
  672. app::CableWidget* clonedCw = new app::CableWidget;
  673. clonedCw->setCable(clonedCable);
  674. clonedCw->color = cw->color;
  675. APP->scene->rack->addCable(clonedCw);
  676. // history::CableAdd
  677. history::CableAdd* hca = new history::CableAdd;
  678. hca->setCable(clonedCw);
  679. h->push(hca);
  680. }
  681. }
  682. APP->history->push(h);
  683. }
  684. void ModuleWidget::bypassAction(bool bypassed) {
  685. assert(module);
  686. // history::ModuleBypass
  687. history::ModuleBypass* h = new history::ModuleBypass;
  688. h->moduleId = module->id;
  689. h->bypassed = bypassed;
  690. if (!bypassed)
  691. h->name = "un-bypass module";
  692. APP->history->push(h);
  693. APP->engine->bypassModule(module, bypassed);
  694. }
  695. void ModuleWidget::removeAction() {
  696. history::ComplexAction* complexAction = new history::ComplexAction;
  697. complexAction->name = "remove module";
  698. appendDisconnectActions(complexAction);
  699. // history::ModuleRemove
  700. history::ModuleRemove* moduleRemove = new history::ModuleRemove;
  701. moduleRemove->setModule(this);
  702. complexAction->push(moduleRemove);
  703. APP->history->push(complexAction);
  704. // This removes the module and transfers ownership to caller
  705. APP->scene->rack->removeModule(this);
  706. delete this;
  707. }
  708. // Create ModulePresetPathItems for each patch in a directory.
  709. static void appendPresetItems(ui::Menu* menu, WeakPtr<ModuleWidget> moduleWidget, std::string presetDir) {
  710. bool hasPresets = false;
  711. // Note: This is not cached, so opening this menu each time might have a bit of latency.
  712. if (system::isDirectory(presetDir)) {
  713. for (std::string path : system::getEntries(presetDir)) {
  714. std::string name = system::getStem(path);
  715. // Remove "1_", "42_", "001_", etc at the beginning of preset filenames
  716. std::regex r("^\\d*_");
  717. name = std::regex_replace(name, r, "");
  718. if (system::isDirectory(path)) {
  719. hasPresets = true;
  720. menu->addChild(createSubmenuItem(name, "", [=](ui::Menu* menu) {
  721. if (!moduleWidget)
  722. return;
  723. appendPresetItems(menu, moduleWidget, path);
  724. }));
  725. }
  726. else if (system::getExtension(path) == ".vcvm") {
  727. hasPresets = true;
  728. menu->addChild(createMenuItem(name, "", [=]() {
  729. if (!moduleWidget)
  730. return;
  731. try {
  732. moduleWidget->loadAction(path);
  733. }
  734. catch (Exception& e) {
  735. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  736. }
  737. }));
  738. }
  739. }
  740. }
  741. if (!hasPresets) {
  742. menu->addChild(createMenuLabel("(None)"));
  743. }
  744. };
  745. void ModuleWidget::createContextMenu() {
  746. ui::Menu* menu = createMenu();
  747. assert(model);
  748. WeakPtr<ModuleWidget> weakThis = this;
  749. // Brand and module name
  750. menu->addChild(createMenuLabel(model->name));
  751. menu->addChild(createMenuLabel(model->plugin->brand));
  752. // Info
  753. menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) {
  754. model->appendContextMenu(menu);
  755. }));
  756. // Preset
  757. menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) {
  758. menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() {
  759. if (!weakThis)
  760. return;
  761. weakThis->copyClipboard();
  762. }));
  763. menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() {
  764. if (!weakThis)
  765. return;
  766. weakThis->pasteClipboardAction();
  767. }));
  768. menu->addChild(createMenuItem("Open", "", [=]() {
  769. if (!weakThis)
  770. return;
  771. weakThis->loadDialog();
  772. }));
  773. menu->addChild(createMenuItem("Save as", "", [=]() {
  774. if (!weakThis)
  775. return;
  776. weakThis->saveDialog();
  777. }));
  778. menu->addChild(createMenuItem("Save template", "", [=]() {
  779. if (!weakThis)
  780. return;
  781. weakThis->saveTemplateDialog();
  782. }));
  783. menu->addChild(createMenuItem("Clear template", "", [=]() {
  784. if (!weakThis)
  785. return;
  786. weakThis->clearTemplateDialog();
  787. }, !weakThis->hasTemplate()));
  788. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  789. menu->addChild(new ui::MenuSeparator);
  790. menu->addChild(createMenuLabel("User presets"));
  791. appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());
  792. // Scan `<plugin dir>/presets/<module slug>` for presets.
  793. menu->addChild(new ui::MenuSeparator);
  794. menu->addChild(createMenuLabel("Factory presets"));
  795. appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
  796. }));
  797. // Initialize
  798. menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() {
  799. if (!weakThis)
  800. return;
  801. weakThis->resetAction();
  802. }));
  803. // Randomize
  804. menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() {
  805. if (!weakThis)
  806. return;
  807. weakThis->randomizeAction();
  808. }));
  809. // Disconnect cables
  810. menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() {
  811. if (!weakThis)
  812. return;
  813. weakThis->disconnectAction();
  814. }));
  815. // Bypass
  816. std::string bypassText = RACK_MOD_CTRL_NAME "+E";
  817. bool bypassed = module && module->isBypassed();
  818. if (bypassed)
  819. bypassText += " " CHECKMARK_STRING;
  820. menu->addChild(createMenuItem("Bypass", bypassText, [=]() {
  821. if (!weakThis)
  822. return;
  823. weakThis->bypassAction(!bypassed);
  824. }));
  825. // Duplicate
  826. menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() {
  827. if (!weakThis)
  828. return;
  829. weakThis->cloneAction();
  830. }));
  831. // Delete
  832. menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() {
  833. if (!weakThis)
  834. return;
  835. weakThis->removeAction();
  836. }, false, true));
  837. appendContextMenu(menu);
  838. }
  839. math::Vec& ModuleWidget::dragOffset() {
  840. return internal->dragOffset;
  841. }
  842. bool& ModuleWidget::dragEnabled() {
  843. return internal->dragEnabled;
  844. }
  845. math::Vec& ModuleWidget::oldPos() {
  846. return internal->oldPos;
  847. }
  848. engine::Module* ModuleWidget::releaseModule() {
  849. engine::Module* module = this->module;
  850. this->module = NULL;
  851. return module;
  852. }
  853. } // namespace app
  854. } // namespace rack