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.

385 lines
12KB

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