StackDockStaionFactory PredefinedSituation (1.0.8)

hi

Just ran into this issue with 1.0.8, it looks like it has to do with switching between 2 different situations with a common tab, I’m working on an example but just in case it is obvious I’ll describe the scenario:
Situation 1 StackDock with 2 DefaultDockables : DA,DB.
Situation 2 StackDock with 1 DefaultDockable : DA.
Start in situation 2.
Go to situation 2 -> 1 shows DA,DB both docks are selectable
Go to situation 1 -> 2 shows DA
Go to situation 2 -> 1 shows DA,DB but DA is not selectable any more.

The reason for DA not being selectable is because it no longer has a parent (even though it appears to be part of the stackdockstation). Tracing the situation it appears that this may be caused by StackDockStaion.setLayout method, since it removes all the child docks, causing the shared dock DA to loose its parent. (I am not sure where the parent are assigned when a situation is changed).

I’ll setup a test case asap for you, but any thoughts ?

thanks
Nz

Not many. Just that StackDockStations with only one child are normally automatically removed by the framework and you should not even be able to create that situation.

The new parent is set in the method „public void setPlaceholders( PlaceholderMap placeholders, final Map<Integer, Dockable> children )“ of StackDockStation. The method does not do any ‚evil‘ thing, I don’t see much that can go wrong in it.

An example would be highly appreciated :wink:

Of course my simple test worked flawlessly… But after more research (and thanks for your help) the situation is beginning to make more sense… Given the following

SplitDockStation : SPLIT
StackDockStation : STACK
Dockable : DA, DB, DC

SCENARIO A
SPLIT has DA & STACK
The STACK has DB, DC

SCENARIO B
SPLIT has DA & STACK
The STACK has DB

When I follow the steps through going “SCENARIO B”->“SCENARIO A” I found the following
Dockable DB was actually located as a child of SPLIT (Since the STACK only had one tab merged up), but since the scenario defined DB,DC as part of the STACK the STACK component was reused and DB,DC was assigned as children. Which is correct, BUT when SPLIT was being laid out it had DA,DC as children so it set the parent to NULL (SplitDockStation.removeDisplayer()) for these dockables and then it (the SPLIT) added the DA and STACK components.

This explains my situation exactly, Ill try to come up with a test scenario for you but I think either you should not set a dockparent to null when the station is not the dock parent or have the dockable remove itself from the station when the dockparent is changed…

NZ

Once I figured out the exact issue a test case was easy, in this case the BLUE dockable is removed and the red panel loses the parent and cannot be selected. Clicking “GO” should toggle between the two scenarios


public class Test extends JFrame {
    
    private Dockable blue;
    boolean one=true;
    public Test() throws Exception {
        DockFrontend frontend = new DockFrontend( this );
        SplitDockStation split = new SplitDockStation();
        final StackDockStation station = new StackDockStation();
        this.add( station.getComponent() );
                Dockable red;
                station.add(red = createDockable( "Red", Color.RED ) , 0);
                station.add(blue = createDockable( "Blue", Color.BLUE ) , 1);
                
                Dockable green = createDockable( "green", Color.GREEN );
        SplitDockProperty sdp = new SplitDockProperty(0, 0, .5, 1);
        split.drop(station, sdp);
        sdp = new SplitDockProperty(0.75, 0, .4, 1);
        split.drop(green, sdp);
                
        frontend.addRoot("aa", split );
//        
        frontend.getController().setTheme(new FlatTheme());
        this.getContentPane().add(split,BorderLayout.CENTER);

        final PredefinedDockSituation pdsa = new PredefinedDockSituation();
        //pdsa.put("root", station);
        
        HashMap<String, DockStation> stationMap = new HashMap<String, DockStation>();
        stationMap.put("root", split);
        XElement element = new XElement("layout");
        pdsa.put("root", split);
        pdsa.put("station", station);
        pdsa.put("red", red);
        pdsa.put("green", green);
        pdsa.put("blue", blue);
        pdsa.writeXML(stationMap, element);
        final StringBuffer  a = new StringBuffer();
        XIO.write(element, a);
        blue.getDockParent().drag(blue);
        final PredefinedDockSituation pdsb = new PredefinedDockSituation();
        stationMap = new HashMap<String, DockStation>();
        stationMap.put("root", split);
        element = new XElement("layout");
        pdsb.put("root", split);
        pdsb.put("station", station);
        pdsb.put("red", red);
        pdsb.put("green", green);
        pdsb.writeXML(stationMap, element);
        final StringBuffer  b = new StringBuffer();
        XIO.write(element, b);
        
        
        this.getContentPane().add(new JButton(new AbstractAction("Go"){

            public void actionPerformed(ActionEvent e) {
                try {
                    if (one) {
                        one=false;
                        pdsa.readXML(XIO.read(a));
                    }
                    else {
                        one=true;
                        pdsb.readXML(XIO.read(b));
                    }
                }
                catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                
            }}), BorderLayout.NORTH);
    }
    public static Dockable createDockable( String title, Color color ){
                JPanel panel = new JPanel();
                panel.setOpaque( true );
                panel.setBackground( color );
                return new DefaultDockable( panel, title );
        }
    
    /**
     * void
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
            Test t = new Test();
            t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            t.pack();
            t.setVisible(true);
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

Ok, that is a mean bug. What happens is that the SplitDockStation thinks that „red“ is its child, but in reality „red“ has already been assigned to the StackDockStation. „split“ then resets the parent of „red“ which results in the bug.

There are two reasons why this bug remained undetected: 1. it is a new one, only present since placeholders are available. 2. Any client not using a PredefinedDockSituation does not experience the bug because DockFrontend cleans up any relations between stations and dockables before applying a new layout.

I added some code to prevent this bug in the future (the fix is already in the repository).

For you there are some immediate solutions available:
[ul][li]Use the current head (user=anonymous, password=anon), altough a head can always have some issues…
[/li][li]Destroy the relations of stations and dockables before loading a layout. Basically a recursive method that calls „DockStation.drag“ for all children of a station.
[/li][li]Or use DockFrontend.save and DockFrontend.load instead of a DockSituation. These methods do almost exactly the same thing as you did (but you have to register „red“, „green“ and „blue“ using „DockFrontend.addDockable“)[/ul]
[/li]
Btw: In a real application I would not call „pdsa.put(„station“, station);“. You never know whether „station“ still exists, and the DockSituation can create new StackDockStations if needed.

Sorry for the inconvinience, I hope your can now work without interruptions :wink:

Hi

That makes a lot of sense. I really appreciate that you are able to resolve this issue so quickly. In my situation the layout is static except for switching between the two different predefinedsituations. So i extended the stations to modularlize the layout… I think the recursive drag should work for now… and i will take your advice about not putting the stations in the situation as the long term fix

thanks again
nz

Hi

I added the following code to empty the tree


        protected void clean( DockStation station ){
                try{
                        for( int i = station.getDockableCount()-1; i >= 0; i-- ){
                            System.out.println(i);
                            Dockable dockable = station.getDockable( i );
                                DockStation check = dockable.asDockStation();
                                if( check != null )
                                    clean( check);
// **** Got error without this
                                if (dockable.getDockParent()==station)
                                    station.drag(dockable);
                        }
                }
                finally{
                    
                }
            }    

This code is called from the Go buttons action like clean(split);

**** I found that one of the stations gave an error without the check for the DockParent (Station is not this dockables parent error)

But now I get the following NPE error


Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
	at bibliothek.gui.dock.station.support.PlaceholderList$Entry.access$100(PlaceholderList.java:808)
	at bibliothek.gui.dock.station.support.PlaceholderList$SubList.get(PlaceholderList.java:1472)
	at bibliothek.gui.dock.StackDockStation.getDockable(StackDockStation.java:594)
	at Test.clean(Test.java:138)

Any thoughts on how to prevent this ?

thanks
nz

I added


controller.getRegister().setStalled( true );

this fixed my clean method…

Sorry

That is probably the SingleParentRemover kicking in. Sorry, I did not think of that, but using “setStalled” is the right solution.