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.

1160 lines
30KB

  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. // Only append "%" if wider than 2 HP
  225. if (box.getWidth() > RACK_GRID_WIDTH * 2)
  226. meterText += "%";
  227. math::Vec pt;
  228. pt.x = box.size.x - bndLabelWidth(args.vg, -1, meterText.c_str()) + 3;
  229. pt.y = plotHeight + 0.5;
  230. bndMenuLabel(args.vg, VEC_ARGS(pt), INFINITY, BND_WIDGET_HEIGHT, -1, meterText.c_str());
  231. }
  232. // Selection
  233. if (APP->scene->rack->isSelected(this)) {
  234. nvgBeginPath(args.vg);
  235. nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size));
  236. nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25));
  237. nvgFill(args.vg);
  238. nvgStrokeWidth(args.vg, 2.0);
  239. nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5));
  240. nvgStroke(args.vg);
  241. }
  242. nvgResetScissor(args.vg);
  243. }
  244. void ModuleWidget::drawLayer(const DrawArgs& args, int layer) {
  245. if (layer == -1) {
  246. nvgBeginPath(args.vg);
  247. float r = 20; // Blur radius
  248. float c = 20; // Corner radius
  249. math::Rect shadowBox = box.zeroPos().grow(math::Vec(10, -30));
  250. math::Rect shadowOutsideBox = shadowBox.grow(math::Vec(r, r));
  251. nvgRect(args.vg, RECT_ARGS(shadowOutsideBox));
  252. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  253. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  254. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, RECT_ARGS(shadowBox), c, r, shadowColor, transparentColor));
  255. nvgFill(args.vg);
  256. }
  257. else {
  258. Widget::drawLayer(args, layer);
  259. }
  260. }
  261. void ModuleWidget::onHover(const HoverEvent& e) {
  262. if (APP->scene->rack->isSelected(this)) {
  263. e.consume(this);
  264. }
  265. OpaqueWidget::onHover(e);
  266. }
  267. void ModuleWidget::onHoverKey(const HoverKeyEvent& e) {
  268. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  269. if (e.key == GLFW_KEY_C && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  270. copyClipboard();
  271. e.consume(this);
  272. }
  273. if (e.key == GLFW_KEY_V && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  274. if (pasteClipboardAction()) {
  275. e.consume(this);
  276. }
  277. }
  278. if (e.key == GLFW_KEY_D && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  279. cloneAction(false);
  280. e.consume(this);
  281. }
  282. if (e.key == GLFW_KEY_D && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  283. cloneAction(true);
  284. e.consume(this);
  285. }
  286. if (e.key == GLFW_KEY_I && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  287. resetAction();
  288. e.consume(this);
  289. }
  290. if (e.key == GLFW_KEY_R && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  291. randomizeAction();
  292. e.consume(this);
  293. }
  294. if (e.key == GLFW_KEY_U && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  295. disconnectAction();
  296. e.consume(this);
  297. }
  298. if (e.key == GLFW_KEY_E && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  299. bypassAction(!module->isBypassed());
  300. e.consume(this);
  301. }
  302. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  303. // Deletes `this`
  304. removeAction();
  305. e.consume(NULL);
  306. return;
  307. }
  308. if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  309. std::string manualUrl = model->getManualUrl();
  310. if (!manualUrl.empty())
  311. system::openBrowser(manualUrl);
  312. e.consume(this);
  313. }
  314. if (e.key == GLFW_KEY_F4 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  315. APP->scene->rackScroll->zoomToBound(getBox());
  316. e.consume(this);
  317. }
  318. }
  319. if (e.isConsumed())
  320. return;
  321. OpaqueWidget::onHoverKey(e);
  322. }
  323. void ModuleWidget::onButton(const ButtonEvent& e) {
  324. bool selected = APP->scene->rack->isSelected(this);
  325. if (selected) {
  326. if (e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  327. if (e.action == GLFW_PRESS) {
  328. // Open selection context menu on right-click
  329. ui::Menu* menu = createMenu();
  330. APP->scene->rack->appendSelectionContextMenu(menu);
  331. }
  332. e.consume(this);
  333. }
  334. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  335. if (e.action == GLFW_PRESS) {
  336. // Toggle selection on Shift-click
  337. if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  338. APP->scene->rack->select(this, false);
  339. e.consume(NULL);
  340. return;
  341. }
  342. // If module positions are locked, don't consume left-click
  343. if (settings::lockModules) {
  344. e.consume(NULL);
  345. return;
  346. }
  347. internal->dragOffset = e.pos;
  348. }
  349. e.consume(this);
  350. }
  351. return;
  352. }
  353. // Dispatch event to children
  354. Widget::onButton(e);
  355. e.stopPropagating();
  356. if (e.isConsumed())
  357. return;
  358. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  359. if (e.action == GLFW_PRESS) {
  360. // Toggle selection on Shift-click
  361. if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  362. APP->scene->rack->select(this, true);
  363. e.consume(NULL);
  364. return;
  365. }
  366. // If module positions are locked, don't consume left-click
  367. if (settings::lockModules) {
  368. e.consume(NULL);
  369. return;
  370. }
  371. internal->dragOffset = e.pos;
  372. }
  373. e.consume(this);
  374. }
  375. // Open context menu on right-click
  376. if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
  377. createContextMenu();
  378. e.consume(this);
  379. }
  380. }
  381. void ModuleWidget::onDragStart(const DragStartEvent& e) {
  382. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  383. // HACK Disable FramebufferWidget redrawing subpixels while dragging
  384. APP->window->fbDirtyOnSubpixelChange() = false;
  385. // Clear dragRack so dragging in not enabled until mouse is moved a bit.
  386. internal->dragRackPos = math::Vec(NAN, NAN);
  387. // Prepare initial position of modules for history.
  388. APP->scene->rack->updateModuleOldPositions();
  389. }
  390. }
  391. void ModuleWidget::onDragEnd(const DragEndEvent& e) {
  392. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  393. APP->window->fbDirtyOnSubpixelChange() = true;
  394. // The next time the module is dragged, it should always move immediately
  395. internal->dragEnabled = true;
  396. history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
  397. if (!h->isEmpty())
  398. APP->history->push(h);
  399. else
  400. delete h;
  401. }
  402. }
  403. void ModuleWidget::onDragMove(const DragMoveEvent& e) {
  404. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  405. math::Vec mousePos = APP->scene->rack->getMousePos();
  406. if (!internal->dragEnabled) {
  407. // Set dragRackPos on the first time after dragging
  408. if (!internal->dragRackPos.isFinite())
  409. internal->dragRackPos = mousePos;
  410. // Check if the mouse has moved enough to start dragging the module.
  411. const float minDist = RACK_GRID_WIDTH;
  412. if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2))
  413. internal->dragEnabled = true;
  414. }
  415. // Move module
  416. if (internal->dragEnabled) {
  417. // Round y coordinate to nearest rack height
  418. math::Vec pos = mousePos;
  419. pos.x -= internal->dragOffset.x;
  420. pos.y -= RACK_GRID_HEIGHT / 2;
  421. if (APP->scene->rack->isSelected(this)) {
  422. pos = (pos / RACK_GRID_SIZE).round() * RACK_GRID_SIZE;
  423. math::Vec delta = pos.minus(box.pos);
  424. APP->scene->rack->setSelectionPosNearest(delta);
  425. }
  426. else {
  427. if (settings::squeezeModules) {
  428. APP->scene->rack->setModulePosSqueeze(this, pos);
  429. }
  430. else {
  431. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  432. APP->scene->rack->setModulePosForce(this, pos);
  433. else
  434. APP->scene->rack->setModulePosNearest(this, pos);
  435. }
  436. }
  437. }
  438. }
  439. }
  440. void ModuleWidget::onDragHover(const DragHoverEvent& e) {
  441. if (APP->scene->rack->isSelected(this)) {
  442. e.consume(this);
  443. }
  444. OpaqueWidget::onDragHover(e);
  445. }
  446. json_t* ModuleWidget::toJson() {
  447. json_t* moduleJ = APP->engine->moduleToJson(module);
  448. return moduleJ;
  449. }
  450. void ModuleWidget::fromJson(json_t* moduleJ) {
  451. APP->engine->moduleFromJson(module, moduleJ);
  452. }
  453. bool ModuleWidget::pasteJsonAction(json_t* moduleJ) {
  454. engine::Module::jsonStripIds(moduleJ);
  455. json_t* oldModuleJ = toJson();
  456. DEFER({json_decref(oldModuleJ);});
  457. try {
  458. fromJson(moduleJ);
  459. }
  460. catch (Exception& e) {
  461. WARN("%s", e.what());
  462. return false;
  463. }
  464. // history::ModuleChange
  465. history::ModuleChange* h = new history::ModuleChange;
  466. h->name = "paste module preset";
  467. h->moduleId = module->id;
  468. json_incref(oldModuleJ);
  469. h->oldModuleJ = oldModuleJ;
  470. json_incref(moduleJ);
  471. h->newModuleJ = moduleJ;
  472. APP->history->push(h);
  473. return true;
  474. }
  475. void ModuleWidget::copyClipboard() {
  476. json_t* moduleJ = toJson();
  477. engine::Module::jsonStripIds(moduleJ);
  478. DEFER({json_decref(moduleJ);});
  479. char* json = json_dumps(moduleJ, JSON_INDENT(2));
  480. DEFER({std::free(json);});
  481. glfwSetClipboardString(APP->window->win, json);
  482. }
  483. bool ModuleWidget::pasteClipboardAction() {
  484. const char* json = glfwGetClipboardString(APP->window->win);
  485. if (!json) {
  486. WARN("Could not get text from clipboard.");
  487. return false;
  488. }
  489. json_error_t error;
  490. json_t* moduleJ = json_loads(json, 0, &error);
  491. if (!moduleJ) {
  492. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  493. return false;
  494. }
  495. DEFER({json_decref(moduleJ);});
  496. return pasteJsonAction(moduleJ);
  497. }
  498. void ModuleWidget::load(std::string filename) {
  499. FILE* file = std::fopen(filename.c_str(), "r");
  500. if (!file)
  501. throw Exception("Could not load patch file %s", filename.c_str());
  502. DEFER({std::fclose(file);});
  503. INFO("Loading preset %s", filename.c_str());
  504. json_error_t error;
  505. json_t* moduleJ = json_loadf(file, 0, &error);
  506. if (!moduleJ)
  507. 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);
  508. DEFER({json_decref(moduleJ);});
  509. engine::Module::jsonStripIds(moduleJ);
  510. fromJson(moduleJ);
  511. }
  512. void ModuleWidget::loadAction(std::string filename) {
  513. // history::ModuleChange
  514. history::ModuleChange* h = new history::ModuleChange;
  515. h->name = "load module preset";
  516. h->moduleId = module->id;
  517. h->oldModuleJ = toJson();
  518. try {
  519. load(filename);
  520. }
  521. catch (Exception& e) {
  522. delete h;
  523. throw;
  524. }
  525. // TODO We can use `moduleJ` here instead to save a toJson() call.
  526. h->newModuleJ = toJson();
  527. APP->history->push(h);
  528. }
  529. void ModuleWidget::loadTemplate() {
  530. std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm");
  531. try {
  532. load(templatePath);
  533. }
  534. catch (Exception& e) {
  535. // Do nothing
  536. }
  537. }
  538. void ModuleWidget::loadDialog() {
  539. std::string presetDir = model->getUserPresetDirectory();
  540. system::createDirectories(presetDir);
  541. // Delete directories if empty
  542. DEFER({
  543. try {
  544. system::remove(presetDir);
  545. system::remove(system::getDirectory(presetDir));
  546. }
  547. catch (Exception& e) {
  548. // Ignore exceptions if directory cannot be removed.
  549. }
  550. });
  551. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  552. DEFER({osdialog_filters_free(filters);});
  553. char* pathC = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters);
  554. if (!pathC) {
  555. // No path selected
  556. return;
  557. }
  558. DEFER({std::free(pathC);});
  559. try {
  560. loadAction(pathC);
  561. }
  562. catch (Exception& e) {
  563. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  564. }
  565. }
  566. void ModuleWidget::save(std::string filename) {
  567. INFO("Saving preset %s", filename.c_str());
  568. json_t* moduleJ = toJson();
  569. assert(moduleJ);
  570. DEFER({json_decref(moduleJ);});
  571. engine::Module::jsonStripIds(moduleJ);
  572. FILE* file = std::fopen(filename.c_str(), "w");
  573. if (!file) {
  574. std::string message = string::f("Could not save preset to file %s", filename);
  575. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  576. return;
  577. }
  578. DEFER({std::fclose(file);});
  579. json_dumpf(moduleJ, file, JSON_INDENT(2));
  580. }
  581. void ModuleWidget::saveTemplate() {
  582. std::string presetDir = model->getUserPresetDirectory();
  583. system::createDirectories(presetDir);
  584. std::string templatePath = system::join(presetDir, "template.vcvm");
  585. save(templatePath);
  586. }
  587. void ModuleWidget::saveTemplateDialog() {
  588. if (hasTemplate()) {
  589. std::string message = string::f("Overwrite default preset for %s?", model->getFullName());
  590. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  591. return;
  592. }
  593. saveTemplate();
  594. }
  595. bool ModuleWidget::hasTemplate() {
  596. std::string presetDir = model->getUserPresetDirectory();
  597. std::string templatePath = system::join(presetDir, "template.vcvm");
  598. return system::exists(templatePath);;
  599. }
  600. void ModuleWidget::clearTemplate() {
  601. std::string presetDir = model->getUserPresetDirectory();
  602. std::string templatePath = system::join(presetDir, "template.vcvm");
  603. system::remove(templatePath);
  604. }
  605. void ModuleWidget::clearTemplateDialog() {
  606. std::string message = string::f("Delete default preset for %s?", model->getFullName());
  607. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  608. return;
  609. clearTemplate();
  610. }
  611. void ModuleWidget::saveDialog() {
  612. std::string presetDir = model->getUserPresetDirectory();
  613. system::createDirectories(presetDir);
  614. // Delete directories if empty
  615. DEFER({
  616. try {
  617. system::remove(presetDir);
  618. system::remove(system::getDirectory(presetDir));
  619. }
  620. catch (Exception& e) {
  621. // Ignore exceptions if directory cannot be removed.
  622. }
  623. });
  624. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  625. DEFER({osdialog_filters_free(filters);});
  626. char* pathC = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters);
  627. if (!pathC) {
  628. // No path selected
  629. return;
  630. }
  631. DEFER({std::free(pathC);});
  632. std::string path = pathC;
  633. // Automatically append .vcvm extension
  634. if (system::getExtension(path) != ".vcvm")
  635. path += ".vcvm";
  636. save(path);
  637. }
  638. void ModuleWidget::disconnect() {
  639. for (PortWidget* pw : getPorts()) {
  640. APP->scene->rack->clearCablesOnPort(pw);
  641. }
  642. }
  643. void ModuleWidget::resetAction() {
  644. assert(module);
  645. // history::ModuleChange
  646. history::ModuleChange* h = new history::ModuleChange;
  647. h->name = "reset module";
  648. h->moduleId = module->id;
  649. h->oldModuleJ = toJson();
  650. APP->engine->resetModule(module);
  651. h->newModuleJ = toJson();
  652. APP->history->push(h);
  653. }
  654. void ModuleWidget::randomizeAction() {
  655. assert(module);
  656. // history::ModuleChange
  657. history::ModuleChange* h = new history::ModuleChange;
  658. h->name = "randomize module";
  659. h->moduleId = module->id;
  660. h->oldModuleJ = toJson();
  661. APP->engine->randomizeModule(module);
  662. h->newModuleJ = toJson();
  663. APP->history->push(h);
  664. }
  665. void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) {
  666. for (PortWidget* pw : getPorts()) {
  667. for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
  668. // history::CableRemove
  669. history::CableRemove* h = new history::CableRemove;
  670. h->setCable(cw);
  671. complexAction->push(h);
  672. // Delete cable
  673. APP->scene->rack->removeCable(cw);
  674. delete cw;
  675. }
  676. };
  677. }
  678. void ModuleWidget::disconnectAction() {
  679. history::ComplexAction* complexAction = new history::ComplexAction;
  680. complexAction->name = "disconnect cables";
  681. appendDisconnectActions(complexAction);
  682. if (!complexAction->isEmpty())
  683. APP->history->push(complexAction);
  684. else
  685. delete complexAction;
  686. }
  687. void ModuleWidget::cloneAction(bool cloneCables) {
  688. // history::ComplexAction
  689. history::ComplexAction* h = new history::ComplexAction;
  690. h->name = "duplicate module";
  691. // Save patch store in this module so we can copy it below
  692. APP->engine->prepareSaveModule(module);
  693. // JSON serialization is the obvious way to do this
  694. json_t* moduleJ = toJson();
  695. DEFER({
  696. json_decref(moduleJ);
  697. });
  698. engine::Module::jsonStripIds(moduleJ);
  699. // Clone Module
  700. INFO("Creating module %s", model->getFullName().c_str());
  701. engine::Module* clonedModule = model->createModule();
  702. // Set ID here so we can copy module storage dir
  703. clonedModule->id = random::u64() % (1ull << 53);
  704. system::copy(module->getPatchStorageDirectory(), clonedModule->getPatchStorageDirectory());
  705. // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
  706. try {
  707. clonedModule->fromJson(moduleJ);
  708. }
  709. catch (Exception& e) {
  710. WARN("%s", e.what());
  711. }
  712. APP->engine->addModule(clonedModule);
  713. // Clone ModuleWidget
  714. INFO("Creating module widget %s", model->getFullName().c_str());
  715. ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
  716. APP->scene->rack->updateModuleOldPositions();
  717. APP->scene->rack->addModule(clonedModuleWidget);
  718. // Place module to the right of `this` module, by forcing it to 1 HP to the right.
  719. math::Vec clonedPos = box.pos;
  720. clonedPos.x += clonedModuleWidget->box.getWidth();
  721. if (settings::squeezeModules)
  722. APP->scene->rack->squeezeModulePos(clonedModuleWidget, clonedPos);
  723. else
  724. APP->scene->rack->setModulePosNearest(clonedModuleWidget, clonedPos);
  725. h->push(APP->scene->rack->getModuleDragAction());
  726. APP->scene->rack->updateExpanders();
  727. // history::ModuleAdd
  728. history::ModuleAdd* hma = new history::ModuleAdd;
  729. hma->setModule(clonedModuleWidget);
  730. h->push(hma);
  731. if (cloneCables) {
  732. // Clone cables attached to input/output ports
  733. for (PortWidget* pw : getPorts()) {
  734. for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) {
  735. // Skip input ports self-patched to this module's outputs, to avoid double-cloning them.
  736. if (pw->type == engine::Port::OUTPUT && cw->cable->inputModule == module)
  737. continue;
  738. // Create cable attached to cloned ModuleWidget's input
  739. engine::Cable* clonedCable = new engine::Cable;
  740. clonedCable->inputModule = cw->cable->inputModule;
  741. clonedCable->inputId = cw->cable->inputId;
  742. clonedCable->outputModule = cw->cable->outputModule;
  743. clonedCable->outputId = cw->cable->outputId;
  744. if (pw->type == engine::Port::INPUT) {
  745. clonedCable->inputModule = clonedModule;
  746. // If cable is self-patched, attach to cloned module instead
  747. if (cw->cable->outputModule == module)
  748. clonedCable->outputModule = clonedModule;
  749. }
  750. else {
  751. clonedCable->outputModule = clonedModule;
  752. }
  753. APP->engine->addCable(clonedCable);
  754. app::CableWidget* clonedCw = new app::CableWidget;
  755. clonedCw->setCable(clonedCable);
  756. clonedCw->color = cw->color;
  757. APP->scene->rack->addCable(clonedCw);
  758. // history::CableAdd
  759. history::CableAdd* hca = new history::CableAdd;
  760. hca->setCable(clonedCw);
  761. h->push(hca);
  762. }
  763. }
  764. }
  765. APP->history->push(h);
  766. }
  767. void ModuleWidget::bypassAction(bool bypassed) {
  768. assert(module);
  769. // history::ModuleBypass
  770. history::ModuleBypass* h = new history::ModuleBypass;
  771. h->moduleId = module->id;
  772. h->bypassed = bypassed;
  773. if (!bypassed)
  774. h->name = "un-bypass module";
  775. APP->history->push(h);
  776. APP->engine->bypassModule(module, bypassed);
  777. }
  778. void ModuleWidget::removeAction() {
  779. history::ComplexAction* h = new history::ComplexAction;
  780. h->name = "delete module";
  781. // Disconnect cables
  782. appendDisconnectActions(h);
  783. // Unset module position from rack.
  784. APP->scene->rack->updateModuleOldPositions();
  785. if (settings::squeezeModules)
  786. APP->scene->rack->unsqueezeModulePos(this);
  787. h->push(APP->scene->rack->getModuleDragAction());
  788. // history::ModuleRemove
  789. history::ModuleRemove* moduleRemove = new history::ModuleRemove;
  790. moduleRemove->setModule(this);
  791. h->push(moduleRemove);
  792. APP->history->push(h);
  793. // This removes the module and transfers ownership to caller
  794. APP->scene->rack->removeModule(this);
  795. delete this;
  796. APP->scene->rack->updateExpanders();
  797. }
  798. // Create ModulePresetPathItems for each patch in a directory.
  799. static void appendPresetItems(ui::Menu* menu, WeakPtr<ModuleWidget> moduleWidget, std::string presetDir) {
  800. bool hasPresets = false;
  801. if (system::isDirectory(presetDir)) {
  802. // Note: This is not cached, so opening this menu each time might have a bit of latency.
  803. std::vector<std::string> entries = system::getEntries(presetDir);
  804. std::sort(entries.begin(), entries.end());
  805. for (std::string path : entries) {
  806. std::string name = system::getStem(path);
  807. // Remove "1_", "42_", "001_", etc at the beginning of preset filenames
  808. std::regex r("^\\d+_");
  809. name = std::regex_replace(name, r, "");
  810. if (system::isDirectory(path)) {
  811. hasPresets = true;
  812. menu->addChild(createSubmenuItem(name, "", [=](ui::Menu* menu) {
  813. if (!moduleWidget)
  814. return;
  815. appendPresetItems(menu, moduleWidget, path);
  816. }));
  817. }
  818. else if (system::getExtension(path) == ".vcvm" && name != "template") {
  819. hasPresets = true;
  820. menu->addChild(createMenuItem(name, "", [=]() {
  821. if (!moduleWidget)
  822. return;
  823. try {
  824. moduleWidget->loadAction(path);
  825. }
  826. catch (Exception& e) {
  827. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  828. }
  829. }));
  830. }
  831. }
  832. }
  833. if (!hasPresets) {
  834. menu->addChild(createMenuLabel("(None)"));
  835. }
  836. };
  837. void ModuleWidget::createContextMenu() {
  838. ui::Menu* menu = createMenu();
  839. assert(model);
  840. WeakPtr<ModuleWidget> weakThis = this;
  841. // Brand and module name
  842. menu->addChild(createMenuLabel(model->name));
  843. menu->addChild(createMenuLabel(model->plugin->brand));
  844. // Info
  845. menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) {
  846. model->appendContextMenu(menu);
  847. if (!weakThis)
  848. return;
  849. menu->addChild(new ui::MenuSeparator);
  850. menu->addChild(createMenuLabel("Module instance ID:"));
  851. menu->addChild(createMenuLabel(string::f("%lld", (long long) weakThis->module->getId())));
  852. }));
  853. // Preset
  854. menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) {
  855. menu->addChild(createMenuItem("Copy", widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL), [=]() {
  856. if (!weakThis)
  857. return;
  858. weakThis->copyClipboard();
  859. }));
  860. menu->addChild(createMenuItem("Paste", widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL), [=]() {
  861. if (!weakThis)
  862. return;
  863. weakThis->pasteClipboardAction();
  864. }));
  865. menu->addChild(createMenuItem("Open", "", [=]() {
  866. if (!weakThis)
  867. return;
  868. weakThis->loadDialog();
  869. }));
  870. menu->addChild(createMenuItem("Save as", "", [=]() {
  871. if (!weakThis)
  872. return;
  873. weakThis->saveDialog();
  874. }));
  875. menu->addChild(createMenuItem("Save default", "", [=]() {
  876. if (!weakThis)
  877. return;
  878. weakThis->saveTemplateDialog();
  879. }));
  880. menu->addChild(createMenuItem("Clear default", "", [=]() {
  881. if (!weakThis)
  882. return;
  883. weakThis->clearTemplateDialog();
  884. }, !weakThis->hasTemplate()));
  885. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  886. menu->addChild(new ui::MenuSeparator);
  887. menu->addChild(createMenuLabel("User presets"));
  888. appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());
  889. // Scan `<plugin dir>/presets/<module slug>` for presets.
  890. menu->addChild(new ui::MenuSeparator);
  891. menu->addChild(createMenuLabel("Factory presets"));
  892. appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
  893. }));
  894. // Initialize
  895. menu->addChild(createMenuItem("Initialize", widget::getKeyCommandName(GLFW_KEY_I, RACK_MOD_CTRL), [=]() {
  896. if (!weakThis)
  897. return;
  898. weakThis->resetAction();
  899. }));
  900. // Randomize
  901. menu->addChild(createMenuItem("Randomize", widget::getKeyCommandName(GLFW_KEY_R, RACK_MOD_CTRL), [=]() {
  902. if (!weakThis)
  903. return;
  904. weakThis->randomizeAction();
  905. }));
  906. // Disconnect cables
  907. menu->addChild(createMenuItem("Disconnect cables", widget::getKeyCommandName(GLFW_KEY_U, RACK_MOD_CTRL), [=]() {
  908. if (!weakThis)
  909. return;
  910. weakThis->disconnectAction();
  911. }));
  912. // Bypass
  913. std::string bypassText = widget::getKeyCommandName(GLFW_KEY_E, RACK_MOD_CTRL);
  914. bool bypassed = module && module->isBypassed();
  915. if (bypassed)
  916. bypassText += " " CHECKMARK_STRING;
  917. menu->addChild(createMenuItem("Bypass", bypassText, [=]() {
  918. if (!weakThis)
  919. return;
  920. weakThis->bypassAction(!bypassed);
  921. }));
  922. // Duplicate
  923. menu->addChild(createMenuItem("Duplicate", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL), [=]() {
  924. if (!weakThis)
  925. return;
  926. weakThis->cloneAction(false);
  927. }));
  928. // Duplicate with cables
  929. menu->addChild(createMenuItem("└ with cables", widget::getKeyCommandName(GLFW_KEY_D, RACK_MOD_CTRL | GLFW_MOD_SHIFT), [=]() {
  930. if (!weakThis)
  931. return;
  932. weakThis->cloneAction(true);
  933. }));
  934. // Delete
  935. menu->addChild(createMenuItem("Delete", widget::getKeyCommandName(GLFW_KEY_BACKSPACE, 0) + "/" + widget::getKeyCommandName(GLFW_KEY_DELETE, 0), [=]() {
  936. if (!weakThis)
  937. return;
  938. weakThis->removeAction();
  939. }, false, true));
  940. // Zoom to fit
  941. menu->addChild(createMenuItem("Zoom to fit", widget::getKeyCommandName(GLFW_KEY_F4, RACK_MOD_CTRL), [=]() {
  942. if (!weakThis)
  943. return;
  944. APP->scene->rackScroll->zoomToBound(weakThis->getBox());
  945. }));
  946. appendContextMenu(menu);
  947. }
  948. math::Vec ModuleWidget::getGridPosition() {
  949. return ((getPosition() - RACK_OFFSET) / RACK_GRID_SIZE).round();
  950. }
  951. void ModuleWidget::setGridPosition(math::Vec pos) {
  952. setPosition(pos * RACK_GRID_SIZE + RACK_OFFSET);
  953. }
  954. math::Vec ModuleWidget::getGridSize() {
  955. return (getSize() / RACK_GRID_SIZE).round();
  956. }
  957. math::Rect ModuleWidget::getGridBox() {
  958. return math::Rect(getGridPosition(), getGridSize());
  959. }
  960. math::Vec& ModuleWidget::dragOffset() {
  961. return internal->dragOffset;
  962. }
  963. bool& ModuleWidget::dragEnabled() {
  964. return internal->dragEnabled;
  965. }
  966. engine::Module* ModuleWidget::releaseModule() {
  967. engine::Module* module = this->module;
  968. this->module = NULL;
  969. return module;
  970. }
  971. } // namespace app
  972. } // namespace rack