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.

462 lines
14KB

  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 metadataTagStart = "JUCER_" "COMPONENT_METADATA_START"; // written like this to avoid thinking this file is a component!
  24. static const char* const metadataTagEnd = "JUCER_" "COMPONENT_METADATA_END";
  25. const char* const ComponentDocument::idProperty = "id";
  26. const char* const ComponentDocument::compBoundsProperty = "position";
  27. const char* const ComponentDocument::memberNameProperty = "memberName";
  28. const char* const ComponentDocument::compNameProperty = "name";
  29. //==============================================================================
  30. ComponentDocument::ComponentDocument (Project* project_, const File& cppFile_)
  31. : project (project_), cppFile (cppFile_), root (componentDocumentTag),
  32. changedSinceSaved (false)
  33. {
  34. reload();
  35. checkRootObject();
  36. root.addListener (this);
  37. }
  38. ComponentDocument::~ComponentDocument()
  39. {
  40. root.removeListener (this);
  41. }
  42. void ComponentDocument::beginNewTransaction()
  43. {
  44. undoManager.beginNewTransaction();
  45. }
  46. void ComponentDocument::valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property)
  47. {
  48. changedSinceSaved = true;
  49. }
  50. void ComponentDocument::valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged)
  51. {
  52. changedSinceSaved = true;
  53. }
  54. void ComponentDocument::valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged)
  55. {
  56. changedSinceSaved = true;
  57. }
  58. bool ComponentDocument::isComponentFile (const File& file)
  59. {
  60. if (! file.hasFileExtension (".cpp"))
  61. return false;
  62. InputStream* in = file.createInputStream();
  63. if (in != 0)
  64. {
  65. BufferedInputStream buf (in, 8192, true);
  66. while (! buf.isExhausted())
  67. if (buf.readNextLine().contains (metadataTagStart))
  68. return true;
  69. }
  70. return false;
  71. }
  72. void ComponentDocument::writeCode (OutputStream& cpp, OutputStream& header)
  73. {
  74. cpp << "/** */"
  75. << newLine << newLine;
  76. header << "/** */"
  77. << newLine << newLine;
  78. }
  79. void ComponentDocument::writeMetadata (OutputStream& out)
  80. {
  81. out << "#if 0" << newLine
  82. << "/** Jucer-generated metadata section - Edit this data at own risk!" << newLine
  83. << metadataTagStart << newLine << newLine;
  84. ScopedPointer<XmlElement> xml (root.createXml());
  85. jassert (xml != 0);
  86. if (xml != 0)
  87. xml->writeToStream (out, String::empty, false, false);
  88. out << newLine
  89. << metadataTagEnd << " */" << newLine
  90. << "#endif" << newLine;
  91. }
  92. bool ComponentDocument::save()
  93. {
  94. MemoryOutputStream cpp, header;
  95. writeCode (cpp, header);
  96. writeMetadata (cpp);
  97. bool savedOk = overwriteFileWithNewDataIfDifferent (cppFile, cpp)
  98. && overwriteFileWithNewDataIfDifferent (cppFile.withFileExtension (".h"), header);
  99. if (savedOk)
  100. changedSinceSaved = false;
  101. return savedOk;
  102. }
  103. bool ComponentDocument::reload()
  104. {
  105. String xmlString;
  106. {
  107. InputStream* in = cppFile.createInputStream();
  108. if (in == 0)
  109. return false;
  110. BufferedInputStream buf (in, 8192, true);
  111. String::Concatenator xml (xmlString);
  112. while (! buf.isExhausted())
  113. {
  114. String line (buf.readNextLine());
  115. if (line.contains (metadataTagStart))
  116. {
  117. while (! buf.isExhausted())
  118. {
  119. line = buf.readNextLine();
  120. if (line.contains (metadataTagEnd))
  121. break;
  122. xml.append (line);
  123. xml.append (newLine);
  124. }
  125. break;
  126. }
  127. }
  128. }
  129. XmlDocument doc (xmlString);
  130. ScopedPointer<XmlElement> xml (doc.getDocumentElement());
  131. if (xml != 0 && xml->hasTagName (componentDocumentTag))
  132. {
  133. ValueTree newTree (ValueTree::fromXml (*xml));
  134. if (newTree.isValid())
  135. {
  136. root = newTree;
  137. checkRootObject();
  138. undoManager.clearUndoHistory();
  139. changedSinceSaved = false;
  140. return true;
  141. }
  142. }
  143. return false;
  144. }
  145. bool ComponentDocument::hasChangedSinceLastSave()
  146. {
  147. return changedSinceSaved;
  148. }
  149. void ComponentDocument::checkRootObject()
  150. {
  151. jassert (root.hasType (componentDocumentTag));
  152. if (! getComponentGroup().isValid())
  153. root.addChild (ValueTree (componentGroupTag), -1, 0);
  154. if (getClassName().toString().isEmpty())
  155. getClassName() = "NewComponent";
  156. if ((int) getCanvasWidth().getValue() <= 0)
  157. getCanvasWidth() = 640;
  158. if ((int) getCanvasHeight().getValue() <= 0)
  159. getCanvasHeight() = 480;
  160. }
  161. //==============================================================================
  162. const int menuItemOffset = 0x63451fa4;
  163. void ComponentDocument::addNewComponentMenuItems (PopupMenu& menu) const
  164. {
  165. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  166. for (int i = 0; i < typeNames.size(); ++i)
  167. menu.addItem (i + menuItemOffset, "New " + typeNames[i]);
  168. }
  169. void ComponentDocument::performNewComponentMenuItem (int menuResultCode)
  170. {
  171. const StringArray typeNames (ComponentTypeManager::getInstance()->getTypeNames());
  172. if (menuResultCode >= menuItemOffset && menuResultCode < menuItemOffset + typeNames.size())
  173. {
  174. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandler (menuResultCode - menuItemOffset);
  175. jassert (handler != 0);
  176. if (handler != 0)
  177. {
  178. ValueTree state (handler->getXmlTag());
  179. state.setProperty (idProperty, createAlphaNumericUID(), 0);
  180. handler->initialiseNewItem (*this, state);
  181. getComponentGroup().addChild (state, -1, getUndoManager());
  182. }
  183. }
  184. }
  185. //==============================================================================
  186. ValueTree ComponentDocument::getComponentGroup() const
  187. {
  188. return root.getChildWithName (componentGroupTag);
  189. }
  190. int ComponentDocument::getNumComponents() const
  191. {
  192. return getComponentGroup().getNumChildren();
  193. }
  194. const ValueTree ComponentDocument::getComponent (int index) const
  195. {
  196. return getComponentGroup().getChild (index);
  197. }
  198. const ValueTree ComponentDocument::getComponentWithMemberName (const String& name) const
  199. {
  200. const ValueTree comps (getComponentGroup());
  201. for (int i = comps.getNumChildren(); --i >= 0;)
  202. {
  203. const ValueTree v (comps.getChild(i));
  204. if (v [memberNameProperty] == name)
  205. return v;
  206. }
  207. return ValueTree::invalid;
  208. }
  209. Component* ComponentDocument::createComponent (int index)
  210. {
  211. const ValueTree v (getComponentGroup().getChild (index));
  212. if (v.isValid())
  213. {
  214. Component* c = ComponentTypeManager::getInstance()->createFromStoredType (*this, v);
  215. c->getProperties().set (idProperty, v[idProperty]);
  216. jassert (c->getProperties()[idProperty].toString().isNotEmpty());
  217. return c;
  218. }
  219. return 0;
  220. }
  221. //==============================================================================
  222. class ComponentMarkerResolver : public Coordinate::MarkerResolver
  223. {
  224. public:
  225. ComponentMarkerResolver (ComponentDocument& doc, const ValueTree& state_, int parentWidth_, int parentHeight_)
  226. : owner (doc), state (state_),
  227. parentWidth (parentWidth_),
  228. parentHeight (parentHeight_)
  229. {}
  230. ~ComponentMarkerResolver() {}
  231. const Coordinate findMarker (const String& name, bool isHorizontal)
  232. {
  233. if (name == "left") return RectangleCoordinates (state [ComponentDocument::compBoundsProperty]).left;
  234. else if (name == "right") return RectangleCoordinates (state [ComponentDocument::compBoundsProperty]).right;
  235. else if (name == "top") return RectangleCoordinates (state [ComponentDocument::compBoundsProperty]).top;
  236. else if (name == "bottom") return RectangleCoordinates (state [ComponentDocument::compBoundsProperty]).bottom;
  237. else if (name == Coordinate::parentRightMarkerName) return Coordinate (parentWidth, isHorizontal);
  238. else if (name == Coordinate::parentBottomMarkerName) return Coordinate (parentHeight, isHorizontal);
  239. return Coordinate (isHorizontal);
  240. }
  241. private:
  242. ComponentDocument& owner;
  243. ValueTree state;
  244. int parentWidth, parentHeight;
  245. };
  246. const RectangleCoordinates ComponentDocument::getCoordsFor (const ValueTree& state) const
  247. {
  248. return RectangleCoordinates (state [compBoundsProperty]);
  249. }
  250. bool ComponentDocument::setCoordsFor (ValueTree& state, const RectangleCoordinates& pr)
  251. {
  252. const String newBoundsString (pr.toString());
  253. if (state[compBoundsProperty] == newBoundsString)
  254. return false;
  255. state.setProperty (compBoundsProperty, newBoundsString, getUndoManager());
  256. return true;
  257. }
  258. Coordinate::MarkerResolver* ComponentDocument::createMarkerResolver (const ValueTree& state)
  259. {
  260. return new ComponentMarkerResolver (*this, state, getCanvasWidth().getValue(), getCanvasHeight().getValue());
  261. }
  262. const StringArray ComponentDocument::getComponentMarkers (bool horizontal) const
  263. {
  264. StringArray s;
  265. if (horizontal)
  266. {
  267. s.add (Coordinate::parentLeftMarkerName);
  268. s.add (Coordinate::parentRightMarkerName);
  269. s.add ("left");
  270. s.add ("right");
  271. }
  272. else
  273. {
  274. s.add (Coordinate::parentTopMarkerName);
  275. s.add (Coordinate::parentBottomMarkerName);
  276. s.add ("top");
  277. s.add ("bottom");
  278. }
  279. return s;
  280. }
  281. void ComponentDocument::updateComponent (Component* comp)
  282. {
  283. const ValueTree v (getComponentState (comp));
  284. if (v.isValid())
  285. {
  286. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  287. jassert (handler != 0);
  288. if (handler != 0)
  289. handler->updateComponent (*this, comp, v);
  290. }
  291. }
  292. bool ComponentDocument::containsComponent (Component* comp) const
  293. {
  294. const ValueTree comps (getComponentGroup());
  295. for (int i = 0; i < comps.getNumChildren(); ++i)
  296. if (isStateForComponent (comps.getChild(i), comp))
  297. return true;
  298. return false;
  299. }
  300. const ValueTree ComponentDocument::getComponentState (Component* comp) const
  301. {
  302. jassert (comp != 0);
  303. const ValueTree comps (getComponentGroup());
  304. for (int i = 0; i < comps.getNumChildren(); ++i)
  305. if (isStateForComponent (comps.getChild(i), comp))
  306. return comps.getChild(i);
  307. jassertfalse;
  308. return ValueTree::invalid;
  309. }
  310. void ComponentDocument::getComponentProperties (Array <PropertyComponent*>& props, Component* comp)
  311. {
  312. ValueTree v (getComponentState (comp));
  313. if (v.isValid())
  314. {
  315. ComponentTypeHandler* handler = ComponentTypeManager::getInstance()->getHandlerFor (v.getType());
  316. jassert (handler != 0);
  317. if (handler != 0)
  318. handler->createPropertyEditors (*this, v, props);
  319. }
  320. }
  321. bool ComponentDocument::isStateForComponent (const ValueTree& storedState, Component* comp) const
  322. {
  323. jassert (comp != 0);
  324. jassert (! storedState [idProperty].isVoid());
  325. return storedState [idProperty] == comp->getProperties() [idProperty];
  326. }
  327. const String ComponentDocument::getNonExistentMemberName (String suggestedName)
  328. {
  329. suggestedName = makeValidCppIdentifier (suggestedName, false, true, false);
  330. const String original (suggestedName);
  331. int num = 1;
  332. while (getComponentWithMemberName (suggestedName).isValid())
  333. {
  334. suggestedName = original;
  335. while (String ("0123456789").containsChar (suggestedName.getLastCharacter()))
  336. suggestedName = suggestedName.dropLastCharacters (1);
  337. suggestedName << num++;
  338. }
  339. return suggestedName;
  340. }
  341. //==============================================================================
  342. UndoManager* ComponentDocument::getUndoManager()
  343. {
  344. return &undoManager;
  345. }
  346. //==============================================================================
  347. void ComponentDocument::createClassProperties (Array <PropertyComponent*>& props)
  348. {
  349. props.add (new TextPropertyComponent (getClassName(), "Class Name", 256, false));
  350. props.getLast()->setTooltip ("The C++ class name for the component class.");
  351. props.add (new TextPropertyComponent (getClassDescription(), "Description", 512, false));
  352. props.getLast()->setTooltip ("A freeform description of the component.");
  353. props.add (new SliderPropertyComponent (getCanvasWidth(), "Initial Width", 1.0, 8192.0, 1.0));
  354. props.getLast()->setTooltip ("The initial width of the component when it is created.");
  355. props.add (new SliderPropertyComponent (getCanvasHeight(), "Initial Height", 1.0, 8192.0, 1.0));
  356. props.getLast()->setTooltip ("The initial height of the component when it is created.");
  357. }