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.

287 lines
7.1KB

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