No undo when changing whole ValueTree via readFromStream


#1

Hey all,
I have a question about the undoManager
I have defined:

um = new UndoManager();
VTree = new AudioProcessorValueTreeState(*this, um);

My methode PlugInAudioProcessor::ParameterWriteInFile(…) for preset storing
writes the whole ValueTree as stream in a file:

VTree ->state.writeToStream(stream);

Loading presets with

VTree = ValueTree::readFromStream(stream);

results the expected change of the parameters with the values restored from the presets
but undo does not work.
So what is lacking in the manager?

Thanks for answering


#2

I think you need to call beginNewTransaction() just before loading preset.


#3

Thank you for the answer, I had already tried, but with the same result.


#4

Can you verify, that the returned tree is valid? It probably is, but it is safer to write:

auto tree = ValueTree::readFromStream(stream);
if (tree.isValid())
{
    VTree = tree;
}
else
{
    DBG ("Read invalid value tree");
}

Second, are you using the Attachment classes? Or how are you synchronising the values?
When you wrote that with a Slider::Listener by hand, it is easy to forget to update the slider from a parameter change…

Edit: corrected the valid check…


#5

Note that when you use operator=, you don’t specify an undomanger so it can’t possibly record the operation!

ValueTree::operator= isn’t really changing any data, you should think of it more like a smart-pointer assignment. None of the trees themselves change, so there’s nothing to undo/redo.

What you’re really attempting to do is to deep-copy the tree, and now that you come to mention it, that’s a function that’s missing from the class. In Tracktion we have a helper function to do it:

static inline juce::ValueTree copyValueTree (juce::ValueTree& dest, const juce::ValueTree& src, juce::UndoManager* um)
{
    if (! dest.getParent().isValid())
    {
        dest = src.createCopy();
    }
    else
    {
        dest.copyPropertiesFrom (src, um);
        dest.removeAllChildren (um);

        for (int i = 0; i < src.getNumChildren(); ++i)
            dest.addChild (src.getChild (i).createCopy(), i, um);
    }

    return dest;
}

…but TBH I should really move this into ValueTree to make it more widely useable, because there’s often a need to do this kind of thing.


#6

Hey Jules, hey Daniel,

thanks for the quick reply and the code snippet. But that I still have the same problem. The preset loads correct but no um.undo() possible or um.canUndo() = true. Changing a parameter via GUI undo works perfect.

@ Jules: I have implemented your function copyValueTree and call it as follows:

void PlugInAudioProcessor::ParameterReadFromFile(File file)
{
_ FileInputStream stream(file);_
_ ValueTree tree = ValueTree::readFromStream(stream);_
_ if (tree.isValid())_
_ {_
_ if (tree.hasType(PARAM_SETNAME))_
_ {_
_ copyValueTree(VTree->state, tree, um);_
_ }_
_ }_
_ return;_
}

Delcaration in PlugInAudioProcessor:

public
UndoManager* um;
AudioProcessorValueTreeState* VTree;

@ Daniel:
I´m using AudioProcessorValueTreeState::SliderAttachment, ComboBoxAttachment etc.
I think, that´s what you ask for.

Do you have any idea what’s going wrong?

Thank you for your answers.


#7

If the preset is loading ok but you can’t undo then probably VTree->state is invalid. As you can see in Jules’ function if the dest-tree is invalid you just copy your source tree without the undomanager knowing what’s going on.

My guess is that you didn’t initialize your AudioProcessorValueTreeState.
Try VTree->state = ValueTree(“STATE”); after you’ve created it.


#8

Yes, I wanted to rule out the option, that the values actually change but you don’t see it, if you would have implemented the updates only one way. But in this case it should be fine.

If you post code, you can use three backticks ``` on a single line before and after the code, then it will be formatted as code and preserve the indent.


#9

We’re on the trail! The query in Jules function executes the if case, so

VTree. isValid() = False

is identified.

VTree is initialized

VTree->state = ValueTree(VtID)

with VtID

Identifier VtID = Identifier(“STATE NAME”)

after

createAndAddParameter

and

addParameterListener.

So VTree->state has no valid parent. But what could be the reason?


#10

The function I quoted was just an example of how you might copy state, and it made the assumption that if the tree you’re copying is just “loose” with no parent, then there’s no point in all the extra work of copying its internal bits over. Obviously if you have different assumptions in your code, then you’d need to do things differently.

BTW I already added a function that probably does what you want: ValueTree::copyPropertiesAndChildrenFrom()


#11

Problem could be solved! I modified the else case. I had misunderstood the intenion of the code example.
Thanks to all involved