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.

1115 lines
29KB

  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 <tag.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. bool selected = false;
  36. };
  37. ModuleWidget::ModuleWidget() {
  38. internal = new Internal;
  39. box.size = math::Vec(0, RACK_GRID_HEIGHT);
  40. }
  41. ModuleWidget::~ModuleWidget() {
  42. clearChildren();
  43. setModule(NULL);
  44. delete internal;
  45. }
  46. plugin::Model* ModuleWidget::getModel() {
  47. return model;
  48. }
  49. void ModuleWidget::setModel(plugin::Model* model) {
  50. assert(!this->model);
  51. this->model = model;
  52. }
  53. engine::Module* ModuleWidget::getModule() {
  54. return module;
  55. }
  56. void ModuleWidget::setModule(engine::Module* module) {
  57. if (this->module) {
  58. APP->engine->removeModule(this->module);
  59. delete this->module;
  60. this->module = NULL;
  61. }
  62. this->module = module;
  63. }
  64. void ModuleWidget::setPanel(widget::Widget* panel) {
  65. // Remove existing panel
  66. if (internal->panel) {
  67. removeChild(internal->panel);
  68. delete internal->panel;
  69. internal->panel = NULL;
  70. }
  71. if (panel) {
  72. addChildBottom(panel);
  73. internal->panel = panel;
  74. box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  75. }
  76. }
  77. void ModuleWidget::setPanel(std::shared_ptr<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::list<ParamWidget*> ModuleWidget::getParams() {
  141. std::list<ParamWidget*> pws;
  142. doIfTypeRecursive<ParamWidget>(this, [&](ParamWidget* pw) {
  143. pws.push_back(pw);
  144. });
  145. return pws;
  146. }
  147. std::list<PortWidget*> ModuleWidget::getPorts() {
  148. std::list<PortWidget*> pws;
  149. doIfTypeRecursive<PortWidget>(this, [&](PortWidget* pw) {
  150. pws.push_back(pw);
  151. });
  152. return pws;
  153. }
  154. std::list<PortWidget*> ModuleWidget::getInputs() {
  155. std::list<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::list<PortWidget*> ModuleWidget::getOutputs() {
  163. std::list<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. float meterMax = 0.f;
  183. float meterAvg = 0.f;
  184. for (int i = 0; i < meterLength; i++) {
  185. float m = meterBuffer[i];
  186. meterAvg += m;
  187. meterMax = std::max(meterMax, m);
  188. }
  189. meterAvg /= meterLength;
  190. float percentMax = meterMax * sampleRate;
  191. float mult = (percentMax <= 0.1f) ? 10.f : 1.f;
  192. // // Text background
  193. // nvgBeginPath(args.vg);
  194. // nvgRect(args.vg, 0.0, box.size.y - infoHeight, box.size.x, infoHeight);
  195. // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  196. // nvgFill(args.vg);
  197. // Draw time plot
  198. nvgBeginPath(args.vg);
  199. nvgMoveTo(args.vg, box.size.x, box.size.y);
  200. for (int i = 0; i < meterLength; i++) {
  201. int index = (meterIndex - i + meterLength) % meterLength;
  202. float percent = math::clamp(meterBuffer[index] * mult * sampleRate, 0.f, 1.f);
  203. math::Vec p;
  204. p.x = (1.f - (float) i / (meterLength - 1)) * box.size.x;
  205. p.y = (1.f - percent) * (box.size.y);
  206. nvgLineTo(args.vg, p.x, p.y);
  207. }
  208. NVGcolor color;
  209. if (mult == 1.f)
  210. color = nvgRGBAf(0.5, 0, 0, 0.85);
  211. else if (mult == 10.f)
  212. color = nvgRGBAf(0.85, 0, 0, 0.85);
  213. nvgLineTo(args.vg, 0.0, box.size.y);
  214. nvgClosePath(args.vg);
  215. nvgFillColor(args.vg, color);
  216. nvgFill(args.vg);
  217. // Text
  218. float percent = meterAvg * sampleRate * 100.f;
  219. float microseconds = meterAvg * 1e6f;
  220. std::string meterText = string::f("%.0fx\n%.2f μs\n%.1f%%", mult, microseconds, percent);
  221. bndLabel(args.vg, 0.0, box.size.y - 60, INFINITY, INFINITY, -1, meterText.c_str());
  222. // Draw border
  223. nvgStrokeColor(args.vg, color);
  224. nvgBeginPath(args.vg);
  225. nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
  226. nvgStrokeWidth(args.vg, 2.0);
  227. nvgStroke(args.vg);
  228. }
  229. // if (module) {
  230. // nvgBeginPath(args.vg);
  231. // nvgRect(args.vg, 0, 0, 20, 20);
  232. // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  233. // nvgFill(args.vg);
  234. // std::string debugText = string::f("%d", module->id);
  235. // bndLabel(args.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str());
  236. // }
  237. // Selection
  238. if (internal->selected) {
  239. nvgBeginPath(args.vg);
  240. nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size));
  241. nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25));
  242. nvgFill(args.vg);
  243. nvgStrokeWidth(args.vg, 2.0);
  244. nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5));
  245. nvgStroke(args.vg);
  246. }
  247. nvgResetScissor(args.vg);
  248. }
  249. void ModuleWidget::drawShadow(const DrawArgs& args) {
  250. nvgBeginPath(args.vg);
  251. float r = 20; // Blur radius
  252. float c = 20; // Corner radius
  253. math::Rect shadowBox = box.zeroPos().grow(math::Vec(10, -30));
  254. math::Rect shadowOutsideBox = shadowBox.grow(math::Vec(r, r));
  255. nvgRect(args.vg, RECT_ARGS(shadowOutsideBox));
  256. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  257. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  258. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, RECT_ARGS(shadowBox), c, r, shadowColor, transparentColor));
  259. nvgFill(args.vg);
  260. }
  261. void ModuleWidget::onHoverKey(const HoverKeyEvent& e) {
  262. OpaqueWidget::onHoverKey(e);
  263. if (e.isConsumed())
  264. return;
  265. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  266. if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  267. resetAction();
  268. e.consume(this);
  269. }
  270. if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  271. randomizeAction();
  272. e.consume(this);
  273. }
  274. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  275. copyClipboard();
  276. e.consume(this);
  277. }
  278. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  279. pasteClipboardAction();
  280. e.consume(this);
  281. }
  282. if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  283. cloneAction();
  284. e.consume(this);
  285. }
  286. if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  287. disconnectAction();
  288. e.consume(this);
  289. }
  290. if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  291. bypassAction();
  292. e.consume(this);
  293. }
  294. }
  295. if (e.action == RACK_HELD) {
  296. if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) {
  297. removeAction();
  298. e.consume(NULL);
  299. }
  300. }
  301. }
  302. void ModuleWidget::onButton(const ButtonEvent& e) {
  303. OpaqueWidget::onButton(e);
  304. if (e.getTarget() == this) {
  305. // Set starting drag position
  306. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  307. internal->dragOffset = e.pos;
  308. }
  309. // Toggle selection on Shift-click
  310. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  311. internal->selected ^= true;
  312. }
  313. }
  314. if (!e.isConsumed()) {
  315. // Open context menu on right-click
  316. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  317. if (internal->selected) {
  318. createSelectionContextMenu();
  319. }
  320. else {
  321. createContextMenu();
  322. }
  323. e.consume(this);
  324. }
  325. }
  326. }
  327. void ModuleWidget::onDragStart(const DragStartEvent& e) {
  328. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  329. // HACK Disable FramebufferWidget redrawing subpixels while dragging
  330. APP->window->fbDirtyOnSubpixelChange() = false;
  331. // Clear dragRack so dragging in not enabled until mouse is moved a bit.
  332. internal->dragRackPos = math::Vec(NAN, NAN);
  333. // Prepare initial position of modules for history.
  334. APP->scene->rack->updateModuleOldPositions();
  335. }
  336. }
  337. void ModuleWidget::onDragEnd(const DragEndEvent& e) {
  338. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  339. APP->window->fbDirtyOnSubpixelChange() = true;
  340. // The next time the module is dragged, it should always move immediately
  341. internal->dragEnabled = true;
  342. history::ComplexAction* h = APP->scene->rack->getModuleDragAction();
  343. if (!h)
  344. return;
  345. APP->history->push(h);
  346. }
  347. }
  348. void ModuleWidget::onDragMove(const DragMoveEvent& e) {
  349. if (e.button == GLFW_MOUSE_BUTTON_LEFT) {
  350. if (!settings::lockModules) {
  351. math::Vec mousePos = APP->scene->rack->getMousePos();
  352. if (!internal->dragEnabled) {
  353. // Set dragRackPos on the first time after dragging
  354. if (!internal->dragRackPos.isFinite())
  355. internal->dragRackPos = mousePos;
  356. // Check if the mouse has moved enough to start dragging the module.
  357. const float minDist = RACK_GRID_WIDTH;
  358. if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2))
  359. internal->dragEnabled = true;
  360. }
  361. // Move module
  362. if (internal->dragEnabled) {
  363. // Round y coordinate to nearest rack height
  364. math::Vec pos = mousePos;
  365. pos.x -= internal->dragOffset.x;
  366. pos.y -= RACK_GRID_HEIGHT / 2;
  367. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  368. APP->scene->rack->setModulePosForce(this, pos);
  369. else
  370. APP->scene->rack->setModulePosNearest(this, pos);
  371. }
  372. }
  373. }
  374. }
  375. json_t* ModuleWidget::toJson() {
  376. json_t* moduleJ = APP->engine->moduleToJson(module);
  377. return moduleJ;
  378. }
  379. void ModuleWidget::fromJson(json_t* rootJ) {
  380. APP->engine->moduleFromJson(module, rootJ);
  381. }
  382. void ModuleWidget::copyClipboard() {
  383. json_t* moduleJ = toJson();
  384. DEFER({json_decref(moduleJ);});
  385. char* moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  386. DEFER({std::free(moduleJson);});
  387. glfwSetClipboardString(APP->window->win, moduleJson);
  388. }
  389. void ModuleWidget::pasteClipboardAction() {
  390. const char* moduleJson = glfwGetClipboardString(APP->window->win);
  391. if (!moduleJson) {
  392. WARN("Could not get text from clipboard.");
  393. return;
  394. }
  395. json_error_t error;
  396. json_t* moduleJ = json_loads(moduleJson, 0, &error);
  397. if (!moduleJ) {
  398. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  399. return;
  400. }
  401. DEFER({json_decref(moduleJ);});
  402. // Don't use IDs from JSON
  403. json_object_del(moduleJ, "id");
  404. json_object_del(moduleJ, "leftModuleId");
  405. json_object_del(moduleJ, "rightModuleId");
  406. json_t* oldModuleJ = toJson();
  407. try {
  408. fromJson(moduleJ);
  409. }
  410. catch (Exception& e) {
  411. WARN("%s", e.what());
  412. json_decref(oldModuleJ);
  413. return;
  414. }
  415. // history::ModuleChange
  416. history::ModuleChange* h = new history::ModuleChange;
  417. h->name = "paste module preset";
  418. h->moduleId = module->id;
  419. h->oldModuleJ = oldModuleJ;
  420. h->newModuleJ = toJson();
  421. APP->history->push(h);
  422. }
  423. void ModuleWidget::load(std::string filename) {
  424. FILE* file = std::fopen(filename.c_str(), "r");
  425. if (!file)
  426. throw Exception("Could not load patch file %s", filename.c_str());
  427. DEFER({std::fclose(file);});
  428. INFO("Loading preset %s", filename.c_str());
  429. json_error_t error;
  430. json_t* moduleJ = json_loadf(file, 0, &error);
  431. if (!moduleJ)
  432. 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);
  433. DEFER({json_decref(moduleJ);});
  434. // Don't use IDs from JSON
  435. json_object_del(moduleJ, "id");
  436. json_object_del(moduleJ, "leftModuleId");
  437. json_object_del(moduleJ, "rightModuleId");
  438. fromJson(moduleJ);
  439. }
  440. void ModuleWidget::loadAction(std::string filename) {
  441. // history::ModuleChange
  442. history::ModuleChange* h = new history::ModuleChange;
  443. h->name = "load module preset";
  444. h->moduleId = module->id;
  445. h->oldModuleJ = toJson();
  446. try {
  447. load(filename);
  448. }
  449. catch (Exception& e) {
  450. delete h;
  451. throw;
  452. }
  453. h->newModuleJ = toJson();
  454. APP->history->push(h);
  455. }
  456. void ModuleWidget::loadTemplate() {
  457. std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm");
  458. try {
  459. load(templatePath);
  460. }
  461. catch (Exception& e) {
  462. // Do nothing
  463. }
  464. }
  465. void ModuleWidget::loadDialog() {
  466. std::string presetDir = model->getUserPresetDirectory();
  467. system::createDirectories(presetDir);
  468. // Delete directories if empty
  469. DEFER({
  470. try {
  471. system::remove(presetDir);
  472. system::remove(system::getDirectory(presetDir));
  473. }
  474. catch (Exception& e) {
  475. // Ignore exceptions if directory cannot be removed.
  476. }
  477. });
  478. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  479. DEFER({osdialog_filters_free(filters);});
  480. char* pathC = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters);
  481. if (!pathC) {
  482. // No path selected
  483. return;
  484. }
  485. DEFER({std::free(pathC);});
  486. try {
  487. loadAction(pathC);
  488. }
  489. catch (Exception& e) {
  490. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  491. }
  492. }
  493. void ModuleWidget::save(std::string filename) {
  494. INFO("Saving preset %s", filename.c_str());
  495. json_t* moduleJ = toJson();
  496. assert(moduleJ);
  497. DEFER({json_decref(moduleJ);});
  498. FILE* file = std::fopen(filename.c_str(), "w");
  499. if (!file) {
  500. std::string message = string::f("Could not save preset to file %s", filename.c_str());
  501. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  502. return;
  503. }
  504. DEFER({std::fclose(file);});
  505. json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  506. }
  507. void ModuleWidget::saveTemplate() {
  508. std::string presetDir = model->getUserPresetDirectory();
  509. system::createDirectories(presetDir);
  510. std::string templatePath = system::join(presetDir, "template.vcvm");
  511. save(templatePath);
  512. }
  513. void ModuleWidget::saveTemplateDialog() {
  514. if (hasTemplate()) {
  515. std::string message = string::f("Overwrite template preset for %s?", model->getFullName().c_str());
  516. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  517. return;
  518. }
  519. saveTemplate();
  520. }
  521. bool ModuleWidget::hasTemplate() {
  522. std::string presetDir = model->getUserPresetDirectory();
  523. std::string templatePath = system::join(presetDir, "template.vcvm");
  524. return system::exists(templatePath);;
  525. }
  526. void ModuleWidget::clearTemplate() {
  527. std::string presetDir = model->getUserPresetDirectory();
  528. std::string templatePath = system::join(presetDir, "template.vcvm");
  529. system::remove(templatePath);
  530. }
  531. void ModuleWidget::clearTemplateDialog() {
  532. std::string message = string::f("Delete template preset for %s?", model->getFullName().c_str());
  533. if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str()))
  534. return;
  535. clearTemplate();
  536. }
  537. void ModuleWidget::saveDialog() {
  538. std::string presetDir = model->getUserPresetDirectory();
  539. system::createDirectories(presetDir);
  540. // Delete directories if empty
  541. DEFER({
  542. try {
  543. system::remove(presetDir);
  544. system::remove(system::getDirectory(presetDir));
  545. }
  546. catch (Exception& e) {
  547. // Ignore exceptions if directory cannot be removed.
  548. }
  549. });
  550. osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS);
  551. DEFER({osdialog_filters_free(filters);});
  552. char* pathC = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters);
  553. if (!pathC) {
  554. // No path selected
  555. return;
  556. }
  557. DEFER({std::free(pathC);});
  558. std::string path = pathC;
  559. // Automatically append .vcvm extension
  560. if (system::getExtension(path) != ".vcvm")
  561. path += ".vcvm";
  562. save(path);
  563. }
  564. void ModuleWidget::disconnect() {
  565. for (PortWidget* pw : getPorts()) {
  566. APP->scene->rack->clearCablesOnPort(pw);
  567. }
  568. }
  569. void ModuleWidget::resetAction() {
  570. assert(module);
  571. // history::ModuleChange
  572. history::ModuleChange* h = new history::ModuleChange;
  573. h->name = "reset module";
  574. h->moduleId = module->id;
  575. h->oldModuleJ = toJson();
  576. APP->engine->resetModule(module);
  577. h->newModuleJ = toJson();
  578. APP->history->push(h);
  579. }
  580. void ModuleWidget::randomizeAction() {
  581. assert(module);
  582. // history::ModuleChange
  583. history::ModuleChange* h = new history::ModuleChange;
  584. h->name = "randomize module";
  585. h->moduleId = module->id;
  586. h->oldModuleJ = toJson();
  587. APP->engine->randomizeModule(module);
  588. h->newModuleJ = toJson();
  589. APP->history->push(h);
  590. }
  591. void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) {
  592. for (PortWidget* pw : getPorts()) {
  593. for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) {
  594. if (!cw->isComplete())
  595. continue;
  596. // history::CableRemove
  597. history::CableRemove* h = new history::CableRemove;
  598. h->setCable(cw);
  599. complexAction->push(h);
  600. // Delete cable
  601. APP->scene->rack->removeCable(cw);
  602. delete cw;
  603. }
  604. };
  605. }
  606. void ModuleWidget::disconnectAction() {
  607. history::ComplexAction* complexAction = new history::ComplexAction;
  608. complexAction->name = "disconnect cables";
  609. appendDisconnectActions(complexAction);
  610. if (!complexAction->isEmpty())
  611. APP->history->push(complexAction);
  612. else
  613. delete complexAction;
  614. }
  615. void ModuleWidget::cloneAction() {
  616. // history::ComplexAction
  617. history::ComplexAction* h = new history::ComplexAction;
  618. // JSON serialization is the obvious way to do this
  619. json_t* moduleJ = toJson();
  620. // Don't use IDs from JSON
  621. json_object_del(moduleJ, "id");
  622. json_object_del(moduleJ, "leftModuleId");
  623. json_object_del(moduleJ, "rightModuleId");
  624. // Clone Module
  625. engine::Module* clonedModule = model->createModule();
  626. // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet.
  627. try {
  628. clonedModule->fromJson(moduleJ);
  629. }
  630. catch (Exception& e) {
  631. WARN("%s", e.what());
  632. }
  633. json_decref(moduleJ);
  634. APP->engine->addModule(clonedModule);
  635. // Clone ModuleWidget
  636. ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
  637. APP->scene->rack->addModuleAtMouse(clonedModuleWidget);
  638. // history::ModuleAdd
  639. history::ModuleAdd* hma = new history::ModuleAdd;
  640. hma->name = "clone modules";
  641. hma->setModule(clonedModuleWidget);
  642. h->push(hma);
  643. // Clone cables attached to input ports
  644. for (PortWidget* pw : getInputs()) {
  645. for (CableWidget* cw : APP->scene->rack->getCablesOnPort(pw)) {
  646. // Create cable attached to cloned ModuleWidget's input
  647. engine::Cable* clonedCable = new engine::Cable;
  648. clonedCable->id = -1;
  649. clonedCable->inputModule = clonedModule;
  650. clonedCable->inputId = cw->cable->inputId;
  651. // If cable is self-patched, attach to cloned module instead
  652. if (cw->cable->outputModule == module)
  653. clonedCable->outputModule = clonedModule;
  654. else
  655. clonedCable->outputModule = cw->cable->outputModule;
  656. clonedCable->outputId = cw->cable->outputId;
  657. APP->engine->addCable(clonedCable);
  658. app::CableWidget* clonedCw = new app::CableWidget;
  659. clonedCw->setCable(clonedCable);
  660. clonedCw->color = cw->color;
  661. APP->scene->rack->addCable(clonedCw);
  662. // history::CableAdd
  663. history::CableAdd* hca = new history::CableAdd;
  664. hca->setCable(clonedCw);
  665. h->push(hca);
  666. }
  667. }
  668. APP->history->push(h);
  669. }
  670. void ModuleWidget::bypassAction() {
  671. assert(module);
  672. APP->engine->bypassModule(module, !module->isBypassed());
  673. // history::ModuleBypass
  674. history::ModuleBypass* h = new history::ModuleBypass;
  675. h->moduleId = module->id;
  676. h->bypassed = module->isBypassed();
  677. APP->history->push(h);
  678. }
  679. void ModuleWidget::removeAction() {
  680. history::ComplexAction* complexAction = new history::ComplexAction;
  681. complexAction->name = "remove module";
  682. appendDisconnectActions(complexAction);
  683. // history::ModuleRemove
  684. history::ModuleRemove* moduleRemove = new history::ModuleRemove;
  685. moduleRemove->setModule(this);
  686. complexAction->push(moduleRemove);
  687. APP->history->push(complexAction);
  688. // This removes the module and transfers ownership to caller
  689. APP->scene->rack->removeModule(this);
  690. delete this;
  691. }
  692. // Create ModulePresetPathItems for each patch in a directory.
  693. static void appendPresetItems(ui::Menu* menu, WeakPtr<ModuleWidget> moduleWidget, std::string presetDir) {
  694. bool hasPresets = false;
  695. // Note: This is not cached, so opening this menu each time might have a bit of latency.
  696. if (system::isDirectory(presetDir)) {
  697. for (std::string path : system::getEntries(presetDir)) {
  698. std::string name = system::getStem(path);
  699. // Remove "1_", "42_", "001_", etc at the beginning of preset filenames
  700. std::regex r("^\\d*_");
  701. name = std::regex_replace(name, r, "");
  702. if (system::isDirectory(path)) {
  703. hasPresets = true;
  704. menu->addChild(createSubmenuItem(name, [=](ui::Menu* menu) {
  705. if (!moduleWidget)
  706. return;
  707. appendPresetItems(menu, moduleWidget, path);
  708. }));
  709. }
  710. else if (system::getExtension(path) == ".vcvm") {
  711. hasPresets = true;
  712. menu->addChild(createMenuItem(name, "", [=]() {
  713. if (!moduleWidget)
  714. return;
  715. try {
  716. moduleWidget->loadAction(path);
  717. }
  718. catch (Exception& e) {
  719. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what());
  720. }
  721. }));
  722. }
  723. }
  724. }
  725. if (!hasPresets) {
  726. menu->addChild(createMenuLabel("(None)"));
  727. }
  728. };
  729. void ModuleWidget::createContextMenu() {
  730. ui::Menu* menu = createMenu();
  731. assert(model);
  732. WeakPtr<ModuleWidget> weakThis = this;
  733. // Brand and module name
  734. menu->addChild(createMenuLabel(model->plugin->brand + " " + model->name));
  735. // Info
  736. menu->addChild(createSubmenuItem("Info", [=](ui::Menu* menu) {
  737. if (!weakThis)
  738. return;
  739. // plugin
  740. menu->addChild(createMenuItem("Plugin: " + model->plugin->name, "", [=]() {
  741. system::openBrowser(model->plugin->pluginUrl);
  742. }, model->plugin->pluginUrl == ""));
  743. // version
  744. menu->addChild(createMenuLabel(model->plugin->version));
  745. // author
  746. if (model->plugin->author != "") {
  747. menu->addChild(createMenuItem("Author: " + model->plugin->author, "", [=]() {
  748. system::openBrowser(model->plugin->authorUrl);
  749. }, model->plugin->authorUrl.empty()));
  750. }
  751. // license
  752. std::string license = model->plugin->license;
  753. if (string::startsWith(license, "https://") || string::startsWith(license, "http://")) {
  754. menu->addChild(createMenuItem("License: Open in browser", "", [=]() {
  755. system::openBrowser(license);
  756. }));
  757. }
  758. else if (license != "") {
  759. menu->addChild(createMenuLabel("License: " + license));
  760. }
  761. // tags
  762. if (!model->tagIds.empty()) {
  763. menu->addChild(createMenuLabel("Tags:"));
  764. for (int tagId : model->tagIds) {
  765. menu->addChild(createMenuLabel("• " + tag::getTag(tagId)));
  766. }
  767. }
  768. menu->addChild(new ui::MenuSeparator);
  769. // VCV Library page
  770. menu->addChild(createMenuItem("VCV Library page", "", [=]() {
  771. system::openBrowser("https://library.vcvrack.com/" + model->plugin->slug + "/" + model->slug);
  772. }));
  773. // modularGridUrl
  774. if (model->modularGridUrl != "") {
  775. menu->addChild(createMenuItem("ModularGrid page", "", [=]() {
  776. system::openBrowser(model->modularGridUrl);
  777. }));
  778. }
  779. // manual
  780. std::string manualUrl = (model->manualUrl != "") ? model->manualUrl : model->plugin->manualUrl;
  781. if (manualUrl != "") {
  782. menu->addChild(createMenuItem("User manual", "", [=]() {
  783. system::openBrowser(manualUrl);
  784. }));
  785. }
  786. // donate
  787. if (model->plugin->donateUrl != "") {
  788. menu->addChild(createMenuItem("Donate", "", [=]() {
  789. system::openBrowser(model->plugin->donateUrl);
  790. }));
  791. }
  792. // source code
  793. if (model->plugin->sourceUrl != "") {
  794. menu->addChild(createMenuItem("Source code", "", [=]() {
  795. system::openBrowser(model->plugin->sourceUrl);
  796. }));
  797. }
  798. // changelog
  799. if (model->plugin->changelogUrl != "") {
  800. menu->addChild(createMenuItem("Changelog", "", [=]() {
  801. system::openBrowser(model->plugin->changelogUrl);
  802. }));
  803. }
  804. // plugin folder
  805. if (model->plugin->path != "") {
  806. menu->addChild(createMenuItem("Open plugin folder", "", [=]() {
  807. system::openDirectory(model->plugin->path);
  808. }));
  809. }
  810. }));
  811. // Preset
  812. menu->addChild(createSubmenuItem("Preset", [=](ui::Menu* menu) {
  813. menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() {
  814. if (!weakThis)
  815. return;
  816. weakThis->copyClipboard();
  817. }));
  818. menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() {
  819. if (!weakThis)
  820. return;
  821. weakThis->pasteClipboardAction();
  822. }));
  823. menu->addChild(createMenuItem("Open", "", [=]() {
  824. if (!weakThis)
  825. return;
  826. weakThis->loadDialog();
  827. }));
  828. menu->addChild(createMenuItem("Save as", "", [=]() {
  829. if (!weakThis)
  830. return;
  831. weakThis->saveDialog();
  832. }));
  833. menu->addChild(createMenuItem("Save template", "", [=]() {
  834. if (!weakThis)
  835. return;
  836. weakThis->saveTemplateDialog();
  837. }));
  838. menu->addChild(createMenuItem("Clear template", "", [=]() {
  839. if (!weakThis)
  840. return;
  841. weakThis->clearTemplateDialog();
  842. }, !weakThis->hasTemplate()));
  843. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  844. menu->addChild(new ui::MenuSeparator);
  845. menu->addChild(createMenuLabel("User presets"));
  846. appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());
  847. // Scan `<plugin dir>/presets/<module slug>` for presets.
  848. menu->addChild(new ui::MenuSeparator);
  849. menu->addChild(createMenuLabel("Factory presets"));
  850. appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
  851. }));
  852. // Initialize
  853. menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() {
  854. if (!weakThis)
  855. return;
  856. weakThis->resetAction();
  857. }));
  858. // Randomize
  859. menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() {
  860. if (!weakThis)
  861. return;
  862. weakThis->randomizeAction();
  863. }));
  864. menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() {
  865. if (!weakThis)
  866. return;
  867. weakThis->disconnectAction();
  868. }));
  869. menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() {
  870. if (!weakThis)
  871. return;
  872. weakThis->cloneAction();
  873. }));
  874. std::string bypassText = RACK_MOD_CTRL_NAME "+E";
  875. if (module && module->isBypassed())
  876. bypassText += " " CHECKMARK_STRING;
  877. menu->addChild(createMenuItem("Bypass", bypassText, [=]() {
  878. if (!weakThis)
  879. return;
  880. weakThis->bypassAction();
  881. }));
  882. menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() {
  883. if (!weakThis)
  884. return;
  885. weakThis->removeAction();
  886. }));
  887. appendContextMenu(menu);
  888. }
  889. void ModuleWidget::createSelectionContextMenu() {
  890. ui::Menu* menu = createMenu();
  891. int n = APP->scene->rack->getNumSelectedModules();
  892. menu->addChild(createMenuLabel(string::f("%d selected %s", n, n == 1 ? "module" : "modules")));
  893. // Initialize
  894. menu->addChild(createMenuItem("Initialize", "", [=]() {
  895. APP->scene->rack->resetSelectedModulesAction();
  896. }));
  897. // Randomize
  898. menu->addChild(createMenuItem("Randomize", "", [=]() {
  899. APP->scene->rack->randomizeSelectedModulesAction();
  900. }));
  901. // Disconnect cables
  902. menu->addChild(createMenuItem("Disconnect cables", "", [=]() {
  903. APP->scene->rack->disconnectSelectedModulesAction();
  904. }));
  905. // Bypass
  906. menu->addChild(createMenuItem("Bypass", "", [=]() {
  907. APP->scene->rack->bypassSelectedModulesAction();
  908. }));
  909. // Delete
  910. menu->addChild(createMenuItem("Delete", "", [=]() {
  911. APP->scene->rack->deleteSelectedModulesAction();
  912. }));
  913. }
  914. math::Vec& ModuleWidget::dragOffset() {
  915. return internal->dragOffset;
  916. }
  917. bool& ModuleWidget::dragEnabled() {
  918. return internal->dragEnabled;
  919. }
  920. math::Vec& ModuleWidget::oldPos() {
  921. return internal->oldPos;
  922. }
  923. engine::Module* ModuleWidget::releaseModule() {
  924. engine::Module* module = this->module;
  925. this->module = NULL;
  926. return module;
  927. }
  928. bool& ModuleWidget::selected() {
  929. return internal->selected;
  930. }
  931. } // namespace app
  932. } // namespace rack