The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

486 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../Application/jucer_Headers.h"
  20. #include "jucer_StoredSettings.h"
  21. #include "../Application/jucer_Application.h"
  22. //==============================================================================
  23. StoredSettings& getAppSettings()
  24. {
  25. return *ProjucerApplication::getApp().settings;
  26. }
  27. PropertiesFile& getGlobalProperties()
  28. {
  29. return getAppSettings().getGlobalProperties();
  30. }
  31. //==============================================================================
  32. StoredSettings::StoredSettings()
  33. : appearance (true),
  34. projectDefaults ("PROJECT_DEFAULT_SETTINGS"),
  35. fallbackPaths ("FALLBACK_PATHS")
  36. {
  37. updateOldProjectSettingsFiles();
  38. reload();
  39. changed (true);
  40. flush();
  41. checkJUCEPaths();
  42. projectDefaults.addListener (this);
  43. fallbackPaths.addListener (this);
  44. }
  45. StoredSettings::~StoredSettings()
  46. {
  47. projectDefaults.removeListener (this);
  48. fallbackPaths.removeListener (this);
  49. flush();
  50. }
  51. PropertiesFile& StoredSettings::getGlobalProperties()
  52. {
  53. return *propertyFiles.getUnchecked (0);
  54. }
  55. static PropertiesFile* createPropsFile (const String& filename, bool isProjectSettings)
  56. {
  57. return new PropertiesFile (ProjucerApplication::getApp()
  58. .getPropertyFileOptionsFor (filename, isProjectSettings));
  59. }
  60. PropertiesFile& StoredSettings::getProjectProperties (const String& projectUID)
  61. {
  62. const auto filename = String ("Projucer_Project_" + projectUID);
  63. for (auto i = propertyFiles.size(); --i >= 0;)
  64. {
  65. auto* const props = propertyFiles.getUnchecked(i);
  66. if (props->getFile().getFileNameWithoutExtension() == filename)
  67. return *props;
  68. }
  69. auto* p = createPropsFile (filename, true);
  70. propertyFiles.add (p);
  71. return *p;
  72. }
  73. void StoredSettings::updateGlobalPreferences()
  74. {
  75. // update 'invisible' global settings
  76. updateRecentFiles();
  77. updateLastWizardFolder();
  78. updateKeyMappings();
  79. }
  80. void StoredSettings::updateRecentFiles()
  81. {
  82. getGlobalProperties().setValue ("recentFiles", recentFiles.toString());
  83. }
  84. void StoredSettings::updateLastWizardFolder()
  85. {
  86. getGlobalProperties().setValue ("lastWizardFolder", lastWizardFolder.getFullPathName());
  87. }
  88. void StoredSettings::updateKeyMappings()
  89. {
  90. getGlobalProperties().removeValue ("keyMappings");
  91. if (auto* commandManager = ProjucerApplication::getApp().commandManager.get())
  92. {
  93. const std::unique_ptr<XmlElement> keys (commandManager->getKeyMappings()->createXml (true));
  94. if (keys != nullptr)
  95. getGlobalProperties().setValue ("keyMappings", keys.get());
  96. }
  97. }
  98. void StoredSettings::flush()
  99. {
  100. updateGlobalPreferences();
  101. saveSwatchColours();
  102. for (auto i = propertyFiles.size(); --i >= 0;)
  103. propertyFiles.getUnchecked(i)->saveIfNeeded();
  104. }
  105. void StoredSettings::reload()
  106. {
  107. propertyFiles.clear();
  108. propertyFiles.add (createPropsFile ("Projucer", false));
  109. std::unique_ptr<XmlElement> projectDefaultsXml (propertyFiles.getFirst()->getXmlValue ("PROJECT_DEFAULT_SETTINGS"));
  110. if (projectDefaultsXml != nullptr)
  111. projectDefaults = ValueTree::fromXml (*projectDefaultsXml);
  112. std::unique_ptr<XmlElement> fallbackPathsXml (propertyFiles.getFirst()->getXmlValue ("FALLBACK_PATHS"));
  113. if (fallbackPathsXml != nullptr)
  114. fallbackPaths = ValueTree::fromXml (*fallbackPathsXml);
  115. // recent files...
  116. recentFiles.restoreFromString (getGlobalProperties().getValue ("recentFiles"));
  117. recentFiles.removeNonExistentFiles();
  118. lastWizardFolder = getGlobalProperties().getValue ("lastWizardFolder");
  119. loadSwatchColours();
  120. }
  121. Array<File> StoredSettings::getLastProjects()
  122. {
  123. StringArray s;
  124. s.addTokens (getGlobalProperties().getValue ("lastProjects"), "|", "");
  125. Array<File> f;
  126. for (int i = 0; i < s.size(); ++i)
  127. f.add (File (s[i]));
  128. return f;
  129. }
  130. void StoredSettings::setLastProjects (const Array<File>& files)
  131. {
  132. StringArray s;
  133. for (int i = 0; i < files.size(); ++i)
  134. s.add (files.getReference(i).getFullPathName());
  135. getGlobalProperties().setValue ("lastProjects", s.joinIntoString ("|"));
  136. }
  137. void StoredSettings::updateOldProjectSettingsFiles()
  138. {
  139. // Global properties file hasn't been created yet so create a dummy file
  140. auto projucerSettingsDirectory = ProjucerApplication::getApp().getPropertyFileOptionsFor ("Dummy", false)
  141. .getDefaultFile().getParentDirectory();
  142. auto newProjectSettingsDir = projucerSettingsDirectory.getChildFile ("ProjectSettings");
  143. newProjectSettingsDir.createDirectory();
  144. DirectoryIterator iter (projucerSettingsDirectory, false, "*.settings");
  145. while (iter.next())
  146. {
  147. auto f = iter.getFile();
  148. auto oldFileName = f.getFileName();
  149. if (oldFileName.contains ("Introjucer"))
  150. {
  151. auto newFileName = oldFileName.replace ("Introjucer", "Projucer");
  152. if (oldFileName.contains ("_Project"))
  153. {
  154. f.moveFileTo (f.getSiblingFile (newProjectSettingsDir.getFileName()).getChildFile (newFileName));
  155. }
  156. else
  157. {
  158. auto newFile = f.getSiblingFile (newFileName);
  159. // don't overwrite newer settings file
  160. if (! newFile.existsAsFile())
  161. f.moveFileTo (f.getSiblingFile (newFileName));
  162. }
  163. }
  164. }
  165. }
  166. void StoredSettings::checkJUCEPaths()
  167. {
  168. auto moduleFolder = projectDefaults.getProperty (Ids::defaultJuceModulePath).toString();
  169. auto juceFolder = projectDefaults.getProperty (Ids::jucePath).toString();
  170. auto validModuleFolder = moduleFolder.isNotEmpty() && isGlobalPathValid ({}, Ids::defaultJuceModulePath, moduleFolder);
  171. auto validJuceFolder = juceFolder.isNotEmpty() && isGlobalPathValid ({}, Ids::jucePath, juceFolder);
  172. if (validModuleFolder && ! validJuceFolder)
  173. projectDefaults.getPropertyAsValue (Ids::jucePath, nullptr) = File (moduleFolder).getParentDirectory().getFullPathName();
  174. else if (! validModuleFolder && validJuceFolder)
  175. projectDefaults.getPropertyAsValue (Ids::defaultJuceModulePath, nullptr) = File (juceFolder).getChildFile ("modules").getFullPathName();
  176. }
  177. bool StoredSettings::shouldAskUserToSetJUCEPath() noexcept
  178. {
  179. if (! isGlobalPathValid ({}, Ids::jucePath, projectDefaults.getProperty (Ids::jucePath).toString())
  180. && getGlobalProperties().getValue ("dontAskAboutJUCEPath", {}).isEmpty())
  181. return true;
  182. return false;
  183. }
  184. void StoredSettings::setDontAskAboutJUCEPathAgain() noexcept
  185. {
  186. getGlobalProperties().setValue ("dontAskAboutJUCEPath", 1);
  187. }
  188. //==============================================================================
  189. void StoredSettings::loadSwatchColours()
  190. {
  191. swatchColours.clear();
  192. #define COL(col) Colours::col,
  193. const Colour colours[] =
  194. {
  195. #include "../Utility/Helpers/jucer_Colours.h"
  196. Colours::transparentBlack
  197. };
  198. #undef COL
  199. const auto numSwatchColours = 24;
  200. auto& props = getGlobalProperties();
  201. for (auto i = 0; i < numSwatchColours; ++i)
  202. swatchColours.add (Colour::fromString (props.getValue ("swatchColour" + String (i),
  203. colours [2 + i].toString())));
  204. }
  205. void StoredSettings::saveSwatchColours()
  206. {
  207. auto& props = getGlobalProperties();
  208. for (auto i = 0; i < swatchColours.size(); ++i)
  209. props.setValue ("swatchColour" + String (i), swatchColours.getReference(i).toString());
  210. }
  211. StoredSettings::ColourSelectorWithSwatches::ColourSelectorWithSwatches() {}
  212. StoredSettings::ColourSelectorWithSwatches::~ColourSelectorWithSwatches() {}
  213. int StoredSettings::ColourSelectorWithSwatches::getNumSwatches() const
  214. {
  215. return getAppSettings().swatchColours.size();
  216. }
  217. Colour StoredSettings::ColourSelectorWithSwatches::getSwatchColour (int index) const
  218. {
  219. return getAppSettings().swatchColours [index];
  220. }
  221. void StoredSettings::ColourSelectorWithSwatches::setSwatchColour (int index, const Colour& newColour)
  222. {
  223. getAppSettings().swatchColours.set (index, newColour);
  224. }
  225. //==============================================================================
  226. Value StoredSettings::getStoredPath (const Identifier& key)
  227. {
  228. auto v = projectDefaults.getPropertyAsValue (key, nullptr);
  229. if (v.toString().isEmpty())
  230. v = getFallbackPathForOS (key, TargetOS::getThisOS()).toString();
  231. return v;
  232. }
  233. Value StoredSettings::getFallbackPathForOS (const Identifier& key, DependencyPathOS os)
  234. {
  235. auto id = identifierForOS (os);
  236. auto osFallback = fallbackPaths.getOrCreateChildWithName (id, nullptr);
  237. auto v = osFallback.getPropertyAsValue (key, nullptr);
  238. if (v.toString().isEmpty())
  239. {
  240. if (key == Ids::jucePath)
  241. {
  242. v = (os == TargetOS::windows ? "C:\\JUCE"
  243. : "~/JUCE");
  244. }
  245. else if (key == Ids::defaultJuceModulePath)
  246. {
  247. v = (os == TargetOS::windows ? "C:\\JUCE\\modules"
  248. : "~/JUCE/modules");
  249. }
  250. else if (key == Ids::defaultUserModulePath)
  251. {
  252. v = (os == TargetOS::windows ? "C:\\modules"
  253. : "~/modules");
  254. }
  255. else if (key == Ids::vst3Path)
  256. {
  257. v = "";
  258. }
  259. else if (key == Ids::rtasPath)
  260. {
  261. if (os == TargetOS::windows) v = "C:\\SDKs\\PT_90_SDK";
  262. else if (os == TargetOS::osx) v = "~/SDKs/PT_90_SDK";
  263. else return {}; // no RTAS on this OS!
  264. }
  265. else if (key == Ids::aaxPath)
  266. {
  267. if (os == TargetOS::windows) v = "C:\\SDKs\\AAX";
  268. else if (os == TargetOS::osx) v = "~/SDKs/AAX";
  269. else return {}; // no AAX on this OS!
  270. }
  271. else if (key == Ids::androidSDKPath)
  272. {
  273. v = "${user.home}/Library/Android/sdk";
  274. }
  275. else if (key == Ids::androidNDKPath)
  276. {
  277. v = "${user.home}/Library/Android/sdk/ndk-bundle";
  278. }
  279. else if (key == Ids::clionExePath)
  280. {
  281. if (os == TargetOS::windows)
  282. {
  283. #if JUCE_WINDOWS
  284. auto regValue = WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\Applications\\clion64.exe\\shell\\open\\command\\", {}, {});
  285. auto openCmd = StringArray::fromTokens (regValue, true);
  286. if (! openCmd.isEmpty())
  287. return Value (openCmd[0].unquoted());
  288. #endif
  289. v = "C:\\Program Files\\JetBrains\\CLion YYYY.MM.DD\\bin\\clion64.exe";
  290. }
  291. else if (os == TargetOS::osx)
  292. {
  293. v = "/Applications/CLion.app";
  294. }
  295. else
  296. {
  297. v = "${user.home}/clion/bin/clion.sh";
  298. }
  299. }
  300. else if (key == Ids::androidStudioExePath)
  301. {
  302. if (os == TargetOS::windows)
  303. {
  304. #if JUCE_WINDOWS
  305. auto path = WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio\\Path", {}, {});
  306. if (! path.isEmpty())
  307. return Value (path.unquoted() + "\\bin\\studio64.exe");
  308. #endif
  309. v = "C:\\Program Files\\Android\\Android Studio\\bin\\studio64.exe";
  310. }
  311. else if (os == TargetOS::osx)
  312. {
  313. v = "/Applications/Android Studio.app";
  314. }
  315. else
  316. {
  317. return {}; // no Android Studio on this OS!
  318. }
  319. }
  320. }
  321. return v;
  322. }
  323. Identifier StoredSettings::identifierForOS (DependencyPathOS os) noexcept
  324. {
  325. if (os == TargetOS::osx) return Ids::osxFallback;
  326. else if (os == TargetOS::windows) return Ids::windowsFallback;
  327. else if (os == TargetOS::linux) return Ids::linuxFallback;
  328. jassertfalse;
  329. return {};
  330. }
  331. static bool doesSDKPathContainFile (const File& relativeTo, const String& path, const String& fileToCheckFor) noexcept
  332. {
  333. auto actualPath = path.replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  334. return relativeTo.getChildFile (actualPath + "/" + fileToCheckFor).exists();
  335. }
  336. bool StoredSettings::isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path) const noexcept
  337. {
  338. String fileToCheckFor;
  339. if (key == Ids::vst3Path)
  340. {
  341. fileToCheckFor = "base/source/baseiids.cpp";
  342. }
  343. else if (key == Ids::rtasPath)
  344. {
  345. fileToCheckFor = "AlturaPorts/TDMPlugIns/PlugInLibrary/EffectClasses/CEffectProcessMIDI.cpp";
  346. }
  347. else if (key == Ids::aaxPath)
  348. {
  349. fileToCheckFor = "Interfaces/AAX_Exports.cpp";
  350. }
  351. else if (key == Ids::androidSDKPath)
  352. {
  353. #if JUCE_WINDOWS
  354. fileToCheckFor = "platform-tools/adb.exe";
  355. #else
  356. fileToCheckFor = "platform-tools/adb";
  357. #endif
  358. }
  359. else if (key == Ids::androidNDKPath)
  360. {
  361. #if JUCE_WINDOWS
  362. fileToCheckFor = "ndk-depends.cmd";
  363. #else
  364. fileToCheckFor = "ndk-depends";
  365. #endif
  366. }
  367. else if (key == Ids::defaultJuceModulePath)
  368. {
  369. fileToCheckFor = "juce_core";
  370. }
  371. else if (key == Ids::defaultUserModulePath)
  372. {
  373. fileToCheckFor = {};
  374. }
  375. else if (key == Ids::clionExePath)
  376. {
  377. #if JUCE_MAC
  378. fileToCheckFor = path.trim().endsWith (".app") ? "Contents/MacOS/clion" : "../clion";
  379. #elif JUCE_WINDOWS
  380. fileToCheckFor = "../clion64.exe";
  381. #else
  382. fileToCheckFor = "../clion.sh";
  383. #endif
  384. }
  385. else if (key == Ids::androidStudioExePath)
  386. {
  387. #if JUCE_MAC
  388. fileToCheckFor = "Android Studio.app";
  389. #elif JUCE_WINDOWS
  390. fileToCheckFor = "studio64.exe";
  391. #endif
  392. }
  393. else if (key == Ids::jucePath)
  394. {
  395. fileToCheckFor = "ChangeList.txt";
  396. }
  397. else
  398. {
  399. // didn't recognise the key provided!
  400. jassertfalse;
  401. return false;
  402. }
  403. return doesSDKPathContainFile (relativeTo, path, fileToCheckFor);
  404. }