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.

539 lines
16KB

  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. static const String componentBoundsToString (const Rectangle<int>& bounds)
  32. {
  33. return bounds.toString();
  34. }
  35. static const Rectangle<int> stringToComponentBounds (const String& s)
  36. {
  37. return Rectangle<int>::fromString (s);
  38. }
  39. //==============================================================================
  40. ComponentTypeHandler::ComponentTypeHandler (const String& name_, const String& xmlTag_,
  41. const String& memberNameRoot_)
  42. : name (name_), xmlTag (xmlTag_),
  43. memberNameRoot (memberNameRoot_)
  44. {
  45. }
  46. ComponentTypeHandler::~ComponentTypeHandler()
  47. {
  48. }
  49. void ComponentTypeHandler::updateComponent (Component* comp, const ValueTree& state)
  50. {
  51. comp->setBounds (stringToComponentBounds (state [compBoundsProperty]));
  52. comp->setName (state [compNameProperty]);
  53. }
  54. const ValueTree ComponentTypeHandler::createNewItem (ComponentDocument& document)
  55. {
  56. ValueTree v (getXmlTag());
  57. v.setProperty (idProperty, createAlphaNumericUID(), 0);
  58. v.setProperty (compNameProperty, String::empty, 0);
  59. v.setProperty (memberNameProperty, document.getNonExistentMemberName (getMemberNameRoot()), 0);
  60. v.setProperty (compBoundsProperty,
  61. componentBoundsToString (getDefaultSize().withPosition (Point<int> (Random::getSystemRandom().nextInt (100) + 100,
  62. Random::getSystemRandom().nextInt (100) + 100))), 0);
  63. return v;
  64. }
  65. //==============================================================================
  66. class ComponentTypeManager : public DeletedAtShutdown
  67. {
  68. public:
  69. ComponentTypeManager()
  70. {
  71. handlers.add (new TextButtonHandler());
  72. handlers.add (new ToggleButtonHandler());
  73. }
  74. ~ComponentTypeManager()
  75. {
  76. }
  77. juce_DeclareSingleton_SingleThreaded_Minimal (ComponentTypeManager);
  78. Component* createFromStoredType (const ValueTree& value)
  79. {
  80. ComponentTypeHandler* handler = getHandlerFor (value.getType());
  81. if (handler == 0)
  82. return 0;
  83. Component* c = handler->createComponent();
  84. if (c != 0)
  85. handler->updateComponent (c, value);
  86. return c;
  87. }
  88. ComponentTypeHandler* getHandlerFor (const String& type)
  89. {
  90. for (int i = handlers.size(); --i >= 0;)
  91. if (handlers.getUnchecked(i)->getXmlTag() == type)
  92. return handlers.getUnchecked(i);
  93. return 0;
  94. }
  95. const StringArray getTypeNames() const
  96. {
  97. StringArray s;
  98. for (int i = 0; i < handlers.size(); ++i)
  99. s.add (handlers.getUnchecked(i)->getName());
  100. return s;
  101. }
  102. int getNumHandlers() const { return handlers.size(); }
  103. ComponentTypeHandler* getHandler (const int index) const { return handlers[index]; }
  104. private:
  105. OwnedArray <ComponentTypeHandler> handlers;
  106. };
  107. juce_ImplementSingleton_SingleThreaded (ComponentTypeManager);
  108. //==============================================================================
  109. ComponentDocument::ComponentDocument (Project* project_, const File& cppFile_)
  110. : project (project_), cppFile (cppFile_), root (componentDocumentTag),
  111. changedSinceSaved (false)
  112. {
  113. reload();
  114. checkRootObject();
  115. root.addListener (this);
  116. }
  117. ComponentDocument::~ComponentDocument()
  118. {
  119. root.removeListener (this);
  120. }
  121. void ComponentDocument::beginNewTransaction()
  122. {
  123. undoManager.beginNewTransaction();
  124. }
  125. void ComponentDocument::valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  126. {
  127. changedSinceSaved = true;
  128. }
  129. void ComponentDocument::valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  130. {
  131. changedSinceSaved = true;
  132. }
  133. void ComponentDocument::valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  134. {
  135. changedSinceSaved = true;
  136. }
  137. bool ComponentDocument::isComponentFile (const File& file)
  138. {
  139. if (! file.hasFileExtension (".cpp"))
  140. return false;
  141. InputStream* in = file.createInputStream();
  142. if (in != 0)
  143. {
  144. BufferedInputStream buf (in, 8192, true);
  145. while (! buf.isExhausted())
  146. if (buf.readNextLine().contains (metadataTagStart))
  147. return true;
  148. }
  149. return false;
  150. }
  151. void ComponentDocument::writeCode (OutputStream& cpp, OutputStream& header)
  152. {
  153. cpp << "/** */"
  154. << newLine << newLine;
  155. header << "/** */"
  156. << newLine << newLine;
  157. }
  158. void ComponentDocument::writeMetadata (OutputStream& out)
  159. {
  160. out << "#if 0" << newLine
  161. << "/** Jucer-generated metadata section - Edit this data at own risk!" << newLine
  162. << metadataTagStart << newLine << newLine;
  163. ScopedPointer<XmlElement> xml (root.createXml());
  164. jassert (xml != 0);
  165. if (xml != 0)
  166. xml->writeToStream (out, String::empty, false, false);
  167. out << newLine
  168. << metadataTagEnd << " */" << newLine
  169. << "#endif" << newLine;
  170. }
  171. bool ComponentDocument::save()
  172. {
  173. MemoryOutputStream cpp, header;
  174. writeCode (cpp, header);
  175. writeMetadata (cpp);
  176. bool savedOk = overwriteFileWithNewDataIfDifferent (cppFile, cpp)
  177. && overwriteFileWithNewDataIfDifferent (cppFile.withFileExtension (".h"), header);
  178. if (savedOk)
  179. changedSinceSaved = false;
  180. return savedOk;
  181. }
  182. bool ComponentDocument::reload()
  183. {
  184. String xmlString;
  185. {
  186. InputStream* in = cppFile.createInputStream();
  187. if (in == 0)
  188. return false;
  189. BufferedInputStream buf (in, 8192, true);
  190. String::Concatenator xml (xmlString);
  191. while (! buf.isExhausted())
  192. {
  193. String line (buf.readNextLine());
  194. if (line.contains (metadataTagStart))
  195. {
  196. while (! buf.isExhausted())
  197. {
  198. line = buf.readNextLine();
  199. if (line.contains (metadataTagEnd))
  200. break;
  201. xml.append (line);
  202. xml.append (newLine);
  203. }
  204. break;
  205. }
  206. }
  207. }
  208. XmlDocument doc (xmlString);
  209. ScopedPointer<XmlElement> xml (doc.getDocumentElement());
  210. if (xml != 0 && xml->hasTagName (componentDocumentTag))
  211. {
  212. ValueTree newTree (ValueTree::fromXml (*xml));
  213. if (newTree.isValid())
  214. {
  215. root = newTree;
  216. checkRootObject();
  217. undoManager.clearUndoHistory();
  218. changedSinceSaved = false;
  219. return true;
  220. }
  221. }
  222. return false;
  223. }
  224. bool ComponentDocument::hasChangedSinceLastSave()
  225. {
  226. return changedSinceSaved;
  227. }
  228. void ComponentDocument::checkRootObject()
  229. {
  230. jassert (root.hasType (componentDocumentTag));
  231. if (! getComponentGroup().isValid())
  232. root.addChild (ValueTree (componentGroupTag), -1, 0);
  233. if (getClassName().toString().isEmpty())
  234. getClassName() = "NewComponent";
  235. }
  236. //==============================================================================
  237. const int menuItemOffset = 0x63451fa4;
  238. void ComponentDocument::addNewComponentMenuItems (PopupMenu& menu) const
  239. {
  240. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  241. for (int i = 0; i < typeNames.size(); ++i)
  242. menu.addItem (i + menuItemOffset, "New " + typeNames[i]);
  243. }
  244. void ComponentDocument::performNewComponentMenuItem (int menuResultCode)
  245. {
  246. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  247. if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeNames.size())
  248. {
  249. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandler (menuResultCode - menuItemOffset);
  250. jassert (handler != 0);
  251. if (handler != 0)
  252. getComponentGroup().addChild (handler->createNewItem (*this), -1, getUndoManager());
  253. }
  254. }
  255. //==============================================================================
  256. ValueTree ComponentDocument::getComponentGroup() const
  257. {
  258. return root.getChildWithName (componentGroupTag);
  259. }
  260. int ComponentDocument::getNumComponents() const
  261. {
  262. return getComponentGroup().getNumChildren();
  263. }
  264. const ValueTree ComponentDocument::getComponent (int index) const
  265. {
  266. return getComponentGroup().getChild (index);
  267. }
  268. const ValueTree ComponentDocument::getComponentWithMemberName (const String& name) const
  269. {
  270. const ValueTree comps (getComponentGroup());
  271. for (int i = comps.getNumChildren(); --i >= 0;)
  272. {
  273. const ValueTree v (comps.getChild(i));
  274. if (v [memberNameProperty] == name)
  275. return v;
  276. }
  277. return ValueTree::invalid;
  278. }
  279. Component* ComponentDocument::createComponent (int index) const
  280. {
  281. const ValueTree v (getComponentGroup().getChild (index));
  282. if (v.isValid())
  283. {
  284. Component* c = ComponentTypeManager::getInstance()->createFromStoredType (v);
  285. c->getProperties().set (idProperty, v[idProperty]);
  286. jassert (c->getProperties()[idProperty].toString().isNotEmpty());
  287. return c;
  288. }
  289. return 0;
  290. }
  291. void ComponentDocument::updateComponent (Component* comp) const
  292. {
  293. const ValueTree v (getComponentState (comp));
  294. if (v.isValid())
  295. {
  296. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  297. jassert (handler != 0);
  298. if (handler != 0)
  299. handler->updateComponent (comp, v);
  300. }
  301. }
  302. bool ComponentDocument::containsComponent (Component* comp) const
  303. {
  304. const ValueTree comps (getComponentGroup());
  305. for (int i = 0; i < comps.getNumChildren(); ++i)
  306. if (isStateForComponent (comps.getChild(i), comp))
  307. return true;
  308. return false;
  309. }
  310. const ValueTree ComponentDocument::getComponentState (Component* comp) const
  311. {
  312. jassert (comp != 0);
  313. const ValueTree comps (getComponentGroup());
  314. for (int i = 0; i < comps.getNumChildren(); ++i)
  315. if (isStateForComponent (comps.getChild(i), comp))
  316. return comps.getChild(i);
  317. jassertfalse;
  318. return ValueTree::invalid;
  319. }
  320. bool ComponentDocument::isStateForComponent (const ValueTree& storedState, Component* comp) const
  321. {
  322. jassert (comp != 0);
  323. jassert (! storedState [idProperty].isVoid());
  324. return storedState [idProperty] == comp->getProperties() [idProperty];
  325. }
  326. const String ComponentDocument::getNonExistentMemberName (String suggestedName)
  327. {
  328. suggestedName = makeValidCppIdentifier (suggestedName, false, true, false);
  329. const String original (suggestedName);
  330. int num = 1;
  331. while (getComponentWithMemberName (suggestedName).isValid())
  332. {
  333. suggestedName = original;
  334. while (String ("0123456789").containsChar (suggestedName.getLastCharacter()))
  335. suggestedName = suggestedName.dropLastCharacters (1);
  336. suggestedName << num++;
  337. }
  338. return suggestedName;
  339. }
  340. //==============================================================================
  341. class ComponentDocument::DragHandler
  342. {
  343. public:
  344. DragHandler (ComponentDocument& document_,
  345. const Array<Component*>& items,
  346. const MouseEvent& e,
  347. const ResizableBorderComponent::Zone& zone_)
  348. : document (document_),
  349. zone (zone_)
  350. {
  351. for (int i = 0; i < items.size(); ++i)
  352. {
  353. Component* comp = items.getUnchecked(i);
  354. jassert (items.getUnchecked(i) != 0);
  355. const ValueTree v (document.getComponentState (comp));
  356. draggedComponents.add (v);
  357. const Rectangle<int> pos (stringToComponentBounds (v [compBoundsProperty]));
  358. originalPositions.add (pos);
  359. const Rectangle<float> floatPos ((float) pos.getX(), (float) pos.getY(),
  360. (float) pos.getWidth(), (float) pos.getHeight());
  361. verticalSnapPositions.add (floatPos.getX());
  362. verticalSnapPositions.add (floatPos.getCentreX());
  363. verticalSnapPositions.add (floatPos.getRight());
  364. horizontalSnapPositions.add (floatPos.getY());
  365. verticalSnapPositions.add (floatPos.getCentreY());
  366. horizontalSnapPositions.add (floatPos.getBottom());
  367. }
  368. document.beginNewTransaction();
  369. }
  370. ~DragHandler()
  371. {
  372. document.beginNewTransaction();
  373. }
  374. void drag (const MouseEvent& e)
  375. {
  376. document.getUndoManager()->undoCurrentTransactionOnly();
  377. for (int i = 0; i < draggedComponents.size(); ++i)
  378. move (draggedComponents.getReference(i), e.getOffsetFromDragStart(), originalPositions.getReference(i));
  379. }
  380. void move (ValueTree& v, const Point<int>& distance, const Rectangle<int>& originalPos)
  381. {
  382. Rectangle<int> newBounds (zone.resizeRectangleBy (originalPos, distance));
  383. v.setProperty (compBoundsProperty, componentBoundsToString (newBounds), document.getUndoManager());
  384. }
  385. const Array<float> getVerticalSnapPositions (const Point<int>& distance) const
  386. {
  387. Array<float> p (verticalSnapPositions);
  388. for (int i = p.size(); --i >= 0;)
  389. p.set (i, p.getUnchecked(i) + distance.getX());
  390. return p;
  391. }
  392. const Array<float> getHorizontalSnapPositions (const Point<int>& distance) const
  393. {
  394. Array<float> p (horizontalSnapPositions);
  395. for (int i = p.size(); --i >= 0;)
  396. p.set (i, p.getUnchecked(i) + distance.getY());
  397. return p;
  398. }
  399. private:
  400. ComponentDocument& document;
  401. Array <ValueTree> draggedComponents;
  402. Array <Rectangle<int> > originalPositions;
  403. Array <float> verticalSnapPositions, horizontalSnapPositions;
  404. const ResizableBorderComponent::Zone zone;
  405. };
  406. void ComponentDocument::beginDrag (const Array<Component*>& items, const MouseEvent& e, const ResizableBorderComponent::Zone& zone)
  407. {
  408. dragger = new DragHandler (*this, items, e, zone);
  409. }
  410. void ComponentDocument::continueDrag (const MouseEvent& e)
  411. {
  412. if (dragger != 0)
  413. dragger->drag (e);
  414. }
  415. void ComponentDocument::endDrag (const MouseEvent& e)
  416. {
  417. if (dragger != 0)
  418. {
  419. dragger->drag (e);
  420. dragger = 0;
  421. }
  422. }