A few questions about moving ValueTrees

Briefly, my questions are:

  1. is it possible to change the type of a ValueTree?
  2. What should I do with a ValueTree that no longer points to meaningful data?

Here is a more detailed explanation:

I am writing a function which will demote this ValueTree to a grandchild position on the tree, while changing its type. It’s a bit easier to picture than to describe, so here is an XML representation:

BEFORE OPERATION:

<roottype>
    <mytype property1="1" property2="2"/>   // This is the element we're going to operate on.
</roottype>

AFTER OPERATION:
<roottype>
    <mytype>
        <newtype property1="1" property2="2"/>  // operation has "moved" the element to a grandparent position, and changed its type.
    </mytype>
</roottype>

Changing the type of a ValueTree seems to be more complicated than it would first appear (question 1). From what I can tell, the only way of doing it is to create a new one of the new type and then copy the properties and children across. This makes things a bit more complicated, and I’m wondering what to do with the old ValueTree once it has been copied from (question 2).

Here’s what I’ve got so far. (Note that I haven’t tested the code yet, so it might be buggy.)

void changeTypeAndMoveDown(ValueTree vtToMove&, const Identifier& newType)
{                                                                                                   // If this is a root ValueTree then the function isn't going to work.
    jassert(vtToMove.getParent().isValid());
                                                                                                    // We'll need to know where this ValueTree's index is in its parent.
    int myIndexInParent = vtToMove.getParent().indexOf(vtToMove);
                                                                                                    // With this information, we can remove it from its parent...
    vtToMove.getParent().removeChild(myIndexInParent, nullptr);
                                                                                                    // ...and replace it with a new ValueTree of the same type.
    vtToMove.getParent().addChild(ValueTree(vtToMove.getType()), myIndexInParent, nullptr);
                                                                                                    // We can't change the type of a ValueTree, so we'll have to create a new one...
    auto movingValueTree = ValueTree(newType);
                                                                                                    // ...and copy all of the properties and children across.
    movingValueTree.copyPropertiesAndChildrenFrom(vtToMove, nullptr);
                                                                                                    // Finally, we'll append the new ValueTree to the new child.
    vtToMove.getChild(myIndexInParent).appendChild(movingValueTree, nullptr);
                                                                                                    // Do we also need to reset the original ValueTree, since it
    vtToMove = movingValueTree;
}

My question is, what should I do with vtToMove now that its data has been copied to movingValueTree and removed from the tree? In the example code I’m ‘resetting’ it to match the new newly created, newly typed movingValueTree node, but I’m not sure that this is the right thing to do. If it is the right thing to do, is the old data deleted automatically, now that there aren’t any living ValueTrees pointing to it? Or should I run removeAllChildren() and 'removeAllProperties()` first?

Yes, does seem to be the case if you want to make a tree with the same content but different type tag

It does seem rather confusing, although I’m not sure I even understand the use case of placing the new copy (“newtype”) inside a node with the old type (“mytype”)…

Perhaps the function should set the vtToMove argument to equal the new node (“newtype”) and then return the parent node (“mytype”)?

Here is the use case:

I am building a split-window component where the user can right-click on any window and split it either vertically or horizontally, so that the screen can look like this…

  ----------------------------------
 |                       |          |
 |                       |          |
 |                       |    (2)   |
 |           (1)         |          |
 |                       |----------|
 |                       |          |
 |                       |    (3)   |
 |                       |          |
  ----------------------------------

…but with separator lines anywhere you like.

The system is modeled by a ValueTree tree, which contains the hierarchy of windows and all of their positions. I am using the following two rules to model the ValueTree tree:

  1. A node can have any number of children, but the children must all be of the same type (vertical or horizontal).
  2. Only the leaf nodes actually display their content (which is determined by their Properties).

So here is an example of a ValueTree tree that would model the screen depicted above:

<root>
    <vertical_window display="display1"/>
    <vertical_window>
        <horizontal_window display="display2"/>
        <horizontal_window display="display3"/>
    <vertical_window/>
</root>

I’ve got the system working pretty well so far, and it’s already possible to add new split windows, so long as they are of the same type. So, for instance, if the user right-clicks on display 3 and selects “split window horizontally”, then this happens:

<root>
    <vertical_window display="display1"/>
    <vertical_window>
        <horizontal_window display="display2"/>
        <horizontal_window display="display3"/>
        <horizontal_window display="display4"/>
    <vertical_window/>
</root>

  ----------------------------------
 |                       |          |
 |                       |          |
 |                       |    (2)   |
 |           (1)         |          |
 |                       |----------|
 |                       |    (3)   |
 |                       |----------|
 |                       |    (4)   |
  ----------------------------------

But what happens when the user right-clicks on display 2 and selects “split window vertically”? By rule 1, I cannot add a vertical window to a list of horizontal windows, so I have to jump down a level. By rule 2, I now have to copy window 2’s display properties to a new leaf. So what I want to achieve is this:

<root>
    <vertical_window display="display1"/>
    <vertical_window>
        <horizontal_window>
            <vertical_window display="display2"/>
            <vertical_window display="display5"/>
        </horizontal_window>
        <horizontal_window display="display3"/>
        <horizontal_window display="display4"/>
    <vertical_window/>
</root>

  ----------------------------------
 |                       |    |     |
 |                       |    |     |
 |                       | (2)| (5) |
 |           (1)         |    |     |
 |                       |----------|
 |                       |    (3)   |
 |                       |----------|
 |                       |    (4)   |
  ----------------------------------

This is what the function I described in my first post is trying to achieve, and it’s proving to be mode perplexing than I first thought. Changing the type of the node and moving it to a location on another branch are operations that normal ValueTree operations don’t allow for easily, although I’m sure that there’s a way.

One additional constraint I’m facing is that the display Components themselves mimic the ValueTree structure through listeners, so whatever operations I perform on the ValueTree hierarchy, the display Components have to be able to follow along. If when converting display2 from horizontal to vertical I have to create a new ValueTree and copy the properties across, this probably means destroying a Component and then creating it again. Ideally, I’d like valueTreeParentChanged() to fire so that I can std::move the component instead.

Gotcha, that makes sense!

It seems like you could simplify it down a bit, perhaps something like:

ValueTree splitView(ValueTree tree, const Identifier &type)
{
   ValueTree result(type);
   result.copyPropertiesFrom(tree, nullptr);
   tree.removeAllProperties(nullptr);
   tree.appendChild(result);

   // Add the other half of the newly split view here...

   return result;
}

So to take your example, splitting a view horizontally:

<root>
    <vertical_window display="display1"/>
</root>

Becomes:

<root>
    <vertical_window>
        <horizontal_window display="display1"/>
        <horizontal_window display="display2"/>
    </vertical_window>
</root>

So you’re keeping the vertical window but just transferring its properties downwards, I don’t think you need to remove it from its parent like in the original post.

It also seems like when you’re splitting a view, that view can’t have any children. Would you need to be copying the ValueTree children? Correct me if I’m misunderstanding here

Not to bikeshed too hard but why use a hierarchy instead of just keeping track of a Grid (or maybe something custom with the operations you need) and components in one container?

@TonyAtHarrison I can’t believe I didn’t think of this! Thanks for spelling it out–it’s really helpful.

@Holy_City there are several reasons that I opted for this approach. Probably the biggest is that alongside vertical and horizontal windows I also have tabs as a window type. I tried out a few approaches first, but ultimately the hierarchical one fit my needs and my knowledge best.

Another factor is that I am not familiar at all with CSS, so perhaps I don’t know what I am missing. But the ValueTree approach is working well, and also gives me file saving and undoing almost for free.

1 Like