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.

978 lines
31KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "jucer_Project.h"
  19. #include "jucer_ProjectType.h"
  20. #include "../Project Saving/jucer_ProjectExporter.h"
  21. #include "../Project Saving/jucer_ProjectSaver.h"
  22. #include "../Application/jucer_OpenDocumentManager.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. const Identifier modulesGroup ("MODULES");
  33. const Identifier module ("MODULE");
  34. }
  35. const char* Project::projectFileExtension = ".jucer";
  36. //==============================================================================
  37. Project::Project (const File& file_)
  38. : FileBasedDocument (projectFileExtension,
  39. String ("*") + projectFileExtension,
  40. "Choose a Jucer project to load",
  41. "Save Jucer project"),
  42. projectRoot (Tags::projectRoot)
  43. {
  44. setFile (file_);
  45. setMissingDefaultValues();
  46. setChangedFlag (false);
  47. mainProjectIcon.setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  48. projectRoot.addListener (this);
  49. }
  50. Project::~Project()
  51. {
  52. projectRoot.removeListener (this);
  53. OpenDocumentManager::getInstance()->closeAllDocumentsUsingProject (*this, false);
  54. }
  55. //==============================================================================
  56. void Project::setTitle (const String& newTitle)
  57. {
  58. projectRoot.setProperty (Ids::name, newTitle, getUndoManagerFor (projectRoot));
  59. getMainGroup().getName() = newTitle;
  60. }
  61. const String Project::getDocumentTitle()
  62. {
  63. return getProjectName().toString();
  64. }
  65. void Project::updateProjectSettings()
  66. {
  67. projectRoot.setProperty (Ids::jucerVersion, ProjectInfo::versionString, 0);
  68. projectRoot.setProperty (Ids::name, getDocumentTitle(), 0);
  69. }
  70. void Project::setMissingDefaultValues()
  71. {
  72. if (! projectRoot.hasProperty (ComponentBuilder::idProperty))
  73. projectRoot.setProperty (ComponentBuilder::idProperty, createAlphaNumericUID(), nullptr);
  74. // Create main file group if missing
  75. if (! projectRoot.getChildWithName (Tags::projectMainGroup).isValid())
  76. {
  77. Item mainGroup (*this, ValueTree (Tags::projectMainGroup));
  78. projectRoot.addChild (mainGroup.state, 0, 0);
  79. }
  80. getMainGroup().initialiseMissingProperties();
  81. if (getDocumentTitle().isEmpty())
  82. setTitle ("Juce Project");
  83. if (! projectRoot.hasProperty (Ids::projectType))
  84. getProjectTypeValue() = ProjectType::getGUIAppTypeName();
  85. if (! projectRoot.hasProperty (Ids::version))
  86. getVersion() = "1.0.0";
  87. updateOldStyleConfigList();
  88. for (Project::ExporterIterator exporter (*this); exporter.next();)
  89. if (exporter->getNumConfigurations() == 0)
  90. exporter->createDefaultConfigs();
  91. if (! projectRoot.getChildWithName (Tags::exporters).isValid())
  92. createDefaultExporters();
  93. getProjectType().setMissingProjectProperties (*this);
  94. if (! projectRoot.hasProperty (Ids::bundleIdentifier))
  95. setBundleIdentifierToDefault();
  96. if (! projectRoot.getChildWithName (Tags::modulesGroup).isValid())
  97. addDefaultModules (false);
  98. }
  99. void Project::updateOldStyleConfigList()
  100. {
  101. ValueTree deprecatedConfigsList (projectRoot.getChildWithName (ProjectExporter::configurations));
  102. if (deprecatedConfigsList.isValid())
  103. {
  104. projectRoot.removeChild (deprecatedConfigsList, nullptr);
  105. for (Project::ExporterIterator exporter (*this); exporter.next();)
  106. {
  107. if (exporter->getNumConfigurations() == 0)
  108. {
  109. ValueTree newConfigs (deprecatedConfigsList.createCopy());
  110. if (! exporter->isXcode())
  111. {
  112. for (int j = newConfigs.getNumChildren(); --j >= 0;)
  113. {
  114. ValueTree config (newConfigs.getChild(j));
  115. config.removeProperty (Ids::osxSDK, nullptr);
  116. config.removeProperty (Ids::osxCompatibility, nullptr);
  117. config.removeProperty (Ids::osxArchitecture, nullptr);
  118. }
  119. }
  120. exporter->settings.addChild (newConfigs, 0, nullptr);
  121. }
  122. }
  123. }
  124. }
  125. void Project::addDefaultModules (bool shouldCopyFilesLocally)
  126. {
  127. addModule ("juce_core", shouldCopyFilesLocally);
  128. if (! isConfigFlagEnabled ("JUCE_ONLY_BUILD_CORE_LIBRARY"))
  129. {
  130. addModule ("juce_events", shouldCopyFilesLocally);
  131. addModule ("juce_graphics", shouldCopyFilesLocally);
  132. addModule ("juce_data_structures", shouldCopyFilesLocally);
  133. addModule ("juce_gui_basics", shouldCopyFilesLocally);
  134. addModule ("juce_gui_extra", shouldCopyFilesLocally);
  135. addModule ("juce_gui_audio", shouldCopyFilesLocally);
  136. addModule ("juce_cryptography", shouldCopyFilesLocally);
  137. addModule ("juce_video", shouldCopyFilesLocally);
  138. addModule ("juce_opengl", shouldCopyFilesLocally);
  139. addModule ("juce_audio_basics", shouldCopyFilesLocally);
  140. addModule ("juce_audio_devices", shouldCopyFilesLocally);
  141. addModule ("juce_audio_formats", shouldCopyFilesLocally);
  142. addModule ("juce_audio_processors", shouldCopyFilesLocally);
  143. }
  144. }
  145. //==============================================================================
  146. const String Project::loadDocument (const File& file)
  147. {
  148. ScopedPointer <XmlElement> xml (XmlDocument::parse (file));
  149. if (xml == nullptr || ! xml->hasTagName (Tags::projectRoot.toString()))
  150. return "Not a valid Jucer project!";
  151. ValueTree newTree (ValueTree::fromXml (*xml));
  152. if (! newTree.hasType (Tags::projectRoot))
  153. return "The document contains errors and couldn't be parsed!";
  154. StoredSettings::getInstance()->recentFiles.addFile (file);
  155. StoredSettings::getInstance()->flush();
  156. projectRoot = newTree;
  157. setMissingDefaultValues();
  158. return String::empty;
  159. }
  160. const String Project::saveDocument (const File& file)
  161. {
  162. updateProjectSettings();
  163. sanitiseConfigFlags();
  164. StoredSettings::getInstance()->recentFiles.addFile (file);
  165. ProjectSaver saver (*this, file);
  166. return saver.save();
  167. }
  168. //==============================================================================
  169. File Project::lastDocumentOpened;
  170. const File Project::getLastDocumentOpened()
  171. {
  172. return lastDocumentOpened;
  173. }
  174. void Project::setLastDocumentOpened (const File& file)
  175. {
  176. lastDocumentOpened = file;
  177. }
  178. //==============================================================================
  179. void Project::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
  180. {
  181. if (property == Ids::projectType)
  182. setMissingDefaultValues();
  183. changed();
  184. }
  185. void Project::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childWhichHasBeenAdded)
  186. {
  187. changed();
  188. }
  189. void Project::valueTreeChildRemoved (ValueTree& parentTree, ValueTree& childWhichHasBeenRemoved)
  190. {
  191. changed();
  192. }
  193. void Project::valueTreeChildOrderChanged (ValueTree& parentTree)
  194. {
  195. changed();
  196. }
  197. void Project::valueTreeParentChanged (ValueTree& tree)
  198. {
  199. }
  200. //==============================================================================
  201. File Project::resolveFilename (String filename) const
  202. {
  203. if (filename.isEmpty())
  204. return File::nonexistent;
  205. filename = replacePreprocessorDefs (getPreprocessorDefs(), filename)
  206. .replaceCharacter ('\\', '/');
  207. if (File::isAbsolutePath (filename))
  208. return File (filename);
  209. return getFile().getSiblingFile (filename);
  210. }
  211. String Project::getRelativePathForFile (const File& file) const
  212. {
  213. String filename (file.getFullPathName());
  214. File relativePathBase (getFile().getParentDirectory());
  215. String p1 (relativePathBase.getFullPathName());
  216. String p2 (file.getFullPathName());
  217. while (p1.startsWithChar (File::separator))
  218. p1 = p1.substring (1);
  219. while (p2.startsWithChar (File::separator))
  220. p2 = p2.substring (1);
  221. if (p1.upToFirstOccurrenceOf (File::separatorString, true, false)
  222. .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::separatorString, true, false)))
  223. {
  224. filename = file.getRelativePathFrom (relativePathBase);
  225. }
  226. return filename;
  227. }
  228. //==============================================================================
  229. const ProjectType& Project::getProjectType() const
  230. {
  231. const ProjectType* type = ProjectType::findType (getProjectTypeValue().toString());
  232. jassert (type != nullptr);
  233. if (type == nullptr)
  234. {
  235. type = ProjectType::findType (ProjectType::getGUIAppTypeName());
  236. jassert (type != nullptr);
  237. }
  238. return *type;
  239. }
  240. //==============================================================================
  241. void Project::createPropertyEditors (PropertyListBuilder& props)
  242. {
  243. props.add (new TextPropertyComponent (getProjectName(), "Project Name", 256, false),
  244. "The name of the project.");
  245. props.add (new TextPropertyComponent (getVersion(), "Project Version", 16, false),
  246. "The project's version number, This should be in the format major.minor.point");
  247. {
  248. StringArray projectTypeNames;
  249. Array<var> projectTypeCodes;
  250. const Array<ProjectType*>& types = ProjectType::getAllTypes();
  251. for (int i = 0; i < types.size(); ++i)
  252. {
  253. projectTypeNames.add (types.getUnchecked(i)->getDescription());
  254. projectTypeCodes.add (types.getUnchecked(i)->getType());
  255. }
  256. props.add (new ChoicePropertyComponent (getProjectTypeValue(), "Project Type", projectTypeNames, projectTypeCodes));
  257. }
  258. props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false),
  259. "A unique identifier for this product, mainly for use in Mac builds. It should be something like 'com.yourcompanyname.yourproductname'");
  260. {
  261. OwnedArray<Project::Item> images;
  262. findAllImageItems (images);
  263. StringArray choices;
  264. Array<var> ids;
  265. choices.add ("<None>");
  266. ids.add (var::null);
  267. choices.add (String::empty);
  268. ids.add (var::null);
  269. for (int i = 0; i < images.size(); ++i)
  270. {
  271. choices.add (images.getUnchecked(i)->getName().toString());
  272. ids.add (images.getUnchecked(i)->getID());
  273. }
  274. props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids),
  275. "Sets an icon to use for the executable.");
  276. props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids),
  277. "Sets an icon to use for the executable.");
  278. }
  279. getProjectType().createPropertyEditors(*this, props);
  280. props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, false),
  281. "Extra preprocessor definitions. Use the form \"NAME1=value NAME2=value\", using whitespace or commas to separate the items - to include a space or comma in a definition, precede it with a backslash.");
  282. props.setPreferredHeight (22);
  283. }
  284. String Project::getVersionAsHex() const
  285. {
  286. StringArray configs;
  287. configs.addTokens (getVersion().toString(), ",.", String::empty);
  288. configs.trim();
  289. configs.removeEmptyStrings();
  290. int value = (configs[0].getIntValue() << 16) + (configs[1].getIntValue() << 8) + configs[2].getIntValue();
  291. if (configs.size() >= 4)
  292. value = (value << 8) + configs[3].getIntValue();
  293. return "0x" + String::toHexString (value);
  294. }
  295. Image Project::getBigIcon()
  296. {
  297. return getMainGroup().findItemWithID (getBigIconImageItemID().toString()).loadAsImageFile();
  298. }
  299. Image Project::getSmallIcon()
  300. {
  301. return getMainGroup().findItemWithID (getSmallIconImageItemID().toString()).loadAsImageFile();
  302. }
  303. StringPairArray Project::getPreprocessorDefs() const
  304. {
  305. return parsePreprocessorDefs (getProjectPreprocessorDefs().toString());
  306. }
  307. //==============================================================================
  308. Project::Item Project::getMainGroup()
  309. {
  310. return Item (*this, projectRoot.getChildWithName (Tags::projectMainGroup));
  311. }
  312. static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
  313. {
  314. if (item.isImageFile())
  315. {
  316. found.add (new Project::Item (item));
  317. }
  318. else if (item.isGroup())
  319. {
  320. for (int i = 0; i < item.getNumChildren(); ++i)
  321. findImages (item.getChild (i), found);
  322. }
  323. }
  324. void Project::findAllImageItems (OwnedArray<Project::Item>& items)
  325. {
  326. findImages (getMainGroup(), items);
  327. }
  328. //==============================================================================
  329. Project::Item::Item (Project& project_, const ValueTree& state_)
  330. : project (project_), state (state_)
  331. {
  332. }
  333. Project::Item::Item (const Item& other)
  334. : project (other.project), state (other.state)
  335. {
  336. }
  337. Project::Item Project::Item::createCopy() { Item i (*this); i.state = i.state.createCopy(); return i; }
  338. String Project::Item::getID() const { return state [ComponentBuilder::idProperty]; }
  339. void Project::Item::setID (const String& newID) { state.setProperty (ComponentBuilder::idProperty, newID, nullptr); }
  340. String Project::Item::getImageFileID() const { return "id:" + getID(); }
  341. Image Project::Item::loadAsImageFile() const
  342. {
  343. return isValid() ? ImageCache::getFromFile (getFile())
  344. : Image::null;
  345. }
  346. Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid)
  347. {
  348. Item group (project, ValueTree (Tags::group));
  349. group.setID (uid);
  350. group.initialiseMissingProperties();
  351. group.getName() = name;
  352. return group;
  353. }
  354. bool Project::Item::isFile() const { return state.hasType (Tags::file); }
  355. bool Project::Item::isGroup() const { return state.hasType (Tags::group) || isMainGroup(); }
  356. bool Project::Item::isMainGroup() const { return state.hasType (Tags::projectMainGroup); }
  357. bool Project::Item::isImageFile() const { return isFile() && getFile().hasFileExtension ("png;jpg;jpeg;gif;drawable"); }
  358. Project::Item Project::Item::findItemWithID (const String& targetId) const
  359. {
  360. if (state [ComponentBuilder::idProperty] == targetId)
  361. return *this;
  362. if (isGroup())
  363. {
  364. for (int i = getNumChildren(); --i >= 0;)
  365. {
  366. Item found (getChild(i).findItemWithID (targetId));
  367. if (found.isValid())
  368. return found;
  369. }
  370. }
  371. return Item (project, ValueTree::invalid);
  372. }
  373. bool Project::Item::canContain (const Item& child) const
  374. {
  375. if (isFile())
  376. return false;
  377. if (isGroup())
  378. return child.isFile() || child.isGroup();
  379. jassertfalse
  380. return false;
  381. }
  382. bool Project::Item::shouldBeAddedToTargetProject() const { return isFile(); }
  383. bool Project::Item::shouldBeCompiled() const { return getShouldCompileValue().getValue(); }
  384. Value Project::Item::getShouldCompileValue() const { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
  385. bool Project::Item::shouldBeAddedToBinaryResources() const { return getShouldAddToResourceValue().getValue(); }
  386. Value Project::Item::getShouldAddToResourceValue() const { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
  387. Value Project::Item::getShouldInhibitWarningsValue() const { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
  388. Value Project::Item::getShouldUseStdCallValue() const { return state.getPropertyAsValue (Ids::useStdCall, nullptr); }
  389. String Project::Item::getFilePath() const
  390. {
  391. if (isFile())
  392. return state [Ids::file].toString();
  393. else
  394. return String::empty;
  395. }
  396. File Project::Item::getFile() const
  397. {
  398. if (isFile())
  399. return project.resolveFilename (state [Ids::file].toString());
  400. else
  401. return File::nonexistent;
  402. }
  403. void Project::Item::setFile (const File& file)
  404. {
  405. setFile (RelativePath (project.getRelativePathForFile (file), RelativePath::projectFolder));
  406. jassert (getFile() == file);
  407. }
  408. void Project::Item::setFile (const RelativePath& file)
  409. {
  410. jassert (file.getRoot() == RelativePath::projectFolder);
  411. jassert (isFile());
  412. state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
  413. state.setProperty (Ids::name, file.getFileName(), getUndoManager());
  414. }
  415. bool Project::Item::renameFile (const File& newFile)
  416. {
  417. const File oldFile (getFile());
  418. if (oldFile.moveFileTo (newFile))
  419. {
  420. setFile (newFile);
  421. OpenDocumentManager::getInstance()->fileHasBeenRenamed (oldFile, newFile);
  422. return true;
  423. }
  424. return false;
  425. }
  426. bool Project::Item::containsChildForFile (const RelativePath& file) const
  427. {
  428. return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
  429. }
  430. Project::Item Project::Item::findItemForFile (const File& file) const
  431. {
  432. if (getFile() == file)
  433. return *this;
  434. if (isGroup())
  435. {
  436. for (int i = getNumChildren(); --i >= 0;)
  437. {
  438. Item found (getChild(i).findItemForFile (file));
  439. if (found.isValid())
  440. return found;
  441. }
  442. }
  443. return Item (project, ValueTree::invalid);
  444. }
  445. File Project::Item::determineGroupFolder() const
  446. {
  447. jassert (isGroup());
  448. File f;
  449. for (int i = 0; i < getNumChildren(); ++i)
  450. {
  451. f = getChild(i).getFile();
  452. if (f.exists())
  453. return f.getParentDirectory();
  454. }
  455. Item parent (getParent());
  456. if (parent != *this)
  457. {
  458. f = parent.determineGroupFolder();
  459. if (f.getChildFile (getName().toString()).isDirectory())
  460. f = f.getChildFile (getName().toString());
  461. }
  462. else
  463. {
  464. f = project.getFile().getParentDirectory();
  465. if (f.getChildFile ("Source").isDirectory())
  466. f = f.getChildFile ("Source");
  467. }
  468. return f;
  469. }
  470. void Project::Item::initialiseMissingProperties()
  471. {
  472. if (! state.hasProperty (ComponentBuilder::idProperty))
  473. setID (createAlphaNumericUID());
  474. if (isFile())
  475. {
  476. state.setProperty (Ids::name, getFile().getFileName(), 0);
  477. }
  478. else if (isGroup())
  479. {
  480. for (int i = getNumChildren(); --i >= 0;)
  481. getChild(i).initialiseMissingProperties();
  482. }
  483. }
  484. Value Project::Item::getName() const
  485. {
  486. return state.getPropertyAsValue (Ids::name, getUndoManager());
  487. }
  488. void Project::Item::addChild (const Item& newChild, int insertIndex)
  489. {
  490. state.addChild (newChild.state, insertIndex, getUndoManager());
  491. }
  492. void Project::Item::removeItemFromProject()
  493. {
  494. state.getParent().removeChild (state, getUndoManager());
  495. }
  496. Project::Item Project::Item::getParent() const
  497. {
  498. if (isMainGroup() || ! isGroup())
  499. return *this;
  500. return Item (project, state.getParent());
  501. }
  502. struct ItemSorter
  503. {
  504. static int compareElements (const ValueTree& first, const ValueTree& second)
  505. {
  506. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  507. }
  508. };
  509. struct ItemSorterWithGroupsAtStart
  510. {
  511. static int compareElements (const ValueTree& first, const ValueTree& second)
  512. {
  513. const bool firstIsGroup = first.hasType (Tags::group);
  514. const bool secondIsGroup = second.hasType (Tags::group);
  515. if (firstIsGroup == secondIsGroup)
  516. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  517. else
  518. return firstIsGroup ? -1 : 1;
  519. }
  520. };
  521. void Project::Item::sortAlphabetically (bool keepGroupsAtStart)
  522. {
  523. if (keepGroupsAtStart)
  524. {
  525. ItemSorterWithGroupsAtStart sorter;
  526. state.sort (sorter, getUndoManager(), true);
  527. }
  528. else
  529. {
  530. ItemSorter sorter;
  531. state.sort (sorter, getUndoManager(), true);
  532. }
  533. }
  534. Project::Item Project::Item::getOrCreateSubGroup (const String& name)
  535. {
  536. for (int i = state.getNumChildren(); --i >= 0;)
  537. {
  538. const ValueTree child (state.getChild (i));
  539. if (child.getProperty (Ids::name) == name && child.hasType (Tags::group))
  540. return Item (project, child);
  541. }
  542. return addNewSubGroup (name, -1);
  543. }
  544. Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
  545. {
  546. Item group (createGroup (project, name, createGUID (getID() + name + String (getNumChildren()))));
  547. jassert (canContain (group));
  548. addChild (group, insertIndex);
  549. return group;
  550. }
  551. bool Project::Item::addFile (const File& file, int insertIndex, const bool shouldCompile)
  552. {
  553. if (file == File::nonexistent || file.isHidden() || file.getFileName().startsWithChar ('.'))
  554. return false;
  555. if (file.isDirectory())
  556. {
  557. Item group (addNewSubGroup (file.getFileNameWithoutExtension(), insertIndex));
  558. DirectoryIterator iter (file, false, "*", File::findFilesAndDirectories);
  559. while (iter.next())
  560. {
  561. if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
  562. group.addFile (iter.getFile(), -1, shouldCompile);
  563. }
  564. group.sortAlphabetically (false);
  565. }
  566. else if (file.existsAsFile())
  567. {
  568. if (! project.getMainGroup().findItemForFile (file).isValid())
  569. addFileUnchecked (file, insertIndex, shouldCompile);
  570. }
  571. else
  572. {
  573. jassertfalse;
  574. }
  575. return true;
  576. }
  577. void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
  578. {
  579. Item item (project, ValueTree (Tags::file));
  580. item.initialiseMissingProperties();
  581. item.getName() = file.getFileName();
  582. item.getShouldCompileValue() = shouldCompile && file.hasFileExtension ("cpp;mm;c;m;cc;cxx;r");
  583. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  584. if (canContain (item))
  585. {
  586. item.setFile (file);
  587. addChild (item, insertIndex);
  588. }
  589. }
  590. bool Project::Item::addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile)
  591. {
  592. Item item (project, ValueTree (Tags::file));
  593. item.initialiseMissingProperties();
  594. item.getName() = file.getFileName();
  595. item.getShouldCompileValue() = shouldCompile;
  596. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  597. if (canContain (item))
  598. {
  599. item.setFile (file);
  600. addChild (item, insertIndex);
  601. return true;
  602. }
  603. return false;
  604. }
  605. const Drawable* Project::Item::getIcon() const
  606. {
  607. if (isFile())
  608. {
  609. if (isImageFile())
  610. return StoredSettings::getInstance()->getImageFileIcon();
  611. return LookAndFeel::getDefaultLookAndFeel().getDefaultDocumentFileImage();
  612. }
  613. else if (isMainGroup())
  614. {
  615. return &(project.mainProjectIcon);
  616. }
  617. return LookAndFeel::getDefaultLookAndFeel().getDefaultFolderImage();
  618. }
  619. //==============================================================================
  620. ValueTree Project::getConfigNode()
  621. {
  622. return projectRoot.getOrCreateChildWithName (Tags::configGroup, nullptr);
  623. }
  624. const char* const Project::configFlagDefault = "default";
  625. const char* const Project::configFlagEnabled = "enabled";
  626. const char* const Project::configFlagDisabled = "disabled";
  627. Value Project::getConfigFlag (const String& name)
  628. {
  629. const ValueTree configNode (getConfigNode());
  630. Value v (configNode.getPropertyAsValue (name, getUndoManagerFor (configNode)));
  631. if (v.getValue().toString().isEmpty())
  632. v = configFlagDefault;
  633. return v;
  634. }
  635. bool Project::isConfigFlagEnabled (const String& name) const
  636. {
  637. return projectRoot.getChildWithName (Tags::configGroup).getProperty (name) == configFlagEnabled;
  638. }
  639. void Project::sanitiseConfigFlags()
  640. {
  641. ValueTree configNode (getConfigNode());
  642. for (int i = configNode.getNumProperties(); --i >= 0;)
  643. {
  644. const var value (configNode [configNode.getPropertyName(i)]);
  645. if (value != configFlagEnabled && value != configFlagDisabled)
  646. configNode.removeProperty (configNode.getPropertyName(i), getUndoManagerFor (configNode));
  647. }
  648. }
  649. //==============================================================================
  650. ValueTree Project::getModulesNode()
  651. {
  652. return projectRoot.getOrCreateChildWithName (Tags::modulesGroup, nullptr);
  653. }
  654. bool Project::isModuleEnabled (const String& moduleID) const
  655. {
  656. ValueTree modules (projectRoot.getChildWithName (Tags::modulesGroup));
  657. for (int i = 0; i < modules.getNumChildren(); ++i)
  658. if (modules.getChild(i) [ComponentBuilder::idProperty] == moduleID)
  659. return true;
  660. return false;
  661. }
  662. Value Project::shouldShowAllModuleFilesInProject (const String& moduleID)
  663. {
  664. return getModulesNode().getChildWithProperty (ComponentBuilder::idProperty, moduleID)
  665. .getPropertyAsValue (Ids::showAllCode, getUndoManagerFor (getModulesNode()));
  666. }
  667. Value Project::shouldCopyModuleFilesLocally (const String& moduleID)
  668. {
  669. return getModulesNode().getChildWithProperty (ComponentBuilder::idProperty, moduleID)
  670. .getPropertyAsValue (Ids::useLocalCopy, getUndoManagerFor (getModulesNode()));
  671. }
  672. void Project::addModule (const String& moduleID, bool shouldCopyFilesLocally)
  673. {
  674. if (! isModuleEnabled (moduleID))
  675. {
  676. ValueTree module (Tags::module);
  677. module.setProperty (ComponentBuilder::idProperty, moduleID, nullptr);
  678. ValueTree modules (getModulesNode());
  679. modules.addChild (module, -1, getUndoManagerFor (modules));
  680. shouldShowAllModuleFilesInProject (moduleID) = true;
  681. }
  682. if (shouldCopyFilesLocally)
  683. shouldCopyModuleFilesLocally (moduleID) = true;
  684. }
  685. void Project::removeModule (const String& moduleID)
  686. {
  687. ValueTree modules (getModulesNode());
  688. for (int i = 0; i < modules.getNumChildren(); ++i)
  689. if (modules.getChild(i) [ComponentBuilder::idProperty] == moduleID)
  690. modules.removeChild (i, getUndoManagerFor (modules));
  691. }
  692. void Project::createRequiredModules (const ModuleList& availableModules, OwnedArray<LibraryModule>& modules) const
  693. {
  694. for (int i = 0; i < availableModules.modules.size(); ++i)
  695. if (isModuleEnabled (availableModules.modules.getUnchecked(i)->uid))
  696. modules.add (availableModules.modules.getUnchecked(i)->create());
  697. }
  698. int Project::getNumModules() const
  699. {
  700. return projectRoot.getChildWithName (Tags::modulesGroup).getNumChildren();
  701. }
  702. String Project::getModuleID (int index) const
  703. {
  704. return projectRoot.getChildWithName (Tags::modulesGroup).getChild (index) [ComponentBuilder::idProperty].toString();
  705. }
  706. //==============================================================================
  707. ValueTree Project::getExporters()
  708. {
  709. ValueTree exporters (projectRoot.getChildWithName (Tags::exporters));
  710. if (! exporters.isValid())
  711. {
  712. projectRoot.addChild (ValueTree (Tags::exporters), 0, getUndoManagerFor (projectRoot));
  713. exporters = getExporters();
  714. }
  715. return exporters;
  716. }
  717. int Project::getNumExporters()
  718. {
  719. return getExporters().getNumChildren();
  720. }
  721. ProjectExporter* Project::createExporter (int index)
  722. {
  723. jassert (index >= 0 && index < getNumExporters());
  724. return ProjectExporter::createExporter (*this, getExporters().getChild (index));
  725. }
  726. void Project::addNewExporter (const String& exporterName)
  727. {
  728. ScopedPointer<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterName));
  729. ValueTree exporters (getExporters());
  730. exporters.addChild (exp->getSettings(), -1, getUndoManagerFor (exporters));
  731. }
  732. void Project::deleteExporter (int index)
  733. {
  734. ValueTree exporters (getExporters());
  735. exporters.removeChild (index, getUndoManagerFor (exporters));
  736. }
  737. void Project::createDefaultExporters()
  738. {
  739. ValueTree exporters (getExporters());
  740. exporters.removeAllChildren (getUndoManagerFor (exporters));
  741. const StringArray exporterNames (ProjectExporter::getDefaultExporters());
  742. for (int i = 0; i < exporterNames.size(); ++i)
  743. addNewExporter (exporterNames[i]);
  744. }
  745. //==============================================================================
  746. String Project::getFileTemplate (const String& templateName)
  747. {
  748. int dataSize;
  749. const char* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize);
  750. if (data == nullptr)
  751. {
  752. jassertfalse;
  753. return String::empty;
  754. }
  755. return String::fromUTF8 (data, dataSize);
  756. }
  757. //==============================================================================
  758. Project::ExporterIterator::ExporterIterator (Project& project_) : index (-1), project (project_) {}
  759. Project::ExporterIterator::~ExporterIterator() {}
  760. bool Project::ExporterIterator::next()
  761. {
  762. if (++index >= project.getNumExporters())
  763. return false;
  764. exporter = project.createExporter (index);
  765. if (exporter == nullptr)
  766. {
  767. jassertfalse; // corrupted project file?
  768. return next();
  769. }
  770. return true;
  771. }