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.

340 lines
8.1KB

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