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.

490 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. //==============================================================================
  167. void StoredSettings::loadSwatchColours()
  168. {
  169. swatchColours.clear();
  170. #define COL(col) Colours::col,
  171. const Colour colours[] =
  172. {
  173. #include "../Utility/Helpers/jucer_Colours.h"
  174. Colours::transparentBlack
  175. };
  176. #undef COL
  177. const auto numSwatchColours = 24;
  178. auto& props = getGlobalProperties();
  179. for (auto i = 0; i < numSwatchColours; ++i)
  180. swatchColours.add (Colour::fromString (props.getValue ("swatchColour" + String (i),
  181. colours [2 + i].toString())));
  182. }
  183. void StoredSettings::saveSwatchColours()
  184. {
  185. auto& props = getGlobalProperties();
  186. for (auto i = 0; i < swatchColours.size(); ++i)
  187. props.setValue ("swatchColour" + String (i), swatchColours.getReference(i).toString());
  188. }
  189. StoredSettings::ColourSelectorWithSwatches::ColourSelectorWithSwatches() {}
  190. StoredSettings::ColourSelectorWithSwatches::~ColourSelectorWithSwatches() {}
  191. int StoredSettings::ColourSelectorWithSwatches::getNumSwatches() const
  192. {
  193. return getAppSettings().swatchColours.size();
  194. }
  195. Colour StoredSettings::ColourSelectorWithSwatches::getSwatchColour (int index) const
  196. {
  197. return getAppSettings().swatchColours [index];
  198. }
  199. void StoredSettings::ColourSelectorWithSwatches::setSwatchColour (int index, const Colour& newColour)
  200. {
  201. getAppSettings().swatchColours.set (index, newColour);
  202. }
  203. //==============================================================================
  204. void StoredSettings::changed (bool isProjectDefaults)
  205. {
  206. std::unique_ptr<XmlElement> data (isProjectDefaults ? projectDefaults.createXml()
  207. : fallbackPaths.createXml());
  208. propertyFiles.getUnchecked (0)->setValue (isProjectDefaults ? "PROJECT_DEFAULT_SETTINGS" : "FALLBACK_PATHS",
  209. data.get());
  210. }
  211. //==============================================================================
  212. static bool doesSDKPathContainFile (const File& relativeTo, const String& path, const String& fileToCheckFor) noexcept
  213. {
  214. auto actualPath = path.replace ("${user.home}", File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
  215. return relativeTo.getChildFile (actualPath + "/" + fileToCheckFor).exists();
  216. }
  217. static bool isGlobalPathValid (const File& relativeTo, const Identifier& key, const String& path)
  218. {
  219. String fileToCheckFor;
  220. if (key == Ids::vstLegacyPath)
  221. {
  222. fileToCheckFor = "pluginterfaces/vst2.x/aeffect.h";
  223. }
  224. if (key == Ids::vst3Path)
  225. {
  226. fileToCheckFor = "base/source/baseiids.cpp";
  227. }
  228. else if (key == Ids::rtasPath)
  229. {
  230. fileToCheckFor = "AlturaPorts/TDMPlugIns/PlugInLibrary/EffectClasses/CEffectProcessMIDI.cpp";
  231. }
  232. else if (key == Ids::aaxPath)
  233. {
  234. fileToCheckFor = "Interfaces/AAX_Exports.cpp";
  235. }
  236. else if (key == Ids::androidSDKPath)
  237. {
  238. #if JUCE_WINDOWS
  239. fileToCheckFor = "platform-tools/adb.exe";
  240. #else
  241. fileToCheckFor = "platform-tools/adb";
  242. #endif
  243. }
  244. else if (key == Ids::androidNDKPath)
  245. {
  246. #if JUCE_WINDOWS
  247. fileToCheckFor = "ndk-depends.cmd";
  248. #else
  249. fileToCheckFor = "ndk-depends";
  250. #endif
  251. }
  252. else if (key == Ids::defaultJuceModulePath)
  253. {
  254. fileToCheckFor = "juce_core";
  255. }
  256. else if (key == Ids::defaultUserModulePath)
  257. {
  258. fileToCheckFor = {};
  259. }
  260. else if (key == Ids::clionExePath)
  261. {
  262. #if JUCE_MAC
  263. fileToCheckFor = path.trim().endsWith (".app") ? "Contents/MacOS/clion" : "../clion";
  264. #elif JUCE_WINDOWS
  265. fileToCheckFor = "../clion64.exe";
  266. #else
  267. fileToCheckFor = "../clion.sh";
  268. #endif
  269. }
  270. else if (key == Ids::androidStudioExePath)
  271. {
  272. #if JUCE_MAC
  273. fileToCheckFor = "Android Studio.app";
  274. #elif JUCE_WINDOWS
  275. fileToCheckFor = "studio64.exe";
  276. #endif
  277. }
  278. else if (key == Ids::jucePath)
  279. {
  280. fileToCheckFor = "ChangeList.txt";
  281. }
  282. else
  283. {
  284. // didn't recognise the key provided!
  285. jassertfalse;
  286. return false;
  287. }
  288. return doesSDKPathContainFile (relativeTo, path, fileToCheckFor);
  289. }
  290. void StoredSettings::checkJUCEPaths()
  291. {
  292. auto moduleFolder = getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString();
  293. auto juceFolder = getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString();
  294. auto validModuleFolder = isGlobalPathValid ({}, Ids::defaultJuceModulePath, moduleFolder);
  295. auto validJuceFolder = isGlobalPathValid ({}, Ids::jucePath, juceFolder);
  296. if (validModuleFolder && ! validJuceFolder)
  297. projectDefaults.getPropertyAsValue (Ids::jucePath, nullptr) = File (moduleFolder).getParentDirectory().getFullPathName();
  298. else if (! validModuleFolder && validJuceFolder)
  299. projectDefaults.getPropertyAsValue (Ids::defaultJuceModulePath, nullptr) = File (juceFolder).getChildFile ("modules").getFullPathName();
  300. }
  301. bool StoredSettings::shouldAskUserToSetJUCEPath() noexcept
  302. {
  303. if (! isGlobalPathValid ({}, Ids::jucePath, getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString())
  304. && getGlobalProperties().getValue ("dontAskAboutJUCEPath", {}).isEmpty())
  305. return true;
  306. return false;
  307. }
  308. void StoredSettings::setDontAskAboutJUCEPathAgain() noexcept
  309. {
  310. getGlobalProperties().setValue ("dontAskAboutJUCEPath", 1);
  311. }
  312. static String getFallbackPathForOS (const Identifier& key, DependencyPathOS os)
  313. {
  314. if (key == Ids::jucePath)
  315. {
  316. return (os == TargetOS::windows ? "C:\\JUCE" : "~/JUCE");
  317. }
  318. else if (key == Ids::defaultJuceModulePath)
  319. {
  320. return (os == TargetOS::windows ? "C:\\JUCE\\modules" : "~/JUCE/modules");
  321. }
  322. else if (key == Ids::defaultUserModulePath)
  323. {
  324. return (os == TargetOS::windows ? "C:\\modules" : "~/modules");
  325. }
  326. else if (key == Ids::vstLegacyPath || key == Ids::vst3Path)
  327. {
  328. return {};
  329. }
  330. else if (key == Ids::rtasPath)
  331. {
  332. if (os == TargetOS::windows) return "C:\\SDKs\\PT_90_SDK";
  333. else if (os == TargetOS::osx) return "~/SDKs/PT_90_SDK";
  334. else return {}; // no RTAS on this OS!
  335. }
  336. else if (key == Ids::aaxPath)
  337. {
  338. if (os == TargetOS::windows) return "C:\\SDKs\\AAX";
  339. else if (os == TargetOS::osx) return "~/SDKs/AAX";
  340. else return {}; // no AAX on this OS!
  341. }
  342. else if (key == Ids::androidSDKPath)
  343. {
  344. return "${user.home}/Library/Android/sdk";
  345. }
  346. else if (key == Ids::androidNDKPath)
  347. {
  348. return "${user.home}/Library/Android/sdk/ndk-bundle";
  349. }
  350. else if (key == Ids::clionExePath)
  351. {
  352. if (os == TargetOS::windows)
  353. {
  354. #if JUCE_WINDOWS
  355. auto regValue = WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Classes\\Applications\\clion64.exe\\shell\\open\\command\\", {}, {});
  356. auto openCmd = StringArray::fromTokens (regValue, true);
  357. if (! openCmd.isEmpty())
  358. return openCmd[0].unquoted();
  359. #endif
  360. return "C:\\Program Files\\JetBrains\\CLion YYYY.MM.DD\\bin\\clion64.exe";
  361. }
  362. else if (os == TargetOS::osx)
  363. {
  364. return "/Applications/CLion.app";
  365. }
  366. else
  367. {
  368. return "${user.home}/clion/bin/clion.sh";
  369. }
  370. }
  371. else if (key == Ids::androidStudioExePath)
  372. {
  373. if (os == TargetOS::windows)
  374. {
  375. #if JUCE_WINDOWS
  376. auto path = WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\SOFTWARE\\Android Studio\\Path", {}, {});
  377. if (! path.isEmpty())
  378. return path.unquoted() + "\\bin\\studio64.exe";
  379. #endif
  380. return "C:\\Program Files\\Android\\Android Studio\\bin\\studio64.exe";
  381. }
  382. else if (os == TargetOS::osx)
  383. {
  384. return "/Applications/Android Studio.app";
  385. }
  386. else
  387. {
  388. return {}; // no Android Studio on this OS!
  389. }
  390. }
  391. // unknown key!
  392. jassertfalse;
  393. return {};
  394. }
  395. static Identifier identifierForOS (DependencyPathOS os) noexcept
  396. {
  397. if (os == TargetOS::osx) return Ids::osxFallback;
  398. else if (os == TargetOS::windows) return Ids::windowsFallback;
  399. else if (os == TargetOS::linux) return Ids::linuxFallback;
  400. jassertfalse;
  401. return {};
  402. }
  403. ValueWithDefault StoredSettings::getStoredPath (const Identifier& key, DependencyPathOS os)
  404. {
  405. auto tree = (os == TargetOS::getThisOS() ? projectDefaults
  406. : fallbackPaths.getOrCreateChildWithName (identifierForOS (os), nullptr));
  407. return { tree, key, nullptr, getFallbackPathForOS (key, os) };
  408. }