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.

382 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 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.addItem ("XML", 1);
  189. typeBox.addItem ("JSON", 2);
  190. typeBox.onChange = [this]
  191. {
  192. if (typeBox.getSelectedId() == 1)
  193. reset (xml);
  194. else
  195. reset (json);
  196. };
  197. comboBoxLabel.setText ("Database Type:", dontSendNotification);
  198. comboBoxLabel.attachToComponent (&typeBox, true);
  199. addAndMakeVisible (codeDocumentComponent);
  200. codeDocument.addListener (this);
  201. addAndMakeVisible (resultsTree);
  202. resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
  203. resultsTree.setDefaultOpenness (true);
  204. addAndMakeVisible (errorMessage);
  205. errorMessage.setReadOnly (true);
  206. errorMessage.setMultiLine (true);
  207. errorMessage.setCaretVisible (false);
  208. errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
  209. errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
  210. typeBox.setSelectedId (1);
  211. }
  212. ~StringsDemo()
  213. {
  214. resultsTree.setRootItem (nullptr);
  215. }
  216. void paint (Graphics& g) override
  217. {
  218. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  219. }
  220. void resized() override
  221. {
  222. Rectangle<int> area (getLocalBounds());
  223. typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
  224. codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8));
  225. resultsTree.setBounds (area.reduced (8));
  226. errorMessage.setBounds (resultsTree.getBounds());
  227. }
  228. private:
  229. ComboBox typeBox;
  230. Label comboBoxLabel;
  231. CodeDocument codeDocument;
  232. CodeEditorComponent codeDocumentComponent;
  233. TreeView resultsTree;
  234. ScopedPointer<TreeViewItem> rootItem;
  235. ScopedPointer<XmlElement> parsedXml;
  236. TextEditor errorMessage;
  237. void rebuildTree()
  238. {
  239. ScopedPointer<XmlElement> openness;
  240. if (rootItem != nullptr)
  241. openness = rootItem->getOpennessState();
  242. createNewRootNode();
  243. if (openness != nullptr && rootItem != nullptr)
  244. rootItem->restoreOpennessState (*openness);
  245. }
  246. void createNewRootNode()
  247. {
  248. // clear the current tree
  249. resultsTree.setRootItem (nullptr);
  250. rootItem.reset();
  251. // try and parse the editor's contents
  252. switch (typeBox.getSelectedItemIndex())
  253. {
  254. case xml: rootItem = rebuildXml(); break;
  255. case json: rootItem = rebuildJson(); break;
  256. default: rootItem.reset(); break;
  257. }
  258. // if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
  259. if (rootItem != nullptr)
  260. errorMessage.clear();
  261. errorMessage.setVisible (! errorMessage.isEmpty());
  262. resultsTree.setRootItem (rootItem);
  263. }
  264. /** Parses the editors contects as XML. */
  265. TreeViewItem* rebuildXml()
  266. {
  267. parsedXml.reset();
  268. XmlDocument doc (codeDocument.getAllContent());
  269. parsedXml = doc.getDocumentElement();
  270. if (parsedXml == nullptr)
  271. {
  272. String error (doc.getLastParseError());
  273. if (error.isEmpty())
  274. error = "Unknown error";
  275. errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
  276. return nullptr;
  277. }
  278. return new XmlTreeItem (*parsedXml);
  279. }
  280. /** Parses the editors contects as JSON. */
  281. TreeViewItem* rebuildJson()
  282. {
  283. var parsedJson;
  284. Result result = JSON::parse (codeDocument.getAllContent(), parsedJson);
  285. if (! result.wasOk())
  286. {
  287. errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
  288. return nullptr;
  289. }
  290. return new JsonTreeItem (Identifier(), parsedJson);
  291. }
  292. /** Clears the editor and loads some default text. */
  293. void reset (Type type)
  294. {
  295. switch (type)
  296. {
  297. case xml: codeDocument.replaceAllContent (BinaryData::treedemo_xml); break;
  298. case json: codeDocument.replaceAllContent (BinaryData::juce_module_info); break;
  299. default: codeDocument.replaceAllContent (String()); break;
  300. }
  301. }
  302. void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
  303. void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
  304. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StringsDemo)
  305. };
  306. // This static object will register this demo type in a global list of demos..
  307. static JuceDemoType<StringsDemo> demo ("40 XML & JSON");