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.

310 lines
7.4KB

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