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.

386 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-12 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 "../JuceDemoHeader.h"
  19. //==============================================================================
  20. class XmlTreeItem : public TreeViewItem
  21. {
  22. public:
  23. XmlTreeItem (XmlElement& x) : xml (x)
  24. {
  25. }
  26. String getUniqueName() const override
  27. {
  28. if (xml.getTagName().isEmpty())
  29. return "unknown";
  30. return xml.getTagName();
  31. }
  32. bool mightContainSubItems() override
  33. {
  34. return xml.getFirstChildElement() != nullptr;
  35. }
  36. void paintItem (Graphics& g, int width, int height) override
  37. {
  38. // if this item is selected, fill it with a background colour..
  39. if (isSelected())
  40. g.fillAll (Colours::blue.withAlpha (0.3f));
  41. // use a "colour" attribute in the xml tag for this node to set the text colour..
  42. g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000")));
  43. g.setFont (height * 0.7f);
  44. // draw the xml element's tag name..
  45. g.drawText (xml.getTagName(),
  46. 4, 0, width - 4, height,
  47. Justification::centredLeft, true);
  48. }
  49. void itemOpennessChanged (bool isNowOpen) override
  50. {
  51. if (isNowOpen)
  52. {
  53. // if we've not already done so, we'll now add the tree's sub-items. You could
  54. // also choose to delete the existing ones and refresh them if that's more suitable
  55. // in your app.
  56. if (getNumSubItems() == 0)
  57. {
  58. // create and add sub-items to this node of the tree, corresponding to
  59. // each sub-element in the XML..
  60. forEachXmlChildElement (xml, child)
  61. {
  62. jassert (child != nullptr);
  63. addSubItem (new XmlTreeItem (*child));
  64. }
  65. }
  66. }
  67. else
  68. {
  69. // in this case, we'll leave any sub-items in the tree when the node gets closed,
  70. // though you could choose to delete them if that's more appropriate for
  71. // your application.
  72. }
  73. }
  74. private:
  75. XmlElement& xml;
  76. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem)
  77. };
  78. //==============================================================================
  79. class JsonTreeItem : public TreeViewItem
  80. {
  81. public:
  82. JsonTreeItem (Identifier i, var value) : identifier (i), json (value)
  83. {
  84. }
  85. String getUniqueName() const override
  86. {
  87. return identifier.toString() + "_id";
  88. }
  89. bool mightContainSubItems() override
  90. {
  91. if (DynamicObject* obj = json.getDynamicObject())
  92. return obj->getProperties().size() > 0;
  93. return json.isArray();
  94. }
  95. void paintItem (Graphics& g, int width, int height) override
  96. {
  97. // if this item is selected, fill it with a background colour..
  98. if (isSelected())
  99. g.fillAll (Colours::blue.withAlpha (0.3f));
  100. g.setColour (Colours::black);
  101. g.setFont (height * 0.7f);
  102. // draw the element's tag name..
  103. g.drawText (getText(),
  104. 4, 0, width - 4, height,
  105. Justification::centredLeft, true);
  106. }
  107. void itemOpennessChanged (bool isNowOpen) override
  108. {
  109. if (isNowOpen)
  110. {
  111. // if we've not already done so, we'll now add the tree's sub-items. You could
  112. // also choose to delete the existing ones and refresh them if that's more suitable
  113. // in your app.
  114. if (getNumSubItems() == 0)
  115. {
  116. // create and add sub-items to this node of the tree, corresponding to
  117. // the type of object this var represents
  118. if (json.isArray())
  119. {
  120. for (int i = 0; i < json.size(); ++i)
  121. {
  122. var& child (json[i]);
  123. jassert (! child.isVoid());
  124. addSubItem (new JsonTreeItem (Identifier::null, child));
  125. }
  126. }
  127. else if (DynamicObject* obj = json.getDynamicObject())
  128. {
  129. NamedValueSet& props (obj->getProperties());
  130. for (int i = 0; i < props.size(); ++i)
  131. {
  132. const Identifier id (props.getName (i));
  133. var child (props[id]);
  134. jassert (! child.isVoid());
  135. addSubItem (new JsonTreeItem (id, child));
  136. }
  137. }
  138. }
  139. }
  140. else
  141. {
  142. // in this case, we'll leave any sub-items in the tree when the node gets closed,
  143. // though you could choose to delete them if that's more appropriate for
  144. // your application.
  145. }
  146. }
  147. private:
  148. Identifier identifier;
  149. var json;
  150. /** Returns the text to display in the tree.
  151. This is a little more complex for JSON than XML as nodes can be strings, objects or arrays.
  152. */
  153. String getText() const
  154. {
  155. String text;
  156. if (identifier.isValid())
  157. text << identifier.toString();
  158. if (! json.isVoid())
  159. {
  160. if (text.isNotEmpty() && (! json.isArray()))
  161. text << ": ";
  162. if (json.isObject() && (! identifier.isValid()))
  163. text << "[Array]";
  164. else if (! json.isArray())
  165. text << json.toString();
  166. }
  167. return text;
  168. }
  169. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem)
  170. };
  171. //==============================================================================
  172. class StringsDemo : public Component,
  173. private ComboBox::Listener,
  174. private CodeDocument::Listener
  175. {
  176. public:
  177. /** The type of database to parse. */
  178. enum Type
  179. {
  180. xml,
  181. json
  182. };
  183. StringsDemo()
  184. : codeDocumentComponent (codeDocument, nullptr)
  185. {
  186. setOpaque (true);
  187. addAndMakeVisible (typeBox);
  188. typeBox.addListener (this);
  189. typeBox.addItem ("XML", 1);
  190. typeBox.addItem ("JSON", 2);
  191. comboBoxLabel.setText ("Database Type:", dontSendNotification);
  192. comboBoxLabel.attachToComponent (&typeBox, true);
  193. addAndMakeVisible (codeDocumentComponent);
  194. codeDocument.addListener (this);
  195. addAndMakeVisible (resultsTree);
  196. resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
  197. resultsTree.setDefaultOpenness (true);
  198. addAndMakeVisible (errorMessage);
  199. errorMessage.setReadOnly (true);
  200. errorMessage.setMultiLine (true);
  201. errorMessage.setCaretVisible (false);
  202. errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
  203. errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
  204. typeBox.setSelectedId (1);
  205. }
  206. ~StringsDemo()
  207. {
  208. resultsTree.setRootItem (nullptr);
  209. }
  210. void paint (Graphics& g) override
  211. {
  212. fillBrushedAluminiumBackground (g);
  213. }
  214. void resized() override
  215. {
  216. Rectangle<int> area (getLocalBounds());
  217. typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
  218. codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8));
  219. resultsTree.setBounds (area.reduced (8));
  220. errorMessage.setBounds (resultsTree.getBounds());
  221. }
  222. private:
  223. ComboBox typeBox;
  224. Label comboBoxLabel;
  225. CodeDocument codeDocument;
  226. CodeEditorComponent codeDocumentComponent;
  227. TreeView resultsTree;
  228. ScopedPointer<TreeViewItem> rootItem;
  229. ScopedPointer<XmlElement> parsedXml;
  230. TextEditor errorMessage;
  231. void rebuildTree()
  232. {
  233. ScopedPointer<XmlElement> openness;
  234. if (rootItem != nullptr)
  235. openness = rootItem->getOpennessState();
  236. createNewRootNode();
  237. if (openness != nullptr && rootItem != nullptr)
  238. rootItem->restoreOpennessState (*openness);
  239. }
  240. void createNewRootNode()
  241. {
  242. // clear the current tree
  243. resultsTree.setRootItem (nullptr);
  244. rootItem = nullptr;
  245. // try and parse the editor's contents
  246. switch (typeBox.getSelectedItemIndex())
  247. {
  248. case xml: rootItem = rebuildXml(); break;
  249. case json: rootItem = rebuildJson(); break;
  250. default: rootItem = nullptr; break;
  251. }
  252. // if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
  253. if (rootItem != nullptr)
  254. errorMessage.clear();
  255. errorMessage.setVisible (! errorMessage.isEmpty());
  256. resultsTree.setRootItem (rootItem);
  257. }
  258. /** Parses the editors contects as XML. */
  259. TreeViewItem* rebuildXml()
  260. {
  261. parsedXml = nullptr;
  262. XmlDocument doc (codeDocument.getAllContent());
  263. parsedXml = doc.getDocumentElement();
  264. if (parsedXml == nullptr)
  265. {
  266. String error (doc.getLastParseError());
  267. if (error.isEmpty())
  268. error = "Unknown error";
  269. errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
  270. return nullptr;
  271. }
  272. return new XmlTreeItem (*parsedXml);
  273. }
  274. /** Parses the editors contects as JSON. */
  275. TreeViewItem* rebuildJson()
  276. {
  277. var parsedJson;
  278. Result result = JSON::parse (codeDocument.getAllContent(), parsedJson);
  279. if (! result.wasOk())
  280. {
  281. errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
  282. return nullptr;
  283. }
  284. return new JsonTreeItem (Identifier::null, parsedJson);
  285. }
  286. /** Clears the editor and loads some default text. */
  287. void reset (Type type)
  288. {
  289. switch (type)
  290. {
  291. case xml: codeDocument.replaceAllContent (BinaryData::treedemo_xml); break;
  292. case json: codeDocument.replaceAllContent (BinaryData::juce_module_info); break;
  293. default: codeDocument.replaceAllContent (String::empty); break;
  294. }
  295. }
  296. void comboBoxChanged (ComboBox* box) override
  297. {
  298. if (box == &typeBox)
  299. {
  300. if (typeBox.getSelectedId() == 1)
  301. reset (xml);
  302. else
  303. reset (json);
  304. }
  305. }
  306. void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
  307. void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
  308. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StringsDemo)
  309. };
  310. // This static object will register this demo type in a global list of demos..
  311. static JuceDemoType<StringsDemo> demo ("40 XML & JSON");