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.

305 lines
7.2KB

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