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.

938 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "jucer_Project.h"
  18. #include "jucer_ProjectType.h"
  19. #include "../Project Saving/jucer_ProjectExporter.h"
  20. #include "../Project Saving/jucer_ProjectSaver.h"
  21. #include "../Application/jucer_OpenDocumentManager.h"
  22. #include "../Application/jucer_Application.h"
  23. //==============================================================================
  24. namespace Tags
  25. {
  26. const Identifier projectRoot ("JUCERPROJECT");
  27. const Identifier projectMainGroup ("MAINGROUP");
  28. const Identifier group ("GROUP");
  29. const Identifier file ("FILE");
  30. const Identifier exporters ("EXPORTFORMATS");
  31. const Identifier configGroup ("JUCEOPTIONS");
  32. }
  33. const char* Project::projectFileExtension = ".jucer";
  34. //==============================================================================
  35. Project::Project (const File& f)
  36. : FileBasedDocument (projectFileExtension,
  37. String ("*") + projectFileExtension,
  38. "Choose a Jucer project to load",
  39. "Save Jucer project"),
  40. projectRoot (Tags::projectRoot)
  41. {
  42. Logger::writeToLog ("Loading project: " + f.getFullPathName());
  43. setFile (f);
  44. removeDefunctExporters();
  45. setMissingDefaultValues();
  46. setChangedFlag (false);
  47. projectRoot.addListener (this);
  48. }
  49. Project::~Project()
  50. {
  51. projectRoot.removeListener (this);
  52. IntrojucerApp::getApp().openDocumentManager.closeAllDocumentsUsingProject (*this, false);
  53. }
  54. //==============================================================================
  55. void Project::setTitle (const String& newTitle)
  56. {
  57. projectRoot.setProperty (Ids::name, newTitle, getUndoManagerFor (projectRoot));
  58. getMainGroup().getNameValue() = newTitle;
  59. }
  60. String Project::getTitle() const
  61. {
  62. return projectRoot.getChildWithName (Tags::projectMainGroup) [Ids::name];
  63. }
  64. String Project::getDocumentTitle()
  65. {
  66. return getTitle();
  67. }
  68. void Project::updateProjectSettings()
  69. {
  70. projectRoot.setProperty (Ids::jucerVersion, ProjectInfo::versionString, nullptr);
  71. projectRoot.setProperty (Ids::name, getDocumentTitle(), nullptr);
  72. }
  73. void Project::setMissingDefaultValues()
  74. {
  75. if (! projectRoot.hasProperty (Ids::ID))
  76. projectRoot.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  77. // Create main file group if missing
  78. if (! projectRoot.getChildWithName (Tags::projectMainGroup).isValid())
  79. {
  80. Item mainGroup (*this, ValueTree (Tags::projectMainGroup));
  81. projectRoot.addChild (mainGroup.state, 0, 0);
  82. }
  83. getMainGroup().initialiseMissingProperties();
  84. if (getDocumentTitle().isEmpty())
  85. setTitle ("JUCE Project");
  86. if (! projectRoot.hasProperty (Ids::projectType))
  87. getProjectTypeValue() = ProjectType::getGUIAppTypeName();
  88. if (! projectRoot.hasProperty (Ids::version))
  89. getVersionValue() = "1.0.0";
  90. updateOldStyleConfigList();
  91. moveOldPropertyFromProjectToAllExporters (Ids::bigIcon);
  92. moveOldPropertyFromProjectToAllExporters (Ids::smallIcon);
  93. getProjectType().setMissingProjectProperties (*this);
  94. if (! projectRoot.getChildWithName (EnabledModuleList::modulesGroupTag).isValid())
  95. getModules().addDefaultModules (false);
  96. if (getBundleIdentifier().toString().isEmpty())
  97. getBundleIdentifier() = getDefaultBundleIdentifier();
  98. if (shouldIncludeBinaryInAppConfig() == var::null)
  99. shouldIncludeBinaryInAppConfig() = true;
  100. IntrojucerApp::getApp().updateNewlyOpenedProject (*this);
  101. }
  102. void Project::updateOldStyleConfigList()
  103. {
  104. ValueTree deprecatedConfigsList (projectRoot.getChildWithName (ProjectExporter::configurations));
  105. if (deprecatedConfigsList.isValid())
  106. {
  107. projectRoot.removeChild (deprecatedConfigsList, nullptr);
  108. for (Project::ExporterIterator exporter (*this); exporter.next();)
  109. {
  110. if (exporter->getNumConfigurations() == 0)
  111. {
  112. ValueTree newConfigs (deprecatedConfigsList.createCopy());
  113. if (! exporter->isXcode())
  114. {
  115. for (int j = newConfigs.getNumChildren(); --j >= 0;)
  116. {
  117. ValueTree config (newConfigs.getChild(j));
  118. config.removeProperty (Ids::osxSDK, nullptr);
  119. config.removeProperty (Ids::osxCompatibility, nullptr);
  120. config.removeProperty (Ids::osxArchitecture, nullptr);
  121. }
  122. }
  123. exporter->settings.addChild (newConfigs, 0, nullptr);
  124. }
  125. }
  126. }
  127. }
  128. void Project::moveOldPropertyFromProjectToAllExporters (Identifier name)
  129. {
  130. if (projectRoot.hasProperty (name))
  131. {
  132. for (Project::ExporterIterator exporter (*this); exporter.next();)
  133. exporter->settings.setProperty (name, projectRoot [name], nullptr);
  134. projectRoot.removeProperty (name, nullptr);
  135. }
  136. }
  137. void Project::removeDefunctExporters()
  138. {
  139. ValueTree exporters (projectRoot.getChildWithName (Tags::exporters));
  140. for (;;)
  141. {
  142. ValueTree oldVC6Exporter (exporters.getChildWithName ("MSVC6"));
  143. if (oldVC6Exporter.isValid())
  144. exporters.removeChild (oldVC6Exporter, nullptr);
  145. else
  146. break;
  147. }
  148. }
  149. File Project::getBinaryDataCppFile (int index) const
  150. {
  151. const File cpp (getGeneratedCodeFolder().getChildFile ("BinaryData.cpp"));
  152. if (index > 0)
  153. return cpp.getSiblingFile (cpp.getFileNameWithoutExtension() + String (index + 1))
  154. .withFileExtension (cpp.getFileExtension());
  155. return cpp;
  156. }
  157. //==============================================================================
  158. static void registerRecentFile (const File& file)
  159. {
  160. RecentlyOpenedFilesList::registerRecentFileNatively (file);
  161. getAppSettings().recentFiles.addFile (file);
  162. getAppSettings().flush();
  163. }
  164. Result Project::loadDocument (const File& file)
  165. {
  166. ScopedPointer <XmlElement> xml (XmlDocument::parse (file));
  167. if (xml == nullptr || ! xml->hasTagName (Tags::projectRoot.toString()))
  168. return Result::fail ("Not a valid Jucer project!");
  169. ValueTree newTree (ValueTree::fromXml (*xml));
  170. if (! newTree.hasType (Tags::projectRoot))
  171. return Result::fail ("The document contains errors and couldn't be parsed!");
  172. registerRecentFile (file);
  173. projectRoot = newTree;
  174. removeDefunctExporters();
  175. setMissingDefaultValues();
  176. setChangedFlag (false);
  177. return Result::ok();
  178. }
  179. Result Project::saveDocument (const File& file)
  180. {
  181. return saveProject (file, false);
  182. }
  183. Result Project::saveProject (const File& file, bool isCommandLineApp)
  184. {
  185. updateProjectSettings();
  186. sanitiseConfigFlags();
  187. if (! isCommandLineApp)
  188. registerRecentFile (file);
  189. ProjectSaver saver (*this, file);
  190. return saver.save (! isCommandLineApp);
  191. }
  192. Result Project::saveResourcesOnly (const File& file)
  193. {
  194. ProjectSaver saver (*this, file);
  195. return saver.saveResourcesOnly();
  196. }
  197. //==============================================================================
  198. static File lastDocumentOpened;
  199. File Project::getLastDocumentOpened() { return lastDocumentOpened; }
  200. void Project::setLastDocumentOpened (const File& file) { lastDocumentOpened = file; }
  201. //==============================================================================
  202. void Project::valueTreePropertyChanged (ValueTree&, const Identifier& property)
  203. {
  204. if (property == Ids::projectType)
  205. setMissingDefaultValues();
  206. changed();
  207. }
  208. void Project::valueTreeChildAdded (ValueTree&, ValueTree&) { changed(); }
  209. void Project::valueTreeChildRemoved (ValueTree&, ValueTree&) { changed(); }
  210. void Project::valueTreeChildOrderChanged (ValueTree&) { changed(); }
  211. void Project::valueTreeParentChanged (ValueTree&) {}
  212. //==============================================================================
  213. File Project::resolveFilename (String filename) const
  214. {
  215. if (filename.isEmpty())
  216. return File::nonexistent;
  217. filename = replacePreprocessorDefs (getPreprocessorDefs(), filename);
  218. if (FileHelpers::isAbsolutePath (filename))
  219. return File::createFileWithoutCheckingPath (FileHelpers::currentOSStylePath (filename)); // (avoid assertions for windows-style paths)
  220. return getFile().getSiblingFile (FileHelpers::currentOSStylePath (filename));
  221. }
  222. String Project::getRelativePathForFile (const File& file) const
  223. {
  224. String filename (file.getFullPathName());
  225. File relativePathBase (getFile().getParentDirectory());
  226. String p1 (relativePathBase.getFullPathName());
  227. String p2 (file.getFullPathName());
  228. while (p1.startsWithChar (File::separator))
  229. p1 = p1.substring (1);
  230. while (p2.startsWithChar (File::separator))
  231. p2 = p2.substring (1);
  232. if (p1.upToFirstOccurrenceOf (File::separatorString, true, false)
  233. .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::separatorString, true, false)))
  234. {
  235. filename = FileHelpers::getRelativePathFrom (file, relativePathBase);
  236. }
  237. return filename;
  238. }
  239. //==============================================================================
  240. const ProjectType& Project::getProjectType() const
  241. {
  242. if (const ProjectType* type = ProjectType::findType (getProjectTypeString()))
  243. return *type;
  244. const ProjectType* guiType = ProjectType::findType (ProjectType::getGUIAppTypeName());
  245. jassert (guiType != nullptr);
  246. return *guiType;
  247. }
  248. //==============================================================================
  249. void Project::createPropertyEditors (PropertyListBuilder& props)
  250. {
  251. props.add (new TextPropertyComponent (getProjectNameValue(), "Project Name", 256, false),
  252. "The name of the project.");
  253. props.add (new TextPropertyComponent (getVersionValue(), "Project Version", 16, false),
  254. "The project's version number, This should be in the format major.minor.point[.point]");
  255. props.add (new TextPropertyComponent (getCompanyName(), "Company Name", 256, false),
  256. "Your company name, which will be added to the properties of the binary where possible");
  257. {
  258. StringArray projectTypeNames;
  259. Array<var> projectTypeCodes;
  260. const Array<ProjectType*>& types = ProjectType::getAllTypes();
  261. for (int i = 0; i < types.size(); ++i)
  262. {
  263. projectTypeNames.add (types.getUnchecked(i)->getDescription());
  264. projectTypeCodes.add (types.getUnchecked(i)->getType());
  265. }
  266. props.add (new ChoicePropertyComponent (getProjectTypeValue(), "Project Type", projectTypeNames, projectTypeCodes));
  267. }
  268. props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false),
  269. "A unique identifier for this product, mainly for use in OSX/iOS builds. It should be something like 'com.yourcompanyname.yourproductname'");
  270. getProjectType().createPropertyEditors (*this, props);
  271. {
  272. const int maxSizes[] = { 20480, 10240, 6144, 2048, 1024, 512, 256, 128, 64 };
  273. StringArray maxSizeNames;
  274. Array<var> maxSizeCodes;
  275. maxSizeNames.add (TRANS("Default"));
  276. maxSizeCodes.add (var::null);
  277. maxSizeNames.add (String::empty);
  278. maxSizeCodes.add (var::null);
  279. for (int i = 0; i < numElementsInArray (maxSizes); ++i)
  280. {
  281. const int sizeInBytes = maxSizes[i] * 1024;
  282. maxSizeNames.add (File::descriptionOfSizeInBytes (sizeInBytes));
  283. maxSizeCodes.add (sizeInBytes);
  284. }
  285. props.add (new ChoicePropertyComponent (getMaxBinaryFileSize(), "BinaryData.cpp size limit", maxSizeNames, maxSizeCodes),
  286. "When splitting binary data into multiple cpp files, the Introjucer attempts to keep the file sizes below this threshold. "
  287. "(Note that individual resource files which are larger than this size cannot be split across multiple cpp files).");
  288. }
  289. props.add (new BooleanPropertyComponent (shouldIncludeBinaryInAppConfig(), "Include Binary",
  290. "Include BinaryData.h in the AppConfig.h file"));
  291. props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, true),
  292. "Global preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace, commas, or "
  293. "new-lines to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  294. props.add (new TextPropertyComponent (getProjectUserNotes(), "Notes", 32768, true),
  295. "Extra comments: This field is not used for code or project generation, it's just a space where you can express your thoughts.");
  296. }
  297. static StringArray getConfigs (const Project& p)
  298. {
  299. StringArray configs;
  300. configs.addTokens (p.getVersionString(), ",.", String::empty);
  301. configs.trim();
  302. configs.removeEmptyStrings();
  303. return configs;
  304. }
  305. int Project::getVersionAsHexInteger() const
  306. {
  307. const StringArray configs (getConfigs (*this));
  308. int value = (configs[0].getIntValue() << 16)
  309. + (configs[1].getIntValue() << 8)
  310. + configs[2].getIntValue();
  311. if (configs.size() >= 4)
  312. value = (value << 8) + configs[3].getIntValue();
  313. return value;
  314. }
  315. String Project::getVersionAsHex() const
  316. {
  317. return "0x" + String::toHexString (getVersionAsHexInteger());
  318. }
  319. StringPairArray Project::getPreprocessorDefs() const
  320. {
  321. return parsePreprocessorDefs (projectRoot [Ids::defines]);
  322. }
  323. //==============================================================================
  324. Project::Item Project::getMainGroup()
  325. {
  326. return Item (*this, projectRoot.getChildWithName (Tags::projectMainGroup));
  327. }
  328. static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
  329. {
  330. if (item.isImageFile())
  331. {
  332. found.add (new Project::Item (item));
  333. }
  334. else if (item.isGroup())
  335. {
  336. for (int i = 0; i < item.getNumChildren(); ++i)
  337. findImages (item.getChild (i), found);
  338. }
  339. }
  340. void Project::findAllImageItems (OwnedArray<Project::Item>& items)
  341. {
  342. findImages (getMainGroup(), items);
  343. }
  344. //==============================================================================
  345. Project::Item::Item (Project& p, const ValueTree& s)
  346. : project (p), state (s)
  347. {
  348. }
  349. Project::Item::Item (const Item& other)
  350. : project (other.project), state (other.state)
  351. {
  352. }
  353. Project::Item Project::Item::createCopy() { Item i (*this); i.state = i.state.createCopy(); return i; }
  354. String Project::Item::getID() const { return state [Ids::ID]; }
  355. void Project::Item::setID (const String& newID) { state.setProperty (Ids::ID, newID, nullptr); }
  356. Image Project::Item::loadAsImageFile() const
  357. {
  358. return isValid() ? ImageCache::getFromFile (getFile())
  359. : Image::null;
  360. }
  361. Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid)
  362. {
  363. Item group (project, ValueTree (Tags::group));
  364. group.setID (uid);
  365. group.initialiseMissingProperties();
  366. group.getNameValue() = name;
  367. return group;
  368. }
  369. bool Project::Item::isFile() const { return state.hasType (Tags::file); }
  370. bool Project::Item::isGroup() const { return state.hasType (Tags::group) || isMainGroup(); }
  371. bool Project::Item::isMainGroup() const { return state.hasType (Tags::projectMainGroup); }
  372. bool Project::Item::isImageFile() const { return isFile() && ImageFileFormat::findImageFormatForFileExtension (getFile()) != nullptr; }
  373. Project::Item Project::Item::findItemWithID (const String& targetId) const
  374. {
  375. if (state [Ids::ID] == targetId)
  376. return *this;
  377. if (isGroup())
  378. {
  379. for (int i = getNumChildren(); --i >= 0;)
  380. {
  381. Item found (getChild(i).findItemWithID (targetId));
  382. if (found.isValid())
  383. return found;
  384. }
  385. }
  386. return Item (project, ValueTree::invalid);
  387. }
  388. bool Project::Item::canContain (const Item& child) const
  389. {
  390. if (isFile())
  391. return false;
  392. if (isGroup())
  393. return child.isFile() || child.isGroup();
  394. jassertfalse;
  395. return false;
  396. }
  397. bool Project::Item::shouldBeAddedToTargetProject() const { return isFile(); }
  398. Value Project::Item::getShouldCompileValue() { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
  399. bool Project::Item::shouldBeCompiled() const { return state [Ids::compile]; }
  400. Value Project::Item::getShouldAddToResourceValue() { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
  401. bool Project::Item::shouldBeAddedToBinaryResources() const { return state [Ids::resource]; }
  402. Value Project::Item::getShouldInhibitWarningsValue() { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
  403. bool Project::Item::shouldInhibitWarnings() const { return state [Ids::noWarnings]; }
  404. Value Project::Item::getShouldUseStdCallValue() { return state.getPropertyAsValue (Ids::useStdCall, nullptr); }
  405. bool Project::Item::shouldUseStdCall() const { return state [Ids::useStdCall]; }
  406. String Project::Item::getFilePath() const
  407. {
  408. if (isFile())
  409. return state [Ids::file].toString();
  410. return String::empty;
  411. }
  412. File Project::Item::getFile() const
  413. {
  414. if (isFile())
  415. return project.resolveFilename (state [Ids::file].toString());
  416. return File::nonexistent;
  417. }
  418. void Project::Item::setFile (const File& file)
  419. {
  420. setFile (RelativePath (project.getRelativePathForFile (file), RelativePath::projectFolder));
  421. jassert (getFile() == file);
  422. }
  423. void Project::Item::setFile (const RelativePath& file)
  424. {
  425. jassert (file.getRoot() == RelativePath::projectFolder);
  426. jassert (isFile());
  427. state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
  428. state.setProperty (Ids::name, file.getFileName(), getUndoManager());
  429. }
  430. bool Project::Item::renameFile (const File& newFile)
  431. {
  432. const File oldFile (getFile());
  433. if (oldFile.moveFileTo (newFile)
  434. || (newFile.exists() && ! oldFile.exists()))
  435. {
  436. setFile (newFile);
  437. IntrojucerApp::getApp().openDocumentManager.fileHasBeenRenamed (oldFile, newFile);
  438. return true;
  439. }
  440. return false;
  441. }
  442. bool Project::Item::containsChildForFile (const RelativePath& file) const
  443. {
  444. return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
  445. }
  446. Project::Item Project::Item::findItemForFile (const File& file) const
  447. {
  448. if (getFile() == file)
  449. return *this;
  450. if (isGroup())
  451. {
  452. for (int i = getNumChildren(); --i >= 0;)
  453. {
  454. Item found (getChild(i).findItemForFile (file));
  455. if (found.isValid())
  456. return found;
  457. }
  458. }
  459. return Item (project, ValueTree::invalid);
  460. }
  461. File Project::Item::determineGroupFolder() const
  462. {
  463. jassert (isGroup());
  464. File f;
  465. for (int i = 0; i < getNumChildren(); ++i)
  466. {
  467. f = getChild(i).getFile();
  468. if (f.exists())
  469. return f.getParentDirectory();
  470. }
  471. Item parent (getParent());
  472. if (parent != *this)
  473. {
  474. f = parent.determineGroupFolder();
  475. if (f.getChildFile (getName()).isDirectory())
  476. f = f.getChildFile (getName());
  477. }
  478. else
  479. {
  480. f = project.getFile().getParentDirectory();
  481. if (f.getChildFile ("Source").isDirectory())
  482. f = f.getChildFile ("Source");
  483. }
  484. return f;
  485. }
  486. void Project::Item::initialiseMissingProperties()
  487. {
  488. if (! state.hasProperty (Ids::ID))
  489. setID (createAlphaNumericUID());
  490. if (isFile())
  491. {
  492. state.setProperty (Ids::name, getFile().getFileName(), nullptr);
  493. }
  494. else if (isGroup())
  495. {
  496. for (int i = getNumChildren(); --i >= 0;)
  497. getChild(i).initialiseMissingProperties();
  498. }
  499. }
  500. Value Project::Item::getNameValue()
  501. {
  502. return state.getPropertyAsValue (Ids::name, getUndoManager());
  503. }
  504. String Project::Item::getName() const
  505. {
  506. return state [Ids::name];
  507. }
  508. void Project::Item::addChild (const Item& newChild, int insertIndex)
  509. {
  510. state.addChild (newChild.state, insertIndex, getUndoManager());
  511. }
  512. void Project::Item::removeItemFromProject()
  513. {
  514. state.getParent().removeChild (state, getUndoManager());
  515. }
  516. Project::Item Project::Item::getParent() const
  517. {
  518. if (isMainGroup() || ! isGroup())
  519. return *this;
  520. return Item (project, state.getParent());
  521. }
  522. struct ItemSorter
  523. {
  524. static int compareElements (const ValueTree& first, const ValueTree& second)
  525. {
  526. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  527. }
  528. };
  529. struct ItemSorterWithGroupsAtStart
  530. {
  531. static int compareElements (const ValueTree& first, const ValueTree& second)
  532. {
  533. const bool firstIsGroup = first.hasType (Tags::group);
  534. const bool secondIsGroup = second.hasType (Tags::group);
  535. if (firstIsGroup == secondIsGroup)
  536. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  537. return firstIsGroup ? -1 : 1;
  538. }
  539. };
  540. void Project::Item::sortAlphabetically (bool keepGroupsAtStart)
  541. {
  542. if (keepGroupsAtStart)
  543. {
  544. ItemSorterWithGroupsAtStart sorter;
  545. state.sort (sorter, getUndoManager(), true);
  546. }
  547. else
  548. {
  549. ItemSorter sorter;
  550. state.sort (sorter, getUndoManager(), true);
  551. }
  552. }
  553. Project::Item Project::Item::getOrCreateSubGroup (const String& name)
  554. {
  555. for (int i = state.getNumChildren(); --i >= 0;)
  556. {
  557. const ValueTree child (state.getChild (i));
  558. if (child.getProperty (Ids::name) == name && child.hasType (Tags::group))
  559. return Item (project, child);
  560. }
  561. return addNewSubGroup (name, -1);
  562. }
  563. Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
  564. {
  565. String newID (createGUID (getID() + name + String (getNumChildren())));
  566. int n = 0;
  567. while (project.getMainGroup().findItemWithID (newID).isValid())
  568. newID = createGUID (newID + String (++n));
  569. Item group (createGroup (project, name, newID));
  570. jassert (canContain (group));
  571. addChild (group, insertIndex);
  572. return group;
  573. }
  574. bool Project::Item::addFile (const File& file, int insertIndex, const bool shouldCompile)
  575. {
  576. if (file == File::nonexistent || file.isHidden() || file.getFileName().startsWithChar ('.'))
  577. return false;
  578. if (file.isDirectory())
  579. {
  580. Item group (addNewSubGroup (file.getFileNameWithoutExtension(), insertIndex));
  581. for (DirectoryIterator iter (file, false, "*", File::findFilesAndDirectories); iter.next();)
  582. if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
  583. group.addFile (iter.getFile(), -1, shouldCompile);
  584. group.sortAlphabetically (false);
  585. }
  586. else if (file.existsAsFile())
  587. {
  588. if (! project.getMainGroup().findItemForFile (file).isValid())
  589. addFileUnchecked (file, insertIndex, shouldCompile);
  590. }
  591. else
  592. {
  593. jassertfalse;
  594. }
  595. return true;
  596. }
  597. void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
  598. {
  599. Item item (project, ValueTree (Tags::file));
  600. item.initialiseMissingProperties();
  601. item.getNameValue() = file.getFileName();
  602. item.getShouldCompileValue() = shouldCompile && file.hasFileExtension ("cpp;mm;c;m;cc;cxx;r");
  603. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  604. if (canContain (item))
  605. {
  606. item.setFile (file);
  607. addChild (item, insertIndex);
  608. }
  609. }
  610. bool Project::Item::addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile)
  611. {
  612. Item item (project, ValueTree (Tags::file));
  613. item.initialiseMissingProperties();
  614. item.getNameValue() = file.getFileName();
  615. item.getShouldCompileValue() = shouldCompile;
  616. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  617. if (canContain (item))
  618. {
  619. item.setFile (file);
  620. addChild (item, insertIndex);
  621. return true;
  622. }
  623. return false;
  624. }
  625. Icon Project::Item::getIcon() const
  626. {
  627. const Icons& icons = getIcons();
  628. if (isFile())
  629. {
  630. if (isImageFile())
  631. return Icon (icons.imageDoc, Colours::blue);
  632. return Icon (icons.document, Colours::yellow);
  633. }
  634. if (isMainGroup())
  635. return Icon (icons.juceLogo, Colours::orange);
  636. return Icon (icons.folder, Colours::darkgrey);
  637. }
  638. bool Project::Item::isIconCrossedOut() const
  639. {
  640. return isFile()
  641. && ! (shouldBeCompiled()
  642. || shouldBeAddedToBinaryResources()
  643. || getFile().hasFileExtension (headerFileExtensions));
  644. }
  645. //==============================================================================
  646. ValueTree Project::getConfigNode()
  647. {
  648. return projectRoot.getOrCreateChildWithName (Tags::configGroup, nullptr);
  649. }
  650. const char* const Project::configFlagDefault = "default";
  651. const char* const Project::configFlagEnabled = "enabled";
  652. const char* const Project::configFlagDisabled = "disabled";
  653. Value Project::getConfigFlag (const String& name)
  654. {
  655. ValueTree configNode (getConfigNode());
  656. Value v (configNode.getPropertyAsValue (name, getUndoManagerFor (configNode)));
  657. if (v.getValue().toString().isEmpty())
  658. v = configFlagDefault;
  659. return v;
  660. }
  661. bool Project::isConfigFlagEnabled (const String& name) const
  662. {
  663. return projectRoot.getChildWithName (Tags::configGroup).getProperty (name) == configFlagEnabled;
  664. }
  665. void Project::sanitiseConfigFlags()
  666. {
  667. ValueTree configNode (getConfigNode());
  668. for (int i = configNode.getNumProperties(); --i >= 0;)
  669. {
  670. const var value (configNode [configNode.getPropertyName(i)]);
  671. if (value != configFlagEnabled && value != configFlagDisabled)
  672. configNode.removeProperty (configNode.getPropertyName(i), getUndoManagerFor (configNode));
  673. }
  674. }
  675. //==============================================================================
  676. EnabledModuleList Project::getModules()
  677. {
  678. return EnabledModuleList (*this, projectRoot.getOrCreateChildWithName (EnabledModuleList::modulesGroupTag, nullptr));
  679. }
  680. //==============================================================================
  681. ValueTree Project::getExporters()
  682. {
  683. return projectRoot.getOrCreateChildWithName (Tags::exporters, nullptr);
  684. }
  685. int Project::getNumExporters()
  686. {
  687. return getExporters().getNumChildren();
  688. }
  689. ProjectExporter* Project::createExporter (int index)
  690. {
  691. jassert (index >= 0 && index < getNumExporters());
  692. return ProjectExporter::createExporter (*this, getExporters().getChild (index));
  693. }
  694. void Project::addNewExporter (const String& exporterName)
  695. {
  696. ScopedPointer<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterName));
  697. ValueTree exporters (getExporters());
  698. exporters.addChild (exp->settings, -1, getUndoManagerFor (exporters));
  699. }
  700. void Project::createExporterForCurrentPlatform()
  701. {
  702. addNewExporter (ProjectExporter::getCurrentPlatformExporterName());
  703. }
  704. //==============================================================================
  705. String Project::getFileTemplate (const String& templateName)
  706. {
  707. int dataSize;
  708. const char* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize);
  709. if (data == nullptr)
  710. {
  711. jassertfalse;
  712. return String::empty;
  713. }
  714. return String::fromUTF8 (data, dataSize);
  715. }
  716. //==============================================================================
  717. Project::ExporterIterator::ExporterIterator (Project& p) : index (-1), project (p) {}
  718. Project::ExporterIterator::~ExporterIterator() {}
  719. bool Project::ExporterIterator::next()
  720. {
  721. if (++index >= project.getNumExporters())
  722. return false;
  723. exporter = project.createExporter (index);
  724. if (exporter == nullptr)
  725. {
  726. jassertfalse; // corrupted project file?
  727. return next();
  728. }
  729. return true;
  730. }
  731. PropertiesFile& Project::getStoredProperties() const
  732. {
  733. return getAppSettings().getProjectProperties (getProjectUID());
  734. }