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.

579 lines
18KB

  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_DrawableDocument.h"
  19. #include "jucer_DrawableTypeHandler.h"
  20. //==============================================================================
  21. namespace Tags
  22. {
  23. const Identifier drawableTag ("DRAWABLE");
  24. const Identifier markersGroupXTag ("MARKERS_X");
  25. const Identifier markersGroupYTag ("MARKERS_Y");
  26. }
  27. //==============================================================================
  28. DrawableDocument::DrawableDocument (Project* project_)
  29. : project (project_),
  30. root (Tags::drawableTag),
  31. saveAsXml (true),
  32. needsSaving (false)
  33. {
  34. DrawableComposite dc;
  35. root.addChild (dc.createValueTree (0), -1, 0);
  36. setName ("Drawable");
  37. checkRootObject();
  38. root.addListener (this);
  39. }
  40. DrawableDocument::~DrawableDocument()
  41. {
  42. root.removeListener (this);
  43. }
  44. void DrawableDocument::recursivelyUpdateIDs (Drawable::ValueTreeWrapperBase& d, StringArray& recentlyUsedIdCache)
  45. {
  46. if (d.getID().isEmpty())
  47. d.setID (createUniqueID (d.getState().getType().toString().toLowerCase() + "1", recentlyUsedIdCache), 0);
  48. if (d.getState().getType() == DrawableComposite::valueTreeType)
  49. {
  50. const DrawableComposite::ValueTreeWrapper composite (d.getState());
  51. for (int i = 0; i < composite.getNumDrawables(); ++i)
  52. {
  53. Drawable::ValueTreeWrapperBase child (composite.getDrawableState (i));
  54. recursivelyUpdateIDs (child, recentlyUsedIdCache);
  55. }
  56. }
  57. }
  58. void DrawableDocument::checkRootObject()
  59. {
  60. if (! root.hasProperty (Ids::id_))
  61. root.setProperty (Ids::id_, createAlphaNumericUID(), 0);
  62. if (markersX == 0)
  63. markersX = new MarkerList (*this, true);
  64. if (markersY == 0)
  65. markersY = new MarkerList (*this, false);
  66. DrawableComposite::ValueTreeWrapper rootObject (getRootDrawableNode());
  67. StringArray idCache;
  68. recursivelyUpdateIDs (rootObject, idCache);
  69. if (rootObject.getNumMarkers (true) < 2 || rootObject.getNumMarkers (false) < 2)
  70. rootObject.setContentArea (RelativeRectangle ("0, 0, 100, 100"), 0);
  71. }
  72. const String DrawableDocument::getUniqueId() const
  73. {
  74. return root [Ids::id_];
  75. }
  76. //==============================================================================
  77. void DrawableDocument::setName (const String& name)
  78. {
  79. root.setProperty (Ids::name, name, getUndoManager());
  80. }
  81. const String DrawableDocument::getName() const
  82. {
  83. return root [Ids::name];
  84. }
  85. bool DrawableDocument::hasChangedSinceLastSave() const
  86. {
  87. return needsSaving;
  88. }
  89. bool DrawableDocument::reload (const File& drawableFile)
  90. {
  91. ScopedPointer <InputStream> stream (drawableFile.createInputStream());
  92. if (stream != 0 && load (*stream))
  93. {
  94. checkRootObject();
  95. undoManager.clearUndoHistory();
  96. needsSaving = false;
  97. return true;
  98. }
  99. return false;
  100. }
  101. bool DrawableDocument::save (const File& drawableFile)
  102. {
  103. TemporaryFile tempFile (drawableFile);
  104. ScopedPointer <OutputStream> out (tempFile.getFile().createOutputStream());
  105. if (out == 0)
  106. return false;
  107. save (*out);
  108. needsSaving = ! tempFile.overwriteTargetFileWithTemporary();
  109. return ! needsSaving;
  110. }
  111. void DrawableDocument::save (OutputStream& output)
  112. {
  113. if (saveAsXml)
  114. {
  115. ScopedPointer <XmlElement> xml (root.createXml());
  116. jassert (xml != 0);
  117. if (xml != 0)
  118. xml->writeToStream (output, String::empty, false, false);
  119. }
  120. else
  121. {
  122. root.writeToStream (output);
  123. }
  124. }
  125. bool DrawableDocument::load (InputStream& input)
  126. {
  127. int64 originalPos = input.getPosition();
  128. ValueTree loadedTree;
  129. XmlDocument xmlDoc (input.readEntireStreamAsString());
  130. ScopedPointer <XmlElement> xml (xmlDoc.getDocumentElement());
  131. if (xml != 0)
  132. {
  133. loadedTree = ValueTree::fromXml (*xml);
  134. }
  135. else
  136. {
  137. input.setPosition (originalPos);
  138. loadedTree = ValueTree::readFromStream (input);
  139. }
  140. if (loadedTree.hasType (Tags::drawableTag))
  141. {
  142. root.removeListener (this);
  143. root = loadedTree;
  144. root.addListener (this);
  145. markersX = 0;
  146. markersY = 0;
  147. valueTreeParentChanged (loadedTree);
  148. needsSaving = false;
  149. undoManager.clearUndoHistory();
  150. return true;
  151. }
  152. return false;
  153. }
  154. void DrawableDocument::changed()
  155. {
  156. needsSaving = true;
  157. }
  158. DrawableComposite::ValueTreeWrapper DrawableDocument::getRootDrawableNode() const
  159. {
  160. return DrawableComposite::ValueTreeWrapper (root.getChild (0));
  161. }
  162. ValueTree DrawableDocument::findDrawableState (const String& objectId, bool recursive) const
  163. {
  164. return getRootDrawableNode().getDrawableWithId (objectId, recursive);
  165. }
  166. const String DrawableDocument::createUniqueID (const String& name, StringArray& recentlyUsedIdCache) const
  167. {
  168. String n (CodeHelpers::makeValidIdentifier (name, false, true, false));
  169. int suffix = 2;
  170. int cacheIndex = -1;
  171. const String withoutNumbers (n.trimCharactersAtEnd ("0123456789"));
  172. for (int i = 0; i < recentlyUsedIdCache.size(); ++i)
  173. {
  174. if (recentlyUsedIdCache[i].startsWith (withoutNumbers))
  175. {
  176. cacheIndex = i;
  177. suffix = jmax (suffix, recentlyUsedIdCache[i].substring (withoutNumbers.length()).getIntValue() + 1);
  178. n = withoutNumbers + String (suffix++);
  179. break;
  180. }
  181. }
  182. while (markersX->getMarkerNamed (n).isValid() || markersY->getMarkerNamed (n).isValid()
  183. || findDrawableState (n, true).isValid())
  184. n = withoutNumbers + String (suffix++);
  185. if (cacheIndex >= 0)
  186. recentlyUsedIdCache.set (cacheIndex, n);
  187. else
  188. recentlyUsedIdCache.add (n);
  189. return n;
  190. }
  191. bool DrawableDocument::createItemProperties (Array <PropertyComponent*>& props, const String& itemId)
  192. {
  193. ValueTree drawable (findDrawableState (itemId.upToFirstOccurrenceOf ("/", false, false), false));
  194. if (drawable.isValid())
  195. {
  196. DrawableTypeInstance item (*this, drawable);
  197. if (itemId.containsChar ('/'))
  198. {
  199. OwnedArray <ControlPoint> points;
  200. item.getAllControlPoints (points);
  201. for (int i = 0; i < points.size(); ++i)
  202. if (points.getUnchecked(i)->getID() == itemId)
  203. points.getUnchecked(i)->createProperties (*this, props);
  204. }
  205. else
  206. {
  207. item.createProperties (props);
  208. }
  209. return true;
  210. }
  211. if (markersX->createProperties (props, itemId)
  212. || markersY->createProperties (props, itemId))
  213. return true;
  214. return false;
  215. }
  216. void DrawableDocument::createItemProperties (Array <PropertyComponent*>& props, const StringArray& selectedItemIds)
  217. {
  218. if (selectedItemIds.size() != 1)
  219. return; //xxx
  220. for (int i = 0; i < selectedItemIds.size(); ++i)
  221. createItemProperties (props, selectedItemIds[i]);
  222. }
  223. //==============================================================================
  224. const int menuItemOffset = 0x63451fa4;
  225. void DrawableDocument::addNewItemMenuItems (PopupMenu& menu) const
  226. {
  227. const StringArray newItems (DrawableTypeManager::getInstance()->getNewItemList());
  228. for (int i = 0; i < newItems.size(); ++i)
  229. menu.addItem (i + menuItemOffset, newItems[i]);
  230. }
  231. const ValueTree DrawableDocument::performNewItemMenuItem (int menuResultCode)
  232. {
  233. const StringArray newItems (DrawableTypeManager::getInstance()->getNewItemList());
  234. int index = menuResultCode - menuItemOffset;
  235. if (index >= 0 && index < newItems.size())
  236. {
  237. ValueTree state (DrawableTypeManager::getInstance()
  238. ->createNewItem (index, *this,
  239. Point<float> (Random::getSystemRandom().nextFloat() * 100.0f + 100.0f,
  240. Random::getSystemRandom().nextFloat() * 100.0f + 100.0f)));
  241. Drawable::ValueTreeWrapperBase wrapper (state);
  242. StringArray idCache;
  243. recursivelyUpdateIDs (wrapper, idCache);
  244. getRootDrawableNode().addDrawable (state, -1, getUndoManager());
  245. return state;
  246. }
  247. return ValueTree::invalid;
  248. }
  249. const ValueTree DrawableDocument::insertSVG (const File& file, const Point<float>& position)
  250. {
  251. ScopedPointer<Drawable> d (Drawable::createFromImageFile (file));
  252. DrawableComposite* dc = dynamic_cast <DrawableComposite*> (static_cast <Drawable*> (d));
  253. if (dc != 0)
  254. {
  255. ValueTree state (dc->createValueTree (this));
  256. if (state.isValid())
  257. {
  258. Drawable::ValueTreeWrapperBase wrapper (state);
  259. getRootDrawableNode().addDrawable (state, -1, getUndoManager());
  260. StringArray idCache;
  261. recursivelyUpdateIDs (wrapper, idCache);
  262. return state;
  263. }
  264. }
  265. return ValueTree::invalid;
  266. }
  267. //==============================================================================
  268. const Image DrawableDocument::getImageForIdentifier (const var& imageIdentifier)
  269. {
  270. const String s (imageIdentifier.toString());
  271. if (s.startsWithIgnoreCase ("id:"))
  272. {
  273. jassert (project != 0);
  274. if (project != 0)
  275. {
  276. Project::Item item (project->getMainGroup().findItemWithID (s.substring (3).trim()));
  277. if (item.isValid())
  278. {
  279. Image im (ImageCache::getFromFile (item.getFile()));
  280. if (im.isValid())
  281. {
  282. im.setTag (imageIdentifier);
  283. return im;
  284. }
  285. }
  286. }
  287. }
  288. static Image dummy;
  289. if (dummy.isNull())
  290. {
  291. dummy = Image (Image::ARGB, 128, 128, true);
  292. Graphics g (dummy);
  293. g.fillAll (Colours::khaki.withAlpha (0.51f));
  294. g.setColour (Colours::grey);
  295. g.drawRect (0, 0, 128, 128);
  296. for (int i = -128; i < 128; i += 16)
  297. g.drawLine ((float) i, 0.0f, i + 128.0f, 128.0f);
  298. g.setColour (Colours::darkgrey);
  299. g.drawRect (0, 0, 128, 128);
  300. g.setFont (16.0f, Font::bold);
  301. g.drawText ("(Image Missing)", 0, 0, 128, 128, Justification::centred, false);
  302. }
  303. return dummy;
  304. }
  305. const var DrawableDocument::getIdentifierForImage (const Image& image)
  306. {
  307. return image.getTag();
  308. }
  309. //==============================================================================
  310. void DrawableDocument::valueTreePropertyChanged (ValueTree& tree, const Identifier& name)
  311. {
  312. changed();
  313. }
  314. void DrawableDocument::valueTreeChildrenChanged (ValueTree& tree)
  315. {
  316. changed();
  317. }
  318. void DrawableDocument::valueTreeParentChanged (ValueTree& tree)
  319. {
  320. changed();
  321. }
  322. //==============================================================================
  323. const RelativeCoordinate DrawableDocument::findNamedCoordinate (const String& objectName, const String& edge) const
  324. {
  325. if (objectName == "parent")
  326. {
  327. jassert (edge != "right" && edge != "bottom"); // drawables don't have a canvas size..
  328. }
  329. if (objectName.isNotEmpty() && edge.isNotEmpty())
  330. {
  331. }
  332. {
  333. const ValueTree marker (getMarkerListX().getMarkerNamed (objectName));
  334. if (marker.isValid())
  335. return getMarkerListX().getCoordinate (marker);
  336. }
  337. {
  338. const ValueTree marker (getMarkerListY().getMarkerNamed (objectName));
  339. if (marker.isValid())
  340. return getMarkerListY().getCoordinate (marker);
  341. }
  342. return RelativeCoordinate();
  343. }
  344. //==============================================================================
  345. DrawableDocument::MarkerList::MarkerList (DrawableDocument& document_, bool isX_)
  346. : MarkerListBase (isX_),
  347. document (document_),
  348. object (document_.getRootDrawableNode())
  349. {
  350. }
  351. const String DrawableDocument::MarkerList::getId (const ValueTree& markerState)
  352. {
  353. return markerState [DrawableComposite::ValueTreeWrapper::nameProperty];
  354. }
  355. int DrawableDocument::MarkerList::size() const
  356. {
  357. return object.getNumMarkers (isX);
  358. }
  359. ValueTree DrawableDocument::MarkerList::getMarker (int index) const
  360. {
  361. return object.getMarkerState (isX, index);
  362. }
  363. ValueTree DrawableDocument::MarkerList::getMarkerNamed (const String& name) const
  364. {
  365. return object.getMarkerState (isX, name);
  366. }
  367. bool DrawableDocument::MarkerList::contains (const ValueTree& markerState) const
  368. {
  369. return object.containsMarker (isX, markerState);
  370. }
  371. void DrawableDocument::MarkerList::createMarker (const String& name, int position)
  372. {
  373. object.setMarker (isX, DrawableComposite::Marker (name, RelativeCoordinate ((double) position, isX)),
  374. getUndoManager());
  375. }
  376. void DrawableDocument::MarkerList::deleteMarker (ValueTree& markerState)
  377. {
  378. object.removeMarker (isX, markerState, getUndoManager());
  379. }
  380. const RelativeCoordinate DrawableDocument::MarkerList::findNamedCoordinate (const String& objectName, const String& edge) const
  381. {
  382. if (objectName == "parent")
  383. {
  384. jassert (edge != "right" && edge != "bottom"); // drawables don't have a canvas size..
  385. }
  386. const ValueTree marker (getMarkerNamed (objectName));
  387. if (marker.isValid())
  388. return getCoordinate (marker);
  389. return RelativeCoordinate();
  390. }
  391. bool DrawableDocument::MarkerList::createProperties (Array <PropertyComponent*>& props, const String& itemId)
  392. {
  393. ValueTree marker (getMarkerNamed (itemId));
  394. if (marker.isValid())
  395. {
  396. props.add (new TextPropertyComponent (marker.getPropertyAsValue (DrawableComposite::ValueTreeWrapper::nameProperty, getUndoManager()),
  397. "Marker Name", 256, false));
  398. props.add (new MarkerListBase::PositionPropertyComponent (*this, "Position", marker,
  399. marker.getPropertyAsValue (DrawableComposite::ValueTreeWrapper::posProperty, getUndoManager())));
  400. return true;
  401. }
  402. return false;
  403. }
  404. void DrawableDocument::MarkerList::addMarkerMenuItem (int i, const RelativeCoordinate& coord, const String& name, const String& edge, PopupMenu& menu,
  405. bool isAnchor1, const String& fullCoordName)
  406. {
  407. RelativeCoordinate requestedCoord (findNamedCoordinate (name, edge));
  408. menu.addItem (i, edge.isEmpty() ? name : (name + "." + edge),
  409. ! (name == fullCoordName || (fullCoordName.isNotEmpty() && requestedCoord.references (fullCoordName, this))),
  410. name == (isAnchor1 ? coord.getAnchorName1() : coord.getAnchorName2()));
  411. }
  412. void DrawableDocument::MarkerList::addMarkerMenuItems (const ValueTree& markerState, const RelativeCoordinate& coord, PopupMenu& menu, bool isAnchor1)
  413. {
  414. const String fullCoordName (getName (markerState));
  415. if (isHorizontal())
  416. addMarkerMenuItem (1, coord, "parent", "left", menu, isAnchor1, fullCoordName);
  417. else
  418. addMarkerMenuItem (1, coord, "parent", "top", menu, isAnchor1, fullCoordName);
  419. menu.addSeparator();
  420. for (int i = 0; i < size(); ++i)
  421. addMarkerMenuItem (100 + i, coord, getName (getMarker (i)),
  422. String::empty, menu, isAnchor1, fullCoordName);
  423. }
  424. const String DrawableDocument::MarkerList::getChosenMarkerMenuItem (const RelativeCoordinate& coord, int i) const
  425. {
  426. if (i == 1) return isHorizontal() ? "parent.left" : "parent.top";
  427. if (i >= 100 && i < 10000)
  428. return getName (getMarker (i - 100));
  429. jassertfalse;
  430. return String::empty;
  431. }
  432. UndoManager* DrawableDocument::MarkerList::getUndoManager() const
  433. {
  434. return document.getUndoManager();
  435. }
  436. const String DrawableDocument::MarkerList::getNonexistentMarkerName (const String& name)
  437. {
  438. return document.getNonexistentMarkerName (name);
  439. }
  440. const String DrawableDocument::getNonexistentMarkerName (const String& name)
  441. {
  442. String n (CodeHelpers::makeValidIdentifier (name, false, true, false));
  443. int suffix = 2;
  444. while (markersX->getMarkerNamed (n).isValid() || markersY->getMarkerNamed (n).isValid())
  445. n = n.trimCharactersAtEnd ("0123456789") + String (suffix++);
  446. return n;
  447. }
  448. void DrawableDocument::MarkerList::renameAnchor (const String& oldName, const String& newName)
  449. {
  450. document.renameAnchor (oldName, newName);
  451. }
  452. void DrawableDocument::renameAnchor (const String& oldName, const String& newName)
  453. {
  454. }