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