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.

652 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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_ComponentDocument.h"
  19. #include "Component Types/jucer_ComponentTypeManager.h"
  20. //==============================================================================
  21. static const char* const componentDocumentTag = "COMPONENT";
  22. static const char* const componentGroupTag = "COMPONENTS";
  23. static const char* const markersGroupXTag = "MARKERS_X";
  24. static const char* const markersGroupYTag = "MARKERS_Y";
  25. static const char* const markerTag = "MARKER";
  26. static const char* const metadataTagStart = "JUCER_" "COMPONENT_METADATA_START"; // written like this to avoid thinking this file is a component!
  27. static const char* const metadataTagEnd = "JUCER_" "COMPONENT_METADATA_END";
  28. const char* const ComponentDocument::idProperty = "id";
  29. const char* const ComponentDocument::compBoundsProperty = "position";
  30. const char* const ComponentDocument::memberNameProperty = "memberName";
  31. const char* const ComponentDocument::compNameProperty = "name";
  32. const char* const ComponentDocument::markerNameProperty = "name";
  33. const char* const ComponentDocument::markerPosProperty = "position";
  34. //==============================================================================
  35. ComponentDocument::ComponentDocument (Project* project_, const File& cppFile_)
  36. : project (project_), cppFile (cppFile_),
  37. root (componentDocumentTag),
  38. changedSinceSaved (false)
  39. {
  40. reload();
  41. checkRootObject();
  42. root.addListener (this);
  43. }
  44. ComponentDocument::~ComponentDocument()
  45. {
  46. root.removeListener (this);
  47. }
  48. void ComponentDocument::beginNewTransaction()
  49. {
  50. undoManager.beginNewTransaction();
  51. }
  52. void ComponentDocument::valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  53. {
  54. changedSinceSaved = true;
  55. }
  56. void ComponentDocument::valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  57. {
  58. changedSinceSaved = true;
  59. }
  60. void ComponentDocument::valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  61. {
  62. changedSinceSaved = true;
  63. }
  64. bool ComponentDocument::isComponentFile (const File& file)
  65. {
  66. if (! file.hasFileExtension (".cpp"))
  67. return false;
  68. InputStream* in = file.createInputStream();
  69. if (in != 0)
  70. {
  71. BufferedInputStream buf (in, 8192, true);
  72. while (! buf.isExhausted())
  73. if (buf.readNextLine().contains (metadataTagStart))
  74. return true;
  75. }
  76. return false;
  77. }
  78. void ComponentDocument::writeCode (OutputStream& cpp, OutputStream& header)
  79. {
  80. cpp << "/** */"
  81. << newLine << newLine;
  82. header << "/** */"
  83. << newLine << newLine;
  84. }
  85. void ComponentDocument::writeMetadata (OutputStream& out)
  86. {
  87. out << "#if 0" << newLine
  88. << "/** Jucer-generated metadata section - Edit this data at own risk!" << newLine
  89. << metadataTagStart << newLine << newLine;
  90. ScopedPointer<XmlElement> xml (root.createXml());
  91. jassert (xml != 0);
  92. if (xml != 0)
  93. xml->writeToStream (out, String::empty, false, false);
  94. out << newLine
  95. << metadataTagEnd << " */" << newLine
  96. << "#endif" << newLine;
  97. }
  98. bool ComponentDocument::save()
  99. {
  100. MemoryOutputStream cpp, header;
  101. writeCode (cpp, header);
  102. writeMetadata (cpp);
  103. bool savedOk = overwriteFileWithNewDataIfDifferent (cppFile, cpp)
  104. && overwriteFileWithNewDataIfDifferent (cppFile.withFileExtension (".h"), header);
  105. if (savedOk)
  106. changedSinceSaved = false;
  107. return savedOk;
  108. }
  109. bool ComponentDocument::reload()
  110. {
  111. String xmlString;
  112. {
  113. InputStream* in = cppFile.createInputStream();
  114. if (in == 0)
  115. return false;
  116. BufferedInputStream buf (in, 8192, true);
  117. String::Concatenator xml (xmlString);
  118. while (! buf.isExhausted())
  119. {
  120. String line (buf.readNextLine());
  121. if (line.contains (metadataTagStart))
  122. {
  123. while (! buf.isExhausted())
  124. {
  125. line = buf.readNextLine();
  126. if (line.contains (metadataTagEnd))
  127. break;
  128. xml.append (line);
  129. xml.append (newLine);
  130. }
  131. break;
  132. }
  133. }
  134. }
  135. XmlDocument doc (xmlString);
  136. ScopedPointer<XmlElement> xml (doc.getDocumentElement());
  137. if (xml != 0 && xml->hasTagName (componentDocumentTag))
  138. {
  139. ValueTree newTree (ValueTree::fromXml (*xml));
  140. if (newTree.isValid())
  141. {
  142. root = newTree;
  143. markersX = 0;
  144. markersY = 0;
  145. checkRootObject();
  146. undoManager.clearUndoHistory();
  147. changedSinceSaved = false;
  148. return true;
  149. }
  150. }
  151. return false;
  152. }
  153. bool ComponentDocument::hasChangedSinceLastSave()
  154. {
  155. return changedSinceSaved;
  156. }
  157. void ComponentDocument::createSubTreeIfNotThere (const String& name)
  158. {
  159. if (! root.getChildWithName (name).isValid())
  160. root.addChild (ValueTree (name), -1, 0);
  161. }
  162. void ComponentDocument::checkRootObject()
  163. {
  164. jassert (root.hasType (componentDocumentTag));
  165. createSubTreeIfNotThere (componentGroupTag);
  166. createSubTreeIfNotThere (markersGroupXTag);
  167. createSubTreeIfNotThere (markersGroupYTag);
  168. if (markersX == 0)
  169. markersX = new MarkerList (*this, true);
  170. if (markersY == 0)
  171. markersY = new MarkerList (*this, false);
  172. if (getClassName().toString().isEmpty())
  173. getClassName() = "NewComponent";
  174. if ((int) getCanvasWidth().getValue() <= 0)
  175. getCanvasWidth() = 640;
  176. if ((int) getCanvasHeight().getValue() <= 0)
  177. getCanvasHeight() = 480;
  178. }
  179. //==============================================================================
  180. const int menuItemOffset = 0x63451fa4;
  181. void ComponentDocument::addNewComponentMenuItems (PopupMenu& menu) const
  182. {
  183. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  184. for (int i = 0; i < typeNames.size(); ++i)
  185. menu.addItem (i + menuItemOffset, "New " + typeNames[i]);
  186. }
  187. void ComponentDocument::performNewComponentMenuItem (int menuResultCode)
  188. {
  189. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  190. if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeNames.size())
  191. {
  192. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandler (menuResultCode - menuItemOffset);
  193. jassert (handler != 0);
  194. if (handler != 0)
  195. {
  196. ValueTree state (handler->getXmlTag());
  197. state.setProperty (idProperty, createAlphaNumericUID(), 0);
  198. handler->initialiseNewItem (*this, state);
  199. getComponentGroup().addChild (state, -1, getUndoManager());
  200. }
  201. }
  202. }
  203. //==============================================================================
  204. ValueTree ComponentDocument::getComponentGroup() const
  205. {
  206. return root.getChildWithName (componentGroupTag);
  207. }
  208. int ComponentDocument::getNumComponents() const
  209. {
  210. return getComponentGroup().getNumChildren();
  211. }
  212. const ValueTree ComponentDocument::getComponent (int index) const
  213. {
  214. return getComponentGroup().getChild (index);
  215. }
  216. const ValueTree ComponentDocument::getComponentWithMemberName (const String& name) const
  217. {
  218. const ValueTree comps (getComponentGroup());
  219. for (int i = comps.getNumChildren(); --i >= 0;)
  220. {
  221. const ValueTree v (comps.getChild(i));
  222. if (v [memberNameProperty] == name)
  223. return v;
  224. }
  225. return ValueTree::invalid;
  226. }
  227. Component* ComponentDocument::createComponent (int index)
  228. {
  229. const ValueTree v (getComponentGroup().getChild (index));
  230. if (v.isValid())
  231. {
  232. Component* c = ComponentTypeManager::getInstance()->createFromStoredType (*this, v);
  233. c->getProperties().set (idProperty, v[idProperty]);
  234. jassert (c->getProperties()[idProperty].toString().isNotEmpty());
  235. return c;
  236. }
  237. return 0;
  238. }
  239. //==============================================================================
  240. const Coordinate ComponentDocument::findMarker (const String& name, bool isHorizontal) const
  241. {
  242. if (name == Coordinate::parentRightMarkerName) return Coordinate ((double) getCanvasWidth().getValue(), isHorizontal);
  243. if (name == Coordinate::parentBottomMarkerName) return Coordinate ((double) getCanvasHeight().getValue(), isHorizontal);
  244. if (name.containsChar ('.'))
  245. {
  246. const String compName (name.upToFirstOccurrenceOf (".", false, false).trim());
  247. const String edge (name.fromFirstOccurrenceOf (".", false, false).trim());
  248. if (compName.isNotEmpty() && edge.isNotEmpty())
  249. {
  250. const ValueTree comp (getComponentWithMemberName (compName));
  251. if (comp.isValid())
  252. {
  253. const RectangleCoordinates coords (getCoordsFor (comp));
  254. if (edge == "left") return coords.left;
  255. if (edge == "right") return coords.right;
  256. if (edge == "top") return coords.top;
  257. if (edge == "bottom") return coords.bottom;
  258. }
  259. }
  260. }
  261. const ValueTree marker (getMarkerList (isHorizontal).getMarkerNamed (name));
  262. if (marker.isValid())
  263. return getMarkerList (isHorizontal).getCoordinate (marker);
  264. return Coordinate (isHorizontal);
  265. }
  266. const RectangleCoordinates ComponentDocument::getCoordsFor (const ValueTree& state) const
  267. {
  268. return RectangleCoordinates (state [compBoundsProperty]);
  269. }
  270. bool ComponentDocument::setCoordsFor (ValueTree& state, const RectangleCoordinates& pr)
  271. {
  272. const String newBoundsString (pr.toString());
  273. if (state[compBoundsProperty] == newBoundsString)
  274. return false;
  275. state.setProperty (compBoundsProperty, newBoundsString, getUndoManager());
  276. return true;
  277. }
  278. void ComponentDocument::addMarkerMenuItem (int i, Coordinate& coord, const String& name, PopupMenu& menu, bool isAnchor1,
  279. const ValueTree& componentState, const String& coordName)
  280. {
  281. const String componentName (componentState [memberNameProperty].toString());
  282. Coordinate requestedCoord (findMarker (name, coord.isHorizontal()));
  283. const String fullCoordName (componentName + "." + coordName);
  284. menu.addItem (i, name,
  285. ! (name == fullCoordName || requestedCoord.referencesIndirectly (fullCoordName, *this)),
  286. name == (isAnchor1 ? coord.getAnchor1() : coord.getAnchor2()));
  287. }
  288. void ComponentDocument::getComponentMarkerMenuItems (const ValueTree& componentState, const String& coordName,
  289. Coordinate& coord, PopupMenu& menu, bool isAnchor1)
  290. {
  291. const String componentName (componentState [memberNameProperty].toString());
  292. if (coord.isHorizontal())
  293. {
  294. addMarkerMenuItem (1, coord, Coordinate::parentLeftMarkerName, menu, isAnchor1, componentState, coordName);
  295. addMarkerMenuItem (2, coord, Coordinate::parentRightMarkerName, menu, isAnchor1, componentState, coordName);
  296. menu.addSeparator();
  297. addMarkerMenuItem (3, coord, componentName + ".left", menu, isAnchor1, componentState, coordName);
  298. addMarkerMenuItem (4, coord, componentName + ".right", menu, isAnchor1, componentState, coordName);
  299. }
  300. else
  301. {
  302. addMarkerMenuItem (1, coord, Coordinate::parentTopMarkerName, menu, isAnchor1, componentState, coordName);
  303. addMarkerMenuItem (2, coord, Coordinate::parentBottomMarkerName, menu, isAnchor1, componentState, coordName);
  304. menu.addSeparator();
  305. addMarkerMenuItem (3, coord, componentName + ".top", menu, isAnchor1, componentState, coordName);
  306. addMarkerMenuItem (4, coord, componentName + ".bottom", menu, isAnchor1, componentState, coordName);
  307. }
  308. menu.addSeparator();
  309. const MarkerList& markerList = getMarkerList (coord.isHorizontal());
  310. int i;
  311. for (i = 0; i < markerList.size(); ++i)
  312. addMarkerMenuItem (100 + i, coord, markerList.getName (markerList.getMarker (i)), menu, isAnchor1, componentState, coordName);
  313. menu.addSeparator();
  314. for (i = 0; i < getNumComponents(); ++i)
  315. {
  316. const String compName (getComponent (i) [memberNameProperty].toString());
  317. if (compName != componentName)
  318. {
  319. if (coord.isHorizontal())
  320. {
  321. addMarkerMenuItem (10000 + i, coord, compName + ".left", menu, isAnchor1, componentState, coordName);
  322. addMarkerMenuItem (10001 + i, coord, compName + ".right", menu, isAnchor1, componentState, coordName);
  323. }
  324. else
  325. {
  326. addMarkerMenuItem (10002 + i, coord, compName + ".top", menu, isAnchor1, componentState, coordName);
  327. addMarkerMenuItem (10003 + i, coord, compName + ".bottom", menu, isAnchor1, componentState, coordName);
  328. }
  329. }
  330. }
  331. }
  332. const String ComponentDocument::getChosenMarkerMenuItem (const ValueTree& componentState, Coordinate& coord, int i) const
  333. {
  334. const String componentName (componentState [memberNameProperty].toString());
  335. if (i == 1) return coord.isHorizontal() ? Coordinate::parentLeftMarkerName : Coordinate::parentTopMarkerName;
  336. if (i == 2) return coord.isHorizontal() ? Coordinate::parentRightMarkerName : Coordinate::parentBottomMarkerName;
  337. if (i == 3) return componentName + (coord.isHorizontal() ? ".left" : ".top");
  338. if (i == 4) return componentName + (coord.isHorizontal() ? ".right" : ".bottom");
  339. const MarkerList& markerList = getMarkerList (coord.isHorizontal());
  340. if (i >= 100 && i < 10000)
  341. return markerList.getName (markerList.getMarker (i - 100));
  342. if (i >= 10000)
  343. {
  344. const String compName (getComponent ((i - 10000) / 4) [memberNameProperty].toString());
  345. switch (i & 3)
  346. {
  347. case 0: return compName + ".left";
  348. case 1: return compName + ".right";
  349. case 2: return compName + ".top";
  350. case 3: return compName + ".bottom";
  351. default: break;
  352. }
  353. }
  354. jassertfalse;
  355. return String::empty;
  356. }
  357. void ComponentDocument::updateComponent (Component* comp)
  358. {
  359. const ValueTree v (getComponentState (comp));
  360. if (v.isValid())
  361. {
  362. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  363. jassert (handler != 0);
  364. if (handler != 0)
  365. handler->updateComponent (*this, comp, v);
  366. }
  367. }
  368. bool ComponentDocument::containsComponent (Component* comp) const
  369. {
  370. const ValueTree comps (getComponentGroup());
  371. for (int i = 0; i < comps.getNumChildren(); ++i)
  372. if (isStateForComponent (comps.getChild(i), comp))
  373. return true;
  374. return false;
  375. }
  376. const ValueTree ComponentDocument::getComponentState (Component* comp) const
  377. {
  378. jassert (comp != 0);
  379. const ValueTree comps (getComponentGroup());
  380. for (int i = 0; i < comps.getNumChildren(); ++i)
  381. if (isStateForComponent (comps.getChild(i), comp))
  382. return comps.getChild(i);
  383. jassertfalse;
  384. return ValueTree::invalid;
  385. }
  386. void ComponentDocument::getComponentProperties (Array <PropertyComponent*>& props, Component* comp)
  387. {
  388. ValueTree v (getComponentState (comp));
  389. if (v.isValid())
  390. {
  391. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  392. jassert (handler != 0);
  393. if (handler != 0)
  394. handler->createPropertyEditors (*this, v, props);
  395. }
  396. }
  397. bool ComponentDocument::isStateForComponent (const ValueTree& storedState, Component* comp) const
  398. {
  399. jassert (comp != 0);
  400. jassert (! storedState [idProperty].isVoid());
  401. return storedState [idProperty] == comp->getProperties() [idProperty];
  402. }
  403. void ComponentDocument::removeComponent (const ValueTree& state)
  404. {
  405. jassert (state.isAChildOf (getComponentGroup()));
  406. getComponentGroup().removeChild (state, getUndoManager());
  407. }
  408. const String ComponentDocument::getNonExistentMemberName (String suggestedName)
  409. {
  410. suggestedName = makeValidCppIdentifier (suggestedName, false, true, false);
  411. const String original (suggestedName);
  412. int num = 1;
  413. while (getComponentWithMemberName (suggestedName).isValid())
  414. {
  415. suggestedName = original;
  416. while (String ("0123456789").containsChar (suggestedName.getLastCharacter()))
  417. suggestedName = suggestedName.dropLastCharacters (1);
  418. suggestedName << num++;
  419. }
  420. return suggestedName;
  421. }
  422. //==============================================================================
  423. ComponentDocument::MarkerList::MarkerList (ComponentDocument& document_, const bool isX_)
  424. : document (document_),
  425. group (document_.getRoot().getChildWithName (isX_ ? markersGroupXTag : markersGroupYTag)),
  426. isX (isX_)
  427. {
  428. jassert (group.isAChildOf (document_.getRoot()));
  429. }
  430. ValueTree& ComponentDocument::MarkerList::getGroup()
  431. {
  432. return group;
  433. }
  434. int ComponentDocument::MarkerList::size() const
  435. {
  436. return group.getNumChildren();
  437. }
  438. ValueTree ComponentDocument::MarkerList::getMarker (int index) const
  439. {
  440. return group.getChild (index);
  441. }
  442. ValueTree ComponentDocument::MarkerList::getMarkerNamed (const String& name) const
  443. {
  444. return group.getChildWithProperty (markerNameProperty, name);
  445. }
  446. bool ComponentDocument::MarkerList::contains (const ValueTree& markerState) const
  447. {
  448. return markerState.isAChildOf (group);
  449. }
  450. const Coordinate ComponentDocument::MarkerList::getCoordinate (const ValueTree& markerState) const
  451. {
  452. return Coordinate (markerState [markerPosProperty].toString(), isX);
  453. }
  454. const String ComponentDocument::MarkerList::getName (const ValueTree& markerState) const
  455. {
  456. return markerState [markerNameProperty].toString();
  457. }
  458. Value ComponentDocument::MarkerList::getNameAsValue (const ValueTree& markerState) const
  459. {
  460. return markerState.getPropertyAsValue (markerNameProperty, document.getUndoManager());
  461. }
  462. void ComponentDocument::MarkerList::setCoordinate (ValueTree& markerState, const Coordinate& newCoord)
  463. {
  464. markerState.setProperty (markerPosProperty, newCoord.toString(), document.getUndoManager());
  465. }
  466. void ComponentDocument::MarkerList::createMarker (const String& name, int position)
  467. {
  468. ValueTree marker (markerTag);
  469. marker.setProperty (markerNameProperty, document.getNonexistentMarkerName (name), 0);
  470. marker.setProperty (markerPosProperty, Coordinate (position, isX).toString(), 0);
  471. group.addChild (marker, -1, document.getUndoManager());
  472. }
  473. void ComponentDocument::MarkerList::deleteMarker (ValueTree& markerState)
  474. {
  475. group.removeChild (markerState, document.getUndoManager());
  476. }
  477. const Coordinate ComponentDocument::MarkerList::findMarker (const String& name, bool isHorizontal) const
  478. {
  479. if (isHorizontal == isX)
  480. {
  481. if (name == Coordinate::parentRightMarkerName) return Coordinate ((double) document.getCanvasWidth().getValue(), isHorizontal);
  482. if (name == Coordinate::parentBottomMarkerName) return Coordinate ((double) document.getCanvasHeight().getValue(), isHorizontal);
  483. const ValueTree marker (document.getMarkerList (isHorizontal).getMarkerNamed (name));
  484. if (marker.isValid())
  485. return document.getMarkerList (isHorizontal).getCoordinate (marker);
  486. }
  487. return Coordinate (isX);
  488. }
  489. const String ComponentDocument::getNonexistentMarkerName (const String& name)
  490. {
  491. String n (makeValidCppIdentifier (name, false, true, false));
  492. int suffix = 2;
  493. while (markersX->getMarkerNamed (n).isValid() || markersY->getMarkerNamed (n).isValid())
  494. n = n.trimCharactersAtEnd ("0123456789") + String (suffix++);
  495. return n;
  496. }
  497. //==============================================================================
  498. UndoManager* ComponentDocument::getUndoManager() const
  499. {
  500. return &undoManager;
  501. }
  502. //==============================================================================
  503. void ComponentDocument::createClassProperties (Array <PropertyComponent*>& props)
  504. {
  505. props.add (new TextPropertyComponent (getClassName(), "Class Name", 256, false));
  506. props.getLast()->setTooltip ("The C++ class name for the component class.");
  507. props.add (new TextPropertyComponent (getClassDescription(), "Description", 512, false));
  508. props.getLast()->setTooltip ("A freeform description of the component.");
  509. props.add (new SliderPropertyComponent (getCanvasWidth(), "Initial Width", 1.0, 8192.0, 1.0));
  510. props.getLast()->setTooltip ("The initial width of the component when it is created.");
  511. props.add (new SliderPropertyComponent (getCanvasHeight(), "Initial Height", 1.0, 8192.0, 1.0));
  512. props.getLast()->setTooltip ("The initial height of the component when it is created.");
  513. }