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.

864 lines
21KB

  1. #include "app/ModuleWidget.hpp"
  2. #include "app/Scene.hpp"
  3. #include "engine/Engine.hpp"
  4. #include "plugin/Plugin.hpp"
  5. #include "system.hpp"
  6. #include "asset.hpp"
  7. #include "helpers.hpp"
  8. #include "app.hpp"
  9. #include "settings.hpp"
  10. #include "history.hpp"
  11. #include "osdialog.h"
  12. #include <thread>
  13. namespace rack {
  14. namespace app {
  15. static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm";
  16. struct ModuleUrlItem : ui::MenuItem {
  17. std::string url;
  18. void onAction(const event::Action &e) override {
  19. std::thread t(system::openBrowser, url);
  20. t.detach();
  21. }
  22. };
  23. struct ModuleFolderItem : ui::MenuItem {
  24. std::string path;
  25. void onAction(const event::Action &e) override {
  26. std::thread t(system::openFolder, path);
  27. t.detach();
  28. }
  29. };
  30. struct ModulePluginItem : ui::MenuItem {
  31. plugin::Plugin *plugin;
  32. ui::Menu *createChildMenu() override {
  33. ui::Menu *menu = new ui::Menu;
  34. ui::MenuLabel *pluginLabel = new ui::MenuLabel;
  35. pluginLabel->text = plugin->name;
  36. menu->addChild(pluginLabel);
  37. ui::MenuLabel *versionLabel = new ui::MenuLabel;
  38. versionLabel->text = "v" + plugin->version;
  39. menu->addChild(versionLabel);
  40. if (!plugin->author.empty()) {
  41. if (!plugin->authorUrl.empty()) {
  42. ModuleUrlItem *authorItem = new ModuleUrlItem;
  43. authorItem->text = plugin->author;
  44. authorItem->url = plugin->authorUrl;
  45. menu->addChild(authorItem);
  46. }
  47. else {
  48. ui::MenuLabel *authorLabel = new ui::MenuLabel;
  49. authorLabel->text = plugin->author;
  50. menu->addChild(authorLabel);
  51. }
  52. }
  53. if (!plugin->pluginUrl.empty()) {
  54. ModuleUrlItem *websiteItem = new ModuleUrlItem;
  55. websiteItem->text = "Website";
  56. websiteItem->url = plugin->pluginUrl;
  57. menu->addChild(websiteItem);
  58. }
  59. if (!plugin->manualUrl.empty()) {
  60. ModuleUrlItem *manualItem = new ModuleUrlItem;
  61. manualItem->text = "Manual";
  62. manualItem->url = plugin->manualUrl;
  63. menu->addChild(manualItem);
  64. }
  65. if (!plugin->sourceUrl.empty()) {
  66. ModuleUrlItem *sourceItem = new ModuleUrlItem;
  67. sourceItem->text = "Source code";
  68. sourceItem->url = plugin->sourceUrl;
  69. menu->addChild(sourceItem);
  70. }
  71. if (!plugin->donateUrl.empty()) {
  72. ModuleUrlItem *donateItem = new ModuleUrlItem;
  73. donateItem->text = "Donate";
  74. donateItem->url = plugin->donateUrl;
  75. menu->addChild(donateItem);
  76. }
  77. if (!plugin->path.empty()) {
  78. ModuleFolderItem *pathItem = new ModuleFolderItem;
  79. pathItem->text = "Open plugin folder";
  80. pathItem->path = plugin->path;
  81. menu->addChild(pathItem);
  82. }
  83. return menu;
  84. }
  85. };
  86. struct ModuleDisconnectItem : ui::MenuItem {
  87. ModuleWidget *moduleWidget;
  88. void onAction(const event::Action &e) override {
  89. moduleWidget->disconnectAction();
  90. }
  91. };
  92. struct ModuleResetItem : ui::MenuItem {
  93. ModuleWidget *moduleWidget;
  94. void onAction(const event::Action &e) override {
  95. moduleWidget->resetAction();
  96. }
  97. };
  98. struct ModuleRandomizeItem : ui::MenuItem {
  99. ModuleWidget *moduleWidget;
  100. void onAction(const event::Action &e) override {
  101. moduleWidget->randomizeAction();
  102. }
  103. };
  104. struct ModuleCopyItem : ui::MenuItem {
  105. ModuleWidget *moduleWidget;
  106. void onAction(const event::Action &e) override {
  107. moduleWidget->copyClipboard();
  108. }
  109. };
  110. struct ModulePasteItem : ui::MenuItem {
  111. ModuleWidget *moduleWidget;
  112. void onAction(const event::Action &e) override {
  113. moduleWidget->pasteClipboardAction();
  114. }
  115. };
  116. struct ModuleSaveItem : ui::MenuItem {
  117. ModuleWidget *moduleWidget;
  118. void onAction(const event::Action &e) override {
  119. moduleWidget->saveDialog();
  120. }
  121. };
  122. struct ModuleLoadItem : ui::MenuItem {
  123. ModuleWidget *moduleWidget;
  124. void onAction(const event::Action &e) override {
  125. moduleWidget->loadDialog();
  126. }
  127. };
  128. struct ModulePresetPathItem : ui::MenuItem {
  129. ModuleWidget *moduleWidget;
  130. std::string presetPath;
  131. void onAction(const event::Action &e) override {
  132. moduleWidget->loadAction(presetPath);
  133. }
  134. };
  135. struct ModulePresetItem : ui::MenuItem {
  136. ModuleWidget *moduleWidget;
  137. ui::Menu *createChildMenu() override {
  138. ui::Menu *menu = new ui::Menu;
  139. ModuleCopyItem *copyItem = new ModuleCopyItem;
  140. copyItem->text = "Copy";
  141. copyItem->rightText = RACK_MOD_CTRL_NAME "+C";
  142. copyItem->moduleWidget = moduleWidget;
  143. menu->addChild(copyItem);
  144. ModulePasteItem *pasteItem = new ModulePasteItem;
  145. pasteItem->text = "Paste";
  146. pasteItem->rightText = RACK_MOD_CTRL_NAME "+V";
  147. pasteItem->moduleWidget = moduleWidget;
  148. menu->addChild(pasteItem);
  149. ModuleLoadItem *loadItem = new ModuleLoadItem;
  150. loadItem->text = "Open";
  151. loadItem->moduleWidget = moduleWidget;
  152. menu->addChild(loadItem);
  153. ModuleSaveItem *saveItem = new ModuleSaveItem;
  154. saveItem->text = "Save as";
  155. saveItem->moduleWidget = moduleWidget;
  156. menu->addChild(saveItem);
  157. if (!moduleWidget->model->presetPaths.empty()) {
  158. menu->addChild(new MenuEntry);
  159. menu->addChild(createMenuLabel("Factory presets"));
  160. for (const std::string &presetPath : moduleWidget->model->presetPaths) {
  161. ModulePresetPathItem *presetItem = new ModulePresetPathItem;
  162. std::string presetName = string::filenameBase(string::filename(presetPath));
  163. presetItem->text = presetName;
  164. presetItem->presetPath = presetPath;
  165. presetItem->moduleWidget = moduleWidget;
  166. menu->addChild(presetItem);
  167. }
  168. }
  169. return menu;
  170. }
  171. };
  172. struct ModuleCloneItem : ui::MenuItem {
  173. ModuleWidget *moduleWidget;
  174. void onAction(const event::Action &e) override {
  175. moduleWidget->cloneAction();
  176. }
  177. };
  178. struct ModuleBypassItem : ui::MenuItem {
  179. ModuleWidget *moduleWidget;
  180. void onAction(const event::Action &e) override {
  181. moduleWidget->bypassAction();
  182. }
  183. };
  184. struct ModuleDeleteItem : ui::MenuItem {
  185. ModuleWidget *moduleWidget;
  186. void onAction(const event::Action &e) override {
  187. moduleWidget->removeAction();
  188. }
  189. };
  190. ModuleWidget::ModuleWidget() {
  191. box.size = math::Vec(0, RACK_GRID_HEIGHT);
  192. panel = new SvgPanel;
  193. addChild(panel);
  194. }
  195. ModuleWidget::~ModuleWidget() {
  196. setModule(NULL);
  197. }
  198. void ModuleWidget::draw(const DrawArgs &args) {
  199. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  200. if (module && module->bypass) {
  201. nvgGlobalAlpha(args.vg, 0.33);
  202. }
  203. Widget::draw(args);
  204. // Power meter
  205. if (module && settings::cpuMeter) {
  206. nvgBeginPath(args.vg);
  207. nvgRect(args.vg,
  208. 0, box.size.y - 20,
  209. 105, 20);
  210. nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  211. nvgFill(args.vg);
  212. std::string cpuText = string::f("%.2f μs %.1f%%", module->cpuTime * 1e6f, module->cpuTime * APP->engine->getSampleRate() * 100);
  213. bndLabel(args.vg, 2.0, box.size.y - 20.0, INFINITY, INFINITY, -1, cpuText.c_str());
  214. float p = math::clamp(module->cpuTime / APP->engine->getSampleTime(), 0.f, 1.f);
  215. nvgBeginPath(args.vg);
  216. nvgRect(args.vg,
  217. 0, (1.f - p) * box.size.y,
  218. 5, p * box.size.y);
  219. nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 1.0));
  220. nvgFill(args.vg);
  221. }
  222. // if (module) {
  223. // nvgBeginPath(args.vg);
  224. // nvgRect(args.vg, 0, 0, 20, 20);
  225. // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75));
  226. // nvgFill(args.vg);
  227. // std::string debugText = string::f("%d", module->id);
  228. // bndLabel(args.vg, 0, 0, INFINITY, INFINITY, -1, debugText.c_str());
  229. // }
  230. nvgResetScissor(args.vg);
  231. }
  232. void ModuleWidget::drawShadow(const DrawArgs &args) {
  233. nvgBeginPath(args.vg);
  234. float r = 20; // Blur radius
  235. float c = 20; // Corner radius
  236. math::Vec b = math::Vec(-10, 30); // Offset from each corner
  237. nvgRect(args.vg, b.x - r, b.y - r, box.size.x - 2*b.x + 2*r, box.size.y - 2*b.y + 2*r);
  238. NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2);
  239. NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0);
  240. nvgFillPaint(args.vg, nvgBoxGradient(args.vg, b.x, b.y, box.size.x - 2*b.x, box.size.y - 2*b.y, c, r, shadowColor, transparentColor));
  241. nvgFill(args.vg);
  242. }
  243. void ModuleWidget::onButton(const event::Button &e) {
  244. OpaqueWidget::onButton(e);
  245. if (e.isConsumed())
  246. return;
  247. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  248. createContextMenu();
  249. e.consume(this);
  250. }
  251. }
  252. void ModuleWidget::onHoverKey(const event::HoverKey &e) {
  253. OpaqueWidget::onHoverKey(e);
  254. if (e.isConsumed())
  255. return;
  256. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  257. switch (e.key) {
  258. case GLFW_KEY_I: {
  259. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  260. resetAction();
  261. e.consume(this);
  262. }
  263. } break;
  264. case GLFW_KEY_R: {
  265. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  266. randomizeAction();
  267. e.consume(this);
  268. }
  269. } break;
  270. case GLFW_KEY_C: {
  271. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  272. copyClipboard();
  273. e.consume(this);
  274. }
  275. } break;
  276. case GLFW_KEY_V: {
  277. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  278. pasteClipboardAction();
  279. e.consume(this);
  280. }
  281. } break;
  282. case GLFW_KEY_D: {
  283. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  284. cloneAction();
  285. e.consume(this);
  286. }
  287. } break;
  288. case GLFW_KEY_U: {
  289. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  290. disconnectAction();
  291. e.consume(this);
  292. }
  293. } break;
  294. case GLFW_KEY_E: {
  295. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  296. bypassAction();
  297. e.consume(this);
  298. }
  299. } break;
  300. }
  301. }
  302. if (e.action == RACK_HELD) {
  303. switch (e.key) {
  304. case GLFW_KEY_DELETE:
  305. case GLFW_KEY_BACKSPACE: {
  306. if ((e.mods & RACK_MOD_MASK) == 0) {
  307. removeAction();
  308. e.consume(NULL);
  309. }
  310. } break;
  311. }
  312. }
  313. }
  314. void ModuleWidget::onDragStart(const event::DragStart &e) {
  315. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  316. return;
  317. oldPos = box.pos;
  318. dragPos = APP->scene->rack->mousePos.minus(box.pos);
  319. APP->scene->rack->updateModuleDragPositions();
  320. }
  321. void ModuleWidget::onDragEnd(const event::DragEnd &e) {
  322. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  323. return;
  324. history::ComplexAction *h = APP->scene->rack->getModuleDragAction();
  325. if (!h) {
  326. delete h;
  327. return;
  328. }
  329. APP->history->push(h);
  330. }
  331. void ModuleWidget::onDragMove(const event::DragMove &e) {
  332. if (e.button != GLFW_MOUSE_BUTTON_LEFT)
  333. return;
  334. if (!settings::lockModules) {
  335. math::Vec pos = APP->scene->rack->mousePos.minus(dragPos);
  336. if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL)
  337. APP->scene->rack->setModulePosForce(this, pos);
  338. else
  339. APP->scene->rack->setModulePosNearest(this, pos);
  340. }
  341. }
  342. void ModuleWidget::setModule(engine::Module *module) {
  343. if (this->module) {
  344. delete this->module;
  345. }
  346. this->module = module;
  347. }
  348. void ModuleWidget::setPanel(std::shared_ptr<Svg> svg) {
  349. assert(panel);
  350. panel->setBackground(svg);
  351. // Set ModuleWidget size based on panel
  352. box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH;
  353. }
  354. void ModuleWidget::addParam(ParamWidget *param) {
  355. params.push_back(param);
  356. addChild(param);
  357. }
  358. void ModuleWidget::addOutput(PortWidget *output) {
  359. assert(output->type == PortWidget::OUTPUT);
  360. outputs.push_back(output);
  361. addChild(output);
  362. }
  363. void ModuleWidget::addInput(PortWidget *input) {
  364. assert(input->type == PortWidget::INPUT);
  365. inputs.push_back(input);
  366. addChild(input);
  367. }
  368. ParamWidget *ModuleWidget::getParam(int paramId) {
  369. for (ParamWidget *param : params) {
  370. if (param->paramQuantity && param->paramQuantity->paramId == paramId)
  371. return param;
  372. }
  373. return NULL;
  374. }
  375. PortWidget *ModuleWidget::getOutput(int outputId) {
  376. for (PortWidget *port : outputs) {
  377. if (port->portId == outputId)
  378. return port;
  379. }
  380. return NULL;
  381. }
  382. PortWidget *ModuleWidget::getInput(int inputId) {
  383. for (PortWidget *port : inputs) {
  384. if (port->portId == inputId)
  385. return port;
  386. }
  387. return NULL;
  388. }
  389. json_t *ModuleWidget::toJson() {
  390. json_t *rootJ = json_object();
  391. // plugin
  392. json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str()));
  393. // version of plugin
  394. if (!model->plugin->version.empty())
  395. json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str()));
  396. // model
  397. json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));
  398. // Merge with module JSON
  399. if (module) {
  400. json_t *moduleJ = module->toJson();
  401. // Merge with rootJ
  402. json_object_update(rootJ, moduleJ);
  403. json_decref(moduleJ);
  404. }
  405. return rootJ;
  406. }
  407. void ModuleWidget::fromJson(json_t *rootJ) {
  408. // Check if plugin and model are incorrect
  409. json_t *pluginJ = json_object_get(rootJ, "plugin");
  410. std::string pluginSlug;
  411. if (pluginJ) {
  412. pluginSlug = json_string_value(pluginJ);
  413. if (pluginSlug != model->plugin->slug) {
  414. WARN("Plugin %s does not match ModuleWidget's plugin %s.", pluginSlug.c_str(), model->plugin->slug.c_str());
  415. return;
  416. }
  417. }
  418. json_t *modelJ = json_object_get(rootJ, "model");
  419. std::string modelSlug;
  420. if (modelJ) {
  421. modelSlug = json_string_value(modelJ);
  422. if (modelSlug != model->slug) {
  423. WARN("Model %s does not match ModuleWidget's model %s.", modelSlug.c_str(), model->slug.c_str());
  424. return;
  425. }
  426. }
  427. // Check plugin version
  428. json_t *versionJ = json_object_get(rootJ, "version");
  429. if (versionJ) {
  430. std::string version = json_string_value(versionJ);
  431. if (version != model->plugin->version) {
  432. INFO("Patch created with %s v%s, currently using v%s.", pluginSlug.c_str(), version.c_str(), model->plugin->version.c_str());
  433. }
  434. }
  435. if (module) {
  436. module->fromJson(rootJ);
  437. }
  438. }
  439. void ModuleWidget::copyClipboard() {
  440. json_t *moduleJ = toJson();
  441. DEFER({
  442. json_decref(moduleJ);
  443. });
  444. char *moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  445. DEFER({
  446. free(moduleJson);
  447. });
  448. glfwSetClipboardString(APP->window->win, moduleJson);
  449. }
  450. void ModuleWidget::pasteClipboardAction() {
  451. const char *moduleJson = glfwGetClipboardString(APP->window->win);
  452. if (!moduleJson) {
  453. WARN("Could not get text from clipboard.");
  454. return;
  455. }
  456. json_error_t error;
  457. json_t *moduleJ = json_loads(moduleJson, 0, &error);
  458. if (!moduleJ) {
  459. WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  460. return;
  461. }
  462. DEFER({
  463. json_decref(moduleJ);
  464. });
  465. // history::ModuleChange
  466. history::ModuleChange *h = new history::ModuleChange;
  467. h->name = "paste module preset";
  468. h->moduleId = module->id;
  469. h->oldModuleJ = toJson();
  470. fromJson(moduleJ);
  471. h->newModuleJ = toJson();
  472. APP->history->push(h);
  473. }
  474. void ModuleWidget::loadAction(std::string filename) {
  475. INFO("Loading preset %s", filename.c_str());
  476. FILE *file = fopen(filename.c_str(), "r");
  477. if (!file) {
  478. WARN("Could not load patch file %s", filename.c_str());
  479. return;
  480. }
  481. DEFER({
  482. fclose(file);
  483. });
  484. json_error_t error;
  485. json_t *moduleJ = json_loadf(file, 0, &error);
  486. if (!moduleJ) {
  487. std::string message = string::f("File is not a valid patch file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  488. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  489. return;
  490. }
  491. DEFER({
  492. json_decref(moduleJ);
  493. });
  494. // history::ModuleChange
  495. history::ModuleChange *h = new history::ModuleChange;
  496. h->name = "load module preset";
  497. h->moduleId = module->id;
  498. h->oldModuleJ = toJson();
  499. fromJson(moduleJ);
  500. h->newModuleJ = toJson();
  501. APP->history->push(h);
  502. }
  503. void ModuleWidget::save(std::string filename) {
  504. INFO("Saving preset %s", filename.c_str());
  505. json_t *moduleJ = toJson();
  506. assert(moduleJ);
  507. DEFER({
  508. json_decref(moduleJ);
  509. });
  510. FILE *file = fopen(filename.c_str(), "w");
  511. if (!file) {
  512. WARN("Could not write to patch file %s", filename.c_str());
  513. }
  514. DEFER({
  515. fclose(file);
  516. });
  517. json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  518. }
  519. void ModuleWidget::loadDialog() {
  520. std::string dir = asset::user("presets");
  521. system::createDirectory(dir);
  522. osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS);
  523. DEFER({
  524. osdialog_filters_free(filters);
  525. });
  526. char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  527. if (!path) {
  528. // No path selected
  529. return;
  530. }
  531. DEFER({
  532. free(path);
  533. });
  534. loadAction(path);
  535. }
  536. void ModuleWidget::saveDialog() {
  537. std::string dir = asset::user("presets");
  538. system::createDirectory(dir);
  539. osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS);
  540. DEFER({
  541. osdialog_filters_free(filters);
  542. });
  543. char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcvm", filters);
  544. if (!path) {
  545. // No path selected
  546. return;
  547. }
  548. DEFER({
  549. free(path);
  550. });
  551. std::string pathStr = path;
  552. std::string extension = string::filenameExtension(string::filename(pathStr));
  553. if (extension.empty()) {
  554. pathStr += ".vcvm";
  555. }
  556. save(pathStr);
  557. }
  558. void ModuleWidget::disconnect() {
  559. for (PortWidget *input : inputs) {
  560. APP->scene->rack->clearCablesOnPort(input);
  561. }
  562. for (PortWidget *output : outputs) {
  563. APP->scene->rack->clearCablesOnPort(output);
  564. }
  565. }
  566. void ModuleWidget::resetAction() {
  567. assert(module);
  568. // history::ModuleChange
  569. history::ModuleChange *h = new history::ModuleChange;
  570. h->name = "reset module";
  571. h->moduleId = module->id;
  572. h->oldModuleJ = toJson();
  573. for (ParamWidget *param : params) {
  574. param->reset();
  575. }
  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. for (ParamWidget *param : params) {
  588. param->randomize();
  589. }
  590. APP->engine->randomizeModule(module);
  591. h->newModuleJ = toJson();
  592. APP->history->push(h);
  593. }
  594. static void disconnectActions(ModuleWidget *mw, history::ComplexAction *complexAction) {
  595. // Add CableRemove action for all cables attached to outputs
  596. for (PortWidget* output : mw->outputs) {
  597. for (CableWidget *cw : APP->scene->rack->getCablesOnPort(output)) {
  598. if (!cw->isComplete())
  599. continue;
  600. // history::CableRemove
  601. history::CableRemove *h = new history::CableRemove;
  602. h->setCable(cw);
  603. complexAction->push(h);
  604. }
  605. }
  606. // Add CableRemove action for all cables attached to inputs
  607. for (PortWidget* input : mw->inputs) {
  608. for (CableWidget *cw : APP->scene->rack->getCablesOnPort(input)) {
  609. if (!cw->isComplete())
  610. continue;
  611. // Avoid creating duplicate actions for self-patched cables
  612. if (cw->outputPort->module == mw->module)
  613. continue;
  614. // history::CableRemove
  615. history::CableRemove *h = new history::CableRemove;
  616. h->setCable(cw);
  617. complexAction->push(h);
  618. }
  619. }
  620. }
  621. void ModuleWidget::disconnectAction() {
  622. history::ComplexAction *complexAction = new history::ComplexAction;
  623. complexAction->name = "disconnect cables";
  624. disconnectActions(this, complexAction);
  625. APP->history->push(complexAction);
  626. disconnect();
  627. }
  628. void ModuleWidget::cloneAction() {
  629. ModuleWidget *clonedModuleWidget = model->createModuleWidget();
  630. assert(clonedModuleWidget);
  631. // JSON serialization is the obvious way to do this
  632. json_t *moduleJ = toJson();
  633. clonedModuleWidget->fromJson(moduleJ);
  634. json_decref(moduleJ);
  635. APP->scene->rack->addModuleAtMouse(clonedModuleWidget);
  636. // history::ModuleAdd
  637. history::ModuleAdd *h = new history::ModuleAdd;
  638. h->name = "clone modules";
  639. h->setModule(clonedModuleWidget);
  640. APP->history->push(h);
  641. }
  642. void ModuleWidget::bypassAction() {
  643. assert(module);
  644. // history::ModuleBypass
  645. history::ModuleBypass *h = new history::ModuleBypass;
  646. h->moduleId = module->id;
  647. h->bypass = !module->bypass;
  648. APP->history->push(h);
  649. h->redo();
  650. }
  651. void ModuleWidget::removeAction() {
  652. history::ComplexAction *complexAction = new history::ComplexAction;
  653. complexAction->name = "remove module";
  654. disconnectActions(this, complexAction);
  655. // history::ModuleRemove
  656. history::ModuleRemove *moduleRemove = new history::ModuleRemove;
  657. moduleRemove->setModule(this);
  658. complexAction->push(moduleRemove);
  659. APP->history->push(complexAction);
  660. // This disconnects cables, removes the module, and transfers ownership to caller
  661. APP->scene->rack->removeModule(this);
  662. delete this;
  663. }
  664. void ModuleWidget::createContextMenu() {
  665. ui::Menu *menu = createMenu();
  666. assert(model);
  667. ui::MenuLabel *modelLabel = new ui::MenuLabel;
  668. modelLabel->text = model->name;
  669. menu->addChild(modelLabel);
  670. ModulePluginItem *pluginItem = new ModulePluginItem;
  671. pluginItem->text = "Plugin";
  672. pluginItem->rightText = RIGHT_ARROW;
  673. pluginItem->plugin = model->plugin;
  674. menu->addChild(pluginItem);
  675. ModulePresetItem *presetsItem = new ModulePresetItem;
  676. presetsItem->text = "Preset";
  677. presetsItem->rightText = RIGHT_ARROW;
  678. presetsItem->moduleWidget = this;
  679. menu->addChild(presetsItem);
  680. ModuleResetItem *resetItem = new ModuleResetItem;
  681. resetItem->text = "Initialize";
  682. resetItem->rightText = RACK_MOD_CTRL_NAME "+I";
  683. resetItem->moduleWidget = this;
  684. menu->addChild(resetItem);
  685. ModuleRandomizeItem *randomizeItem = new ModuleRandomizeItem;
  686. randomizeItem->text = "Randomize";
  687. randomizeItem->rightText = RACK_MOD_CTRL_NAME "+R";
  688. randomizeItem->moduleWidget = this;
  689. menu->addChild(randomizeItem);
  690. ModuleDisconnectItem *disconnectItem = new ModuleDisconnectItem;
  691. disconnectItem->text = "Disconnect cables";
  692. disconnectItem->rightText = RACK_MOD_CTRL_NAME "+U";
  693. disconnectItem->moduleWidget = this;
  694. menu->addChild(disconnectItem);
  695. ModuleCloneItem *cloneItem = new ModuleCloneItem;
  696. cloneItem->text = "Duplicate";
  697. cloneItem->rightText = RACK_MOD_CTRL_NAME "+D";
  698. cloneItem->moduleWidget = this;
  699. menu->addChild(cloneItem);
  700. ModuleBypassItem *bypassItem = new ModuleBypassItem;
  701. bypassItem->text = "Disable";
  702. bypassItem->rightText = RACK_MOD_CTRL_NAME "+E";
  703. if (module && module->bypass)
  704. bypassItem->rightText = CHECKMARK_STRING " " + bypassItem->rightText;
  705. bypassItem->moduleWidget = this;
  706. menu->addChild(bypassItem);
  707. ModuleDeleteItem *deleteItem = new ModuleDeleteItem;
  708. deleteItem->text = "Delete";
  709. deleteItem->rightText = "Backspace/Delete";
  710. deleteItem->moduleWidget = this;
  711. menu->addChild(deleteItem);
  712. appendContextMenu(menu);
  713. }
  714. } // namespace app
  715. } // namespace rack