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.

387 lines
12KB

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