25bd5d8adb
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
401 lines
12 KiB
C++
401 lines
12 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE examples.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
The code included in this file is provided under the terms of the ISC license
|
|
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
|
To use, copy, modify, and/or distribute this software for any purpose with or
|
|
without fee is hereby granted provided that the above copyright notice and
|
|
this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
|
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
|
PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
/*******************************************************************************
|
|
The block below describes the properties of this PIP. A PIP is a short snippet
|
|
of code that can be read by the Projucer and used to generate a JUCE project.
|
|
|
|
BEGIN_JUCE_PIP_METADATA
|
|
|
|
name: XMLandJSONDemo
|
|
version: 1.0.0
|
|
vendor: JUCE
|
|
website: http://juce.com
|
|
description: Reads XML and JSON files.
|
|
|
|
dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
|
|
juce_gui_basics, juce_gui_extra
|
|
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
|
|
|
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
|
|
|
type: Component
|
|
mainClass: XMLandJSONDemo
|
|
|
|
useLocalCopy: 1
|
|
|
|
END_JUCE_PIP_METADATA
|
|
|
|
*******************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#include "../Assets/DemoUtilities.h"
|
|
|
|
//==============================================================================
|
|
class XmlTreeItem : public TreeViewItem
|
|
{
|
|
public:
|
|
XmlTreeItem (XmlElement& x) : xml (x) {}
|
|
|
|
String getUniqueName() const override
|
|
{
|
|
if (xml.getTagName().isEmpty())
|
|
return "unknown";
|
|
|
|
return xml.getTagName();
|
|
}
|
|
|
|
bool mightContainSubItems() override
|
|
{
|
|
return xml.getFirstChildElement() != nullptr;
|
|
}
|
|
|
|
void paintItem (Graphics& g, int width, int height) override
|
|
{
|
|
// if this item is selected, fill it with a background colour..
|
|
if (isSelected())
|
|
g.fillAll (Colours::blue.withAlpha (0.3f));
|
|
|
|
// use a "colour" attribute in the xml tag for this node to set the text colour..
|
|
g.setColour (Colour::fromString (xml.getStringAttribute ("colour", "ff000000")));
|
|
g.setFont ((float) height * 0.7f);
|
|
|
|
// draw the xml element's tag name..
|
|
g.drawText (xml.getTagName(),
|
|
4, 0, width - 4, height,
|
|
Justification::centredLeft, true);
|
|
}
|
|
|
|
void itemOpennessChanged (bool isNowOpen) override
|
|
{
|
|
if (isNowOpen)
|
|
{
|
|
// if we've not already done so, we'll now add the tree's sub-items. You could
|
|
// also choose to delete the existing ones and refresh them if that's more suitable
|
|
// in your app.
|
|
if (getNumSubItems() == 0)
|
|
{
|
|
// create and add sub-items to this node of the tree, corresponding to
|
|
// each sub-element in the XML..
|
|
|
|
for (auto* child : xml.getChildIterator())
|
|
if (child != nullptr)
|
|
addSubItem (new XmlTreeItem (*child));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in this case, we'll leave any sub-items in the tree when the node gets closed,
|
|
// though you could choose to delete them if that's more appropriate for
|
|
// your application.
|
|
}
|
|
}
|
|
|
|
private:
|
|
XmlElement& xml;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTreeItem)
|
|
};
|
|
|
|
//==============================================================================
|
|
class JsonTreeItem : public TreeViewItem
|
|
{
|
|
public:
|
|
JsonTreeItem (Identifier i, var value)
|
|
: identifier (i),
|
|
json (value)
|
|
{}
|
|
|
|
String getUniqueName() const override
|
|
{
|
|
return identifier.toString() + "_id";
|
|
}
|
|
|
|
bool mightContainSubItems() override
|
|
{
|
|
if (auto* obj = json.getDynamicObject())
|
|
return obj->getProperties().size() > 0;
|
|
|
|
return json.isArray();
|
|
}
|
|
|
|
void paintItem (Graphics& g, int width, int height) override
|
|
{
|
|
// if this item is selected, fill it with a background colour..
|
|
if (isSelected())
|
|
g.fillAll (Colours::blue.withAlpha (0.3f));
|
|
|
|
g.setColour (Colours::black);
|
|
g.setFont ((float) height * 0.7f);
|
|
|
|
// draw the element's tag name..
|
|
g.drawText (getText(),
|
|
4, 0, width - 4, height,
|
|
Justification::centredLeft, true);
|
|
}
|
|
|
|
void itemOpennessChanged (bool isNowOpen) override
|
|
{
|
|
if (isNowOpen)
|
|
{
|
|
// if we've not already done so, we'll now add the tree's sub-items. You could
|
|
// also choose to delete the existing ones and refresh them if that's more suitable
|
|
// in your app.
|
|
if (getNumSubItems() == 0)
|
|
{
|
|
// create and add sub-items to this node of the tree, corresponding to
|
|
// the type of object this var represents
|
|
|
|
if (json.isArray())
|
|
{
|
|
for (int i = 0; i < json.size(); ++i)
|
|
{
|
|
auto& child = json[i];
|
|
jassert (! child.isVoid());
|
|
addSubItem (new JsonTreeItem ({}, child));
|
|
}
|
|
}
|
|
else if (auto* obj = json.getDynamicObject())
|
|
{
|
|
auto& props = obj->getProperties();
|
|
|
|
for (int i = 0; i < props.size(); ++i)
|
|
{
|
|
auto id = props.getName (i);
|
|
|
|
auto child = props[id];
|
|
jassert (! child.isVoid());
|
|
|
|
addSubItem (new JsonTreeItem (id, child));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// in this case, we'll leave any sub-items in the tree when the node gets closed,
|
|
// though you could choose to delete them if that's more appropriate for
|
|
// your application.
|
|
}
|
|
}
|
|
|
|
private:
|
|
Identifier identifier;
|
|
var json;
|
|
|
|
/** Returns the text to display in the tree.
|
|
This is a little more complex for JSON than XML as nodes can be strings, objects or arrays.
|
|
*/
|
|
String getText() const
|
|
{
|
|
String text;
|
|
|
|
if (identifier.isValid())
|
|
text << identifier.toString();
|
|
|
|
if (! json.isVoid())
|
|
{
|
|
if (text.isNotEmpty() && (! json.isArray()))
|
|
text << ": ";
|
|
|
|
if (json.isObject() && (! identifier.isValid()))
|
|
text << "[Array]";
|
|
else if (! json.isArray())
|
|
text << json.toString();
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JsonTreeItem)
|
|
};
|
|
|
|
//==============================================================================
|
|
class XMLandJSONDemo : public Component,
|
|
private CodeDocument::Listener
|
|
{
|
|
public:
|
|
/** The type of database to parse. */
|
|
enum Type
|
|
{
|
|
xml,
|
|
json
|
|
};
|
|
|
|
XMLandJSONDemo()
|
|
{
|
|
setOpaque (true);
|
|
|
|
addAndMakeVisible (typeBox);
|
|
typeBox.addItem ("XML", 1);
|
|
typeBox.addItem ("JSON", 2);
|
|
|
|
typeBox.onChange = [this]
|
|
{
|
|
if (typeBox.getSelectedId() == 1)
|
|
reset (xml);
|
|
else
|
|
reset (json);
|
|
};
|
|
|
|
comboBoxLabel.attachToComponent (&typeBox, true);
|
|
|
|
addAndMakeVisible (codeDocumentComponent);
|
|
codeDocument.addListener (this);
|
|
|
|
resultsTree.setTitle ("Results");
|
|
addAndMakeVisible (resultsTree);
|
|
resultsTree.setColour (TreeView::backgroundColourId, Colours::white);
|
|
resultsTree.setDefaultOpenness (true);
|
|
|
|
addAndMakeVisible (errorMessage);
|
|
errorMessage.setReadOnly (true);
|
|
errorMessage.setMultiLine (true);
|
|
errorMessage.setCaretVisible (false);
|
|
errorMessage.setColour (TextEditor::outlineColourId, Colours::transparentWhite);
|
|
errorMessage.setColour (TextEditor::shadowColourId, Colours::transparentWhite);
|
|
|
|
typeBox.setSelectedId (1);
|
|
|
|
setSize (500, 500);
|
|
}
|
|
|
|
~XMLandJSONDemo() override
|
|
{
|
|
resultsTree.setRootItem (nullptr);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
auto area = getLocalBounds();
|
|
|
|
typeBox.setBounds (area.removeFromTop (36).removeFromRight (150).reduced (8));
|
|
codeDocumentComponent.setBounds (area.removeFromTop(area.getHeight() / 2).reduced (8));
|
|
resultsTree .setBounds (area.reduced (8));
|
|
errorMessage .setBounds (resultsTree.getBounds());
|
|
}
|
|
|
|
private:
|
|
ComboBox typeBox;
|
|
Label comboBoxLabel { {}, "Database Type:" };
|
|
CodeDocument codeDocument;
|
|
CodeEditorComponent codeDocumentComponent { codeDocument, nullptr };
|
|
TreeView resultsTree;
|
|
|
|
std::unique_ptr<TreeViewItem> rootItem;
|
|
std::unique_ptr<XmlElement> parsedXml;
|
|
TextEditor errorMessage;
|
|
|
|
void rebuildTree()
|
|
{
|
|
std::unique_ptr<XmlElement> openness;
|
|
|
|
if (rootItem.get() != nullptr)
|
|
openness = rootItem->getOpennessState();
|
|
|
|
createNewRootNode();
|
|
|
|
if (openness.get() != nullptr && rootItem.get() != nullptr)
|
|
rootItem->restoreOpennessState (*openness);
|
|
}
|
|
|
|
void createNewRootNode()
|
|
{
|
|
// clear the current tree
|
|
resultsTree.setRootItem (nullptr);
|
|
rootItem.reset();
|
|
|
|
// try and parse the editor's contents
|
|
switch (typeBox.getSelectedItemIndex())
|
|
{
|
|
case xml: rootItem.reset (rebuildXml()); break;
|
|
case json: rootItem.reset (rebuildJson()); break;
|
|
default: rootItem.reset(); break;
|
|
}
|
|
|
|
// if we have a valid TreeViewItem hide any old error messages and set our TreeView to use it
|
|
if (rootItem.get() != nullptr)
|
|
errorMessage.clear();
|
|
|
|
errorMessage.setVisible (! errorMessage.isEmpty());
|
|
|
|
resultsTree.setRootItem (rootItem.get());
|
|
}
|
|
|
|
/** Parses the editor's contents as XML. */
|
|
TreeViewItem* rebuildXml()
|
|
{
|
|
parsedXml.reset();
|
|
|
|
XmlDocument doc (codeDocument.getAllContent());
|
|
parsedXml = doc.getDocumentElement();
|
|
|
|
if (parsedXml.get() == nullptr)
|
|
{
|
|
auto error = doc.getLastParseError();
|
|
|
|
if (error.isEmpty())
|
|
error = "Unknown error";
|
|
|
|
errorMessage.setText ("Error parsing XML: " + error, dontSendNotification);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
return new XmlTreeItem (*parsedXml);
|
|
}
|
|
|
|
/** Parses the editor's contents as JSON. */
|
|
TreeViewItem* rebuildJson()
|
|
{
|
|
var parsedJson;
|
|
auto result = JSON::parse (codeDocument.getAllContent(), parsedJson);
|
|
|
|
if (! result.wasOk())
|
|
{
|
|
errorMessage.setText ("Error parsing JSON: " + result.getErrorMessage());
|
|
return nullptr;
|
|
}
|
|
|
|
return new JsonTreeItem (Identifier(), parsedJson);
|
|
}
|
|
|
|
/** Clears the editor and loads some default text. */
|
|
void reset (Type type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case xml: codeDocument.replaceAllContent (loadEntireAssetIntoString ("treedemo.xml")); break;
|
|
case json: codeDocument.replaceAllContent (loadEntireAssetIntoString ("juce_module_info")); break;
|
|
default: codeDocument.replaceAllContent ({}); break;
|
|
}
|
|
}
|
|
|
|
void codeDocumentTextInserted (const String&, int) override { rebuildTree(); }
|
|
void codeDocumentTextDeleted (int, int) override { rebuildTree(); }
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XMLandJSONDemo)
|
|
};
|