SplitDockGrid - odd behavior when hiding/showing components

Hi Beni

I am creating a layout of dockables using SplitDockGrid. However when I hide and then show a component, it’s position seems to be inaccurate.

In the code sample below. first run it with line 42 commented out, and you will notice that the “RE” dockable appears below the “GT” dockable. This is the way it should be. Now uncomment the line we had commented out earlier (this causes all dockables except “GT” to be hidden followed by dockable “RE” being shown), and run it again. You will notice that “RE” now appears on the top of “GT”.

Is this a bug by any chance? Is there any way to fix this?


Thanks
Parag

import java.util.Map;
import java.util.Set;

import javax.swing.JFrame;

import bibliothek.gui.DockFrontend;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.station.split.SplitDockGrid;

public class DockingGridFillIssue
{
	private static DockFrontend frontend;
	private static Map<String, Dockable> dockables = 
		new HashMap<String, Dockable>();
	
	public static void main(String args[])
	{
		SplitDockGrid splitDockGrid = new SplitDockGrid();
		splitDockGrid.addDockable(0, 0, 3, 10, getDockable("FP"));
		splitDockGrid.addDockable(0, 0, 3, 10, getDockable("MP"));
		splitDockGrid.addDockable(0, 0, 3, 10, getDockable("MGP"));
		splitDockGrid.addDockable(0, 0, 3, 10, getDockable("PIP"));
		splitDockGrid.addDockable(3, 0, 5, 8, getDockable("GT"));
		splitDockGrid.addDockable(3, 8, 7, 2, getDockable("RE"));
		splitDockGrid.addDockable(8, 0, 2, 8, getDockable("CP"));
		
		frontend = new DockFrontend();
		SplitDockStation station = new SplitDockStation();
		frontend.addRoot(station, "rootDockingStation");
		
		station.dropTree(splitDockGrid.toTree());
		
		JFrame frame = new JFrame();
		frame.add(station.getComponent());
		frame.setBounds(10, 30, 600, 400);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);		
		
		hideAndShowDockables();
		
	}
	
	private static void hideAndShowDockables()
	{
		//hide all dockables except "GT"
		Set<String> keys = dockables.keySet();
		for(String key : keys)
		{
			if(!"GT".equals(key))
			{
				frontend.hide(dockables.get(key));
			}
		}
		
		//show "RE"
		frontend.show(dockables.get("RE"));
		
	}

	private static Dockable getDockable(String title)
	{
		DefaultDockable dockable = new DefaultDockable(title);
		dockables.put(title, dockable);
		return dockable;
	}
}

There are two issues resulting in the wrong location.

  1. DockFrontend does not store location information of Dockables which are not registered (this prevents memory leaks). So you have to register all Dockables, like this:
			frontend.addDockable( entry.getKey(), entry.getValue() );
		}```

2. Now RE will appear right of GT. If you check the order in which the dockables are removed, you will notice that RE ist the first one to be removed. On the SplitDockStation dockables are organized in a binary tree, the nodes are either horizontal or vertical. The first node of this tree is a horizontal node, and RE is placed in the right sub-tree. When making RE visible again later, SplitDockStation tries to put it on the right sub-tree again, resulting in RE being placed right of GT.

Hi Beni,

You are right. However, this messes up the location of certain dockables when they are displayed (in the event that some other dockables which could have defined a proper structure are hidden)

I vaguely remember discussing something similar with you a long time back. Is it possible to implement another strategy for the way dockables are represented, so that dockables maintain a certain position on a grid when they are displayed.

I realize that when they are hidden, their place will be taken by the visible components. When the hidden dockable is shown again we will have to figure out how to resize the component which takes their place. So some assumptions will have to be made. I am not very clear of all the roadblocks we will have to deal with.

However in a abstract sense, does a grid based strategy for representing the location of dockables at runtime, seem reasonable? If so, is it possible to implement it?


Thanks & Regards
Parag Shah

I assume you don’t want to change the way SplitDockStation internally works, because that would be a project for some weeks… :wink:

I’d like to explain why I used the tree-based approach, and why I think it is better than a grid-based approach. If you’d like to experiment yourself, I can tell you which methods to override.

The current way, storing the position in the tree, is actually the second try to get SplitDockStation working properly. The first attempt was using the actual position of the Dockable on its parent (the x/y/width/height properties), as I understand this was a „grid based approach“.

If you wish you can reactivate the old behavior and make comparisons by overriding „getDockableProperty“ of SplitDockStation, and just return the result of „getDockableLocationProperty“.

All approaches have problems when the Dockables are removed in another order than they are later added. At least for the two implementations I made it is easy to come up with some bad examples. An additional complication is that Dockables can be „merged“ (put on a StackDockStation). When adding a Dockable to a SplitDockStation the station has not only to decide where to put the Dockable, but also whether to merge it with another element.

Let me give an example how it worked in earlier versions with the grid-based approach: if a Dockable is dropped on a SplitDockStation and its grid-coordinate tell the station that the dropped Dockable overlaps another Dockable by more than 75%, then the two Dockables are merged (put onto a StackDockStation). While it is possible to play around with the threshold, it looks very awkward if a dropped Dockable squeezes the other Dockables to an almost invisible sizes.

Assume having two Dockables ‚a‘ and ‚b‘, ‚a‘ takes 20% of the space, ‚b‘ takes 80% of the space. Now ‚b‘ is removed, and added again. Because of the threshold ‚a‘ and ‚b‘ are merged, resulting in a layout that differs from the original.

If using tree-information this merging does not happen, because with the location in the tree the SplitDockStation knows exactly that the two elements are meant to be in different sub-trees.

Finally I decided to stick with the tree-based approach because all the tree-locations together carry enough information to exactly rebuild the original layout, while all the grid-locations together may not carry enough information.

Hi Beni,

I re-activated the old behavior and it does eliminate the current layout problem, but as you rightly suggested, I was able to come up with a use case where the layout got messed up.

I also agree that all approaches will have some drawbacks, but I am trying to think if there is an approach which has fewer issues.

I tried to figure out what’s happening in code, and would like to verify my understanding of how internal data structures work in DockingFrames. Let’s assume we are using the new approach (which uses SplitDockPathProperty) and have such a layout:


  • BBBBB*CCCCC AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • BBBBB*CCCCC AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • BBBBB*CCCCC AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • BBBBB******* AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA*
  • BBBBB* DDDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  • BBBBB* DDDDD************************************** *
  • BBBBB* DDDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE

Now let’s assume I hide these in this order: D, C, B, E

and then if I show D, I get the following layout:


AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD


whereas I would have preferred to get (because before it was hidden D was to the left of A)


DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
DDDDDDDDDDDDD * AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


I tried debugging and realized a few things which I would like to confirm with you.

I noticed two data structures are being used:

The first one (I believe this is the data structure you mentioned as the binary tree in your reply) is the abstract representation of dockables. It has a Root object at it’s root, and SplitNode, and Leaf nodes under it. I will refer to this as the binary tree.

The second one is the DockInfo which among other things stores the location of dockables.

When I hide D, the binary tree has the full representation of all the dockables present and their SplitDockStations. Regarding the location, at this point the location of D has 3 parent nodes.

Now I hide C, B, and E. This causes several SplitDockStation’s to become redundant, because now there is just one component which is being showed. Hence the binary tree now only has a root node and a leaf (containing A). However, the location of D still has the 3 parent nodes.

Now when I show D, DockingFrames will try to insert it into the binary tree. First the root node’s insert is called, and then immediately the Leaf node’s insert is called. There is a mis-match (in terms of the number of parents D had) between the binary tree which existed when D was hidden, and the binary tree which exists now. This mismatch causes D to appear at the bottom of A, instead to the left of A.

Now comes the part of which I am not very sure about. This mismatch is because, when a dockable is hidden, a SplitDockStation might become redundant. In such cases, both the dockable and the SplitDockStation are removed from the binary tree.

If my understanding is correct, I feel that we may be able to eliminate the problem by retaining the entire binary tree, even when dockables are hidden. The nodes which are no longer needed can be marked as hidden so they are not rendered. While rendering the view, we ignore the hidden nodes (is this possible, or will we run into issues? ).

Now when a dockable is shown (after being hidden), we will have to mark some of the hidden nodes as shown. We un-hide not only the dockable which is being shown, but also any SplitNode parents which earlier contained the dockable. This will ensure that if D was at the left of A just before it was hidden, it will still appear at the left of A when it is shown, instead of appearing at the bottom of A.

Does this make sense… is it reasonable to implement it?

Please accept my apologies for subjecting you to such a long post, and thanks a lot for taking the time to read it :slight_smile:


Thanks & Regards
Parag Shah

Yes, Root, Node and Leaf, all extending SplitNode, are the SplitDockStations representation of the Dockables. DockInfo contains the information DockFrontend requires for a Dockable. They are not related, SplitDockStation knows nothing about DockInfo, and DockFrontend knows nothing about the tree.

When I hide D, the binary tree has the full representation of all the dockables present and their SplitDockStations. Regarding the location, at this point the location of D has 3 parent nodes.

Now I hide C, B, and E. This causes several SplitDockStation’s to become redundant, because now there is just one component which is being showed. Hence the binary tree now only has a root node and a leaf (containing A). However, the location of D still has the 3 parent nodes.

First of all, there are no SplitDockStations becoming redundant, because there is only one. What becomes redundant are the nodes of the tree. But the rest you say is correct, the tree shrinks but the location-information related to d remains unchanged.

Now when I show D, DockingFrames will try to insert it into the binary tree. First the root node’s insert is called, and then immediately the Leaf node’s insert is called. There is a mis-match (in terms of the number of parents D had) between the binary tree which existed when D was hidden, and the binary tree which exists now. This mismatch causes D to appear at the bottom of A, instead to the left of A.

Actually no, when D was removed the first node in the tree was a left-right node, causing D to appear left of A. I’m quite sure about this, because I wrote your example as code (attached at the end of this post).

Now comes the part of which I am not very sure about. This mismatch is because, when a dockable is hidden, a SplitDockStation might become redundant. In such cases, both the dockable and the SplitDockStation are removed from the binary tree.

When a Dockable is removed from a SplitDockStation, the tree shrinks. And if the tree shrinks too much the Dockable cannot be reattached at its old location (because there is no longer any parent node at the required location).

If my understanding is correct, I feel that we may be able to eliminate the problem by retaining the entire binary tree, even when dockables are hidden. The nodes which are no longer needed can be marked as hidden so they are not rendered. While rendering the view, we ignore the hidden nodes (is this possible, or will we run into issues? ).

We run into issues:

  1. it is a memory leak: SplitDockStation would store information about Dockables that have been removed from the framework. In the core framework being known to the DockController and being visible to the user is the same thing. So how to prevent an evergrowing tree?
  2. what about storing the layout into a file? The SplitDockStation later would require to map the leafs of the tree to newly created, or not yet existing dockables. This could be done if every Dockable would have a unique identifier (a String or a number), but unique ids are unknown to the core framework.

p.s.: in this context, DockFrontend is not part of the core framework. As its name suggest, it is a frontend providing easier access.

Does this make sense… is it reasonable to implement it?

I think the basic idea would work. Dockables would appear at their correct position. However it contradicts several design decisions I made a long time ago and that are now hard to undo or change. I’ll sure let this suggestion wander around in my head. Perhaps there is another way to achieve similar effects?

Code to your example.

    	JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(500, 500);
        frame.setLayout(new BorderLayout());
        final CControl control = new CControl(frame);

        frame.add(control.getContentArea(), BorderLayout.CENTER);
        
        CGrid grid = new CGrid( control );
        
        DefaultSingleCDockable a = new DefaultSingleCDockable( "a", "a" );
        DefaultSingleCDockable b = new DefaultSingleCDockable( "b", "b" );
        DefaultSingleCDockable c = new DefaultSingleCDockable( "c", "c" );
        DefaultSingleCDockable d = new DefaultSingleCDockable( "d", "d" );
        DefaultSingleCDockable e = new DefaultSingleCDockable( "e", "e" );
        
        grid.add( 0, 0, 4, 6, b );
        grid.add( 4, 0, 4, 3, c );
        grid.add( 4, 3, 4, 3, d );
        grid.add( 8, 0, 10, 5, a  );
        grid.add( 8, 5, 10, 1, e );
        
        control.getContentArea().deploy( grid );
        
        frame.setVisible(true);
        
        d.setVisible( false );
        c.setVisible( false );
        b.setVisible( false );
        e.setVisible( false );
        
        d.setVisible( true );
    }```

ok, now I understand this. There is only 1 SplitDockStation, and various nodes in the abstract tree are managed by setting bounds of the components in SplitDockStation$Content.

Do you mention the memory leak because leafs hold on to their dockables? Could we allow hidden leafs to release their dockables, if so desired? It should also be possible to hold on to the dockables (for speed performance when a dockable is re-shown).

Are you thinking of Serializing components here?

Thanks :slight_smile:


Regards
Parag Shah

Do you mention the memory leak because leafs hold on to their dockables? Could we allow hidden leafs to release their dockables, if so desired? It should also be possible to hold on to the dockables (for speed performance when a dockable is re-shown).

Yes, leafs hold on to their dockables. But when to release? You can’t have the developer doing this, because he will forget (or just not know). The core framework does not have a state „invisible“, just a state „exists“ and „does not exist“. And changing the core framework is a very tricky thing.

Are you thinking of Serializing components here?

Serializing components? That is a sick idea :wink:
I’m thinking of a mapping between the Components (Dockables) and their location information. For example „dockable abc“ is at „position 123 in SplitDockStation xyz“. After thinking over it is not such a problem as I thought before.
I thought because Dockables do not have a unique identifier the mapping „abc“ to „123“ is very hard. But there is no rule forbidding me to assign temporarily identifiers when storing the layout.