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.

278 lines
6.8KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 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 patch.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 "AsyncDialog.hpp"
  28. #include "PluginContext.hpp"
  29. #include <asset.hpp>
  30. #include <context.hpp>
  31. #include <history.hpp>
  32. #include <patch.hpp>
  33. #include <string.hpp>
  34. #include <system.hpp>
  35. #include <app/Scene.hpp>
  36. #include <window/Window.hpp>
  37. #ifdef NDEBUG
  38. # undef DEBUG
  39. #endif
  40. // for finding home dir
  41. #ifndef ARCH_WIN
  42. # include <pwd.h>
  43. # include <unistd.h>
  44. #endif
  45. const std::string CARDINAL_VERSION = "22.02";
  46. namespace rack {
  47. namespace settings {
  48. int rateLimit = 0;
  49. }
  50. }
  51. namespace patchUtils
  52. {
  53. using namespace rack;
  54. #ifndef HEADLESS
  55. static void promptClear(const char* const message, const std::function<void()> action)
  56. {
  57. if (APP->history->isSaved() || APP->scene->rack->hasModules())
  58. return action();
  59. asyncDialog::create(message, action);
  60. }
  61. static std::string homeDir()
  62. {
  63. # ifdef ARCH_WIN
  64. if (const char* const userprofile = getenv("USERPROFILE"))
  65. {
  66. return userprofile;
  67. }
  68. else if (const char* const homedrive = getenv("HOMEDRIVE"))
  69. {
  70. if (const char* const homepath = getenv("HOMEPATH"))
  71. return system::join(homedrive, homepath);
  72. }
  73. # else
  74. if (const char* const home = getenv("HOME"))
  75. return home;
  76. else if (struct passwd* const pwd = getpwuid(getuid()))
  77. return pwd->pw_dir;
  78. # endif
  79. return {};
  80. }
  81. #endif
  82. void loadDialog()
  83. {
  84. #ifndef HEADLESS
  85. promptClear("The current patch is unsaved. Clear it and open a new patch?", []() {
  86. std::string dir;
  87. if (! APP->patch->path.empty())
  88. dir = system::getDirectory(APP->patch->path);
  89. else
  90. dir = homeDir();
  91. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  92. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  93. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  94. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  95. FileBrowserOptions opts;
  96. opts.startDir = dir.c_str();
  97. opts.saving = ui->saving = false;
  98. ui->openFileBrowser(opts);
  99. });
  100. #endif
  101. }
  102. void loadPathDialog(const std::string& path)
  103. {
  104. #ifndef HEADLESS
  105. promptClear("The current patch is unsaved. Clear it and open the new patch?", [path]() {
  106. APP->patch->loadAction(path);
  107. });
  108. #endif
  109. }
  110. void loadSelectionDialog()
  111. {
  112. app::RackWidget* const w = APP->scene->rack;
  113. std::string selectionDir = asset::user("selections");
  114. system::createDirectories(selectionDir);
  115. async_dialog_filebrowser(false, selectionDir.c_str(), "Import selection", [w](char* pathC) {
  116. if (!pathC) {
  117. // No path selected
  118. return;
  119. }
  120. try {
  121. w->loadSelection(pathC);
  122. }
  123. catch (Exception& e) {
  124. async_dialog_message(e.what());
  125. }
  126. std::free(pathC);
  127. });
  128. }
  129. void loadTemplateDialog()
  130. {
  131. #ifndef HEADLESS
  132. promptClear("The current patch is unsaved. Clear it and start a new patch?", []() {
  133. APP->patch->loadTemplate();
  134. });
  135. #endif
  136. }
  137. void revertDialog()
  138. {
  139. #ifndef HEADLESS
  140. if (APP->patch->path.empty())
  141. return;
  142. promptClear("Revert patch to the last saved state?", []{
  143. APP->patch->loadAction(APP->patch->path);
  144. });
  145. #endif
  146. }
  147. void saveDialog(const std::string& path)
  148. {
  149. #ifndef HEADLESS
  150. if (path.empty()) {
  151. return;
  152. }
  153. // Note: If save() fails below, this should probably be reset. But we need it so toJson() doesn't set the "unsaved" property.
  154. APP->history->setSaved();
  155. try {
  156. APP->patch->save(path);
  157. }
  158. catch (Exception& e) {
  159. asyncDialog::create(string::f("Could not save patch: %s", e.what()).c_str());
  160. return;
  161. }
  162. #endif
  163. }
  164. #ifndef HEADLESS
  165. static void saveAsDialog(const bool uncompressed)
  166. {
  167. std::string dir;
  168. if (! APP->patch->path.empty())
  169. dir = system::getDirectory(APP->patch->path);
  170. else
  171. dir = homeDir();
  172. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  173. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  174. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  175. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  176. FileBrowserOptions opts;
  177. opts.startDir = dir.c_str();
  178. opts.saving = ui->saving = true;
  179. ui->savingUncompressed = true;
  180. ui->openFileBrowser(opts);
  181. }
  182. #endif
  183. void saveAsDialog()
  184. {
  185. #ifndef HEADLESS
  186. saveAsDialog(false);
  187. #endif
  188. }
  189. void saveAsDialogUncompressed()
  190. {
  191. #ifndef HEADLESS
  192. saveAsDialog(true);
  193. #endif
  194. }
  195. }
  196. void async_dialog_filebrowser(const bool saving,
  197. const char* const startDir,
  198. const char* const title,
  199. const std::function<void(char* path)> action)
  200. {
  201. #ifndef HEADLESS
  202. CardinalPluginContext* const pcontext = static_cast<CardinalPluginContext*>(APP);
  203. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr,);
  204. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(pcontext->ui);
  205. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  206. // only 1 dialog possible at a time
  207. DISTRHO_SAFE_ASSERT_RETURN(ui->filebrowserhandle == nullptr,);
  208. FileBrowserOptions opts;
  209. opts.saving = saving;
  210. opts.startDir = startDir;
  211. opts.title = title;
  212. ui->filebrowseraction = action;
  213. ui->filebrowserhandle = fileBrowserCreate(true, pcontext->nativeWindowId, pcontext->window->pixelRatio, opts);
  214. #endif
  215. }
  216. void async_dialog_message(const char* const message)
  217. {
  218. #ifndef HEADLESS
  219. asyncDialog::create(message);
  220. #endif
  221. }
  222. void async_dialog_message(const char* const message, const std::function<void()> action)
  223. {
  224. #ifndef HEADLESS
  225. asyncDialog::create(message, action);
  226. #endif
  227. }
  228. void async_dialog_text_input(const char* const message, const char* const text,
  229. const std::function<void(char* newText)> action)
  230. {
  231. #ifndef HEADLESS
  232. asyncDialog::textInput(message, text, action);
  233. #endif
  234. }