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.

1074 lines
35KB

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