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.

1121 lines
37KB

  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 configurations ("CONFIGURATIONS");
  31. const Identifier configuration ("CONFIGURATION");
  32. const Identifier exporters ("EXPORTFORMATS");
  33. const Identifier configGroup ("JUCEOPTIONS");
  34. const Identifier modulesGroup ("MODULES");
  35. const Identifier module ("MODULE");
  36. }
  37. const char* Project::projectFileExtension = ".jucer";
  38. //==============================================================================
  39. Project::Project (const File& file_)
  40. : FileBasedDocument (projectFileExtension,
  41. String ("*") + projectFileExtension,
  42. "Choose a Jucer project to load",
  43. "Save Jucer project"),
  44. projectRoot (Tags::projectRoot)
  45. {
  46. setFile (file_);
  47. setMissingDefaultValues();
  48. setChangedFlag (false);
  49. mainProjectIcon.setImage (ImageCache::getFromMemory (BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize));
  50. projectRoot.addListener (this);
  51. }
  52. Project::~Project()
  53. {
  54. projectRoot.removeListener (this);
  55. OpenDocumentManager::getInstance()->closeAllDocumentsUsingProject (*this, false);
  56. }
  57. //==============================================================================
  58. void Project::setTitle (const String& newTitle)
  59. {
  60. projectRoot.setProperty (Ids::name, newTitle, getUndoManagerFor (projectRoot));
  61. getMainGroup().getName() = newTitle;
  62. }
  63. const String Project::getDocumentTitle()
  64. {
  65. return getProjectName().toString();
  66. }
  67. void Project::updateProjectSettings()
  68. {
  69. projectRoot.setProperty (Ids::jucerVersion, ProjectInfo::versionString, 0);
  70. projectRoot.setProperty (Ids::name, getDocumentTitle(), 0);
  71. }
  72. void Project::setMissingDefaultValues()
  73. {
  74. if (! projectRoot.hasProperty (ComponentBuilder::idProperty))
  75. projectRoot.setProperty (ComponentBuilder::idProperty, createAlphaNumericUID(), nullptr);
  76. // Create main file group if missing
  77. if (! projectRoot.getChildWithName (Tags::projectMainGroup).isValid())
  78. {
  79. Item mainGroup (*this, ValueTree (Tags::projectMainGroup));
  80. projectRoot.addChild (mainGroup.state, 0, 0);
  81. }
  82. getMainGroup().initialiseMissingProperties();
  83. if (getDocumentTitle().isEmpty())
  84. setTitle ("Juce Project");
  85. if (! projectRoot.hasProperty (Ids::projectType))
  86. getProjectTypeValue() = ProjectType::getGUIAppTypeName();
  87. if (! projectRoot.hasProperty (Ids::version))
  88. getVersion() = "1.0.0";
  89. // Create configs group
  90. if (! projectRoot.getChildWithName (Tags::configurations).isValid())
  91. {
  92. projectRoot.addChild (ValueTree (Tags::configurations), 0, 0);
  93. createDefaultConfigs();
  94. }
  95. if (! projectRoot.getChildWithName (Tags::exporters).isValid())
  96. createDefaultExporters();
  97. getProjectType().setMissingProjectProperties (*this);
  98. if (! projectRoot.hasProperty (Ids::bundleIdentifier))
  99. setBundleIdentifierToDefault();
  100. if (! projectRoot.getChildWithName (Tags::modulesGroup).isValid())
  101. addDefaultModules (false);
  102. }
  103. void Project::addDefaultModules (bool shouldCopyFilesLocally)
  104. {
  105. addModule ("juce_core", shouldCopyFilesLocally);
  106. if (! isConfigFlagEnabled ("JUCE_ONLY_BUILD_CORE_LIBRARY"))
  107. {
  108. addModule ("juce_events", shouldCopyFilesLocally);
  109. addModule ("juce_graphics", shouldCopyFilesLocally);
  110. addModule ("juce_data_structures", shouldCopyFilesLocally);
  111. addModule ("juce_gui_basics", shouldCopyFilesLocally);
  112. addModule ("juce_gui_extra", shouldCopyFilesLocally);
  113. addModule ("juce_gui_audio", shouldCopyFilesLocally);
  114. addModule ("juce_cryptography", shouldCopyFilesLocally);
  115. addModule ("juce_video", shouldCopyFilesLocally);
  116. addModule ("juce_opengl", shouldCopyFilesLocally);
  117. addModule ("juce_audio_basics", shouldCopyFilesLocally);
  118. addModule ("juce_audio_devices", shouldCopyFilesLocally);
  119. addModule ("juce_audio_formats", shouldCopyFilesLocally);
  120. addModule ("juce_audio_processors", shouldCopyFilesLocally);
  121. }
  122. }
  123. //==============================================================================
  124. const String Project::loadDocument (const File& file)
  125. {
  126. ScopedPointer <XmlElement> xml (XmlDocument::parse (file));
  127. if (xml == nullptr || ! xml->hasTagName (Tags::projectRoot.toString()))
  128. return "Not a valid Jucer project!";
  129. ValueTree newTree (ValueTree::fromXml (*xml));
  130. if (! newTree.hasType (Tags::projectRoot))
  131. return "The document contains errors and couldn't be parsed!";
  132. StoredSettings::getInstance()->recentFiles.addFile (file);
  133. StoredSettings::getInstance()->flush();
  134. projectRoot = newTree;
  135. setMissingDefaultValues();
  136. return String::empty;
  137. }
  138. const String Project::saveDocument (const File& file)
  139. {
  140. updateProjectSettings();
  141. sanitiseConfigFlags();
  142. StoredSettings::getInstance()->recentFiles.addFile (file);
  143. ProjectSaver saver (*this, file);
  144. return saver.save();
  145. }
  146. //==============================================================================
  147. File Project::lastDocumentOpened;
  148. const File Project::getLastDocumentOpened()
  149. {
  150. return lastDocumentOpened;
  151. }
  152. void Project::setLastDocumentOpened (const File& file)
  153. {
  154. lastDocumentOpened = file;
  155. }
  156. //==============================================================================
  157. void Project::valueTreePropertyChanged (ValueTree& tree, const Identifier& property)
  158. {
  159. if (property == Ids::projectType)
  160. setMissingDefaultValues();
  161. changed();
  162. }
  163. void Project::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childWhichHasBeenAdded)
  164. {
  165. changed();
  166. }
  167. void Project::valueTreeChildRemoved (ValueTree& parentTree, ValueTree& childWhichHasBeenRemoved)
  168. {
  169. changed();
  170. }
  171. void Project::valueTreeChildOrderChanged (ValueTree& parentTree)
  172. {
  173. changed();
  174. }
  175. void Project::valueTreeParentChanged (ValueTree& tree)
  176. {
  177. }
  178. //==============================================================================
  179. File Project::resolveFilename (String filename) const
  180. {
  181. if (filename.isEmpty())
  182. return File::nonexistent;
  183. filename = replacePreprocessorDefs (getPreprocessorDefs(), filename)
  184. .replaceCharacter ('\\', '/');
  185. if (File::isAbsolutePath (filename))
  186. return File (filename);
  187. return getFile().getSiblingFile (filename);
  188. }
  189. String Project::getRelativePathForFile (const File& file) const
  190. {
  191. String filename (file.getFullPathName());
  192. File relativePathBase (getFile().getParentDirectory());
  193. String p1 (relativePathBase.getFullPathName());
  194. String p2 (file.getFullPathName());
  195. while (p1.startsWithChar (File::separator))
  196. p1 = p1.substring (1);
  197. while (p2.startsWithChar (File::separator))
  198. p2 = p2.substring (1);
  199. if (p1.upToFirstOccurrenceOf (File::separatorString, true, false)
  200. .equalsIgnoreCase (p2.upToFirstOccurrenceOf (File::separatorString, true, false)))
  201. {
  202. filename = file.getRelativePathFrom (relativePathBase);
  203. }
  204. return filename;
  205. }
  206. //==============================================================================
  207. const ProjectType& Project::getProjectType() const
  208. {
  209. const ProjectType* type = ProjectType::findType (getProjectTypeValue().toString());
  210. jassert (type != nullptr);
  211. if (type == nullptr)
  212. {
  213. type = ProjectType::findType (ProjectType::getGUIAppTypeName());
  214. jassert (type != nullptr);
  215. }
  216. return *type;
  217. }
  218. //==============================================================================
  219. void Project::createPropertyEditors (PropertyListBuilder& props)
  220. {
  221. props.add (new TextPropertyComponent (getProjectName(), "Project Name", 256, false),
  222. "The name of the project.");
  223. props.add (new TextPropertyComponent (getVersion(), "Project Version", 16, false),
  224. "The project's version number, This should be in the format major.minor.point");
  225. {
  226. StringArray projectTypeNames;
  227. Array<var> projectTypeCodes;
  228. const Array<ProjectType*>& types = ProjectType::getAllTypes();
  229. for (int i = 0; i < types.size(); ++i)
  230. {
  231. projectTypeNames.add (types.getUnchecked(i)->getDescription());
  232. projectTypeCodes.add (types.getUnchecked(i)->getType());
  233. }
  234. props.add (new ChoicePropertyComponent (getProjectTypeValue(), "Project Type", projectTypeNames, projectTypeCodes));
  235. }
  236. props.add (new TextPropertyComponent (getBundleIdentifier(), "Bundle Identifier", 256, false),
  237. "A unique identifier for this product, mainly for use in Mac builds. It should be something like 'com.yourcompanyname.yourproductname'");
  238. {
  239. OwnedArray<Project::Item> images;
  240. findAllImageItems (images);
  241. StringArray choices;
  242. Array<var> ids;
  243. choices.add ("<None>");
  244. ids.add (var::null);
  245. choices.add (String::empty);
  246. ids.add (var::null);
  247. for (int i = 0; i < images.size(); ++i)
  248. {
  249. choices.add (images.getUnchecked(i)->getName().toString());
  250. ids.add (images.getUnchecked(i)->getID());
  251. }
  252. props.add (new ChoicePropertyComponent (getSmallIconImageItemID(), "Icon (small)", choices, ids),
  253. "Sets an icon to use for the executable.");
  254. props.add (new ChoicePropertyComponent (getBigIconImageItemID(), "Icon (large)", choices, ids),
  255. "Sets an icon to use for the executable.");
  256. }
  257. getProjectType().createPropertyEditors(*this, props);
  258. props.add (new TextPropertyComponent (getProjectPreprocessorDefs(), "Preprocessor definitions", 32768, false),
  259. "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.");
  260. props.setPreferredHeight (22);
  261. }
  262. String Project::getVersionAsHex() const
  263. {
  264. StringArray configs;
  265. configs.addTokens (getVersion().toString(), ",.", String::empty);
  266. configs.trim();
  267. configs.removeEmptyStrings();
  268. int value = (configs[0].getIntValue() << 16) + (configs[1].getIntValue() << 8) + configs[2].getIntValue();
  269. if (configs.size() >= 4)
  270. value = (value << 8) + configs[3].getIntValue();
  271. return "0x" + String::toHexString (value);
  272. }
  273. Image Project::getBigIcon()
  274. {
  275. return getMainGroup().findItemWithID (getBigIconImageItemID().toString()).loadAsImageFile();
  276. }
  277. Image Project::getSmallIcon()
  278. {
  279. return getMainGroup().findItemWithID (getSmallIconImageItemID().toString()).loadAsImageFile();
  280. }
  281. StringPairArray Project::getPreprocessorDefs() const
  282. {
  283. return parsePreprocessorDefs (getProjectPreprocessorDefs().toString());
  284. }
  285. //==============================================================================
  286. Project::Item Project::getMainGroup()
  287. {
  288. return Item (*this, projectRoot.getChildWithName (Tags::projectMainGroup));
  289. }
  290. static void findImages (const Project::Item& item, OwnedArray<Project::Item>& found)
  291. {
  292. if (item.isImageFile())
  293. {
  294. found.add (new Project::Item (item));
  295. }
  296. else if (item.isGroup())
  297. {
  298. for (int i = 0; i < item.getNumChildren(); ++i)
  299. findImages (item.getChild (i), found);
  300. }
  301. }
  302. void Project::findAllImageItems (OwnedArray<Project::Item>& items)
  303. {
  304. findImages (getMainGroup(), items);
  305. }
  306. //==============================================================================
  307. Project::Item::Item (Project& project_, const ValueTree& state_)
  308. : project (project_), state (state_)
  309. {
  310. }
  311. Project::Item::Item (const Item& other)
  312. : project (other.project), state (other.state)
  313. {
  314. }
  315. Project::Item Project::Item::createCopy() { Item i (*this); i.state = i.state.createCopy(); return i; }
  316. String Project::Item::getID() const { return state [ComponentBuilder::idProperty]; }
  317. void Project::Item::setID (const String& newID) { state.setProperty (ComponentBuilder::idProperty, newID, nullptr); }
  318. String Project::Item::getImageFileID() const { return "id:" + getID(); }
  319. Image Project::Item::loadAsImageFile() const
  320. {
  321. return isValid() ? ImageCache::getFromFile (getFile())
  322. : Image::null;
  323. }
  324. Project::Item Project::Item::createGroup (Project& project, const String& name, const String& uid)
  325. {
  326. Item group (project, ValueTree (Tags::group));
  327. group.setID (uid);
  328. group.initialiseMissingProperties();
  329. group.getName() = name;
  330. return group;
  331. }
  332. bool Project::Item::isFile() const { return state.hasType (Tags::file); }
  333. bool Project::Item::isGroup() const { return state.hasType (Tags::group) || isMainGroup(); }
  334. bool Project::Item::isMainGroup() const { return state.hasType (Tags::projectMainGroup); }
  335. bool Project::Item::isImageFile() const { return isFile() && getFile().hasFileExtension ("png;jpg;jpeg;gif;drawable"); }
  336. Project::Item Project::Item::findItemWithID (const String& targetId) const
  337. {
  338. if (state [ComponentBuilder::idProperty] == targetId)
  339. return *this;
  340. if (isGroup())
  341. {
  342. for (int i = getNumChildren(); --i >= 0;)
  343. {
  344. Item found (getChild(i).findItemWithID (targetId));
  345. if (found.isValid())
  346. return found;
  347. }
  348. }
  349. return Item (project, ValueTree::invalid);
  350. }
  351. bool Project::Item::canContain (const Item& child) const
  352. {
  353. if (isFile())
  354. return false;
  355. if (isGroup())
  356. return child.isFile() || child.isGroup();
  357. jassertfalse
  358. return false;
  359. }
  360. bool Project::Item::shouldBeAddedToTargetProject() const { return isFile(); }
  361. bool Project::Item::shouldBeCompiled() const { return getShouldCompileValue().getValue(); }
  362. Value Project::Item::getShouldCompileValue() const { return state.getPropertyAsValue (Ids::compile, getUndoManager()); }
  363. bool Project::Item::shouldBeAddedToBinaryResources() const { return getShouldAddToResourceValue().getValue(); }
  364. Value Project::Item::getShouldAddToResourceValue() const { return state.getPropertyAsValue (Ids::resource, getUndoManager()); }
  365. Value Project::Item::getShouldInhibitWarningsValue() const { return state.getPropertyAsValue (Ids::noWarnings, getUndoManager()); }
  366. Value Project::Item::getShouldUseStdCallValue() const { return state.getPropertyAsValue (Ids::useStdCall, nullptr); }
  367. String Project::Item::getFilePath() const
  368. {
  369. if (isFile())
  370. return state [Ids::file].toString();
  371. else
  372. return String::empty;
  373. }
  374. File Project::Item::getFile() const
  375. {
  376. if (isFile())
  377. return project.resolveFilename (state [Ids::file].toString());
  378. else
  379. return File::nonexistent;
  380. }
  381. void Project::Item::setFile (const File& file)
  382. {
  383. setFile (RelativePath (project.getRelativePathForFile (file), RelativePath::projectFolder));
  384. jassert (getFile() == file);
  385. }
  386. void Project::Item::setFile (const RelativePath& file)
  387. {
  388. jassert (file.getRoot() == RelativePath::projectFolder);
  389. jassert (isFile());
  390. state.setProperty (Ids::file, file.toUnixStyle(), getUndoManager());
  391. state.setProperty (Ids::name, file.getFileName(), getUndoManager());
  392. }
  393. bool Project::Item::renameFile (const File& newFile)
  394. {
  395. const File oldFile (getFile());
  396. if (oldFile.moveFileTo (newFile))
  397. {
  398. setFile (newFile);
  399. OpenDocumentManager::getInstance()->fileHasBeenRenamed (oldFile, newFile);
  400. return true;
  401. }
  402. return false;
  403. }
  404. bool Project::Item::containsChildForFile (const RelativePath& file) const
  405. {
  406. return state.getChildWithProperty (Ids::file, file.toUnixStyle()).isValid();
  407. }
  408. Project::Item Project::Item::findItemForFile (const File& file) const
  409. {
  410. if (getFile() == file)
  411. return *this;
  412. if (isGroup())
  413. {
  414. for (int i = getNumChildren(); --i >= 0;)
  415. {
  416. Item found (getChild(i).findItemForFile (file));
  417. if (found.isValid())
  418. return found;
  419. }
  420. }
  421. return Item (project, ValueTree::invalid);
  422. }
  423. File Project::Item::determineGroupFolder() const
  424. {
  425. jassert (isGroup());
  426. File f;
  427. for (int i = 0; i < getNumChildren(); ++i)
  428. {
  429. f = getChild(i).getFile();
  430. if (f.exists())
  431. return f.getParentDirectory();
  432. }
  433. Item parent (getParent());
  434. if (parent != *this)
  435. {
  436. f = parent.determineGroupFolder();
  437. if (f.getChildFile (getName().toString()).isDirectory())
  438. f = f.getChildFile (getName().toString());
  439. }
  440. else
  441. {
  442. f = project.getFile().getParentDirectory();
  443. if (f.getChildFile ("Source").isDirectory())
  444. f = f.getChildFile ("Source");
  445. }
  446. return f;
  447. }
  448. void Project::Item::initialiseMissingProperties()
  449. {
  450. if (! state.hasProperty (ComponentBuilder::idProperty))
  451. setID (createAlphaNumericUID());
  452. if (isFile())
  453. {
  454. state.setProperty (Ids::name, getFile().getFileName(), 0);
  455. }
  456. else if (isGroup())
  457. {
  458. for (int i = getNumChildren(); --i >= 0;)
  459. getChild(i).initialiseMissingProperties();
  460. }
  461. }
  462. Value Project::Item::getName() const
  463. {
  464. return state.getPropertyAsValue (Ids::name, getUndoManager());
  465. }
  466. void Project::Item::addChild (const Item& newChild, int insertIndex)
  467. {
  468. state.addChild (newChild.state, insertIndex, getUndoManager());
  469. }
  470. void Project::Item::removeItemFromProject()
  471. {
  472. state.getParent().removeChild (state, getUndoManager());
  473. }
  474. Project::Item Project::Item::getParent() const
  475. {
  476. if (isMainGroup() || ! isGroup())
  477. return *this;
  478. return Item (project, state.getParent());
  479. }
  480. struct ItemSorter
  481. {
  482. static int compareElements (const ValueTree& first, const ValueTree& second)
  483. {
  484. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  485. }
  486. };
  487. struct ItemSorterWithGroupsAtStart
  488. {
  489. static int compareElements (const ValueTree& first, const ValueTree& second)
  490. {
  491. const bool firstIsGroup = first.hasType (Tags::group);
  492. const bool secondIsGroup = second.hasType (Tags::group);
  493. if (firstIsGroup == secondIsGroup)
  494. return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
  495. else
  496. return firstIsGroup ? -1 : 1;
  497. }
  498. };
  499. void Project::Item::sortAlphabetically (bool keepGroupsAtStart)
  500. {
  501. if (keepGroupsAtStart)
  502. {
  503. ItemSorterWithGroupsAtStart sorter;
  504. state.sort (sorter, getUndoManager(), true);
  505. }
  506. else
  507. {
  508. ItemSorter sorter;
  509. state.sort (sorter, getUndoManager(), true);
  510. }
  511. }
  512. Project::Item Project::Item::getOrCreateSubGroup (const String& name)
  513. {
  514. for (int i = state.getNumChildren(); --i >= 0;)
  515. {
  516. const ValueTree child (state.getChild (i));
  517. if (child.getProperty (Ids::name) == name && child.hasType (Tags::group))
  518. return Item (project, child);
  519. }
  520. return addNewSubGroup (name, -1);
  521. }
  522. Project::Item Project::Item::addNewSubGroup (const String& name, int insertIndex)
  523. {
  524. Item group (createGroup (project, name, createGUID (getID() + name + String (getNumChildren()))));
  525. jassert (canContain (group));
  526. addChild (group, insertIndex);
  527. return group;
  528. }
  529. bool Project::Item::addFile (const File& file, int insertIndex, const bool shouldCompile)
  530. {
  531. if (file == File::nonexistent || file.isHidden() || file.getFileName().startsWithChar ('.'))
  532. return false;
  533. if (file.isDirectory())
  534. {
  535. Item group (addNewSubGroup (file.getFileNameWithoutExtension(), insertIndex));
  536. DirectoryIterator iter (file, false, "*", File::findFilesAndDirectories);
  537. while (iter.next())
  538. {
  539. if (! project.getMainGroup().findItemForFile (iter.getFile()).isValid())
  540. group.addFile (iter.getFile(), -1, shouldCompile);
  541. }
  542. group.sortAlphabetically (false);
  543. }
  544. else if (file.existsAsFile())
  545. {
  546. if (! project.getMainGroup().findItemForFile (file).isValid())
  547. addFileUnchecked (file, insertIndex, shouldCompile);
  548. }
  549. else
  550. {
  551. jassertfalse;
  552. }
  553. return true;
  554. }
  555. void Project::Item::addFileUnchecked (const File& file, int insertIndex, const bool shouldCompile)
  556. {
  557. Item item (project, ValueTree (Tags::file));
  558. item.initialiseMissingProperties();
  559. item.getName() = file.getFileName();
  560. item.getShouldCompileValue() = shouldCompile && file.hasFileExtension ("cpp;mm;c;m;cc;cxx;r");
  561. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  562. if (canContain (item))
  563. {
  564. item.setFile (file);
  565. addChild (item, insertIndex);
  566. }
  567. }
  568. bool Project::Item::addRelativeFile (const RelativePath& file, int insertIndex, bool shouldCompile)
  569. {
  570. Item item (project, ValueTree (Tags::file));
  571. item.initialiseMissingProperties();
  572. item.getName() = file.getFileName();
  573. item.getShouldCompileValue() = shouldCompile;
  574. item.getShouldAddToResourceValue() = project.shouldBeAddedToBinaryResourcesByDefault (file);
  575. if (canContain (item))
  576. {
  577. item.setFile (file);
  578. addChild (item, insertIndex);
  579. return true;
  580. }
  581. return false;
  582. }
  583. const Drawable* Project::Item::getIcon() const
  584. {
  585. if (isFile())
  586. {
  587. if (isImageFile())
  588. return StoredSettings::getInstance()->getImageFileIcon();
  589. return LookAndFeel::getDefaultLookAndFeel().getDefaultDocumentFileImage();
  590. }
  591. else if (isMainGroup())
  592. {
  593. return &(project.mainProjectIcon);
  594. }
  595. return LookAndFeel::getDefaultLookAndFeel().getDefaultFolderImage();
  596. }
  597. //==============================================================================
  598. ValueTree Project::getConfigNode()
  599. {
  600. return projectRoot.getOrCreateChildWithName (Tags::configGroup, nullptr);
  601. }
  602. const char* const Project::configFlagDefault = "default";
  603. const char* const Project::configFlagEnabled = "enabled";
  604. const char* const Project::configFlagDisabled = "disabled";
  605. Value Project::getConfigFlag (const String& name)
  606. {
  607. const ValueTree configNode (getConfigNode());
  608. Value v (configNode.getPropertyAsValue (name, getUndoManagerFor (configNode)));
  609. if (v.getValue().toString().isEmpty())
  610. v = configFlagDefault;
  611. return v;
  612. }
  613. bool Project::isConfigFlagEnabled (const String& name) const
  614. {
  615. return projectRoot.getChildWithName (Tags::configGroup).getProperty (name) == configFlagEnabled;
  616. }
  617. void Project::sanitiseConfigFlags()
  618. {
  619. ValueTree configNode (getConfigNode());
  620. for (int i = configNode.getNumProperties(); --i >= 0;)
  621. {
  622. const var value (configNode [configNode.getPropertyName(i)]);
  623. if (value != configFlagEnabled && value != configFlagDisabled)
  624. configNode.removeProperty (configNode.getPropertyName(i), getUndoManagerFor (configNode));
  625. }
  626. }
  627. //==============================================================================
  628. ValueTree Project::getModulesNode()
  629. {
  630. return projectRoot.getOrCreateChildWithName (Tags::modulesGroup, nullptr);
  631. }
  632. bool Project::isModuleEnabled (const String& moduleID) const
  633. {
  634. ValueTree modules (projectRoot.getChildWithName (Tags::modulesGroup));
  635. for (int i = 0; i < modules.getNumChildren(); ++i)
  636. if (modules.getChild(i) [ComponentBuilder::idProperty] == moduleID)
  637. return true;
  638. return false;
  639. }
  640. Value Project::shouldShowAllModuleFilesInProject (const String& moduleID)
  641. {
  642. return getModulesNode().getChildWithProperty (ComponentBuilder::idProperty, moduleID)
  643. .getPropertyAsValue (Ids::showAllCode, getUndoManagerFor (getModulesNode()));
  644. }
  645. Value Project::shouldCopyModuleFilesLocally (const String& moduleID)
  646. {
  647. return getModulesNode().getChildWithProperty (ComponentBuilder::idProperty, moduleID)
  648. .getPropertyAsValue (Ids::useLocalCopy, getUndoManagerFor (getModulesNode()));
  649. }
  650. void Project::addModule (const String& moduleID, bool shouldCopyFilesLocally)
  651. {
  652. if (! isModuleEnabled (moduleID))
  653. {
  654. ValueTree module (Tags::module);
  655. module.setProperty (ComponentBuilder::idProperty, moduleID, nullptr);
  656. ValueTree modules (getModulesNode());
  657. modules.addChild (module, -1, getUndoManagerFor (modules));
  658. shouldShowAllModuleFilesInProject (moduleID) = true;
  659. }
  660. if (shouldCopyFilesLocally)
  661. shouldCopyModuleFilesLocally (moduleID) = true;
  662. }
  663. void Project::removeModule (const String& moduleID)
  664. {
  665. ValueTree modules (getModulesNode());
  666. for (int i = 0; i < modules.getNumChildren(); ++i)
  667. if (modules.getChild(i) [ComponentBuilder::idProperty] == moduleID)
  668. modules.removeChild (i, getUndoManagerFor (modules));
  669. }
  670. void Project::createRequiredModules (const ModuleList& availableModules, OwnedArray<LibraryModule>& modules) const
  671. {
  672. for (int i = 0; i < availableModules.modules.size(); ++i)
  673. if (isModuleEnabled (availableModules.modules.getUnchecked(i)->uid))
  674. modules.add (availableModules.modules.getUnchecked(i)->create());
  675. }
  676. int Project::getNumModules() const
  677. {
  678. return projectRoot.getChildWithName (Tags::modulesGroup).getNumChildren();
  679. }
  680. String Project::getModuleID (int index) const
  681. {
  682. return projectRoot.getChildWithName (Tags::modulesGroup).getChild (index) [ComponentBuilder::idProperty].toString();
  683. }
  684. //==============================================================================
  685. ValueTree Project::getConfigurations() const
  686. {
  687. return projectRoot.getChildWithName (Tags::configurations);
  688. }
  689. int Project::getNumConfigurations() const
  690. {
  691. return getConfigurations().getNumChildren();
  692. }
  693. Project::BuildConfiguration Project::getConfiguration (int index)
  694. {
  695. jassert (index < getConfigurations().getNumChildren());
  696. return BuildConfiguration (this, getConfigurations().getChild (index));
  697. }
  698. bool Project::hasConfigurationNamed (const String& name) const
  699. {
  700. const ValueTree configs (getConfigurations());
  701. for (int i = configs.getNumChildren(); --i >= 0;)
  702. if (configs.getChild(i) [Ids::name].toString() == name)
  703. return true;
  704. return false;
  705. }
  706. String Project::getUniqueConfigName (String name) const
  707. {
  708. String nameRoot (name);
  709. while (CharacterFunctions::isDigit (nameRoot.getLastCharacter()))
  710. nameRoot = nameRoot.dropLastCharacters (1);
  711. nameRoot = nameRoot.trim();
  712. int suffix = 2;
  713. while (hasConfigurationNamed (name))
  714. name = nameRoot + " " + String (suffix++);
  715. return name;
  716. }
  717. void Project::addNewConfiguration (BuildConfiguration* configToCopy)
  718. {
  719. const String configName (getUniqueConfigName (configToCopy != nullptr ? configToCopy->config [Ids::name].toString()
  720. : "New Build Configuration"));
  721. ValueTree configs (getConfigurations());
  722. if (! configs.isValid())
  723. {
  724. projectRoot.addChild (ValueTree (Tags::configurations), 0, getUndoManagerFor (projectRoot));
  725. configs = getConfigurations();
  726. }
  727. ValueTree newConfig (Tags::configuration);
  728. if (configToCopy != nullptr)
  729. newConfig = configToCopy->config.createCopy();
  730. newConfig.setProperty (Ids::name, configName, 0);
  731. configs.addChild (newConfig, -1, getUndoManagerFor (configs));
  732. }
  733. void Project::deleteConfiguration (int index)
  734. {
  735. ValueTree configs (getConfigurations());
  736. configs.removeChild (index, getUndoManagerFor (getConfigurations()));
  737. }
  738. void Project::createDefaultConfigs()
  739. {
  740. for (int i = 0; i < 2; ++i)
  741. {
  742. addNewConfiguration (nullptr);
  743. BuildConfiguration config = getConfiguration (i);
  744. const bool debugConfig = i == 0;
  745. config.getName() = debugConfig ? "Debug" : "Release";
  746. config.isDebug() = debugConfig;
  747. config.getOptimisationLevel() = debugConfig ? 1 : 2;
  748. config.getTargetBinaryName() = getProjectFilenameRoot();
  749. }
  750. }
  751. //==============================================================================
  752. Project::BuildConfiguration::BuildConfiguration (Project* project_, const ValueTree& configNode)
  753. : project (project_),
  754. config (configNode)
  755. {
  756. }
  757. Project::BuildConfiguration::BuildConfiguration (const BuildConfiguration& other)
  758. : project (other.project),
  759. config (other.config)
  760. {
  761. }
  762. const Project::BuildConfiguration& Project::BuildConfiguration::operator= (const BuildConfiguration& other)
  763. {
  764. project = other.project;
  765. config = other.config;
  766. return *this;
  767. }
  768. Project::BuildConfiguration::~BuildConfiguration()
  769. {
  770. }
  771. String Project::BuildConfiguration::getGCCOptimisationFlag() const
  772. {
  773. const int level = (int) getOptimisationLevel().getValue();
  774. return String (level <= 1 ? "0" : (level == 2 ? "s" : "3"));
  775. }
  776. const char* const Project::BuildConfiguration::osxVersionDefault = "default";
  777. const char* const Project::BuildConfiguration::osxVersion10_4 = "10.4 SDK";
  778. const char* const Project::BuildConfiguration::osxVersion10_5 = "10.5 SDK";
  779. const char* const Project::BuildConfiguration::osxVersion10_6 = "10.6 SDK";
  780. const char* const Project::BuildConfiguration::osxArch_Default = "default";
  781. const char* const Project::BuildConfiguration::osxArch_Native = "Native";
  782. const char* const Project::BuildConfiguration::osxArch_32BitUniversal = "32BitUniversal";
  783. const char* const Project::BuildConfiguration::osxArch_64BitUniversal = "64BitUniversal";
  784. const char* const Project::BuildConfiguration::osxArch_64Bit = "64BitIntel";
  785. void Project::BuildConfiguration::createPropertyEditors (PropertyListBuilder& props)
  786. {
  787. props.add (new TextPropertyComponent (getName(), "Name", 96, false),
  788. "The name of this configuration.");
  789. props.add (new BooleanPropertyComponent (isDebug(), "Debug mode", "Debugging enabled"),
  790. "If enabled, this means that the configuration should be built with debug synbols.");
  791. const char* optimisationLevels[] = { "No optimisation", "Optimise for size and speed", "Optimise for maximum speed", 0 };
  792. const int optimisationLevelValues[] = { 1, 2, 3, 0 };
  793. props.add (new ChoicePropertyComponent (getOptimisationLevel(), "Optimisation", StringArray (optimisationLevels), Array<var> (optimisationLevelValues)),
  794. "The optimisation level for this configuration");
  795. props.add (new TextPropertyComponent (getTargetBinaryName(), "Binary name", 256, false),
  796. "The filename to use for the destination binary executable file. Don't add a suffix to this, because platform-specific suffixes will be added for each target platform.");
  797. props.add (new TextPropertyComponent (getTargetBinaryRelativePath(), "Binary location", 1024, false),
  798. "The folder in which the finished binary should be placed. Leave this blank to cause the binary to be placed in its default location in the build folder.");
  799. props.add (new TextPropertyComponent (getHeaderSearchPath(), "Header search path", 16384, false),
  800. "Extra header search paths. Use semi-colons to separate multiple paths.");
  801. props.add (new TextPropertyComponent (getBuildConfigPreprocessorDefs(), "Preprocessor definitions", 32768, false),
  802. "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.");
  803. if (getMacSDKVersion().toString().isEmpty())
  804. getMacSDKVersion() = osxVersionDefault;
  805. const char* osxVersions[] = { "Use Default", osxVersion10_4, osxVersion10_5, osxVersion10_6, 0 };
  806. const char* osxVersionValues[] = { osxVersionDefault, osxVersion10_4, osxVersion10_5, osxVersion10_6, 0 };
  807. props.add (new ChoicePropertyComponent (getMacSDKVersion(), "OSX Base SDK Version", StringArray (osxVersions), Array<var> (osxVersionValues)),
  808. "The version of OSX to link against in the XCode build.");
  809. if (getMacCompatibilityVersion().toString().isEmpty())
  810. getMacCompatibilityVersion() = osxVersionDefault;
  811. props.add (new ChoicePropertyComponent (getMacCompatibilityVersion(), "OSX Compatibility Version", StringArray (osxVersions), Array<var> (osxVersionValues)),
  812. "The minimum version of OSX that the target binary will be compatible with.");
  813. const char* osxArch[] = { "Use Default", "Native architecture of build machine", "Universal Binary (32-bit)", "Universal Binary (64-bit)", "64-bit Intel", 0 };
  814. const char* osxArchValues[] = { osxArch_Default, osxArch_Native, osxArch_32BitUniversal, osxArch_64BitUniversal, osxArch_64Bit, 0 };
  815. if (getMacArchitecture().toString().isEmpty())
  816. getMacArchitecture() = osxArch_Default;
  817. props.add (new ChoicePropertyComponent (getMacArchitecture(), "OSX Architecture", StringArray (osxArch), Array<var> (osxArchValues)),
  818. "The type of OSX binary that will be produced.");
  819. props.setPreferredHeight (22);
  820. }
  821. StringPairArray Project::BuildConfiguration::getAllPreprocessorDefs() const
  822. {
  823. return mergePreprocessorDefs (project->getPreprocessorDefs(),
  824. parsePreprocessorDefs (getBuildConfigPreprocessorDefs().toString()));
  825. }
  826. StringArray Project::BuildConfiguration::getHeaderSearchPaths() const
  827. {
  828. StringArray s;
  829. s.addTokens (getHeaderSearchPath().toString(), ";", String::empty);
  830. return s;
  831. }
  832. //==============================================================================
  833. ValueTree Project::getExporters()
  834. {
  835. ValueTree exporters (projectRoot.getChildWithName (Tags::exporters));
  836. if (! exporters.isValid())
  837. {
  838. projectRoot.addChild (ValueTree (Tags::exporters), 0, getUndoManagerFor (projectRoot));
  839. exporters = getExporters();
  840. }
  841. return exporters;
  842. }
  843. int Project::getNumExporters()
  844. {
  845. return getExporters().getNumChildren();
  846. }
  847. ProjectExporter* Project::createExporter (int index)
  848. {
  849. jassert (index >= 0 && index < getNumExporters());
  850. return ProjectExporter::createExporter (*this, getExporters().getChild (index));
  851. }
  852. void Project::addNewExporter (const String& exporterName)
  853. {
  854. ScopedPointer<ProjectExporter> exp (ProjectExporter::createNewExporter (*this, exporterName));
  855. ValueTree exporters (getExporters());
  856. exporters.addChild (exp->getSettings(), -1, getUndoManagerFor (exporters));
  857. }
  858. void Project::deleteExporter (int index)
  859. {
  860. ValueTree exporters (getExporters());
  861. exporters.removeChild (index, getUndoManagerFor (exporters));
  862. }
  863. void Project::createDefaultExporters()
  864. {
  865. ValueTree exporters (getExporters());
  866. exporters.removeAllChildren (getUndoManagerFor (exporters));
  867. const StringArray exporterNames (ProjectExporter::getDefaultExporters());
  868. for (int i = 0; i < exporterNames.size(); ++i)
  869. addNewExporter (exporterNames[i]);
  870. }
  871. //==============================================================================
  872. String Project::getFileTemplate (const String& templateName)
  873. {
  874. int dataSize;
  875. const char* data = BinaryData::getNamedResource (templateName.toUTF8(), dataSize);
  876. if (data == nullptr)
  877. {
  878. jassertfalse;
  879. return String::empty;
  880. }
  881. return String::fromUTF8 (data, dataSize);
  882. }