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.

232 lines
4.9KB

  1. #include "patch.hpp"
  2. #include "asset.hpp"
  3. #include "system.hpp"
  4. #include "app.hpp"
  5. #include "app/Scene.hpp"
  6. #include "app/RackWidget.hpp"
  7. #include "history.hpp"
  8. #include "osdialog.h"
  9. namespace rack {
  10. static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv";
  11. void PatchManager::reset() {
  12. app()->history->clear();
  13. app()->scene->rackWidget->clear();
  14. app()->scene->scrollWidget->offset = math::Vec(0, 0);
  15. // Fails silently if file does not exist
  16. load(asset::user("template.vcv"));
  17. legacy = 0;
  18. path = "";
  19. }
  20. void PatchManager::resetDialog() {
  21. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear patch and start over?")) {
  22. reset();
  23. }
  24. }
  25. void PatchManager::save(std::string path) {
  26. INFO("Saving patch %s", path.c_str());
  27. json_t *rootJ = toJson();
  28. if (!rootJ)
  29. return;
  30. DEFER({
  31. json_decref(rootJ);
  32. });
  33. FILE *file = std::fopen(path.c_str(), "w");
  34. if (!file) {
  35. // Fail silently
  36. return;
  37. }
  38. DEFER({
  39. std::fclose(file);
  40. });
  41. json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
  42. }
  43. void PatchManager::saveDialog() {
  44. if (!path.empty()) {
  45. save(path);
  46. }
  47. else {
  48. saveAsDialog();
  49. }
  50. }
  51. void PatchManager::saveAsDialog() {
  52. std::string dir;
  53. std::string filename;
  54. if (path.empty()) {
  55. dir = asset::user("patches");
  56. system::createDirectory(dir);
  57. }
  58. else {
  59. dir = string::directory(path);
  60. filename = string::filename(path);
  61. }
  62. osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str());
  63. DEFER({
  64. osdialog_filters_free(filters);
  65. });
  66. char *pathC = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters);
  67. if (!pathC) {
  68. // Fail silently
  69. return;
  70. }
  71. DEFER({
  72. free(pathC);
  73. });
  74. std::string pathStr = pathC;
  75. if (string::extension(pathStr).empty()) {
  76. pathStr += ".vcv";
  77. }
  78. save(pathStr);
  79. path = pathStr;
  80. }
  81. void PatchManager::saveTemplateDialog() {
  82. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) {
  83. save(asset::user("template.vcv"));
  84. }
  85. }
  86. void PatchManager::load(std::string path) {
  87. INFO("Loading patch %s", path.c_str());
  88. FILE *file = std::fopen(path.c_str(), "r");
  89. if (!file) {
  90. // Exit silently
  91. return;
  92. }
  93. DEFER({
  94. std::fclose(file);
  95. });
  96. json_error_t error;
  97. json_t *rootJ = json_loadf(file, 0, &error);
  98. if (!rootJ) {
  99. std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
  100. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str());
  101. return;
  102. }
  103. DEFER({
  104. json_decref(rootJ);
  105. });
  106. app()->history->clear();
  107. app()->scene->rackWidget->clear();
  108. app()->scene->scrollWidget->offset = math::Vec(0, 0);
  109. fromJson(rootJ);
  110. }
  111. void PatchManager::loadDialog() {
  112. std::string dir;
  113. if (path.empty()) {
  114. dir = asset::user("patches");
  115. system::createDirectory(dir);
  116. }
  117. else {
  118. dir = string::directory(path);
  119. }
  120. osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str());
  121. DEFER({
  122. osdialog_filters_free(filters);
  123. });
  124. char *pathC = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters);
  125. if (!pathC) {
  126. // Fail silently
  127. return;
  128. }
  129. DEFER({
  130. free(pathC);
  131. });
  132. load(pathC);
  133. path = pathC;
  134. }
  135. void PatchManager::revertDialog() {
  136. if (path.empty())
  137. return;
  138. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert patch to the last saved state?")) {
  139. load(path);
  140. }
  141. }
  142. void PatchManager::disconnectDialog() {
  143. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?"))
  144. return;
  145. app()->scene->rackWidget->clearCablesAction();
  146. }
  147. json_t *PatchManager::toJson() {
  148. // root
  149. json_t *rootJ = json_object();
  150. // version
  151. json_t *versionJ = json_string(APP_VERSION.c_str());
  152. json_object_set_new(rootJ, "version", versionJ);
  153. // Merge with RackWidget JSON
  154. json_t *rackJ = app()->scene->rackWidget->toJson();
  155. // Merge with rootJ
  156. json_object_update(rootJ, rackJ);
  157. json_decref(rackJ);
  158. return rootJ;
  159. }
  160. void PatchManager::fromJson(json_t *rootJ) {
  161. legacy = 0;
  162. // version
  163. std::string version;
  164. json_t *versionJ = json_object_get(rootJ, "version");
  165. if (versionJ)
  166. version = json_string_value(versionJ);
  167. if (version != APP_VERSION) {
  168. INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str());
  169. }
  170. // Detect old patches with ModuleWidget::params/inputs/outputs indices.
  171. // (We now use Module::params/inputs/outputs indices.)
  172. if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") {
  173. legacy = 1;
  174. }
  175. else if (string::startsWith(version, "0.6.")) {
  176. legacy = 2;
  177. }
  178. if (legacy) {
  179. INFO("Loading patch using legacy mode %d", legacy);
  180. }
  181. app()->scene->rackWidget->fromJson(rootJ);
  182. // Display a message if we have something to say
  183. if (!warningLog.empty()) {
  184. osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, warningLog.c_str());
  185. }
  186. warningLog = "";
  187. }
  188. bool PatchManager::isLegacy(int level) {
  189. return legacy && legacy <= level;
  190. }
  191. } // namespace rack