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.

261 lines
6.5KB

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