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.

382 lines
11KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. /**
  18. * This file is partially based on VCVRack's ModuleWidget.cpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include "CardinalCommon.hpp"
  27. #include <app/ModuleWidget.hpp>
  28. #include <app/RackWidget.hpp>
  29. #include <app/Scene.hpp>
  30. #include <engine/Engine.hpp>
  31. #include <ui/MenuSeparator.hpp>
  32. #include <asset.hpp>
  33. #include <context.hpp>
  34. #include <helpers.hpp>
  35. #include <system.hpp>
  36. namespace rack {
  37. namespace app {
  38. struct CardinalModuleWidget : ModuleWidget {
  39. CardinalModuleWidget() : ModuleWidget() {}
  40. DEPRECATED CardinalModuleWidget(engine::Module* module) : ModuleWidget() {
  41. setModule(module);
  42. }
  43. void onButton(const ButtonEvent& e) override;
  44. };
  45. struct ModuleWidget::Internal {
  46. math::Vec dragOffset;
  47. math::Vec dragRackPos;
  48. bool dragEnabled;
  49. math::Vec oldPos;
  50. widget::Widget* panel;
  51. };
  52. static void CardinalModuleWidget__loadDialog(ModuleWidget* const w)
  53. {
  54. std::string presetDir = w->model->getUserPresetDirectory();
  55. system::createDirectories(presetDir);
  56. WeakPtr<ModuleWidget> weakThis = w;
  57. async_dialog_filebrowser(false, presetDir.c_str(), "Load preset", [=](char* pathC) {
  58. // Delete directories if empty
  59. DEFER({
  60. try {
  61. system::remove(presetDir);
  62. system::remove(system::getDirectory(presetDir));
  63. }
  64. catch (Exception& e) {
  65. // Ignore exceptions if directory cannot be removed.
  66. }
  67. });
  68. if (!weakThis)
  69. return;
  70. if (!pathC)
  71. return;
  72. try {
  73. weakThis->loadAction(pathC);
  74. }
  75. catch (Exception& e) {
  76. async_dialog_message(e.what());
  77. }
  78. std::free(pathC);
  79. });
  80. }
  81. void CardinalModuleWidget__saveDialog(ModuleWidget* const w)
  82. {
  83. const std::string presetDir = w->model->getUserPresetDirectory();
  84. system::createDirectories(presetDir);
  85. WeakPtr<ModuleWidget> weakThis = w;
  86. async_dialog_filebrowser(true, presetDir.c_str(), "Save preset", [=](char* pathC) {
  87. // Delete directories if empty
  88. DEFER({
  89. try {
  90. system::remove(presetDir);
  91. system::remove(system::getDirectory(presetDir));
  92. }
  93. catch (Exception& e) {
  94. // Ignore exceptions if directory cannot be removed.
  95. }
  96. });
  97. if (!weakThis)
  98. return;
  99. if (!pathC)
  100. return;
  101. std::string path = pathC;
  102. std::free(pathC);
  103. // Automatically append .vcvm extension
  104. if (system::getExtension(path) != ".vcvm")
  105. path += ".vcvm";
  106. weakThis->save(path);
  107. });
  108. }
  109. static void CardinalModuleWidget__createContextMenu(ModuleWidget* const w,
  110. plugin::Model* const model,
  111. engine::Module* const module) {
  112. DISTRHO_SAFE_ASSERT_RETURN(model != nullptr,);
  113. ui::Menu* menu = createMenu();
  114. WeakPtr<ModuleWidget> weakThis = w;
  115. // Brand and module name
  116. menu->addChild(createMenuLabel(model->name));
  117. menu->addChild(createMenuLabel(model->plugin->brand));
  118. // Info
  119. menu->addChild(createSubmenuItem("Info", "", [model](ui::Menu* menu) {
  120. model->appendContextMenu(menu);
  121. }));
  122. // Preset
  123. menu->addChild(createSubmenuItem("Preset", "", [weakThis](ui::Menu* menu) {
  124. menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [weakThis]() {
  125. if (!weakThis)
  126. return;
  127. weakThis->copyClipboard();
  128. }));
  129. menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [weakThis]() {
  130. if (!weakThis)
  131. return;
  132. weakThis->pasteClipboardAction();
  133. }));
  134. menu->addChild(createMenuItem("Open", "", [weakThis]() {
  135. if (!weakThis)
  136. return;
  137. CardinalModuleWidget__loadDialog(weakThis);
  138. }));
  139. menu->addChild(createMenuItem("Save as", "", [weakThis]() {
  140. if (!weakThis)
  141. return;
  142. CardinalModuleWidget__saveDialog(weakThis);
  143. }));
  144. /* TODO
  145. // Scan `<user dir>/presets/<plugin slug>/<module slug>` for presets.
  146. menu->addChild(new ui::MenuSeparator);
  147. menu->addChild(createMenuLabel("User presets"));
  148. appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory());
  149. // Scan `<plugin dir>/presets/<module slug>` for presets.
  150. menu->addChild(new ui::MenuSeparator);
  151. menu->addChild(createMenuLabel("Factory presets"));
  152. appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory());
  153. */
  154. }));
  155. // Initialize
  156. menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [weakThis]() {
  157. if (!weakThis)
  158. return;
  159. weakThis->resetAction();
  160. }));
  161. // Randomize
  162. menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [weakThis]() {
  163. if (!weakThis)
  164. return;
  165. weakThis->randomizeAction();
  166. }));
  167. // Disconnect cables
  168. menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [weakThis]() {
  169. if (!weakThis)
  170. return;
  171. weakThis->disconnectAction();
  172. }));
  173. // Bypass
  174. std::string bypassText = RACK_MOD_CTRL_NAME "+E";
  175. bool bypassed = module && module->isBypassed();
  176. if (bypassed)
  177. bypassText += " " CHECKMARK_STRING;
  178. menu->addChild(createMenuItem("Bypass", bypassText, [weakThis, bypassed]() {
  179. if (!weakThis)
  180. return;
  181. weakThis->bypassAction(!bypassed);
  182. }));
  183. // Duplicate
  184. menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [weakThis]() {
  185. if (!weakThis)
  186. return;
  187. weakThis->cloneAction(false);
  188. }));
  189. // Duplicate with cables
  190. menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [weakThis]() {
  191. if (!weakThis)
  192. return;
  193. weakThis->cloneAction(true);
  194. }));
  195. // Delete
  196. menu->addChild(createMenuItem("Delete", "Backspace/Delete", [weakThis]() {
  197. if (!weakThis)
  198. return;
  199. weakThis->removeAction();
  200. }, false, true));
  201. w->appendContextMenu(menu);
  202. }
  203. static void CardinalModuleWidget__saveSelectionDialog(RackWidget* const w)
  204. {
  205. std::string selectionDir = asset::user("selections");
  206. system::createDirectories(selectionDir);
  207. async_dialog_filebrowser(true, selectionDir.c_str(), "Save selection as", [w](char* pathC) {
  208. if (!pathC) {
  209. // No path selected
  210. return;
  211. }
  212. std::string path = pathC;
  213. std::free(pathC);
  214. // Automatically append .vcvs extension
  215. if (system::getExtension(path) != ".vcvs")
  216. path += ".vcvs";
  217. w->saveSelection(path);
  218. });
  219. }
  220. void CardinalModuleWidget::onButton(const ButtonEvent& e)
  221. {
  222. bool selected = APP->scene->rack->isSelected(this);
  223. if (selected) {
  224. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  225. ui::Menu* menu = createMenu();
  226. patchUtils::appendSelectionContextMenu(menu);
  227. }
  228. e.consume(this);
  229. }
  230. OpaqueWidget::onButton(e);
  231. if (e.getTarget() == this) {
  232. // Set starting drag position
  233. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  234. internal->dragOffset = e.pos;
  235. }
  236. // Toggle selection on Shift-click
  237. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  238. APP->scene->rack->select(this, !selected);
  239. }
  240. }
  241. if (!e.isConsumed() && !selected) {
  242. // Open context menu on right-click
  243. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  244. CardinalModuleWidget__createContextMenu(this, model, module);
  245. e.consume(this);
  246. }
  247. }
  248. }
  249. }
  250. }
  251. namespace patchUtils
  252. {
  253. using namespace rack;
  254. void appendSelectionContextMenu(ui::Menu* const menu)
  255. {
  256. app::RackWidget* const w = APP->scene->rack;
  257. int n = w->getSelected().size();
  258. menu->addChild(createMenuLabel(string::f("%d selected %s", n, n == 1 ? "module" : "modules")));
  259. // Enable alwaysConsume of menu items if the number of selected modules changes
  260. // Select all
  261. menu->addChild(createMenuItem("Select all", RACK_MOD_CTRL_NAME "+A", [w]() {
  262. w->selectAll();
  263. }, false, true));
  264. // Deselect
  265. menu->addChild(createMenuItem("Deselect", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+A", [w]() {
  266. w->deselectAll();
  267. }, n == 0, true));
  268. // Copy
  269. menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [w]() {
  270. w->copyClipboardSelection();
  271. }, n == 0));
  272. // Paste
  273. menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [w]() {
  274. w->pasteClipboardAction();
  275. }, false, true));
  276. // Save
  277. menu->addChild(createMenuItem("Save selection as", "", [w]() {
  278. CardinalModuleWidget__saveSelectionDialog(w);
  279. }, n == 0));
  280. // Initialize
  281. menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [w]() {
  282. w->resetSelectionAction();
  283. }, n == 0));
  284. // Randomize
  285. menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [w]() {
  286. w->randomizeSelectionAction();
  287. }, n == 0));
  288. // Disconnect cables
  289. menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [w]() {
  290. w->disconnectSelectionAction();
  291. }, n == 0));
  292. // Bypass
  293. std::string bypassText = RACK_MOD_CTRL_NAME "+E";
  294. bool bypassed = (n > 0) && w->isSelectionBypassed();
  295. if (bypassed)
  296. bypassText += " " CHECKMARK_STRING;
  297. menu->addChild(createMenuItem("Bypass", bypassText, [w, bypassed]() {
  298. w->bypassSelectionAction(!bypassed);
  299. }, n == 0, true));
  300. // Duplicate
  301. menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [w]() {
  302. w->cloneSelectionAction(false);
  303. }, n == 0));
  304. // Duplicate with cables
  305. menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [w]() {
  306. w->cloneSelectionAction(true);
  307. }, n == 0));
  308. // Delete
  309. menu->addChild(createMenuItem("Delete", "Backspace/Delete", [w]() {
  310. w->deleteSelectionAction();
  311. }, n == 0, true));
  312. }
  313. }