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.

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