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:             ValueTreesDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Showcases value tree features.
31 
32  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
33                    juce_gui_basics
34  exporters:        xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
35 
36  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
37 
38  type:             Component
39  mainClass:        ValueTreesDemo
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 ValueTreeItem  : public TreeViewItem,
53                        private ValueTree::Listener
54 {
55 public:
ValueTreeItem(const ValueTree & v,UndoManager & um)56     ValueTreeItem (const ValueTree& v, UndoManager& um)
57         : tree (v), undoManager (um)
58     {
59         tree.addListener (this);
60     }
61 
getUniqueName()62     String getUniqueName() const override
63     {
64         return tree["name"].toString();
65     }
66 
mightContainSubItems()67     bool mightContainSubItems() override
68     {
69         return tree.getNumChildren() > 0;
70     }
71 
paintItem(Graphics & g,int width,int height)72     void paintItem (Graphics& g, int width, int height) override
73     {
74         if (isSelected())
75             g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::highlightedFill,
76                                                Colours::teal));
77 
78         g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
79                                              Colours::black));
80         g.setFont (15.0f);
81 
82         g.drawText (tree["name"].toString(),
83                     4, 0, width - 4, height,
84                     Justification::centredLeft, true);
85     }
86 
itemOpennessChanged(bool isNowOpen)87     void itemOpennessChanged (bool isNowOpen) override
88     {
89         if (isNowOpen && getNumSubItems() == 0)
90             refreshSubItems();
91         else
92             clearSubItems();
93     }
94 
getDragSourceDescription()95     var getDragSourceDescription() override
96     {
97         return "Drag Demo";
98     }
99 
isInterestedInDragSource(const DragAndDropTarget::SourceDetails & dragSourceDetails)100     bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
101     {
102         return dragSourceDetails.description == "Drag Demo";
103     }
104 
itemDropped(const DragAndDropTarget::SourceDetails &,int insertIndex)105     void itemDropped (const DragAndDropTarget::SourceDetails&, int insertIndex) override
106     {
107         OwnedArray<ValueTree> selectedTrees;
108         getSelectedTreeViewItems (*getOwnerView(), selectedTrees);
109 
110         moveItems (*getOwnerView(), selectedTrees, tree, insertIndex, undoManager);
111     }
112 
moveItems(TreeView & treeView,const OwnedArray<ValueTree> & items,ValueTree newParent,int insertIndex,UndoManager & undoManager)113     static void moveItems (TreeView& treeView, const OwnedArray<ValueTree>& items,
114                            ValueTree newParent, int insertIndex, UndoManager& undoManager)
115     {
116         if (items.size() > 0)
117         {
118             std::unique_ptr<XmlElement> oldOpenness (treeView.getOpennessState (false));
119 
120             for (auto* v : items)
121             {
122                 if (v->getParent().isValid() && newParent != *v && ! newParent.isAChildOf (*v))
123                 {
124                     if (v->getParent() == newParent && newParent.indexOf (*v) < insertIndex)
125                         --insertIndex;
126 
127                     v->getParent().removeChild (*v, &undoManager);
128                     newParent.addChild (*v, insertIndex, &undoManager);
129                 }
130             }
131 
132             if (oldOpenness.get() != nullptr)
133                 treeView.restoreOpennessState (*oldOpenness, false);
134         }
135     }
136 
getSelectedTreeViewItems(TreeView & treeView,OwnedArray<ValueTree> & items)137     static void getSelectedTreeViewItems (TreeView& treeView, OwnedArray<ValueTree>& items)
138     {
139         auto numSelected = treeView.getNumSelectedItems();
140 
141         for (int i = 0; i < numSelected; ++i)
142             if (auto* vti = dynamic_cast<ValueTreeItem*> (treeView.getSelectedItem (i)))
143                 items.add (new ValueTree (vti->tree));
144     }
145 
146 private:
147     ValueTree tree;
148     UndoManager& undoManager;
149 
refreshSubItems()150     void refreshSubItems()
151     {
152         clearSubItems();
153 
154         for (int i = 0; i < tree.getNumChildren(); ++i)
155             addSubItem (new ValueTreeItem (tree.getChild (i), undoManager));
156     }
157 
valueTreePropertyChanged(ValueTree &,const Identifier &)158     void valueTreePropertyChanged (ValueTree&, const Identifier&) override
159     {
160         repaintItem();
161     }
162 
valueTreeChildAdded(ValueTree & parentTree,ValueTree &)163     void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override         { treeChildrenChanged (parentTree); }
valueTreeChildRemoved(ValueTree & parentTree,ValueTree &,int)164     void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override  { treeChildrenChanged (parentTree); }
valueTreeChildOrderChanged(ValueTree & parentTree,int,int)165     void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override    { treeChildrenChanged (parentTree); }
valueTreeParentChanged(ValueTree &)166     void valueTreeParentChanged (ValueTree&) override {}
167 
treeChildrenChanged(const ValueTree & parentTree)168     void treeChildrenChanged (const ValueTree& parentTree)
169     {
170         if (parentTree == tree)
171         {
172             refreshSubItems();
173             treeHasChanged();
174             setOpen (true);
175         }
176     }
177 
178     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeItem)
179 };
180 
181 //==============================================================================
182 class ValueTreesDemo   : public Component,
183                          public DragAndDropContainer,
184                          private Timer
185 {
186 public:
ValueTreesDemo()187     ValueTreesDemo()
188     {
189         addAndMakeVisible (tree);
190 
191         tree.setDefaultOpenness (true);
192         tree.setMultiSelectEnabled (true);
193         rootItem.reset (new ValueTreeItem (createRootValueTree(), undoManager));
194         tree.setRootItem (rootItem.get());
195 
196         addAndMakeVisible (undoButton);
197         addAndMakeVisible (redoButton);
198         undoButton.onClick = [this] { undoManager.undo(); };
199         redoButton.onClick = [this] { undoManager.redo(); };
200 
201         startTimer (500);
202 
203         setSize (500, 500);
204     }
205 
~ValueTreesDemo()206     ~ValueTreesDemo() override
207     {
208         tree.setRootItem (nullptr);
209     }
210 
paint(Graphics & g)211     void paint (Graphics& g) override
212     {
213         g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
214     }
215 
resized()216     void resized() override
217     {
218         auto r = getLocalBounds().reduced (8);
219 
220         auto buttons = r.removeFromBottom (22);
221         undoButton.setBounds (buttons.removeFromLeft (100));
222         buttons.removeFromLeft (6);
223         redoButton.setBounds (buttons.removeFromLeft (100));
224 
225         r.removeFromBottom (4);
226         tree.setBounds (r);
227     }
228 
createTree(const String & desc)229     static ValueTree createTree (const String& desc)
230     {
231         ValueTree t ("Item");
232         t.setProperty ("name", desc, nullptr);
233         return t;
234     }
235 
createRootValueTree()236     static ValueTree createRootValueTree()
237     {
238         auto vt = createTree ("This demo displays a ValueTree as a treeview.");
239         vt.appendChild (createTree ("You can drag around the nodes to rearrange them"),               nullptr);
240         vt.appendChild (createTree ("..and press 'delete' or 'backspace' to delete them"),            nullptr);
241         vt.appendChild (createTree ("Then, you can use the undo/redo buttons to undo these changes"), nullptr);
242 
243         int n = 1;
244         vt.appendChild (createRandomTree (n, 0), nullptr);
245 
246         return vt;
247     }
248 
createRandomTree(int & counter,int depth)249     static ValueTree createRandomTree (int& counter, int depth)
250     {
251         auto t = createTree ("Item " + String (counter++));
252 
253         if (depth < 3)
254             for (int i = 1 + Random::getSystemRandom().nextInt (7); --i >= 0;)
255                 t.appendChild (createRandomTree (counter, depth + 1), nullptr);
256 
257         return t;
258     }
259 
deleteSelectedItems()260     void deleteSelectedItems()
261     {
262         OwnedArray<ValueTree> selectedItems;
263         ValueTreeItem::getSelectedTreeViewItems (tree, selectedItems);
264 
265         for (auto* v : selectedItems)
266         {
267             if (v->getParent().isValid())
268                 v->getParent().removeChild (*v, &undoManager);
269         }
270     }
271 
keyPressed(const KeyPress & key)272     bool keyPressed (const KeyPress& key) override
273     {
274         if (key == KeyPress::deleteKey || key == KeyPress::backspaceKey)
275         {
276             deleteSelectedItems();
277             return true;
278         }
279 
280         if (key == KeyPress ('z', ModifierKeys::commandModifier, 0))
281         {
282             undoManager.undo();
283             return true;
284         }
285 
286         if (key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0))
287         {
288             undoManager.redo();
289             return true;
290         }
291 
292         return Component::keyPressed (key);
293     }
294 
295 private:
296     TreeView tree;
297     TextButton undoButton  { "Undo" },
298                redoButton  { "Redo" };
299 
300     std::unique_ptr<ValueTreeItem> rootItem;
301     UndoManager undoManager;
302 
timerCallback()303     void timerCallback() override
304     {
305         undoManager.beginNewTransaction();
306     }
307 
308     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreesDemo)
309 };
310