1 /* 2 ============================================================================== 3 4 This file is part of the JUCE examples. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 The code included in this file is provided under the terms of the ISC license 8 http://www.isc.org/downloads/software-support-policy/isc-license. Permission 9 To use, copy, modify, and/or distribute this software for any purpose with or 10 without fee is hereby granted provided that the above copyright notice and 11 this permission notice appear in all copies. 12 13 THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 14 WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 15 PURPOSE, ARE DISCLAIMED. 16 17 ============================================================================== 18 */ 19 20 /******************************************************************************* 21 The block below describes the properties of this PIP. A PIP is a short snippet 22 of code that can be read by the Projucer and used to generate a JUCE project. 23 24 BEGIN_JUCE_PIP_METADATA 25 26 name: XMLandJSONDemo 27 version: 1.0.0 28 vendor: JUCE 29 website: http://juce.com 30 description: Reads XML and JSON files. 31 32 dependencies: juce_core, juce_data_structures, juce_events, juce_graphics, 33 juce_gui_basics, juce_gui_extra 34 exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone 35 36 moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 37 38 type: Component 39 mainClass: XMLandJSONDemo 40 41 useLocalCopy: 1 42 43 END_JUCE_PIP_METADATA 44 45 *******************************************************************************/ 46 47 #pragma once 48 49 #include "../Assets/DemoUtilities.h" 50 51 //============================================================================== 52 class XmlTreeItem : public TreeViewItem 53 { 54 public: XmlTreeItem(XmlElement & x)55 XmlTreeItem (XmlElement& x) : xml (x) {} 56 getUniqueName()57 String getUniqueName() const override 58 { 59 if (xml.getTagName().isEmpty()) 60 return "unknown"; 61 62 return xml.getTagName(); 63 } 64 mightContainSubItems()65 bool mightContainSubItems() override 66 { 67 return xml.getFirstChildElement() != nullptr; 68 } 69 paintItem(Graphics & g,int width,int height)70 void paintItem (Graphics& g, int width, int height) override 71 { 72 // if this item is selected, fill it with a background colour.. 73 if (isSelected()) 74 g.fillAll (Colours::blue.withAlpha (0.3f)); 75 76 // use a "colour" attribute in the xml tag for this node to set the text colour.. 77 g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000"))); 78 g.setFont ((float) height * 0.7f); 79 80 // draw the xml element's tag name.. 81 g.drawText (xml.getTagName(), 82 4, 0, width - 4, height, 83 Justification::centredLeft, true); 84 } 85 itemOpennessChanged(bool isNowOpen)86 void itemOpennessChanged (bool isNowOpen) override 87 { 88 if (isNowOpen) 89 { 90 // if we've not already done so, we'll now add the tree's sub-items. You could 91 // also choose to delete the existing ones and refresh them if that's more suitable 92 // in your app. 93 if (getNumSubItems() == 0) 94 { 95 // create and add sub-items to this node of the tree, corresponding to 96 // each sub-element in the XML.. 97 98 for (auto* child : xml.getChildIterator()) 99 { 100 jassert (child != nullptr); 101 addSubItem (new XmlTreeItem (*child)); 102 } 103 } 104 } 105 else 106 { 107 // in this case, we'll leave any sub-items in the tree when the node gets closed, 108 // though you could choose to delete them if that's more appropriate for 109 // your application. 110 } 111 } 112 113 private: 114 XmlElement& xml; 115 116 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem) 117 }; 118 119 //============================================================================== 120 class JsonTreeItem : public TreeViewItem 121 { 122 public: JsonTreeItem(Identifier i,var value)123 JsonTreeItem (Identifier i, var value) 124 : identifier (i), 125 json (value) 126 {} 127 getUniqueName()128 String getUniqueName() const override 129 { 130 return identifier.toString() + "_id"; 131 } 132 mightContainSubItems()133 bool mightContainSubItems() override 134 { 135 if (auto* obj = json.getDynamicObject()) 136 return obj->getProperties().size() > 0; 137 138 return json.isArray(); 139 } 140 paintItem(Graphics & g,int width,int height)141 void paintItem (Graphics& g, int width, int height) override 142 { 143 // if this item is selected, fill it with a background colour.. 144 if (isSelected()) 145 g.fillAll (Colours::blue.withAlpha (0.3f)); 146 147 g.setColour (Colours::black); 148 g.setFont ((float) height * 0.7f); 149 150 // draw the element's tag name.. 151 g.drawText (getText(), 152 4, 0, width - 4, height, 153 Justification::centredLeft, true); 154 } 155 itemOpennessChanged(bool isNowOpen)156 void itemOpennessChanged (bool isNowOpen) override 157 { 158 if (isNowOpen) 159 { 160 // if we've not already done so, we'll now add the tree's sub-items. You could 161 // also choose to delete the existing ones and refresh them if that's more suitable 162 // in your app. 163 if (getNumSubItems() == 0) 164 { 165 // create and add sub-items to this node of the tree, corresponding to 166 // the type of object this var represents 167 168 if (json.isArray()) 169 { 170 for (int i = 0; i < json.size(); ++i) 171 { 172 auto& child = json[i]; 173 jassert (! child.isVoid()); 174 addSubItem (new JsonTreeItem ({}, child)); 175 } 176 } 177 else if (auto* obj = json.getDynamicObject()) 178 { 179 auto& props = obj->getProperties(); 180 181 for (int i = 0; i < props.size(); ++i) 182 { 183 auto id = props.getName (i); 184 185 auto child = props[id]; 186 jassert (! child.isVoid()); 187 188 addSubItem (new JsonTreeItem (id, child)); 189 } 190 } 191 } 192 } 193 else 194 { 195 // in this case, we'll leave any sub-items in the tree when the node gets closed, 196 // though you could choose to delete them if that's more appropriate for 197 // your application. 198 } 199 } 200 201 private: 202 Identifier identifier; 203 var json; 204 205 /** Returns the text to display in the tree. 206 This is a little more complex for JSON than XML as nodes can be strings, objects or arrays. 207 */ getText()208 String getText() const 209 { 210 String text; 211 212 if (identifier.isValid()) 213 text << identifier.toString(); 214 215 if (! json.isVoid()) 216 { 217 if (text.isNotEmpty() && (! json.isArray())) 218 text << ": "; 219 220 if (json.isObject() && (! identifier.isValid())) 221 text << "[Array]"; 222 else if (! json.isArray()) 223 text << json.toString(); 224 } 225 226 return text; 227 } 228 229 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem) 230 }; 231 232 //============================================================================== 233 class XMLandJSONDemo : public Component, 234 private CodeDocument::Listener 235 { 236 public: 237 /** The type of database to parse. */ 238 enum Type 239 { 240 xml, 241 json 242 }; 243 XMLandJSONDemo()244 XMLandJSONDemo() 245 { 246 setOpaque (true); 247 248 addAndMakeVisible (typeBox); 249 typeBox.addItem ("XML", 1); 250 typeBox.addItem ("JSON", 2); 251 252 typeBox.onChange = [this] 253 { 254 if (typeBox.getSelectedId() == 1) 255 reset (xml); 256 else 257 reset (json); 258 }; 259 260 comboBoxLabel.attachToComponent (&typeBox, true); 261 262 addAndMakeVisible (codeDocumentComponent); 263 codeDocument.addListener (this); 264 265 addAndMakeVisible (resultsTree); 266 resultsTree.setColour (TreeView::backgroundColourId, Colours::white); 267 resultsTree.setDefaultOpenness (true); 268 269 addAndMakeVisible (errorMessage); 270 errorMessage.setReadOnly (true); 271 errorMessage.setMultiLine (true); 272 errorMessage.setCaretVisible (false); 273 errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite); 274 errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite); 275 276 typeBox.setSelectedId (1); 277 278 setSize (500, 500); 279 } 280 ~XMLandJSONDemo()281 ~XMLandJSONDemo() override 282 { 283 resultsTree.setRootItem (nullptr); 284 } 285 paint(Graphics & g)286 void paint (Graphics& g) override 287 { 288 g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground)); 289 } 290 resized()291 void resized() override 292 { 293 auto area = getLocalBounds(); 294 295 typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8)); 296 codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8)); 297 resultsTree .setBounds (area.reduced (8)); 298 errorMessage .setBounds (resultsTree.getBounds()); 299 } 300 301 private: 302 ComboBox typeBox; 303 Label comboBoxLabel { {}, "Database Type:" }; 304 CodeDocument codeDocument; 305 CodeEditorComponent codeDocumentComponent { codeDocument, nullptr }; 306 TreeView resultsTree; 307 308 std::unique_ptr<TreeViewItem> rootItem; 309 std::unique_ptr<XmlElement> parsedXml; 310 TextEditor errorMessage; 311 rebuildTree()312 void rebuildTree() 313 { 314 std::unique_ptr<XmlElement> openness; 315 316 if (rootItem.get() != nullptr) 317 openness = rootItem->getOpennessState(); 318 319 createNewRootNode(); 320 321 if (openness.get() != nullptr && rootItem.get() != nullptr) 322 rootItem->restoreOpennessState (*openness); 323 } 324 createNewRootNode()325 void createNewRootNode() 326 { 327 // clear the current tree 328 resultsTree.setRootItem (nullptr); 329 rootItem.reset(); 330 331 // try and parse the editor's contents 332 switch (typeBox.getSelectedItemIndex()) 333 { 334 case xml: rootItem.reset (rebuildXml()); break; 335 case json: rootItem.reset (rebuildJson()); break; 336 default: rootItem.reset(); break; 337 } 338 339 // if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it 340 if (rootItem.get() != nullptr) 341 errorMessage.clear(); 342 343 errorMessage.setVisible (! errorMessage.isEmpty()); 344 345 resultsTree.setRootItem (rootItem.get()); 346 } 347 348 /** Parses the editor's contents as XML. */ rebuildXml()349 TreeViewItem* rebuildXml() 350 { 351 parsedXml.reset(); 352 353 XmlDocument doc (codeDocument.getAllContent()); 354 parsedXml = doc.getDocumentElement(); 355 356 if (parsedXml.get() == nullptr) 357 { 358 auto error = doc.getLastParseError(); 359 360 if (error.isEmpty()) 361 error = "Unknown error"; 362 363 errorMessage.setText ("Error parsing XML: " + error, dontSendNotification); 364 365 return nullptr; 366 } 367 368 return new XmlTreeItem (*parsedXml); 369 } 370 371 /** Parses the editor's contents as JSON. */ rebuildJson()372 TreeViewItem* rebuildJson() 373 { 374 var parsedJson; 375 auto result = JSON::parse (codeDocument.getAllContent(), parsedJson); 376 377 if (! result.wasOk()) 378 { 379 errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage()); 380 return nullptr; 381 } 382 383 return new JsonTreeItem (Identifier(), parsedJson); 384 } 385 386 /** Clears the editor and loads some default text. */ reset(Type type)387 void reset (Type type) 388 { 389 switch (type) 390 { 391 case xml: codeDocument.replaceAllContent (loadEntireAssetIntoString ("treedemo.xml")); break; 392 case json: codeDocument.replaceAllContent (loadEntireAssetIntoString ("juce_module_info")); break; 393 default: codeDocument.replaceAllContent ({}); break; 394 } 395 } 396 codeDocumentTextInserted(const String &,int)397 void codeDocumentTextInserted (const String&, int) override { rebuildTree(); } codeDocumentTextDeleted(int,int)398 void codeDocumentTextDeleted (int, int) override { rebuildTree(); } 399 400 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XMLandJSONDemo) 401 }; 402