text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR

namespace LiveConstantEditor
{

//==============================================================================
class AllComponentRepainter  : private Timer,
                               private DeletedAtShutdown
{
public:
    AllComponentRepainter()  {}
    ~AllComponentRepainter() { clearSingletonInstance(); }

    juce_DeclareSingleton (AllComponentRepainter, false)

    void trigger()
    {
        if (! isTimerRunning())
            startTimer (100);
    }

private:
    void timerCallback() override
    {
        stopTimer();

        Array<Component*> alreadyDone;

        for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
            if (auto* c = TopLevelWindow::getTopLevelWindow(i))
                repaintAndResizeAllComps (c, alreadyDone);

        auto& desktop = Desktop::getInstance();

        for (int i = desktop.getNumComponents(); --i >= 0;)
            if (auto* c = desktop.getComponent(i))
                repaintAndResizeAllComps (c, alreadyDone);
    }

    static void repaintAndResizeAllComps (Component::SafePointer<Component> c,
                                          Array<Component*>& alreadyDone)
    {
        if (c->isVisible() && ! alreadyDone.contains (c))
        {
            c->repaint();
            c->resized();

            for (int i = c->getNumChildComponents(); --i >= 0;)
            {
                if (auto* child = c->getChildComponent(i))
                {
                    repaintAndResizeAllComps (child, alreadyDone);
                    alreadyDone.add (child);
                }

                if (c == nullptr)
                    break;
            }
        }
    }
};

juce_ImplementSingleton (AllComponentRepainter)
juce_ImplementSingleton (ValueList)

//==============================================================================
int64 parseInt (String s)
{
    s = s.trimStart();

    if (s.startsWithChar ('-'))
        return -parseInt (s.substring (1));

    if (s.startsWith ("0x"))
        return s.substring(2).getHexValue64();

    return s.getLargeIntValue();
}

double parseDouble (const String& s)
{
    return s.retainCharacters ("0123456789.eE-").getDoubleValue();
}

String intToString (int   v, bool preferHex)    { return preferHex ? "0x" + String::toHexString (v) : String (v); }
String intToString (int64 v, bool preferHex)    { return preferHex ? "0x" + String::toHexString (v) : String (v); }

//==============================================================================
LiveValueBase::LiveValueBase (const char* file, int line)
    : sourceFile (file), sourceLine (line)
{
    name = File (sourceFile).getFileName() + " : " + String (sourceLine);
}

LiveValueBase::~LiveValueBase()
{
}

//==============================================================================
LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d)
    : value (v), document (d), sourceEditor (document, &tokeniser)
{
    setSize (600, 100);

    addAndMakeVisible (name);
    addAndMakeVisible (resetButton);
    addAndMakeVisible (valueEditor);
    addAndMakeVisible (sourceEditor);

    findOriginalValueInCode();
    selectOriginalValue();

    name.setFont (13.0f);
    name.setText (v.name, dontSendNotification);
    valueEditor.setMultiLine (v.isString());
    valueEditor.setReturnKeyStartsNewLine (v.isString());
    valueEditor.setText (v.getStringValue (wasHex), dontSendNotification);
    valueEditor.addListener (this);
    sourceEditor.setReadOnly (true);
    sourceEditor.setFont (sourceEditor.getFont().withHeight (13.0f));
    resetButton.addListener (this);
}

void LivePropertyEditorBase::paint (Graphics& g)
{
    g.setColour (Colours::white);
    g.fillRect (getLocalBounds().removeFromBottom (1));
}

void LivePropertyEditorBase::resized()
{
    auto r = getLocalBounds().reduced (0, 3).withTrimmedBottom (1);

    auto left = r.removeFromLeft (jmax (200, r.getWidth() / 3));

    auto top = left.removeFromTop (25);
    resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));
    name.setBounds (top);

    if (customComp != nullptr)
    {
        valueEditor.setBounds (left.removeFromTop (25));
        left.removeFromTop (2);
        customComp->setBounds (left);
    }
    else
    {
        valueEditor.setBounds (left);
    }

    r.removeFromLeft (4);
    sourceEditor.setBounds (r);
}

void LivePropertyEditorBase::textEditorTextChanged (TextEditor&)
{
    applyNewValue (valueEditor.getText());
}

void LivePropertyEditorBase::buttonClicked (Button*)
{
    applyNewValue (value.getOriginalStringValue (wasHex));
}

void LivePropertyEditorBase::applyNewValue (const String& s)
{
    value.setStringValue (s);

    document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));
    document.clearUndoHistory();
    selectOriginalValue();

    valueEditor.setText (s, dontSendNotification);
    AllComponentRepainter::getInstance()->trigger();
}

void LivePropertyEditorBase::selectOriginalValue()
{
    sourceEditor.selectRegion (valueStart, valueEnd);
}

void LivePropertyEditorBase::findOriginalValueInCode()
{
    CodeDocument::Position pos (document, value.sourceLine, 0);
    auto line = pos.getLineText();
    auto p = line.getCharPointer();

    p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));

    if (p.isEmpty())
    {
        // Not sure how this would happen - some kind of mix-up between source code and line numbers..
        jassertfalse;
        return;
    }

    p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);
    p = p.findEndOfWhitespace();

    if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())
    {
        // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!
        // They're identified by their line number, so you must make sure each
        // one goes on a separate line!
        jassertfalse;
    }

    if (p.getAndAdvance() == '(')
    {
        auto start = p, end = p;

        int depth = 1;

        while (! end.isEmpty())
        {
            auto c = end.getAndAdvance();

            if (c == '(')  ++depth;
            if (c == ')')  --depth;

            if (depth == 0)
            {
                --end;
                break;
            }
        }

        if (end > start)
        {
            valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));
            valueEnd   = CodeDocument::Position (document, value.sourceLine, (int) (end   - line.getCharPointer()));

            valueStart.setPositionMaintained (true);
            valueEnd.setPositionMaintained (true);

            wasHex = String (start, end).containsIgnoreCase ("0x");
        }
    }
}

//==============================================================================
class ValueListHolderComponent  : public Component
{
public:
    ValueListHolderComponent (ValueList& l) : valueList (l)
    {
        setVisible (true);
    }

    void addItem (int width, LiveValueBase& v, CodeDocument& doc)
    {
        addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));
        layout (width);
    }

    void layout (int width)
    {
        setSize (width, editors.size() * itemHeight);
        resized();
    }

    void resized() override
    {
        Rectangle<int> r (getLocalBounds().reduced (2, 0));

        for (int i = 0; i < editors.size(); ++i)
            editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight));
    }

    enum { itemHeight = 120 };

    ValueList& valueList;
    OwnedArray<LivePropertyEditorBase> editors;
};

//==============================================================================
class ValueList::EditorWindow  : public DocumentWindow,
                                 private DeletedAtShutdown
{
public:
    EditorWindow (ValueList& list)
        : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)
    {
        setLookAndFeel (&lookAndFeel);
        setUsingNativeTitleBar (true);

        viewport.setViewedComponent (new ValueListHolderComponent (list), true);
        viewport.setSize (700, 600);
        viewport.setScrollBarsShown (true, false);

        setContentNonOwned (&viewport, true);
        setResizable (true, false);
        setResizeLimits (500, 400, 10000, 10000);
        centreWithSize (getWidth(), getHeight());
        setVisible (true);
    }

    void closeButtonPressed() override
    {
        setVisible (false);
    }

    void updateItems (ValueList& list)
    {
        if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
        {
            while (l->getNumChildComponents() < list.values.size())
            {
                if (auto* v = list.values [l->getNumChildComponents()])
                    l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));
                else
                    break;
            }

            setVisible (true);
        }
    }

    void resized() override
    {
        DocumentWindow::resized();

        if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
            l->layout (viewport.getMaximumVisibleWidth());
    }

    Viewport viewport;
    LookAndFeel_V3 lookAndFeel;
};

//==============================================================================
ValueList::ValueList()  {}
ValueList::~ValueList() { clearSingletonInstance(); }

void ValueList::addValue (LiveValueBase* v)
{
    values.add (v);
    triggerAsyncUpdate();
}

void ValueList::handleAsyncUpdate()
{
    if (editorWindow == nullptr)
        editorWindow = new EditorWindow (*this);

    editorWindow->updateItems (*this);
}

CodeDocument& ValueList::getDocument (const File& file)
{
    const int index = documentFiles.indexOf (file.getFullPathName());

    if (index >= 0)
        return *documents.getUnchecked (index);

    auto* doc = documents.add (new CodeDocument());
    documentFiles.add (file);
    doc->replaceAllContent (file.loadFileAsString());
    doc->clearUndoHistory();
    return *doc;
}

//==============================================================================
struct ColourEditorComp  : public Component,
                           private ChangeListener
{
    ColourEditorComp (LivePropertyEditorBase& e)  : editor (e)
    {
        setMouseCursor (MouseCursor::PointingHandCursor);
    }

    Colour getColour() const
    {
        return Colour ((uint32) parseInt (editor.value.getStringValue (false)));
    }

    void paint (Graphics& g) override
    {
        g.fillCheckerBoard (getLocalBounds(), 6, 6,
                            Colour (0xffdddddd).overlaidWith (getColour()),
                            Colour (0xffffffff).overlaidWith (getColour()));
    }

    void mouseDown (const MouseEvent&) override
    {
        auto* colourSelector = new ColourSelector();
        colourSelector->setName ("Colour");
        colourSelector->setCurrentColour (getColour());
        colourSelector->addChangeListener (this);
        colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
        colourSelector->setSize (300, 400);

        CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr);
    }

    void changeListenerCallback (ChangeBroadcaster* source) override
    {
        if (auto* cs = dynamic_cast<ColourSelector*> (source))
            editor.applyNewValue (getAsString (cs->getCurrentColour(), true));

        repaint();
    }

    LivePropertyEditorBase& editor;
};

Component* createColourEditor (LivePropertyEditorBase& editor)
{
    return new ColourEditorComp (editor);
}

//==============================================================================
struct SliderComp   : public Component,
                      private Slider::Listener
{
    SliderComp (LivePropertyEditorBase& e, bool useFloat)
        : editor (e), isFloat (useFloat)
    {
        slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
        addAndMakeVisible (slider);
        updateRange();
        slider.addListener (this);
    }

    virtual void updateRange()
    {
        double v = isFloat ? parseDouble (editor.value.getStringValue (false))
                           : (double) parseInt (editor.value.getStringValue (false));

        double range = isFloat ? 10 : 100;

        slider.setRange (v - range, v + range);
        slider.setValue (v, dontSendNotification);
    }

    void sliderValueChanged (Slider*) override
    {
        editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)
                                      : getAsString ((int64)  slider.getValue(), editor.wasHex));

    }

    void sliderDragStarted (Slider*) override  {}
    void sliderDragEnded (Slider*) override    { updateRange(); }

    void resized() override
    {
        slider.setBounds (getLocalBounds().removeFromTop (25));
    }

    LivePropertyEditorBase& editor;
    Slider slider;
    bool isFloat;
};

//==============================================================================
struct BoolSliderComp  : public SliderComp
{
    BoolSliderComp (LivePropertyEditorBase& e) : SliderComp (e, false) {}

    void updateRange() override
    {
        slider.setRange (0.0, 1.0, dontSendNotification);
        slider.setValue (editor.value.getStringValue (false) == "true", dontSendNotification);
    }

    void sliderValueChanged (Slider*) override  { editor.applyNewValue (slider.getValue() > 0.5 ? "true" : "false"); }
};

Component* createIntegerSlider (LivePropertyEditorBase& editor)  { return new SliderComp (editor, false); }
Component* createFloatSlider   (LivePropertyEditorBase& editor)  { return new SliderComp (editor, true);  }
Component* createBoolSlider    (LivePropertyEditorBase& editor)  { return new BoolSliderComp (editor); }

}

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

class KeyMappingEditorComponent::ChangeKeyButton  : public Button
{
public:
    ChangeKeyButton (KeyMappingEditorComponent& kec, const CommandID command,
                     const String& keyName, const int keyIndex)
        : Button (keyName),
          owner (kec),
          commandID (command),
          keyNum (keyIndex)
    {
        setWantsKeyboardFocus (false);
        setTriggeredOnMouseDown (keyNum >= 0);

        setTooltip (keyIndex < 0 ? TRANS("Adds a new key-mapping")
                                 : TRANS("Click to change this key-mapping"));
    }

    void paintButton (Graphics& g, bool /*isOver*/, bool /*isDown*/) override
    {
        getLookAndFeel().drawKeymapChangeButton (g, getWidth(), getHeight(), *this,
                                                 keyNum >= 0 ? getName() : String());
    }

    static void menuCallback (int result, ChangeKeyButton* button)
    {
        if (button != nullptr)
        {
            switch (result)
            {
                case 1: button->assignNewKey(); break;
                case 2: button->owner.getMappings().removeKeyPress (button->commandID, button->keyNum); break;
                default: break;
            }
        }
    }

    void clicked() override
    {
        if (keyNum >= 0)
        {
            // existing key clicked..
            PopupMenu m;
            m.addItem (1, TRANS("Change this key-mapping"));
            m.addSeparator();
            m.addItem (2, TRANS("Remove this key-mapping"));

            m.showMenuAsync (PopupMenu::Options(),
                             ModalCallbackFunction::forComponent (menuCallback, this));
        }
        else
        {
            assignNewKey();  // + button pressed..
        }
    }

    void fitToContent (const int h) noexcept
    {
        if (keyNum < 0)
            setSize (h, h);
        else
            setSize (jlimit (h * 4, h * 8, 6 + Font (h * 0.6f).getStringWidth (getName())), h);
    }

    //==============================================================================
    class KeyEntryWindow  : public AlertWindow
    {
    public:
        KeyEntryWindow (KeyMappingEditorComponent& kec)
            : AlertWindow (TRANS("New key-mapping"),
                           TRANS("Please press a key combination now..."),
                           AlertWindow::NoIcon),
              owner (kec)
        {
            addButton (TRANS("OK"), 1);
            addButton (TRANS("Cancel"), 0);

            // (avoid return + escape keys getting processed by the buttons..)
            for (auto* child : getChildren())
                child->setWantsKeyboardFocus (false);

            setWantsKeyboardFocus (true);
            grabKeyboardFocus();
        }

        bool keyPressed (const KeyPress& key) override
        {
            lastPress = key;
            String message (TRANS("Key") + ": " + owner.getDescriptionForKeyPress (key));

            auto previousCommand = owner.getMappings().findCommandForKeyPress (key);

            if (previousCommand != 0)
                message << "\n\n("
                        << TRANS("Currently assigned to \"CMDN\"")
                            .replace ("CMDN", TRANS (owner.getCommandManager().getNameOfCommand (previousCommand)))
                        << ')';

            setMessage (message);
            return true;
        }

        bool keyStateChanged (bool) override
        {
            return true;
        }

        KeyPress lastPress;

    private:
        KeyMappingEditorComponent& owner;

        JUCE_DECLARE_NON_COPYABLE (KeyEntryWindow)
    };

    static void assignNewKeyCallback (int result, ChangeKeyButton* button, KeyPress newKey)
    {
        if (result != 0 && button != nullptr)
            button->setNewKey (newKey, true);
    }

    void setNewKey (const KeyPress& newKey, bool dontAskUser)
    {
        if (newKey.isValid())
        {
            auto previousCommand = owner.getMappings().findCommandForKeyPress (newKey);

            if (previousCommand == 0 || dontAskUser)
            {
                owner.getMappings().removeKeyPress (newKey);

                if (keyNum >= 0)
                    owner.getMappings().removeKeyPress (commandID, keyNum);

                owner.getMappings().addKeyPress (commandID, newKey, keyNum);
            }
            else
            {
                AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
                                              TRANS("Change key-mapping"),
                                              TRANS("This key is already assigned to the command \"CMDN\"")
                                                  .replace ("CMDN", owner.getCommandManager().getNameOfCommand (previousCommand))
                                                + "\n\n"
                                                + TRANS("Do you want to re-assign it to this new command instead?"),
                                              TRANS("Re-assign"),
                                              TRANS("Cancel"),
                                              this,
                                              ModalCallbackFunction::forComponent (assignNewKeyCallback,
                                                                                this, KeyPress (newKey)));
            }
        }
    }

    static void keyChosen (int result, ChangeKeyButton* button)
    {
        if (button != nullptr && button->currentKeyEntryWindow != nullptr)
        {
            if (result != 0)
            {
                button->currentKeyEntryWindow->setVisible (false);
                button->setNewKey (button->currentKeyEntryWindow->lastPress, false);
            }

            button->currentKeyEntryWindow = nullptr;
        }
    }

    void assignNewKey()
    {
        currentKeyEntryWindow = new KeyEntryWindow (owner);
        currentKeyEntryWindow->enterModalState (true, ModalCallbackFunction::forComponent (keyChosen, this));
    }

private:
    KeyMappingEditorComponent& owner;
    const CommandID commandID;
    const int keyNum;
    ScopedPointer<KeyEntryWindow> currentKeyEntryWindow;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeKeyButton)
};

//==============================================================================
class KeyMappingEditorComponent::ItemComponent  : public Component
{
public:
    ItemComponent (KeyMappingEditorComponent& kec, CommandID command)
        : owner (kec), commandID (command)
    {
        setInterceptsMouseClicks (false, true);

        const bool isReadOnly = owner.isCommandReadOnly (commandID);

        auto keyPresses = owner.getMappings().getKeyPressesAssignedToCommand (commandID);

        for (int i = 0; i < jmin ((int) maxNumAssignments, keyPresses.size()); ++i)
            addKeyPressButton (owner.getDescriptionForKeyPress (keyPresses.getReference (i)), i, isReadOnly);

        addKeyPressButton (String(), -1, isReadOnly);
    }

    void addKeyPressButton (const String& desc, const int index, const bool isReadOnly)
    {
        auto* b = new ChangeKeyButton (owner, commandID, desc, index);
        keyChangeButtons.add (b);

        b->setEnabled (! isReadOnly);
        b->setVisible (keyChangeButtons.size() <= (int) maxNumAssignments);
        addChildComponent (b);
    }

    void paint (Graphics& g) override
    {
        g.setFont (getHeight() * 0.7f);
        g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId));

        g.drawFittedText (TRANS (owner.getCommandManager().getNameOfCommand (commandID)),
                          4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(),
                          Justification::centredLeft, true);
    }

    void resized() override
    {
        int x = getWidth() - 4;

        for (int i = keyChangeButtons.size(); --i >= 0;)
        {
            auto* b = keyChangeButtons.getUnchecked(i);

            b->fitToContent (getHeight() - 2);
            b->setTopRightPosition (x, 1);
            x = b->getX() - 5;
        }
    }

private:
    KeyMappingEditorComponent& owner;
    OwnedArray<ChangeKeyButton> keyChangeButtons;
    const CommandID commandID;

    enum { maxNumAssignments = 3 };

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent)
};

//==============================================================================
class KeyMappingEditorComponent::MappingItem  : public TreeViewItem
{
public:
    MappingItem (KeyMappingEditorComponent& kec, CommandID command)
        : owner (kec), commandID (command)
    {}

    String getUniqueName() const override         { return String ((int) commandID) + "_id"; }
    bool mightContainSubItems() override          { return false; }
    int getItemHeight() const override            { return 20; }
    Component* createItemComponent() override     { return new ItemComponent (owner, commandID); }

private:
    KeyMappingEditorComponent& owner;
    const CommandID commandID;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingItem)
};

//==============================================================================
class KeyMappingEditorComponent::CategoryItem  : public TreeViewItem
{
public:
    CategoryItem (KeyMappingEditorComponent& kec, const String& name)
        : owner (kec), categoryName (name)
    {}

    String getUniqueName() const override       { return categoryName + "_cat"; }
    bool mightContainSubItems() override        { return true; }
    int getItemHeight() const override          { return 22; }

    void paintItem (Graphics& g, int width, int height) override
    {
        g.setFont (Font (height * 0.7f, Font::bold));
        g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId));

        g.drawText (TRANS (categoryName), 2, 0, width - 2, height, Justification::centredLeft, true);
    }

    void itemOpennessChanged (bool isNowOpen) override
    {
        if (isNowOpen)
        {
            if (getNumSubItems() == 0)
                for (auto command : owner.getCommandManager().getCommandsInCategory (categoryName))
                    if (owner.shouldCommandBeIncluded (command))
                        addSubItem (new MappingItem (owner, command));
        }
        else
        {
            clearSubItems();
        }
    }

private:
    KeyMappingEditorComponent& owner;
    String categoryName;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CategoryItem)
};

//==============================================================================
class KeyMappingEditorComponent::TopLevelItem   : public TreeViewItem,
                                                  public Button::Listener,
                                                  private ChangeListener
{
public:
    TopLevelItem (KeyMappingEditorComponent& kec)   : owner (kec)
    {
        setLinesDrawnForSubItems (false);
        owner.getMappings().addChangeListener (this);
    }

    ~TopLevelItem()
    {
        owner.getMappings().removeChangeListener (this);
    }

    bool mightContainSubItems() override             { return true; }
    String getUniqueName() const override            { return "keys"; }

    void changeListenerCallback (ChangeBroadcaster*) override
    {
        const OpennessRestorer opennessRestorer (*this);
        clearSubItems();

        for (auto category : owner.getCommandManager().getCommandCategories())
        {
            int count = 0;

            for (auto command : owner.getCommandManager().getCommandsInCategory (category))
                if (owner.shouldCommandBeIncluded (command))
                    ++count;

            if (count > 0)
                addSubItem (new CategoryItem (owner, category));
        }
    }

    static void resetToDefaultsCallback (int result, KeyMappingEditorComponent* owner)
    {
        if (result != 0 && owner != nullptr)
            owner->getMappings().resetToDefaultMappings();
    }

    void buttonClicked (Button*) override
    {
        AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon,
                                      TRANS("Reset to defaults"),
                                      TRANS("Are you sure you want to reset all the key-mappings to their default state?"),
                                      TRANS("Reset"),
                                      String(),
                                      &owner,
                                      ModalCallbackFunction::forComponent (resetToDefaultsCallback, &owner));
    }

private:
    KeyMappingEditorComponent& owner;
};

//==============================================================================
KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet& mappingManager,
                                                      const bool showResetToDefaultButton)
    : mappings (mappingManager),
      resetButton (TRANS ("reset to defaults"))
{
    treeItem = new TopLevelItem (*this);

    if (showResetToDefaultButton)
    {
        addAndMakeVisible (resetButton);
        resetButton.addListener (treeItem);
    }

    addAndMakeVisible (tree);
    tree.setColour (TreeView::backgroundColourId, findColour (backgroundColourId));
    tree.setRootItemVisible (false);
    tree.setDefaultOpenness (true);
    tree.setRootItem (treeItem);
    tree.setIndentSize (12);
}

KeyMappingEditorComponent::~KeyMappingEditorComponent()
{
    tree.setRootItem (nullptr);
}

//==============================================================================
void KeyMappingEditorComponent::setColours (Colour mainBackground,
                                            Colour textColour)
{
    setColour (backgroundColourId, mainBackground);
    setColour (textColourId, textColour);
    tree.setColour (TreeView::backgroundColourId, mainBackground);
}

void KeyMappingEditorComponent::parentHierarchyChanged()
{
    treeItem->changeListenerCallback (nullptr);
}

void KeyMappingEditorComponent::resized()
{
    int h = getHeight();

    if (resetButton.isVisible())
    {
        const int buttonHeight = 20;
        h -= buttonHeight + 8;
        int x = getWidth() - 8;

        resetButton.changeWidthToFitText (buttonHeight);
        resetButton.setTopRightPosition (x, h + 6);
    }

    tree.setBounds (0, 0, getWidth(), h);
}

//==============================================================================
bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID)
{
    auto* ci = mappings.getCommandManager().getCommandForID (commandID);

    return ci != nullptr && (ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0;
}

bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID)
{
    auto* ci = mappings.getCommandManager().getCommandForID (commandID);

    return ci != nullptr && (ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0;
}

String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key)
{
    return key.getTextDescription();
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
#if JUCE_MAC || DOXYGEN
/**
    Receives events from an Apple IR remote control device (Only available in OSX!).

    To use it, just create a subclass of this class, implementing the buttonPressed()
    callback, then call start() and stop() to start or stop receiving events.
*/
class JUCE_API  AppleRemoteDevice
{
public:
    //==============================================================================
    AppleRemoteDevice();
    virtual ~AppleRemoteDevice();

    //==============================================================================
    /** The set of buttons that may be pressed.
        @see buttonPressed
    */
    enum ButtonType
    {
        menuButton = 0,     /**< The menu button (if it's held for a short time). */
        playButton,         /**< The play button. */
        plusButton,         /**< The plus or volume-up button. */
        minusButton,        /**< The minus or volume-down button. */
        rightButton,        /**< The right button (if it's held for a short time). */
        leftButton,         /**< The left button (if it's held for a short time). */
        rightButton_Long,   /**< The right button (if it's held for a long time). */
        leftButton_Long,    /**< The menu button (if it's held for a long time). */
        menuButton_Long,    /**< The menu button (if it's held for a long time). */
        playButtonSleepMode,
        switched
    };

    //==============================================================================
    /** Override this method to receive the callback about a button press.

        The callback will happen on the application's message thread.

        Some buttons trigger matching up and down events, in which the isDown parameter
        will be true and then false. Others only send a single event when the
        button is pressed.
    */
    virtual void buttonPressed (ButtonType buttonId, bool isDown) = 0;

    //==============================================================================
    /** Starts the device running and responding to events.

        Returns true if it managed to open the device.

        @param inExclusiveMode  if true, the remote will be grabbed exclusively for this app,
                                and will not be available to any other part of the system. If
                                false, it will be shared with other apps.
        @see stop
    */
    bool start (bool inExclusiveMode);

    /** Stops the device running.
        @see start
    */
    void stop();

    /** Returns true if the device has been started successfully.
    */
    bool isActive() const;

    /** Returns the ID number of the remote, if it has sent one.
    */
    int getRemoteId() const                     { return remoteId; }

    //==============================================================================
    /** @internal */
    void handleCallbackInternal();

private:
    void* device;
    void* queue;
    int remoteId;

    bool open (bool openInExclusiveMode);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AppleRemoteDevice)
};

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

#ifdef JUCE_DATA_STRUCTURES_H_INCLUDED
 /* When you add this cpp file to your project, you mustn't include it in a file where you've
    already included any other headers - just put it inside a file on its own, possibly with your config
    flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
    header files that the compiler may be using.
 */
 #error "Incorrect use of JUCE cpp file"
#endif

#include "juce_data_structures.h"

#include "values/juce_Value.cpp"
#include "values/juce_ValueTree.cpp"
#include "values/juce_ValueTreeSynchroniser.cpp"
#include "values/juce_CachedValue.cpp"
#include "undomanager/juce_UndoManager.cpp"
#include "app_properties/juce_ApplicationProperties.cpp"
#include "app_properties/juce_PropertiesFile.cpp"


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

#include "juce_data_structures.cpp"


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE 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 module, and is read by
 the Projucer to automatically generate project code that uses it.
 For details about the syntax and how to create or use a module, see the
 JUCE Module Format.txt file.

 BEGIN_JUCE_MODULE_DECLARATION

  ID:               juce_data_structures
  vendor:           juce
  version:          5.2.0
  name:             JUCE data model helper classes
  description:      Classes for undo/redo management, and smart data structures.
  website:          http://www.juce.com/juce
  license:          GPL/Commercial

  dependencies:     juce_events

 END_JUCE_MODULE_DECLARATION

*******************************************************************************/

#pragma once
#define JUCE_DATA_STRUCTURES_H_INCLUDED

//==============================================================================
#include <juce_events/juce_events.h>

#include "undomanager/juce_UndoableAction.h"
#include "undomanager/juce_UndoManager.h"
#include "values/juce_Value.h"
#include "values/juce_ValueTree.h"
#include "values/juce_ValueTreeSynchroniser.h"
#include "values/juce_CachedValue.h"
#include "app_properties/juce_PropertiesFile.h"
#include "app_properties/juce_ApplicationProperties.h"


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

struct UndoManager::ActionSet
{
    ActionSet (const String& transactionName)
        : name (transactionName),
          time (Time::getCurrentTime())
    {}

    bool perform() const
    {
        for (int i = 0; i < actions.size(); ++i)
            if (! actions.getUnchecked(i)->perform())
                return false;

        return true;
    }

    bool undo() const
    {
        for (int i = actions.size(); --i >= 0;)
            if (! actions.getUnchecked(i)->undo())
                return false;

        return true;
    }

    int getTotalSize() const
    {
        int total = 0;

        for (int i = actions.size(); --i >= 0;)
            total += actions.getUnchecked(i)->getSizeInUnits();

        return total;
    }

    OwnedArray<UndoableAction> actions;
    String name;
    Time time;
};

//==============================================================================
UndoManager::UndoManager (const int maxNumberOfUnitsToKeep,
                          const int minimumTransactions)
   : totalUnitsStored (0),
     nextIndex (0),
     newTransaction (true),
     reentrancyCheck (false)
{
    setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep,
                               minimumTransactions);
}

UndoManager::~UndoManager()
{
}

//==============================================================================
void UndoManager::clearUndoHistory()
{
    transactions.clear();
    totalUnitsStored = 0;
    nextIndex = 0;
    sendChangeMessage();
}

int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const
{
    return totalUnitsStored;
}

void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep,
                                             const int minimumTransactions)
{
    maxNumUnitsToKeep          = jmax (1, maxNumberOfUnitsToKeep);
    minimumTransactionsToKeep  = jmax (1, minimumTransactions);
}

//==============================================================================
bool UndoManager::perform (UndoableAction* const newAction, const String& actionName)
{
    if (perform (newAction))
    {
        if (actionName.isNotEmpty())
            setCurrentTransactionName (actionName);

        return true;
    }

    return false;
}

bool UndoManager::perform (UndoableAction* const newAction)
{
    if (newAction != nullptr)
    {
        ScopedPointer<UndoableAction> action (newAction);

        if (reentrancyCheck)
        {
            jassertfalse;  // don't call perform() recursively from the UndoableAction::perform()
                           // or undo() methods, or else these actions will be discarded!
            return false;
        }

        if (action->perform())
        {
            ActionSet* actionSet = getCurrentSet();

            if (actionSet != nullptr && ! newTransaction)
            {
                if (UndoableAction* const lastAction = actionSet->actions.getLast())
                {
                    if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action))
                    {
                        action = coalescedAction;
                        totalUnitsStored -= lastAction->getSizeInUnits();
                        actionSet->actions.removeLast();
                    }
                }
            }
            else
            {
                actionSet = new ActionSet (newTransactionName);
                transactions.insert (nextIndex, actionSet);
                ++nextIndex;
            }

            totalUnitsStored += action->getSizeInUnits();
            actionSet->actions.add (action.release());
            newTransaction = false;

            moveFutureTransactionsToStash();
            dropOldTransactionsIfTooLarge();
            sendChangeMessage();
            return true;
        }
    }

    return false;
}

void UndoManager::moveFutureTransactionsToStash()
{
    if (nextIndex < transactions.size())
    {
        stashedFutureTransactions.clear();

        while (nextIndex < transactions.size())
        {
            ActionSet* removed = transactions.removeAndReturn (nextIndex);
            stashedFutureTransactions.add (removed);
            totalUnitsStored -= removed->getTotalSize();
        }
    }
}

void UndoManager::restoreStashedFutureTransactions()
{
    while (nextIndex < transactions.size())
    {
        totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize();
        transactions.remove (nextIndex);
    }

    for (int i = 0; i < stashedFutureTransactions.size(); ++i)
    {
        ActionSet* action = stashedFutureTransactions.removeAndReturn (i);
        totalUnitsStored += action->getTotalSize();
        transactions.add (action);
    }

    stashedFutureTransactions.clearQuick (false);
}

void UndoManager::dropOldTransactionsIfTooLarge()
{
    while (nextIndex > 0
            && totalUnitsStored > maxNumUnitsToKeep
            && transactions.size() > minimumTransactionsToKeep)
    {
        totalUnitsStored -= transactions.getFirst()->getTotalSize();
        transactions.remove (0);
        --nextIndex;

        // if this fails, then some actions may not be returning
        // consistent results from their getSizeInUnits() method
        jassert (totalUnitsStored >= 0);
    }
}

void UndoManager::beginNewTransaction() noexcept
{
    beginNewTransaction (String());
}

void UndoManager::beginNewTransaction (const String& actionName) noexcept
{
    newTransaction = true;
    newTransactionName = actionName;
}

void UndoManager::setCurrentTransactionName (const String& newName) noexcept
{
    if (newTransaction)
        newTransactionName = newName;
    else if (ActionSet* action = getCurrentSet())
        action->name = newName;
}

String UndoManager::getCurrentTransactionName() const noexcept
{
    if (ActionSet* action = getCurrentSet())
        return action->name;

    return newTransactionName;
}

//==============================================================================
UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept     { return transactions [nextIndex - 1]; }
UndoManager::ActionSet* UndoManager::getNextSet() const noexcept        { return transactions [nextIndex]; }

bool UndoManager::canUndo() const noexcept   { return getCurrentSet() != nullptr; }
bool UndoManager::canRedo() const noexcept   { return getNextSet()    != nullptr; }

bool UndoManager::undo()
{
    if (const ActionSet* const s = getCurrentSet())
    {
        const ScopedValueSetter<bool> setter (reentrancyCheck, true);

        if (s->undo())
            --nextIndex;
        else
            clearUndoHistory();

        beginNewTransaction();
        sendChangeMessage();
        return true;
    }

    return false;
}

bool UndoManager::redo()
{
    if (const ActionSet* const s = getNextSet())
    {
        const ScopedValueSetter<bool> setter (reentrancyCheck, true);

        if (s->perform())
            ++nextIndex;
        else
            clearUndoHistory();

        beginNewTransaction();
        sendChangeMessage();
        return true;
    }

    return false;
}

String UndoManager::getUndoDescription() const
{
    if (auto* s = getCurrentSet())
        return s->name;

    return {};
}

String UndoManager::getRedoDescription() const
{
    if (auto* s = getNextSet())
        return s->name;

    return {};
}

Time UndoManager::getTimeOfUndoTransaction() const
{
    if (auto* s = getCurrentSet())
        return s->time;

    return {};
}

Time UndoManager::getTimeOfRedoTransaction() const
{
    if (auto* s = getNextSet())
        return s->time;

    return Time::getCurrentTime();
}

bool UndoManager::undoCurrentTransactionOnly()
{
    if ((! newTransaction) && undo())
    {
        restoreStashedFutureTransactions();
        return true;
    }

    return false;
}

void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const
{
    if (! newTransaction)
        if (const ActionSet* const s = getCurrentSet())
            for (int i = 0; i < s->actions.size(); ++i)
                actionsFound.add (s->actions.getUnchecked(i));
}

int UndoManager::getNumActionsInCurrentTransaction() const
{
    if (! newTransaction)
        if (const ActionSet* const s = getCurrentSet())
            return s->actions.size();

    return 0;
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Used by the UndoManager class to store an action which can be done
    and undone.

    @see UndoManager
*/
class JUCE_API  UndoableAction
{
protected:
    /** Creates an action. */
    UndoableAction() noexcept   {}

public:
    /** Destructor. */
    virtual ~UndoableAction()   {}

    //==============================================================================
    /** Overridden by a subclass to perform the action.

        This method is called by the UndoManager, and shouldn't be used directly by
        applications.

        Be careful not to make any calls in a perform() method that could call
        recursively back into the UndoManager::perform() method

        @returns    true if the action could be performed.
        @see UndoManager::perform
    */
    virtual bool perform() = 0;

    /** Overridden by a subclass to undo the action.

        This method is called by the UndoManager, and shouldn't be used directly by
        applications.

        Be careful not to make any calls in an undo() method that could call
        recursively back into the UndoManager::perform() method

        @returns    true if the action could be undone without any errors.
        @see UndoManager::perform
    */
    virtual bool undo() = 0;

    //==============================================================================
    /** Returns a value to indicate how much memory this object takes up.

        Because the UndoManager keeps a list of UndoableActions, this is used
        to work out how much space each one will take up, so that the UndoManager
        can work out how many to keep.

        The default value returned here is 10 - units are arbitrary and
        don't have to be accurate.

        @see UndoManager::getNumberOfUnitsTakenUpByStoredCommands,
             UndoManager::setMaxNumberOfStoredUnits
    */
    virtual int getSizeInUnits()    { return 10; }

    /** Allows multiple actions to be coalesced into a single action object, to reduce storage space.

        If possible, this method should create and return a single action that does the same job as
        this one followed by the supplied action.

        If it's not possible to merge the two actions, the method should return a nullptr.
    */
    virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction)  { ignoreUnused (nextAction); return nullptr; }
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Manages a list of undo/redo commands.

    An UndoManager object keeps a list of past actions and can use these actions
    to move backwards and forwards through an undo history.

    To use it, create subclasses of UndoableAction which perform all the
    actions you need, then when you need to actually perform an action, create one
    and pass it to the UndoManager's perform() method.

    The manager also uses the concept of 'transactions' to group the actions
    together - all actions performed between calls to beginNewTransaction() are
    grouped together and are all undone/redone as a group.

    The UndoManager is a ChangeBroadcaster, so listeners can register to be told
    when actions are performed or undone.

    @see UndoableAction
*/
class JUCE_API  UndoManager  : public ChangeBroadcaster
{
public:
    //==============================================================================
    /** Creates an UndoManager.

        @param maxNumberOfUnitsToKeep       each UndoableAction object returns a value
                                            to indicate how much storage it takes up
                                            (UndoableAction::getSizeInUnits()), so this
                                            lets you specify the maximum total number of
                                            units that the undomanager is allowed to
                                            keep in memory before letting the older actions
                                            drop off the end of the list.
        @param minimumTransactionsToKeep    this specifies the minimum number of transactions
                                            that will be kept, even if this involves exceeding
                                            the amount of space specified in maxNumberOfUnitsToKeep
    */
    UndoManager (int maxNumberOfUnitsToKeep = 30000,
                 int minimumTransactionsToKeep = 30);

    /** Destructor. */
    ~UndoManager();

    //==============================================================================
    /** Deletes all stored actions in the list. */
    void clearUndoHistory();

    /** Returns the current amount of space to use for storing UndoableAction objects.
        @see setMaxNumberOfStoredUnits
    */
    int getNumberOfUnitsTakenUpByStoredCommands() const;

    /** Sets the amount of space that can be used for storing UndoableAction objects.

        @param maxNumberOfUnitsToKeep       each UndoableAction object returns a value
                                            to indicate how much storage it takes up
                                            (UndoableAction::getSizeInUnits()), so this
                                            lets you specify the maximum total number of
                                            units that the undomanager is allowed to
                                            keep in memory before letting the older actions
                                            drop off the end of the list.
        @param minimumTransactionsToKeep    this specifies the minimum number of transactions
                                            that will be kept, even if this involves exceeding
                                            the amount of space specified in maxNumberOfUnitsToKeep
        @see getNumberOfUnitsTakenUpByStoredCommands
    */
    void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep,
                                    int minimumTransactionsToKeep);

    //==============================================================================
    /** Performs an action and adds it to the undo history list.

        @param action   the action to perform - this object will be deleted by
                        the UndoManager when no longer needed
        @returns true if the command succeeds - see UndoableAction::perform
        @see beginNewTransaction
    */
    bool perform (UndoableAction* action);

    /** Performs an action and also gives it a name.

        @param action       the action to perform - this object will be deleted by
                            the UndoManager when no longer needed
        @param actionName   if this string is non-empty, the current transaction will be
                            given this name; if it's empty, the current transaction name will
                            be left unchanged. See setCurrentTransactionName()
        @returns true if the command succeeds - see UndoableAction::perform
        @see beginNewTransaction
    */
    bool perform (UndoableAction* action, const String& actionName);

    /** Starts a new group of actions that together will be treated as a single transaction.

        All actions that are passed to the perform() method between calls to this
        method are grouped together and undone/redone together by a single call to
        undo() or redo().
    */
    void beginNewTransaction() noexcept;

    /** Starts a new group of actions that together will be treated as a single transaction.

        All actions that are passed to the perform() method between calls to this
        method are grouped together and undone/redone together by a single call to
        undo() or redo().

        @param actionName   a description of the transaction that is about to be
                            performed
    */
    void beginNewTransaction (const String& actionName) noexcept;

    /** Changes the name stored for the current transaction.

        Each transaction is given a name when the beginNewTransaction() method is
        called, but this can be used to change that name without starting a new
        transaction.
    */
    void setCurrentTransactionName (const String& newName) noexcept;

    /** Returns the name of the current transaction.
        @see setCurrentTransactionName
    */
    String getCurrentTransactionName() const noexcept;

    //==============================================================================
    /** Returns true if there's at least one action in the list to undo.
        @see getUndoDescription, undo, canRedo
    */
    bool canUndo() const noexcept;

    /** Returns the name of the transaction that will be rolled-back when undo() is called.
        @see undo
    */
    String getUndoDescription() const;

    /** Tries to roll-back the last transaction.
        @returns    true if the transaction can be undone, and false if it fails, or
                    if there aren't any transactions to undo
    */
    bool undo();

    /** Tries to roll-back any actions that were added to the current transaction.

        This will perform an undo() only if there are some actions in the undo list
        that were added after the last call to beginNewTransaction().

        This is useful because it lets you call beginNewTransaction(), then
        perform an operation which may or may not actually perform some actions, and
        then call this method to get rid of any actions that might have been done
        without it rolling back the previous transaction if nothing was actually
        done.

        @returns true if any actions were undone.
    */
    bool undoCurrentTransactionOnly();

    /** Returns a list of the UndoableAction objects that have been performed during the
        transaction that is currently open.

        Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly()
        were to be called now.

        The first item in the list is the earliest action performed.
    */
    void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const;

    /** Returns the number of UndoableAction objects that have been performed during the
        transaction that is currently open.
        @see getActionsInCurrentTransaction
    */
    int getNumActionsInCurrentTransaction() const;

    /** Returns the time to which the state would be restored if undo() was to be called.
        If an undo isn't currently possible, it'll return Time().
    */
    Time getTimeOfUndoTransaction() const;

    /** Returns the time to which the state would be restored if redo() was to be called.
        If a redo isn't currently possible, it'll return Time::getCurrentTime().
    */
    Time getTimeOfRedoTransaction() const;

    //==============================================================================
    /** Returns true if there's at least one action in the list to redo.
        @see getRedoDescription, redo, canUndo
    */
    bool canRedo() const noexcept;

    /** Returns the name of the transaction that will be redone when redo() is called.
        @see redo
    */
    String getRedoDescription() const;

    /** Tries to redo the last transaction that was undone.
        @returns   true if the transaction can be redone, and false if it fails, or
                   if there aren't any transactions to redo
    */
    bool redo();

private:
    //==============================================================================
    struct ActionSet;
    friend struct ContainerDeletePolicy<ActionSet>;
    OwnedArray<ActionSet> transactions, stashedFutureTransactions;
    String newTransactionName;
    int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex;
    bool newTransaction, reentrancyCheck;
    ActionSet* getCurrentSet() const noexcept;
    ActionSet* getNextSet() const noexcept;
    void moveFutureTransactionsToStash();
    void restoreStashedFutureTransactions();
    void dropOldTransactionsIfTooLarge();

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This class can be used to watch for all changes to the state of a ValueTree,
    and to convert them to a transmittable binary encoding.

    The purpose of this class is to allow two or more ValueTrees to be remotely
    synchronised by transmitting encoded changes over some kind of transport
    mechanism.

    To use it, you'll need to implement a subclass of ValueTreeSynchroniser
    and implement the stateChanged() method to transmit the encoded change (maybe
    via a network or other means) to a remote destination, where it can be
    applied to a target tree.
*/
class JUCE_API  ValueTreeSynchroniser  : private ValueTree::Listener
{
public:
    /** Creates a ValueTreeSynchroniser that watches the given tree.

        After creating an instance of this class and somehow attaching it to
        a target tree, you probably want to call sendFullSyncCallback() to
        get them into a common starting state.
    */
    ValueTreeSynchroniser (const ValueTree& tree);

    /** Destructor. */
    virtual ~ValueTreeSynchroniser();

    /** This callback happens when the ValueTree changes and the given state-change message
        needs to be applied to any other trees that need to stay in sync with it.
        The data is an opaque blob of binary that you should transmit to wherever your
        target tree lives, and use applyChange() to apply this to the target tree.
    */
    virtual void stateChanged (const void* encodedChange, size_t encodedChangeSize) = 0;

    /** Forces the sending of a full state message, which may be large, as it
        encodes the entire ValueTree.

        This will internally invoke stateChanged() with the encoded version of the state.
    */
    void sendFullSyncCallback();

    /** Applies an encoded change to the given destination tree.

        When you implement a receiver for changes that were sent by the stateChanged()
        message, this is the function that you'll need to call to apply them to the
        target tree that you want to be synced.
    */
    static bool applyChange (ValueTree& target,
                             const void* encodedChangeData, size_t encodedChangeDataSize,
                             UndoManager* undoManager);

    /** Returns the root ValueTree that is being observed. */
    const ValueTree& getRoot() noexcept       { return valueTree; }

private:
    ValueTree valueTree;

    void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
    void valueTreeChildAdded (ValueTree&, ValueTree&) override;
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
    void valueTreeChildOrderChanged (ValueTree&, int, int) override;
    void valueTreeParentChanged (ValueTree&) override;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace ValueTreeSynchroniserHelpers
{
    enum ChangeType
    {
        propertyChanged  = 1,
        fullSync         = 2,
        childAdded       = 3,
        childRemoved     = 4,
        childMoved       = 5,
        propertyRemoved  = 6
    };

    static void getValueTreePath (ValueTree v, const ValueTree& topLevelTree, Array<int>& path)
    {
        while (v != topLevelTree)
        {
            ValueTree parent (v.getParent());

            if (! parent.isValid())
                break;

            path.add (parent.indexOf (v));
            v = parent;
        }
    }

    static void writeHeader (MemoryOutputStream& stream, ChangeType type)
    {
        stream.writeByte ((char) type);
    }

    static void writeHeader (ValueTreeSynchroniser& target, MemoryOutputStream& stream,
                             ChangeType type, ValueTree v)
    {
        writeHeader (stream, type);

        Array<int> path;
        getValueTreePath (v, target.getRoot(), path);

        stream.writeCompressedInt (path.size());

        for (int i = path.size(); --i >= 0;)
            stream.writeCompressedInt (path.getUnchecked(i));
    }

    static ValueTree readSubTreeLocation (MemoryInputStream& input, ValueTree v)
    {
        const int numLevels = input.readCompressedInt();

        if (! isPositiveAndBelow (numLevels, 65536)) // sanity-check
            return {};

        for (int i = numLevels; --i >= 0;)
        {
            const int index = input.readCompressedInt();

            if (! isPositiveAndBelow (index, v.getNumChildren()))
                return {};

            v = v.getChild (index);
        }

        return v;
    }
}

ValueTreeSynchroniser::ValueTreeSynchroniser (const ValueTree& tree)  : valueTree (tree)
{
    valueTree.addListener (this);
}

ValueTreeSynchroniser::~ValueTreeSynchroniser()
{
    valueTree.removeListener (this);
}

void ValueTreeSynchroniser::sendFullSyncCallback()
{
    MemoryOutputStream m;
    writeHeader (m, ValueTreeSynchroniserHelpers::fullSync);
    valueTree.writeToStream (m);
    stateChanged (m.getData(), m.getDataSize());
}

void ValueTreeSynchroniser::valueTreePropertyChanged (ValueTree& vt, const Identifier& property)
{
    MemoryOutputStream m;

    if (auto* value = vt.getPropertyPointer (property))
    {
        ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyChanged, vt);
        m.writeString (property.toString());
        value->writeToStream (m);
    }
    else
    {
        ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyRemoved, vt);
        m.writeString (property.toString());
    }

    stateChanged (m.getData(), m.getDataSize());
}

void ValueTreeSynchroniser::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree)
{
    const int index = parentTree.indexOf (childTree);
    jassert (index >= 0);

    MemoryOutputStream m;
    ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childAdded, parentTree);
    m.writeCompressedInt (index);
    childTree.writeToStream (m);
    stateChanged (m.getData(), m.getDataSize());
}

void ValueTreeSynchroniser::valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int oldIndex)
{
    MemoryOutputStream m;
    ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childRemoved, parentTree);
    m.writeCompressedInt (oldIndex);
    stateChanged (m.getData(), m.getDataSize());
}

void ValueTreeSynchroniser::valueTreeChildOrderChanged (ValueTree& parent, int oldIndex, int newIndex)
{
    MemoryOutputStream m;
    ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childMoved, parent);
    m.writeCompressedInt (oldIndex);
    m.writeCompressedInt (newIndex);
    stateChanged (m.getData(), m.getDataSize());
}

void ValueTreeSynchroniser::valueTreeParentChanged (ValueTree&)  {} // (No action needed here)

bool ValueTreeSynchroniser::applyChange (ValueTree& root, const void* data, size_t dataSize, UndoManager* undoManager)
{
    MemoryInputStream input (data, dataSize, false);

    const ValueTreeSynchroniserHelpers::ChangeType type = (ValueTreeSynchroniserHelpers::ChangeType) input.readByte();

    if (type == ValueTreeSynchroniserHelpers::fullSync)
    {
        root = ValueTree::readFromStream (input);
        return true;
    }

    ValueTree v (ValueTreeSynchroniserHelpers::readSubTreeLocation (input, root));

    if (! v.isValid())
        return false;

    switch (type)
    {
        case ValueTreeSynchroniserHelpers::propertyChanged:
        {
            Identifier property (input.readString());
            v.setProperty (property, var::readFromStream (input), undoManager);
            return true;
        }

        case ValueTreeSynchroniserHelpers::propertyRemoved:
        {
            Identifier property (input.readString());
            v.removeProperty (property, undoManager);
            return true;
        }

        case ValueTreeSynchroniserHelpers::childAdded:
        {
            const int index = input.readCompressedInt();
            v.addChild (ValueTree::readFromStream (input), index, undoManager);
            return true;
        }

        case ValueTreeSynchroniserHelpers::childRemoved:
        {
            const int index = input.readCompressedInt();

            if (isPositiveAndBelow (index, v.getNumChildren()))
            {
                v.removeChild (index, undoManager);
                return true;
            }

            jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
            break;
        }

        case ValueTreeSynchroniserHelpers::childMoved:
        {
            const int oldIndex = input.readCompressedInt();
            const int newIndex = input.readCompressedInt();

            if (isPositiveAndBelow (oldIndex, v.getNumChildren())
                 && isPositiveAndBelow (newIndex, v.getNumChildren()))
            {
                v.moveChild (oldIndex, newIndex, undoManager);
                return true;
            }

            jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync
            break;
        }

        default:
            jassertfalse; // Seem to have received some corrupt data?
            break;
    }

    return false;
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This class acts as a typed wrapper around a property inside a ValueTree.

    A CachedValue provides an easy way to read and write a ValueTree property with
    a chosen type. So for example a CachedValue<int> allows you to read or write the
    property as an int, and a CachedValue<String> lets you work with it as a String.

    It also allows efficient access to the value, by caching a copy of it in the
    type that is being used.

    You can give the CachedValue an optional UndoManager which it will use when writing
    to the underlying ValueTree.

    If the property inside the ValueTree is missing, the CachedValue will automatically
    return an optional default value, which can be specified when initialising the CachedValue.

    To create one, you can either use the constructor to attach the CachedValue to a
    ValueTree, or can create an uninitialised CachedValue with its default constructor and
    then attach it later with the referTo() methods.

    Common types like String, int, double which can be easily converted to a var should work
    out-of-the-box, but if you want to use more complex custom types, you may need to implement
    some template specialisations of VariantConverter which this class uses to convert between
    the type and the ValueTree's internal var.
*/
template <typename Type>
class CachedValue   : private ValueTree::Listener
{
public:
    //==============================================================================
    /** Default constructor.
        Creates a default CachedValue not referring to any property. To initialise the
        object, call one of the referTo() methods.
    */
    CachedValue();

    /** Constructor.

        Creates a CachedValue referring to a Value property inside a ValueTree.
        If you use this constructor, the fallback value will be a default-constructed
        instance of Type.

        @param tree          The ValueTree containing the property
        @param propertyID    The identifier of the property
        @param undoManager   The UndoManager to use when writing to the property
    */
    CachedValue (ValueTree& tree, const Identifier& propertyID,
                 UndoManager* undoManager);

    /** Constructor.

        Creates a default Cached Value referring to a Value property inside a ValueTree,
        and specifies a fallback value to use if the property does not exist.

        @param tree          The ValueTree containing the property
        @param propertyID    The identifier of the property
        @param undoManager   The UndoManager to use when writing to the property
        @param defaultToUse  The fallback default value to use.
    */
    CachedValue (ValueTree& tree, const Identifier& propertyID,
                 UndoManager* undoManager, const Type& defaultToUse);

    //==============================================================================
    /** Returns the current value of the property. If the property does not exist,
        returns the fallback default value.

        This is the same as calling get().
    */
    operator Type() const noexcept                   { return cachedValue; }

    /** Returns the current value of the property. If the property does not exist,
        returns the fallback default value.
    */
    Type get() const noexcept                        { return cachedValue; }

    /** Dereference operator. Provides direct access to the property.  */
    Type& operator*() noexcept                       { return cachedValue; }

    /** Dereference operator. Provides direct access to members of the property
        if it is of object type.
    */
    Type* operator->() noexcept                      { return &cachedValue; }

    /** Returns true if the current value of the property (or the fallback value)
        is equal to other.
    */
    template <typename OtherType>
    bool operator== (const OtherType& other) const   { return cachedValue == other; }

    /** Returns true if the current value of the property (or the fallback value)
        is not equal to other.
     */
    template <typename OtherType>
    bool operator!= (const OtherType& other) const   { return cachedValue != other; }

    //==============================================================================
    /** Returns the current property as a Value object. */
    Value getPropertyAsValue();

    /** Returns true if the current property does not exist and the CachedValue is using
        the fallback default value instead.
    */
    bool isUsingDefault() const;

    /** Returns the current fallback default value. */
    Type getDefault() const                          { return defaultValue; }

    //==============================================================================
    /** Sets the property. This will actually modify the property in the referenced ValueTree. */
    CachedValue& operator= (const Type& newValue);

    /** Sets the property. This will actually modify the property in the referenced ValueTree. */
    void setValue (const Type& newValue, UndoManager* undoManagerToUse);

    /** Removes the property from the referenced ValueTree and makes the CachedValue
        return the fallback default value instead.
     */
    void resetToDefault();

    /** Removes the property from the referenced ValueTree and makes the CachedValue
        return the fallback default value instead.
     */
    void resetToDefault (UndoManager* undoManagerToUse);

    /** Resets the fallback default value. */
    void setDefault (const Type& value)                { defaultValue = value; }

    //==============================================================================
    /** Makes the CachedValue refer to the specified property inside the given ValueTree. */
    void referTo (ValueTree& tree, const Identifier& property, UndoManager* um);

    /** Makes the CachedValue refer to the specified property inside the given ValueTree,
        and specifies a fallback value to use if the property does not exist.
    */
    void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal);

    /** Force an update in case the referenced property has been changed from elsewhere.

        Note: The CachedValue is a ValueTree::Listener and therefore will be informed of
        changes of the referenced property anyway (and update itself). But this may happen
        asynchronously. forceUpdateOfCachedValue() forces an update immediately.
    */
    void forceUpdateOfCachedValue();

    //==============================================================================
    /** Returns a reference to the ValueTree containing the referenced property. */
    ValueTree& getValueTree() noexcept                      { return targetTree; }

    /** Returns the property ID of the referenced property. */
    const Identifier& getPropertyID() const noexcept        { return targetProperty; }

private:
    //==============================================================================
    ValueTree targetTree;
    Identifier targetProperty;
    UndoManager* undoManager;
    Type defaultValue;
    Type cachedValue;

    //==============================================================================
    void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&);
    Type getTypedValue() const;

    void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override;
    void valueTreeChildAdded (ValueTree&, ValueTree&) override {}
    void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {}
    void valueTreeChildOrderChanged (ValueTree&, int, int) override {}
    void valueTreeParentChanged (ValueTree&) override {}

    JUCE_DECLARE_NON_COPYABLE (CachedValue)
};

//==============================================================================
template <typename Type>
inline CachedValue<Type>::CachedValue()  : undoManager (nullptr) {}

template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um)
    : targetTree (v), targetProperty (i), undoManager (um),
      defaultValue(), cachedValue (getTypedValue())
{
    targetTree.addListener (this);
}

template <typename Type>
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse)
    : targetTree (v), targetProperty (i), undoManager (um),
      defaultValue (defaultToUse), cachedValue (getTypedValue())
{
    targetTree.addListener (this);
}

template <typename Type>
inline Value CachedValue<Type>::getPropertyAsValue()
{
    return targetTree.getPropertyAsValue (targetProperty, undoManager);
}

template <typename Type>
inline bool CachedValue<Type>::isUsingDefault() const
{
    return ! targetTree.hasProperty (targetProperty);
}

template <typename Type>
inline CachedValue<Type>& CachedValue<Type>::operator= (const Type& newValue)
{
    setValue (newValue, undoManager);
    return *this;
}

template <typename Type>
inline void CachedValue<Type>::setValue (const Type& newValue, UndoManager* undoManagerToUse)
{
    if (cachedValue != newValue || isUsingDefault())
    {
        cachedValue = newValue;
        targetTree.setProperty (targetProperty, VariantConverter<Type>::toVar (newValue), undoManagerToUse);
    }
}

template <typename Type>
inline void CachedValue<Type>::resetToDefault()
{
    resetToDefault (undoManager);
}

template <typename Type>
inline void CachedValue<Type>::resetToDefault (UndoManager* undoManagerToUse)
{
    targetTree.removeProperty (targetProperty, undoManagerToUse);
    forceUpdateOfCachedValue();
}

template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um)
{
    referToWithDefault (v, i, um, Type());
}

template <typename Type>
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
    referToWithDefault (v, i, um, defaultVal);
}

template <typename Type>
inline void CachedValue<Type>::forceUpdateOfCachedValue()
{
    cachedValue = getTypedValue();
}

template <typename Type>
inline void CachedValue<Type>::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal)
{
    targetTree.removeListener (this);
    targetTree = v;
    targetProperty = i;
    undoManager = um;
    defaultValue = defaultVal;
    cachedValue = getTypedValue();
    targetTree.addListener (this);
}

template <typename Type>
inline Type CachedValue<Type>::getTypedValue() const
{
    if (const var* property = targetTree.getPropertyPointer (targetProperty))
        return VariantConverter<Type>::fromVar (*property);

    return defaultValue;
}

template <typename Type>
inline void CachedValue<Type>::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty)
{
    if (changedProperty == targetProperty && targetTree == changedTree)
        forceUpdateOfCachedValue();
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_UNIT_TESTS

class CachedValueTests  : public UnitTest
{
public:
    CachedValueTests() : UnitTest ("CachedValues", "Values") {}

    void runTest() override
    {
        beginTest ("default constructor");
        {
            CachedValue<String> cv;
            expect (cv.isUsingDefault());
            expect (cv.get() == String());
        }

        beginTest ("without default value");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", "testvalue", nullptr);

            CachedValue<String> cv (t, "testkey", nullptr);

            expect (! cv.isUsingDefault());
            expect (cv.get() == "testvalue");

            cv.resetToDefault();

            expect (cv.isUsingDefault());
            expect (cv.get() == String());
        }

        beginTest ("with default value");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", "testvalue", nullptr);

            CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");

            expect (! cv.isUsingDefault());
            expect (cv.get() == "testvalue");

            cv.resetToDefault();

            expect (cv.isUsingDefault());
            expect (cv.get() == "defaultvalue");
        }

        beginTest ("with default value (int)");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", 23, nullptr);

            CachedValue<int> cv (t, "testkey", nullptr, 34);

            expect (! cv.isUsingDefault());
            expect (cv == 23);
            expectEquals (cv.get(), 23);

            cv.resetToDefault();

            expect (cv.isUsingDefault());
            expect (cv == 34);
        }

        beginTest ("with void value");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", var(), nullptr);

            CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");

            expect (! cv.isUsingDefault());
            expect (cv == "");
            expectEquals (cv.get(), String());
        }

        beginTest ("with non-existent value");
        {
            ValueTree t ("root");

            CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");

            expect (cv.isUsingDefault());
            expect (cv == "defaultvalue");
            expect (cv.get() == "defaultvalue");
        }

        beginTest ("with value changing");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", "oldvalue", nullptr);

            CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue");
            expect (cv == "oldvalue");

            t.setProperty ("testkey", "newvalue", nullptr);
            expect (cv != "oldvalue");
            expect (cv == "newvalue");
        }

        beginTest ("set value");
        {
            ValueTree t ("root");
            t.setProperty ("testkey", 23, nullptr);

            CachedValue<int> cv (t, "testkey", nullptr, 45);
            cv = 34;

            expectEquals ((int) t["testkey"], 34);

            cv.resetToDefault();
            expect (cv == 45);
            expectEquals (cv.get(), 45);

            expect (t["testkey"] == var());
        }

        beginTest ("reset value");
        {

        }
    }
};

static CachedValueTests cachedValueTests;

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

Value::ValueSource::ValueSource()
{
}

Value::ValueSource::~ValueSource()
{
    cancelPendingUpdate();
}

void Value::ValueSource::handleAsyncUpdate()
{
    sendChangeMessage (true);
}

void Value::ValueSource::sendChangeMessage (const bool synchronous)
{
    const int numListeners = valuesWithListeners.size();

    if (numListeners > 0)
    {
        if (synchronous)
        {
            const ReferenceCountedObjectPtr<ValueSource> localRef (this);

            cancelPendingUpdate();

            for (int i = numListeners; --i >= 0;)
                if (Value* const v = valuesWithListeners[i])
                    v->callListeners();
        }
        else
        {
            triggerAsyncUpdate();
        }
    }
}

//==============================================================================
class SimpleValueSource  : public Value::ValueSource
{
public:
    SimpleValueSource()
    {
    }

    SimpleValueSource (const var& initialValue)
        : value (initialValue)
    {
    }

    var getValue() const override
    {
        return value;
    }

    void setValue (const var& newValue) override
    {
        if (! newValue.equalsWithSameType (value))
        {
            value = newValue;
            sendChangeMessage (false);
        }
    }

private:
    var value;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource)
};

//==============================================================================
Value::Value()  : value (new SimpleValueSource())
{
}

Value::Value (ValueSource* const v)  : value (v)
{
    jassert (v != nullptr);
}

Value::Value (const var& initialValue)  : value (new SimpleValueSource (initialValue))
{
}

Value::Value (const Value& other)  : value (other.value)
{
}

Value::Value (Value&& other) noexcept
{
    // moving a Value with listeners will lose those listeners, which
    // probably isn't what you wanted to happen!
    jassert (other.listeners.size() == 0);

    other.removeFromListenerList();
    value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value);
}

Value& Value::operator= (Value&& other) noexcept
{
    // moving a Value with listeners will lose those listeners, which
    // probably isn't what you wanted to happen!
    jassert (other.listeners.size() == 0);

    other.removeFromListenerList();
    value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value);
    return *this;
}

Value::~Value()
{
    removeFromListenerList();
}

void Value::removeFromListenerList()
{
    if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation
        value->valuesWithListeners.removeValue (this);
}

//==============================================================================
var Value::getValue() const
{
    return value->getValue();
}

Value::operator var() const
{
    return value->getValue();
}

void Value::setValue (const var& newValue)
{
    value->setValue (newValue);
}

String Value::toString() const
{
    return value->getValue().toString();
}

Value& Value::operator= (const var& newValue)
{
    value->setValue (newValue);
    return *this;
}

void Value::referTo (const Value& valueToReferTo)
{
    if (valueToReferTo.value != value)
    {
        if (listeners.size() > 0)
        {
            value->valuesWithListeners.removeValue (this);
            valueToReferTo.value->valuesWithListeners.add (this);
        }

        value = valueToReferTo.value;
        callListeners();
    }
}

bool Value::refersToSameSourceAs (const Value& other) const
{
    return value == other.value;
}

bool Value::operator== (const Value& other) const
{
    return value == other.value || value->getValue() == other.getValue();
}

bool Value::operator!= (const Value& other) const
{
    return value != other.value && value->getValue() != other.getValue();
}

//==============================================================================
void Value::addListener (ValueListener* const listener)
{
    if (listener != nullptr)
    {
        if (listeners.size() == 0)
            value->valuesWithListeners.add (this);

        listeners.add (listener);
    }
}

void Value::removeListener (ValueListener* const listener)
{
    listeners.remove (listener);

    if (listeners.size() == 0)
        value->valuesWithListeners.removeValue (this);
}

void Value::callListeners()
{
    if (listeners.size() > 0)
    {
        Value v (*this); // (create a copy in case this gets deleted by a callback)
        listeners.call (&ValueListener::valueChanged, v);
    }
}

OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value)
{
    return stream << value.toString();
}

} // namespace juce