[Erledigt] Size of dockables when they are moved

Hi,

I noticed some issues with the size of dockables when they are moved so that they are tabbed together and then split again. To reproduce the problem:

  1. Run the ‘help’ demo
  2. Close ‘Classes’, ‘Constructors’, ‘Fields’, ‘Methods’, and ‘Hierarchy’
  3. You should now see ‘Packages’, and ‘Content’
  4. Resize, so ‘Packages’ take roughly 30% and ‘Content’ takes 70%
  5. Drag ‘Packages’ over ‘Content’, so they get tabbed together
  6. Move ‘Packages’ out of the tab so that it is split with ‘Content’ like it was earlier

You will notice the size of ‘Packages’ has changed.

I tried digging into the code, and from what I understand, old size of dockables when they are moved around is not stored, which is why we are unable to bring ‘Packages’ back to it’s original size.

I have tried to fix this by listening to events when dockables are dragged. Every time a dockable is dragged, it’s size (as a percentage) is saved for later use. When that dockable is dropped back into a SplitDockStation, it retains it’s original percent of screen area (in relation to the SplitDockStation).

I have created a custom SplitLayoutManager and have re-implemented the calculateDivider() method.

Posting the code in a separate post.

Thanks & Regards
Parag

Posting my code (since the forum does not allow posts beyond 11000 chars, I may have to split this into multiple posts :slight_smile: ) …

Here are the classes I have implemented.

LbSplitDockStationFactory creates SplitDockStation objects that use LbSplitLayoutManager.


public class LbSplitDockStationFactory extends SplitDockStationFactory
{
    @Override
    public SplitDockStation createStation()
    {
        SplitDockStation splitDockStation = new SplitDockStation();
        splitDockStation.setSplitLayoutManager(new LbSplitLayoutManager());
        splitDockStation.setDividerSize(1);        
        return splitDockStation;
  }
}

LbSplitLayoutManager overrides the calculateDivider method.


public class LbSplitLayoutManager extends DefaultSplitLayoutManager
{
    private static final double MINIMUM_ORIGINAL_SIZE = 0.25;
    private static final Dimension cDefaultMinimumSize = new Dimension(10, 10);
    //cache LbDockableSizeManager since it will be needed very often
    LbDockableSizeManager _lbDockableSizeManager = null;
    
    @Override
    public void calculateDivider( SplitDockStation station, 
                                            PutInfo putInfo, 
                                            Leaf origin )
    {
        
        Dockable dockable = putInfo.getDockable();
        if(origin != null)
        {
            super.calculateDivider(station, putInfo, origin);
        }
       // LbDockableSizeManager.getSize(dockable);
        if(_lbDockableSizeManager == null)
        {
            _lbDockableSizeManager = 
                LbDockableSizeManager.getInstance(station.getController());
        }
        double percentSize = _lbDockableSizeManager.getOldSize(dockable); 
        
        if(percentSize == 0) //we do not have a stored size
        {
            super.calculateDivider(station, putInfo, origin);
        }
        else
        {
            SplitNode other = putInfo.getNode();
            Dimension oldSize = dockable.getComponent().getSize();                      
         int size = Math.min( oldSize.width, oldSize.height );
         
            double divider = 0.5;
            if( putInfo.getPut() == PutInfo.Put.TOP )
            {
                divider = 
                    validateDivider(station, 
                                         percentSize, 
                                         cDefaultMinimumSize, //dockable.getComponent().getMinimumSize(),
                                         cDefaultMinimumSize, //other.getMinimumSize(), 
                                         Orientation.VERTICAL, 
                                         other.getWidth(), 
                                         other.getHeight());
                if( divider > 1 - MINIMUM_ORIGINAL_SIZE )
               divider = 1 - MINIMUM_ORIGINAL_SIZE;
            }
            else if(putInfo.getPut() == PutInfo.Put.BOTTOM)
            {
                divider = 
                    validateDivider(station, 
                                         1.0-percentSize,
                                         cDefaultMinimumSize, //other.getMinimumSize(),
                                         cDefaultMinimumSize, //dockable.getComponent().getMinimumSize(),                                          
                                         Orientation.VERTICAL, 
                                         other.getWidth(), 
                                         other.getHeight());
                if( divider < MINIMUM_ORIGINAL_SIZE )
               divider = MINIMUM_ORIGINAL_SIZE;
            }
            else if(putInfo.getPut() == PutInfo.Put.LEFT)
            {
                divider = 
                    validateDivider(station, 
                                         percentSize, 
                                         cDefaultMinimumSize, //dockable.getComponent().getMinimumSize(),
                                         cDefaultMinimumSize, //other.getMinimumSize(), 
                                         Orientation.HORIZONTAL, 
                                         other.getWidth(), other.getHeight() );
                    if( divider > 1 - MINIMUM_ORIGINAL_SIZE )
                        divider = 1 - MINIMUM_ORIGINAL_SIZE;
            }
            else if(putInfo.getPut() == PutInfo.Put.RIGHT)
            {
                divider = 
                    validateDivider(station, 
                                          1.0-percentSize, 
                                          cDefaultMinimumSize, //other.getMinimumSize(),
                                          cDefaultMinimumSize, //dockable.getComponent().getMinimumSize(),                                          
                                         Orientation.HORIZONTAL, 
                                         other.getWidth(), 
                                         other.getHeight() );
                if( divider < MINIMUM_ORIGINAL_SIZE )
               divider = MINIMUM_ORIGINAL_SIZE;
            }
            putInfo.setDivider(divider);
            putInfo.setOldSize(size);
        }
    }
}

LbDockableSizeManager saves the size of a dockable when it is dragged.


Thanks & Regards
Parag


/**
 * This class manages the size of dockables as they are moved around. With the
 * default implementation of docking frames, when a component is dragged out
 * of a plit docking station and into a tabbed station, it does not retail it's
 * earlier size when it is dragged out of the tabbed region into another split
 * region.
 * 
 * There will exist one instance of this class per DockController.
 * 
 */
public class LbDockableSizeManager
{
    private static Map<DockController, LbDockableSizeManager> _managers;
    private Map<Dockable, Double> oldSizes = new HashMap<Dockable, Double>();
    
    static
    {
        _managers = new HashMap<DockController, LbDockableSizeManager>();
    }
    
    private LbDockableSizeManager()
    {
        
    }
    
    public static synchronized 
        LbDockableSizeManager getInstance(DockController controller)
    {
        LbDockableSizeManager manager = _managers.get(controller);
        if(manager == null)
        {
            manager = new LbDockableSizeManager();
            _managers.put(controller, manager);
        }
        return manager;
    }
    
    /**
     * Returns the old size (as a percentage of the size of the parent compoent) 
     * of the specified dockable. This size will be used for the dockable before
     * dropping it in a split panel.  
     * @param dockable
     * @return The size that should be used for dropping this dockable. This size 
     *           is specified as a percent of the size of the parent component.
     */
    public double getOldSize(Dockable dockable)
    {
        double size = 0.0;
        Double dSize = oldSizes.get(dockable);
        if(dSize != null)
        {
            size = dSize.doubleValue();
        }
        return size;    
    }
    
    /**
     * Tracks the size of dockables when a drag is initiated on them.
     * @param frontend
     */
    public void trackSizeChanges(DockFrontend frontend)
    {
        frontend.
        getController().
            getRelocator().addDockRelocatorListener(new DockRelocatorAdapter()
        {
            @Override
            public void init(DockController controller, Dockable dockable)
            {
                //we are dragging out of a split dock station
                if(needToTrackChange(dockable))
                {
                    double dockableSize = computeRelativeSizeOfDockable(dockable);
                    put(dockable, dockableSize);
                }        
            }            
        });
    }
        
    /**
     * If the dockable is in a tabbed dock station such that this station is the
     * only station in our root dock station then we do not want to track the
     * size of a dockable when it dragged out of this station (because that 
     * dockable occupies 100% of the available area so it does not have any 
     * relative size to it's split dock station parent).
     *  
     * @param dockable
     * @return
     */
    private boolean needToTrackChange(Dockable dockable)
    {
        SplitDockStation parentStation = getFirstAncestorSplitDockStation(dockable);
        boolean needToTrack = false;
        if(parentStation != null)
        {
            Root root = parentStation.getRoot();
            SplitNode child = root.getChild();
            if(child instanceof Node)
            {
                needToTrack = true;
            }
        }
        return needToTrack;        
    }

    /**
     * Computes the size of a dockable in relation to it's parent/ancestor
     * split docking station
     * @param dockable
     * @return
     */
    private double computeRelativeSizeOfDockable(Dockable dockable)
    {
        double size = 0.0;        
        SplitDockStation station = getFirstAncestorSplitDockStation(dockable);
        if(station != null)
           {
            SplitDockStation splitDockStation = (SplitDockStation)station;
               SplitNode splitNode = splitDockStation.getRoot().getChild();
               double stationSize = 0.0;
               double dockableSize = 0.0;
               SplitDockStation.Orientation orientation = 
                   SplitDockStation.Orientation.HORIZONTAL; 
               if(splitNode instanceof Node)
               {
                   orientation = ((Node)splitNode).getOrientation();
               }
               else
               {
                   System.out.println("The Node object representing this " + 
                                   "SplitDockStation does not have an orientation. Using " +
                                   "default orientation of HORIZONTAL"    + splitDockStation);
               }
               if(orientation.equals(SplitDockStation.Orientation.HORIZONTAL))
               {
                   stationSize = splitDockStation.getWidth() - 
                                       (splitDockStation.getInsets().left + 
                                               splitDockStation.getInsets().right);
                   dockableSize = dockable.getComponent().getWidth();
               }
               else
               {
                   stationSize = splitDockStation.getHeight() -
                                         (splitDockStation.getInsets().top + 
                                             splitDockStation.getInsets().bottom);;
                   dockableSize = dockable.getComponent().getHeight();
               }
               size = dockableSize/stationSize;
           }
        return size;
    }
    
    private SplitDockStation getFirstAncestorSplitDockStation(Dockable dockable)
    {
        Dockable currentDockable = dockable;
        SplitDockStation splitDockStation = null;
        while(currentDockable.getDockParent() != null)
        {
            DockStation station = currentDockable.getDockParent();
            if(station instanceof SplitDockStation)
            {
                splitDockStation = (SplitDockStation)station;
                break;
            }
            else if(station instanceof Dockable)
            {
                currentDockable = ((Dockable)station);
            }
            else
                break;
        }
        return splitDockStation;
    }
    
    private void put(Dockable dockable, Double size)
    {
        oldSizes.put(dockable, size);
    }
}

Here’s the client code that creates the frontend and sets things up.


 LbSplitDockStationFactory splitDockStationFactory = new LbSplitDockStationFactory();
 _frontend = createFrontend(splitDockStationFactory);
 _rootDockingStation = splitDockStationFactory.createStation();
 _frontend.addRoot(_rootDockingStation, ROOT_DOCKING_STATION);
 
 private DockFrontend createFrontend(LbSplitDockStationFactory factory)
 {
     DockFrontend frontend = new DockFrontend();
     frontend.registerFactory(factory);
     //add dockale size management capability
     LbDockableSizeManager.getInstance(frontend.getController()).trackSizeChanges(frontend);        
     return frontend;    
 }

This approach seems to work for me, however there is a little problem. Every time I move a dockable from a split to a tab and then back to a split, it loses a couple of pixels. Perhaps I am doing something wrong when I calculate the percentage in LbDockableSizeManager.computeRelativeSizeOfDockable(Dockable dockable).

I would like to ask you if the approach I have taken is appropriate. Is it possible to do something so I do not lose those pixels when dockables are moved around?

If the approach is reasonable, can we integrate this or something similar in the next release of DockingFrames (you are welcome to use code I have pasted)?


Thanks & regards
Parag

Wow, you really did dig into the framework, that looks impressive.

I’ll sure check it out at the weekend (too tired during the week).

The only issue I see right now is: what happens if a Dockable gets removed? The LbDockableSizeManager seems not to notice and a reference remains. But I’m sure that can be solved. Perhaps the presence of a DockFrontend will be necessary to ensure not too much gets removed. Hm… we will see…

If this code works as promised, then I think it should be integrated. Either as extension or if possible as replacement of the default-layout.

And you earn a place on the „contributors“-list :stuck_out_tongue:

Btw. you seem to write your own framework… for what stands „Lb“? And will we ever see the application you are working on?

Thanks… no problems, take your time.

Very good point about lingering references. Perhaps I should add a listener in LbDockingSizeManager to remove references to dockable sizes if the dockable is removed.

If this code works as promised, then I think it should be integrated. Either as extension or if possible as replacement of the default-layout.

And you earn a place on the „contributors“-list :stuck_out_tongue:

Cool! Thanks :slight_smile:

Btw. you seem to write your own framework… for what stands „Lb“? And will we ever see the application you are working on?

Lb stands for LogicBlox (the company I work for). I will definitely try and arrange to show you the product.

I am actually working on a product. We have started using docking in several places where a component has multiple child components in tabs or splits.


Thanks and Regards
Parag Shah