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.

662 lines
21KB

  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_TextButton.h"
  20. #include "Component Types/jucer_ToggleButton.h"
  21. //==============================================================================
  22. static const char* const componentDocumentTag = "COMPONENT";
  23. static const char* const componentGroupTag = "COMPONENTS";
  24. static const char* const idProperty = "id";
  25. static const char* const compBoundsProperty = "position";
  26. static const char* const memberNameProperty = "memberName";
  27. static const char* const compNameProperty = "name";
  28. static const char* const metadataTagStart = "JUCER_" "COMPONENT_METADATA_START"; // written like this to avoid thinking this file is a component!
  29. static const char* const metadataTagEnd = "JUCER_" "COMPONENT_METADATA_END";
  30. //==============================================================================
  31. ComponentTypeHandler::ComponentTypeHandler (const String& name_, const String& xmlTag_,
  32. const String& memberNameRoot_)
  33. : name (name_), xmlTag (xmlTag_),
  34. memberNameRoot (memberNameRoot_)
  35. {
  36. }
  37. ComponentTypeHandler::~ComponentTypeHandler()
  38. {
  39. }
  40. Value ComponentTypeHandler::getValue (const var::identifier& name, ValueTree& state, ComponentDocument& document) const
  41. {
  42. return state.getPropertyAsValue (name, document.getUndoManager());
  43. }
  44. void ComponentTypeHandler::updateComponent (ComponentDocument& document, Component* comp, const ValueTree& state)
  45. {
  46. if (comp->getParentComponent() != 0)
  47. {
  48. RectangleCoordinates pos (state [compBoundsProperty].toString());
  49. ScopedPointer<Coordinate::MarkerResolver> markers (document.createMarkerResolver (state, comp->getParentComponent()));
  50. comp->setBounds (pos.resolve (*markers));
  51. }
  52. comp->setName (state [compNameProperty]);
  53. }
  54. void ComponentTypeHandler::initialiseNewItem (ComponentDocument& document, ValueTree& state)
  55. {
  56. state.setProperty (compNameProperty, String::empty, 0);
  57. state.setProperty (memberNameProperty, document.getNonExistentMemberName (getMemberNameRoot()), 0);
  58. const Rectangle<int> bounds (getDefaultSize().withPosition (Point<int> (Random::getSystemRandom().nextInt (100) + 100,
  59. Random::getSystemRandom().nextInt (100) + 100)));
  60. state.setProperty (compBoundsProperty, RectangleCoordinates (bounds).toString(), 0);
  61. }
  62. void ComponentTypeHandler::createPropertyEditors (ComponentDocument& document, ValueTree& state, Array <PropertyComponent*>& props)
  63. {
  64. props.add (new TextPropertyComponent (getValue (compBoundsProperty, state, document), "Bounds", 512, false));
  65. props.getLast()->setTooltip ("The component's position.");
  66. }
  67. //==============================================================================
  68. class ComponentTypeManager : public DeletedAtShutdown
  69. {
  70. public:
  71. ComponentTypeManager()
  72. {
  73. handlers.add (new TextButtonHandler());
  74. handlers.add (new ToggleButtonHandler());
  75. }
  76. ~ComponentTypeManager()
  77. {
  78. }
  79. juce_DeclareSingleton_SingleThreaded_Minimal (ComponentTypeManager);
  80. Component* createFromStoredType (ComponentDocument& document, const ValueTree& value)
  81. {
  82. ComponentTypeHandler* handler = getHandlerFor (value.getType());
  83. if (handler == 0)
  84. return 0;
  85. Component* c = handler->createComponent();
  86. if (c != 0)
  87. handler->updateComponent (document, c, value);
  88. return c;
  89. }
  90. ComponentTypeHandler* getHandlerFor (const String& type)
  91. {
  92. for (int i = handlers.size(); --i >= 0;)
  93. if (handlers.getUnchecked(i)->getXmlTag() == type)
  94. return handlers.getUnchecked(i);
  95. return 0;
  96. }
  97. const StringArray getTypeNames() const
  98. {
  99. StringArray s;
  100. for (int i = 0; i < handlers.size(); ++i)
  101. s.add (handlers.getUnchecked(i)->getName());
  102. return s;
  103. }
  104. int getNumHandlers() const { return handlers.size(); }
  105. ComponentTypeHandler* getHandler (const int index) const { return handlers[index]; }
  106. private:
  107. OwnedArray <ComponentTypeHandler> handlers;
  108. };
  109. juce_ImplementSingleton_SingleThreaded (ComponentTypeManager);
  110. //==============================================================================
  111. ComponentDocument::ComponentDocument (Project* project_, const File& cppFile_)
  112. : project (project_), cppFile (cppFile_), root (componentDocumentTag),
  113. changedSinceSaved (false)
  114. {
  115. reload();
  116. checkRootObject();
  117. root.addListener (this);
  118. }
  119. ComponentDocument::~ComponentDocument()
  120. {
  121. root.removeListener (this);
  122. }
  123. void ComponentDocument::beginNewTransaction()
  124. {
  125. undoManager.beginNewTransaction();
  126. }
  127. void ComponentDocument::valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  128. {
  129. changedSinceSaved = true;
  130. }
  131. void ComponentDocument::valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  132. {
  133. changedSinceSaved = true;
  134. }
  135. void ComponentDocument::valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  136. {
  137. changedSinceSaved = true;
  138. }
  139. bool ComponentDocument::isComponentFile (const File& file)
  140. {
  141. if (! file.hasFileExtension (".cpp"))
  142. return false;
  143. InputStream* in = file.createInputStream();
  144. if (in != 0)
  145. {
  146. BufferedInputStream buf (in, 8192, true);
  147. while (! buf.isExhausted())
  148. if (buf.readNextLine().contains (metadataTagStart))
  149. return true;
  150. }
  151. return false;
  152. }
  153. void ComponentDocument::writeCode (OutputStream& cpp, OutputStream& header)
  154. {
  155. cpp << "/** */"
  156. << newLine << newLine;
  157. header << "/** */"
  158. << newLine << newLine;
  159. }
  160. void ComponentDocument::writeMetadata (OutputStream& out)
  161. {
  162. out << "#if 0" << newLine
  163. << "/** Jucer-generated metadata section - Edit this data at own risk!" << newLine
  164. << metadataTagStart << newLine << newLine;
  165. ScopedPointer<XmlElement> xml (root.createXml());
  166. jassert (xml != 0);
  167. if (xml != 0)
  168. xml->writeToStream (out, String::empty, false, false);
  169. out << newLine
  170. << metadataTagEnd << " */" << newLine
  171. << "#endif" << newLine;
  172. }
  173. bool ComponentDocument::save()
  174. {
  175. MemoryOutputStream cpp, header;
  176. writeCode (cpp, header);
  177. writeMetadata (cpp);
  178. bool savedOk = overwriteFileWithNewDataIfDifferent (cppFile, cpp)
  179. && overwriteFileWithNewDataIfDifferent (cppFile.withFileExtension (".h"), header);
  180. if (savedOk)
  181. changedSinceSaved = false;
  182. return savedOk;
  183. }
  184. bool ComponentDocument::reload()
  185. {
  186. String xmlString;
  187. {
  188. InputStream* in = cppFile.createInputStream();
  189. if (in == 0)
  190. return false;
  191. BufferedInputStream buf (in, 8192, true);
  192. String::Concatenator xml (xmlString);
  193. while (! buf.isExhausted())
  194. {
  195. String line (buf.readNextLine());
  196. if (line.contains (metadataTagStart))
  197. {
  198. while (! buf.isExhausted())
  199. {
  200. line = buf.readNextLine();
  201. if (line.contains (metadataTagEnd))
  202. break;
  203. xml.append (line);
  204. xml.append (newLine);
  205. }
  206. break;
  207. }
  208. }
  209. }
  210. XmlDocument doc (xmlString);
  211. ScopedPointer<XmlElement> xml (doc.getDocumentElement());
  212. if (xml != 0 && xml->hasTagName (componentDocumentTag))
  213. {
  214. ValueTree newTree (ValueTree::fromXml (*xml));
  215. if (newTree.isValid())
  216. {
  217. root = newTree;
  218. checkRootObject();
  219. undoManager.clearUndoHistory();
  220. changedSinceSaved = false;
  221. return true;
  222. }
  223. }
  224. return false;
  225. }
  226. bool ComponentDocument::hasChangedSinceLastSave()
  227. {
  228. return changedSinceSaved;
  229. }
  230. void ComponentDocument::checkRootObject()
  231. {
  232. jassert (root.hasType (componentDocumentTag));
  233. if (! getComponentGroup().isValid())
  234. root.addChild (ValueTree (componentGroupTag), -1, 0);
  235. if (getClassName().toString().isEmpty())
  236. getClassName() = "NewComponent";
  237. }
  238. //==============================================================================
  239. const int menuItemOffset = 0x63451fa4;
  240. void ComponentDocument::addNewComponentMenuItems (PopupMenu& menu) const
  241. {
  242. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  243. for (int i = 0; i < typeNames.size(); ++i)
  244. menu.addItem (i + menuItemOffset, "New " + typeNames[i]);
  245. }
  246. void ComponentDocument::performNewComponentMenuItem (int menuResultCode)
  247. {
  248. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  249. if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeNames.size())
  250. {
  251. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandler (menuResultCode - menuItemOffset);
  252. jassert (handler != 0);
  253. if (handler != 0)
  254. {
  255. ValueTree state (handler->getXmlTag());
  256. state.setProperty (idProperty, createAlphaNumericUID(), 0);
  257. handler->initialiseNewItem (*this, state);
  258. getComponentGroup().addChild (state, -1, getUndoManager());
  259. }
  260. }
  261. }
  262. //==============================================================================
  263. ValueTree ComponentDocument::getComponentGroup() const
  264. {
  265. return root.getChildWithName (componentGroupTag);
  266. }
  267. int ComponentDocument::getNumComponents() const
  268. {
  269. return getComponentGroup().getNumChildren();
  270. }
  271. const ValueTree ComponentDocument::getComponent (int index) const
  272. {
  273. return getComponentGroup().getChild (index);
  274. }
  275. const ValueTree ComponentDocument::getComponentWithMemberName (const String& name) const
  276. {
  277. const ValueTree comps (getComponentGroup());
  278. for (int i = comps.getNumChildren(); --i >= 0;)
  279. {
  280. const ValueTree v (comps.getChild(i));
  281. if (v [memberNameProperty] == name)
  282. return v;
  283. }
  284. return ValueTree::invalid;
  285. }
  286. Component* ComponentDocument::createComponent (int index)
  287. {
  288. const ValueTree v (getComponentGroup().getChild (index));
  289. if (v.isValid())
  290. {
  291. Component* c = ComponentTypeManager::getInstance()->createFromStoredType (*this, v);
  292. c->getProperties().set (idProperty, v[idProperty]);
  293. jassert (c->getProperties()[idProperty].toString().isNotEmpty());
  294. return c;
  295. }
  296. return 0;
  297. }
  298. //==============================================================================
  299. class ComponentMarkerResolver : public Coordinate::MarkerResolver
  300. {
  301. public:
  302. ComponentMarkerResolver (ComponentDocument& doc, const ValueTree& state_, Component* parentComponent_)
  303. : owner (doc), state (state_), parentComponent (parentComponent_)
  304. {}
  305. ~ComponentMarkerResolver() {}
  306. const Coordinate findMarker (const String& name, bool isHorizontal)
  307. {
  308. if (name == "left") return RectangleCoordinates (state [compBoundsProperty]).left;
  309. else if (name == "right") return RectangleCoordinates (state [compBoundsProperty]).right;
  310. else if (name == "top") return RectangleCoordinates (state [compBoundsProperty]).top;
  311. else if (name == "bottom") return RectangleCoordinates (state [compBoundsProperty]).bottom;
  312. else if (name == Coordinate::parentRightMarkerName) return Coordinate ((double) parentComponent->getWidth(), isHorizontal);
  313. else if (name == Coordinate::parentBottomMarkerName) return Coordinate ((double) parentComponent->getHeight(), isHorizontal);
  314. return Coordinate (isHorizontal);
  315. }
  316. private:
  317. ComponentDocument& owner;
  318. ValueTree state;
  319. Component* parentComponent;
  320. };
  321. const RectangleCoordinates ComponentDocument::getCoordsFor (const ValueTree& state) const
  322. {
  323. return RectangleCoordinates (state [compBoundsProperty]);
  324. }
  325. Coordinate::MarkerResolver* ComponentDocument::createMarkerResolver (const ValueTree& state, Component* parentComponent)
  326. {
  327. jassert (parentComponent != 0);
  328. return new ComponentMarkerResolver (*this, state, parentComponent);
  329. }
  330. void ComponentDocument::updateComponent (Component* comp)
  331. {
  332. const ValueTree v (getComponentState (comp));
  333. if (v.isValid())
  334. {
  335. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  336. jassert (handler != 0);
  337. if (handler != 0)
  338. handler->updateComponent (*this, comp, v);
  339. }
  340. }
  341. bool ComponentDocument::containsComponent (Component* comp) const
  342. {
  343. const ValueTree comps (getComponentGroup());
  344. for (int i = 0; i < comps.getNumChildren(); ++i)
  345. if (isStateForComponent (comps.getChild(i), comp))
  346. return true;
  347. return false;
  348. }
  349. const ValueTree ComponentDocument::getComponentState (Component* comp) const
  350. {
  351. jassert (comp != 0);
  352. const ValueTree comps (getComponentGroup());
  353. for (int i = 0; i < comps.getNumChildren(); ++i)
  354. if (isStateForComponent (comps.getChild(i), comp))
  355. return comps.getChild(i);
  356. jassertfalse;
  357. return ValueTree::invalid;
  358. }
  359. void ComponentDocument::getComponentProperties (Array <PropertyComponent*>& props, Component* comp)
  360. {
  361. ValueTree v (getComponentState (comp));
  362. if (v.isValid())
  363. {
  364. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  365. jassert (handler != 0);
  366. if (handler != 0)
  367. handler->createPropertyEditors (*this, v, props);
  368. }
  369. }
  370. bool ComponentDocument::isStateForComponent (const ValueTree& storedState, Component* comp) const
  371. {
  372. jassert (comp != 0);
  373. jassert (! storedState [idProperty].isVoid());
  374. return storedState [idProperty] == comp->getProperties() [idProperty];
  375. }
  376. const String ComponentDocument::getNonExistentMemberName (String suggestedName)
  377. {
  378. suggestedName = makeValidCppIdentifier (suggestedName, false, true, false);
  379. const String original (suggestedName);
  380. int num = 1;
  381. while (getComponentWithMemberName (suggestedName).isValid())
  382. {
  383. suggestedName = original;
  384. while (String ("0123456789").containsChar (suggestedName.getLastCharacter()))
  385. suggestedName = suggestedName.dropLastCharacters (1);
  386. suggestedName << num++;
  387. }
  388. return suggestedName;
  389. }
  390. //==============================================================================
  391. UndoManager* ComponentDocument::getUndoManager()
  392. {
  393. return &undoManager;
  394. }
  395. //==============================================================================
  396. class ComponentDocument::DragHandler
  397. {
  398. public:
  399. DragHandler (ComponentDocument& document_,
  400. const Array<Component*>& items,
  401. const MouseEvent& e,
  402. const ResizableBorderComponent::Zone& zone_,
  403. Component* parentForOverlays)
  404. : parentComponent (0),
  405. document (document_),
  406. zone (zone_)
  407. {
  408. for (int i = 0; i < items.size(); ++i)
  409. {
  410. Component* comp = items.getUnchecked(i);
  411. jassert (comp != 0);
  412. if (parentComponent == 0)
  413. parentComponent = comp->getParentComponent();
  414. const ValueTree v (document.getComponentState (comp));
  415. draggedComponents.add (v);
  416. Rectangle<int> pos;
  417. {
  418. RectangleCoordinates relativePos (v [compBoundsProperty].toString());
  419. ScopedPointer<Coordinate::MarkerResolver> markers (document.createMarkerResolver (v, parentComponent));
  420. pos = relativePos.resolve (*markers);
  421. originalPositions.add (pos);
  422. }
  423. const Rectangle<float> floatPos ((float) pos.getX(), (float) pos.getY(),
  424. (float) pos.getWidth(), (float) pos.getHeight());
  425. if (zone.isDraggingWholeObject() || zone.isDraggingLeftEdge())
  426. verticalSnapPositions.add (floatPos.getX());
  427. if (zone.isDraggingWholeObject() || zone.isDraggingLeftEdge() || zone.isDraggingRightEdge())
  428. verticalSnapPositions.add (floatPos.getCentreX());
  429. if (zone.isDraggingWholeObject() || zone.isDraggingRightEdge())
  430. verticalSnapPositions.add (floatPos.getRight());
  431. if (zone.isDraggingWholeObject() || zone.isDraggingTopEdge())
  432. horizontalSnapPositions.add (floatPos.getY());
  433. if (zone.isDraggingWholeObject() || zone.isDraggingTopEdge() || zone.isDraggingBottomEdge())
  434. verticalSnapPositions.add (floatPos.getCentreY());
  435. if (zone.isDraggingWholeObject() || zone.isDraggingBottomEdge())
  436. horizontalSnapPositions.add (floatPos.getBottom());
  437. }
  438. document.beginNewTransaction();
  439. }
  440. ~DragHandler()
  441. {
  442. document.beginNewTransaction();
  443. }
  444. void drag (const MouseEvent& e)
  445. {
  446. document.getUndoManager()->undoCurrentTransactionOnly();
  447. for (int n = 50;;)
  448. {
  449. // Need to repeatedly apply the new positions until they all settle down, in case some of
  450. // the coords are relative to each other..
  451. bool anyUpdated = false;
  452. for (int i = 0; i < draggedComponents.size(); ++i)
  453. if (dragItem (draggedComponents.getReference(i), e.getOffsetFromDragStart(), originalPositions.getReference(i)))
  454. anyUpdated = true;
  455. if (! anyUpdated)
  456. break;
  457. if (--n == 0)
  458. {
  459. jassertfalse;
  460. break;
  461. }
  462. }
  463. }
  464. bool dragItem (ValueTree& v, const Point<int>& distance, const Rectangle<int>& originalPos)
  465. {
  466. const Rectangle<int> newBounds (zone.resizeRectangleBy (originalPos, distance));
  467. RectangleCoordinates pr (v [compBoundsProperty].toString());
  468. ScopedPointer<Coordinate::MarkerResolver> markers (document.createMarkerResolver (v, parentComponent));
  469. pr.moveToAbsolute (newBounds, *markers);
  470. const String newBoundsString (pr.toString());
  471. if (v[compBoundsProperty] == newBoundsString)
  472. return false;
  473. v.setProperty (compBoundsProperty, newBoundsString, document.getUndoManager());
  474. return true;
  475. }
  476. const Array<float> getVerticalSnapPositions (const Point<int>& distance) const
  477. {
  478. Array<float> p (verticalSnapPositions);
  479. for (int i = p.size(); --i >= 0;)
  480. p.set (i, p.getUnchecked(i) + distance.getX());
  481. return p;
  482. }
  483. const Array<float> getHorizontalSnapPositions (const Point<int>& distance) const
  484. {
  485. Array<float> p (horizontalSnapPositions);
  486. for (int i = p.size(); --i >= 0;)
  487. p.set (i, p.getUnchecked(i) + distance.getY());
  488. return p;
  489. }
  490. private:
  491. Component* parentComponent;
  492. ComponentDocument& document;
  493. Array <ValueTree> draggedComponents;
  494. Array <Rectangle<int> > originalPositions;
  495. Array <float> verticalSnapPositions, horizontalSnapPositions;
  496. const ResizableBorderComponent::Zone zone;
  497. };
  498. void ComponentDocument::beginDrag (const Array<Component*>& items, const MouseEvent& e,
  499. Component* parentForOverlays, const ResizableBorderComponent::Zone& zone)
  500. {
  501. dragger = new DragHandler (*this, items, e, zone, parentForOverlays);
  502. }
  503. void ComponentDocument::continueDrag (const MouseEvent& e)
  504. {
  505. if (dragger != 0)
  506. dragger->drag (e);
  507. }
  508. void ComponentDocument::endDrag (const MouseEvent& e)
  509. {
  510. if (dragger != 0)
  511. {
  512. dragger->drag (e);
  513. dragger = 0;
  514. }
  515. }