Docking inside externalized dialogs

Hi all,

I have a question about externalized/floating components:
is it possible to dock multiple docking components next to each other
inside an externalized dialog?
I know you can put them on top of each other and access through tabs.
What I would like to do is to detach one component from the main window
and then treat it as a separate docking area
(e.g. have two docking areas in a dual screen setup).

Thanks!

Maciej

The framework would support a floating SplitDockStation, the main issue is to get it there in the first place. There is an interface “Combiner” which originally was intended to handle cases like yours, but thinking about it: the interface currently just does not have enough power.

I’ll give the interface a bit more power, and I’ll write again once your request can be fullfilled easily. That will be in the next two days or so.

I’ve just uploaded a new version (1.1.0p1). The code below will work with it, if you drag a Dockable onto the screen, you can put another one at its side. You might have to do some fine-tuning, just ask if you don’t know how to solve a problem.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.extension.gui.dock.station.SplitCombiner;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockTheme;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CMaximizeBehavior;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.theme.CBasicTheme;
import bibliothek.gui.dock.common.theme.CDockThemeFactory;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.themes.BasicTheme;
import bibliothek.gui.dock.themes.ThemePropertyFactory;
import bibliothek.gui.dock.util.DockUtilities;

public class Dock26 {
    public static void main( String[] args ){
        JFrame frame = new JFrame();
        CControl control = new CControl(frame);
        control.setMaximizeBehavior( new CustomCMaximizeBehavior() );

        // set a new theme using our Combiner
        control.getThemes().put( ThemeMap.KEY_BASIC_THEME, new CustomBasicThemeFactory( control ) );
        control.getThemes().select( ThemeMap.KEY_BASIC_THEME, true );
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize( 700, 700 );
    
        DefaultSingleCDockable red = create( control, "Red", Color.RED );
        DefaultSingleCDockable green = create( control, "Green", Color.GREEN );
        DefaultSingleCDockable blue = create( control, "Blue", Color.BLUE );
        
        CGrid grid = new CGrid( control );
        grid.add( 0, 0, 10, 10, red );
        grid.add( 10, 0, 5, 5, green );
        grid.add( 10, 5, 5, 5, blue );
            
        control.getContentArea().deploy( grid );
        frame.getContentPane().add( control.getContentArea(), BorderLayout.CENTER);
        frame.setSize(new Dimension(600, 600));
        frame.setVisible( true );
    }
    
    public static DefaultSingleCDockable create( CControl control, String title, Color color ){
        JPanel panel = new JPanel();
        panel.setOpaque(true);
        panel.setBackground(color);

        final DefaultSingleCDockable singleDockable = new DefaultSingleCDockable(title, title, panel);
        
        CPanelPopup popup = new CPanelPopup();
        JButton button = new JButton("Button");
        button.setPreferredSize(new Dimension(400, 300));
        popup.setContent(button);
        
        singleDockable.addAction(popup);

        return singleDockable;
    }

    // maximization of a floating SplitDockStation: this behavior will maximize the station too
    private static class CustomCMaximizeBehavior implements CMaximizeBehavior{
        public Dockable getMaximizingElement( Dockable dockable ){
            DockStation parent = dockable.getDockParent();
            
            if( parent instanceof StackDockStation || (parent instanceof SplitDockStation && parent.asDockable().getDockParent() instanceof ScreenDockStation )){
                for( int i = 0, n = parent.getDockableCount(); i<n; i++ ){
                    Dockable check = parent.getDockable( i );
                    if( check != dockable ){
                        if( check instanceof CommonDockable ){
                            CDockable fdock = ((CommonDockable)check).getDockable();
                            if( !fdock.isMaximizable() )
                                return dockable;
                        }
                    }
                }
                return parent.asDockable();
            }
            
            return dockable;
        }
        
        public Dockable getMaximizingElement( Dockable old, Dockable dockable ){
            if( old == dockable )
                return null;
            
            if( !DockUtilities.isAncestor( old, dockable ) )
                return null;
            
            DockStation station = old.asDockStation();
            if( station == null )
                return old;
            
            if( station.getDockableCount() == 2 ){
                if( station.getDockable( 0 ) == dockable )
                    return station.getDockable( 1 );
                if( station.getDockable( 1 ) == dockable )
                    return station.getDockable( 0 );
            }
            
            return old;
        }
    };

    // Factory for a custom theme that uses a special combiner, 
    // the SplitCombiner creates SplitDockStations
    private static class CustomBasicThemeFactory extends CDockThemeFactory<BasicTheme>{
        public CustomBasicThemeFactory( CControl control ){
            super( new ThemePropertyFactory<BasicTheme>( BasicTheme.class ), control );
        }

        public DockTheme create( CControl control ){
            BasicTheme theme = new BasicTheme();
            theme.setCombiner( new SplitCombiner() );
            return new CBasicTheme( control, theme );
        }        
    }
}

Hi Beni,

thank you for a very fast reply and the example source!

I was able to run it also with the eclipse theme (which I will probably be using).
However in case of the eclipse theme there is a problem with moving
the externalized dialog with multiple docking components.
When you drag one of the docking components it detaches from the other.
Do I need to use a decorated dialog in this case?

I also have a question about the maximized behavior: is CustomCMaximizeBehavior going to
be the default behavior for externalized dialogs or will the user have to provide
his own (as in your example source)?
I think a more consistent behavior would be to maximize the docking component
within the current externalized dialog (like in the main application window).
However then there has to be another way of maximizing the whole externalized dialog…

I saw that the close “X” button in the externalized dialog is disabled.
Since the externalized dialog can now contain many docking components,
maybe “X” it could close all the docking components inside (and the dialog).

Thanks again!

Maciej

[QUOTE=mmodelski]However in case of the eclipse theme there is a problem with moving
the externalized dialog with multiple docking components.
When you drag one of the docking components it detaches from the other.
Do I need to use a decorated dialog in this case?
[/QUOTE]
If you put the mouse over the top border, in the middle, you can move around the entire dialog. The feature is a bit hidden (the border has a size of about 2 pixels), and my todo-list tells me to make this feature somehow more visible (like a bigger border). I do not yet know how exactly a new border would look like. In any case it will be replaceable by a custom border.

Or you can use a decorated dialog. The “DefaultScreenDockWindowFactory” already has a method “setUndecorated” to switch the behavior. A new, customized factory can be set with the property key “ScreenDockStation.WINDOW_FACTORY”.

[QUOTE=mmodelski;13542]I also have a question about the maximized behavior: is CustomCMaximizeBehavior going to
be the default behavior for externalized dialogs or will the user have to provide
his own (as in your example source)?
I think a more consistent behavior would be to maximize the docking component
within the current externalized dialog (like in the main application window).
However then there has to be another way of maximizing the whole externalized dialog…[/QUOTE]
You will always have to provide a custom behavior, I do not want to change original strategy as there could be unwanted side effects.

There actually was a button that allowed to maximize a Docakble on the dialog alone. Make a copy of the SplitCombiner and remove the lines 143-147 (no anonymous subclass of SplitDockStation) to reenable it. There are some drawbacks, most notably that the same icon appears twice. You could draw your own icon and and use the IconManager (DockController.getIcons) to replace the old ones. The important keys for the IconManager would be “split.maximize”, “split.normalize” and “location.maximize”, “location.normalize”.

[QUOTE=mmodelski;13542]I saw that the close “X” button in the externalized dialog is disabled.
Since the externalized dialog can now contain many docking components,
maybe “X” it could close all the docking components inside (and the dialog).[/QUOTE]

Wouldn’t “but everything back on the mainframe” be more convenient? I think I’ll add some code to at least enabled/disable and catch the button. I might force you to write your own code for closing all Dockables.

[QUOTE=Beni]If you put the mouse over the top border, in the middle, you can move around the entire dialog. The feature is a bit hidden (the border has a size of about 2 pixels), and my todo-list tells me to make this feature somehow more visible (like a bigger border). I do not yet know how exactly a new border would look like. In any case it will be replaceable by a custom border.
[/QUOTE]

I think it is a good solution. Maybe double click could maximize or invoke some action?

I found a small bug when maximizing an external dialog - it covers the whole screen - also the taskbar.
Probably in some place the screen size is taken instead of the maximum window bounds.
I think the code should look something like this:
GraphicsEnvironment graphicsEnvironment=GraphicsEnvironment.getLocalGraphicsEnvironment();
Rectangle maximumWindowBounds=graphicsEnvironment.getMaximumWindowBounds();

I keep the double click in mind when I redesign the border. Normally double click on the title of a Dockable makes it already fullscreen.

The bounds have always been troublesome. Because supporting taskbars is one thing, supporting multy-screen environments another… I’ll test if your suggestion works with multi-screens as well :wink:

Hi Beni,

I played a bit with the above solution and I see one problem:
When I have two dockables in one external dialog (docked to the sides)
I have a SplitDockStation on a ScreenDockStation (SplitDockStation is created by the combiner).
It then works fine - I can dock more dockables in the external dialog or rearrange them.
However when I stack the dockables on top of each other
it becomes a StackDockStation on top of ScreenDockStation.
I cannot dock to the sides anymore.
The same happens when you externalize one dockable and stack another on top of it,
the SplitDockStation is not created.

It there a way to always have a SplitDockStation in an externalized dialog?
It would mean that the ScreenDockStation would contain SplitDockStations
and the SplitDockStations would contain further children?

Thanks!
Maciej Modelski

I’ll answer you on the weekend. This problem obviously needs some more thinking, and perhaps some modification of the framework itself.

Ok, this is a different approach. Instead of creating a SplitDockStation when combining items, we create the station in any case as soon as a Dockable is dropped on the ScreenDockStation. This needs a bit more setting up, because we have to make sure that the SplitDockStation is not automatically removed.

I think you’ll have to update the framework, the line “… = control.getStation( CControl.EXTERNALIZED_STATION_ID );” will probably not work in an older version.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.ListeningDockAction;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CMaximizeBehavior;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.station.split.SplitDockStationFactory;
import bibliothek.gui.dock.util.DockUtilities;

public class Dock34 {
	@SuppressWarnings("unchecked")
	public static void main( String[] args ){
		// setting up frame and controller
		JFrame frame = new JFrame();
		CControl control = new CControl( frame );
		control.setMaximizeBehavior( new CustomCMaximizeBehavior() );
		
		// if we ever want to work with layouts, we have install a factory for our specialized SplitDockStation
		control.intern().registerFactory( new CustomSplitDockStationFactory() );
		
		// now access the DockStation which shows our detached (externalized) items
		CStation<ScreenDockStation> screen = (CStation<ScreenDockStation>) control.getStation( CControl.EXTERNALIZED_STATION_ID );

		// if a Dockable is added to that station... 
		screen.getStation().addDockStationListener( new DockStationAdapter(){
			@Override
			public void dockableAdded( DockStation station, Dockable dockable ){
				// ... and the new child is not a SplitDockStation ...
				if( !(dockable instanceof SplitDockStation )){
					// .. then we just insert a SplitDockStation
					SplitDockStation split = new CustomSplitDockStation();
					
					DockController controller = station.getController();
					
					try{
						// disable events while rearanging our layout
						controller.freezeLayout();
						
						station.replace( dockable, split );
						split.drop( dockable );
					}
					finally{
						// and enable events after we finished
						controller.meltLayout();
					}
				}
			}
		});
		
		// make sure a SplitDockStation with one child and a parent that is a ScreenDockStation does not get removed
		control.intern().getController().setSingleParentRemover( new CSingleParentRemover( control ){
			@Override
			protected boolean shouldTest( DockStation station ){
				if( station instanceof SplitDockStation ){
					SplitDockStation split = (SplitDockStation)station;
					if( split.getDockParent() instanceof ScreenDockStation ){
						// but we want to remove the station if it does not have any children at all
						return split.getDockableCount() == 0;
					}
				}
				return super.shouldTest( station );
			}
		});

		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setSize( 700, 700 );

		DefaultSingleCDockable red = create( control, "Red", Color.RED );
		DefaultSingleCDockable green = create( control, "Green", Color.GREEN );
		DefaultSingleCDockable blue = create( control, "Blue", Color.BLUE );

		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 10, 10, red );
		grid.add( 10, 0, 5, 5, green );
		grid.add( 10, 5, 5, 5, blue );

		control.getContentArea().deploy( grid );
		frame.getContentPane().add( control.getContentArea(), BorderLayout.CENTER );
		frame.setSize( new Dimension( 600, 600 ) );
		frame.setVisible( true );
	}

	public static DefaultSingleCDockable create( CControl control, String title, Color color ){
		JPanel panel = new JPanel();
		panel.setOpaque( true );
		panel.setBackground( color );

		final DefaultSingleCDockable singleDockable = new DefaultSingleCDockable( title, title, panel );

		CPanelPopup popup = new CPanelPopup();
		JButton button = new JButton( "Button" );
		button.setPreferredSize( new Dimension( 400, 300 ) );
		popup.setContent( button );

		singleDockable.addAction( popup );

		return singleDockable;
	}
	
	// we write a custom factory for our SplitDockStation
	private static class CustomSplitDockStationFactory extends SplitDockStationFactory{
		public static final String ID = "custom split dock station";
		
		protected SplitDockStation createStation(){
			return new CustomSplitDockStation();
		}
		
		@Override
		public String getID(){
			return ID;
		}
	}
	
	// we use a custom type for the SplitDockStation, in this case the station will not show
	// a fullscreen-action
	private static class CustomSplitDockStation extends SplitDockStation{
		public String getFactoryID(){
			return CustomSplitDockStationFactory.ID;
		}
		
		protected ListeningDockAction createFullScreenAction(){
			return null;
		}
	}

	// maximization of a floating SplitDockStation: this behavior will maximize the station too
	private static class CustomCMaximizeBehavior implements CMaximizeBehavior {
		public Dockable getMaximizingElement( Dockable dockable ){
			DockStation parent = dockable.getDockParent();

			if( parent instanceof StackDockStation || (parent instanceof SplitDockStation && parent.asDockable().getDockParent() instanceof ScreenDockStation) ) {
				for( int i = 0, n = parent.getDockableCount(); i < n; i++ ) {
					Dockable check = parent.getDockable( i );
					if( check != dockable ) {
						if( check instanceof CommonDockable ) {
							CDockable fdock = ((CommonDockable) check).getDockable();
							if( !fdock.isMaximizable() )
								return dockable;
						}
					}
				}
				return parent.asDockable();
			}

			return dockable;
		}

		public Dockable getMaximizingElement( Dockable old, Dockable dockable ){
			if( old == dockable )
				return null;

			if( !DockUtilities.isAncestor( old, dockable ) )
				return null;

			DockStation station = old.asDockStation();
			if( station == null )
				return old;

			if( station.getDockableCount() == 2 ) {
				if( station.getDockable( 0 ) == dockable )
					return station.getDockable( 1 );
				if( station.getDockable( 1 ) == dockable )
					return station.getDockable( 0 );
			}

			return old;
		}
	};
}```

Hi Beni,

Thank you very much!
This works great!

best regards,
Maciej Modelski

I modified the above code so that the maximize button does exactly
the same as in the main dock station - maximizes only the dockable.
I keep the SplitDockStation maximize action and remove the standard one when externalizing
(I use a listener I found in another thread on the forum:)).

package test;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.action.predefined.CBlank;
import bibliothek.gui.dock.common.event.CDockableStateListener;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.DefaultCDockable;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.event.DockStationAdapter;
 
public class Dock34 {
    @SuppressWarnings("unchecked")
    public static void main( String[] args ){
        // setting up frame and controller
        JFrame frame = new JFrame();
        CControl control = new CControl( frame );
        
        // a listener that removes the standard maximize action when externalizing
        // and adds it back when unexternalizing
        control.addStateListener(new WMSMultipleStateListener());
        
        // now access the DockStation which shows our detached (externalized) items
        CStation<ScreenDockStation> screen = (CStation<ScreenDockStation>) control.getStation( CControl.EXTERNALIZED_STATION_ID );
 
        // if a Dockable is added to that station...
        screen.getStation().addDockStationListener( new DockStationAdapter(){
            @Override
            public void dockableAdded( DockStation station, Dockable dockable ){
                // ... and the new child is not a SplitDockStation ...
                if( !(dockable instanceof SplitDockStation )){
                    // .. then we just insert a SplitDockStation
                    SplitDockStation split = new SplitDockStation();
                   
                    DockController controller = station.getController();
                   
                    try{
                        // disable events while rearanging our layout
                        controller.freezeLayout();
                       
                        station.replace( dockable, split );
                        split.drop( dockable );
                    }
                    finally{
                        // and enable events after we finished
                        controller.meltLayout();
                    }
                }
            }
        });
       
        // make sure a SplitDockStation with one child and a parent that is a ScreenDockStation does not get removed
        control.intern().getController().setSingleParentRemover( new CSingleParentRemover( control ){
            @Override
            protected boolean shouldTest( DockStation station ){
                if( station instanceof SplitDockStation ){
                    SplitDockStation split = (SplitDockStation)station;
                    if( split.getDockParent() instanceof ScreenDockStation ){
                        // but we want to remove the station if it does not have any children at all
                        return split.getDockableCount() == 0;
                    }
                }
                return super.shouldTest( station );
            }
        });
 
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 700, 700 );
 
        DefaultSingleCDockable red = create( control, "Red", Color.RED );
        DefaultSingleCDockable green = create( control, "Green", Color.GREEN );
        DefaultSingleCDockable blue = create( control, "Blue", Color.BLUE );
 
        CGrid grid = new CGrid( control );
        grid.add( 0, 0, 10, 10, red );
        grid.add( 10, 0, 5, 5, green );
        grid.add( 10, 5, 5, 5, blue );
 
        control.getContentArea().deploy( grid );
        frame.getContentPane().add( control.getContentArea(), BorderLayout.CENTER );
        frame.setSize( new Dimension( 600, 600 ) );
        frame.setVisible( true );
    }
 
    public static DefaultSingleCDockable create( CControl control, String title, Color color ){
        JPanel panel = new JPanel();
        panel.setOpaque( true );
        panel.setBackground( color );
 
        final DefaultSingleCDockable singleDockable = new DefaultSingleCDockable( title, title, panel );
 
        CPanelPopup popup = new CPanelPopup();
        JButton button = new JButton( "Button" );
        button.setPreferredSize( new Dimension( 400, 300 ) );
        popup.setContent( button );
 
        singleDockable.addAction( popup );
 
        return singleDockable;
    }
 
    public static class WMSMultipleStateListener implements CDockableStateListener
    {
      public void setVisible(CDockable cd, boolean visible)
      {
        if ( cd instanceof DefaultCDockable )
        {
          DefaultCDockable dockable = (DefaultCDockable) cd;
          if ( visible )
          {
            dockable.putAction(CDockable.ACTION_KEY_MAXIMIZE, null);
          }
          else
          {
            dockable.putAction(CDockable.ACTION_KEY_MAXIMIZE, CBlank.BLANK);
          }
        }
      }

      public void minimized(CDockable cd)
      {
        setVisible(cd, true);
      }

      public void maximized(CDockable cd)
      {
        setVisible(cd, true);
      }

      public void normalized(CDockable cd)
      {
        setVisible(cd, true);
      }

      public void externalized(CDockable cd)
      {
        setVisible(cd, false);
      }

      public void visibilityChanged(CDockable cd)
      {
        // ignore
      }
    }    
}

Hi Beni,

I tried external docking with 1.1.0p6f and it now prints out exceptions
when you drag a dockable outside of the main frame:

java.lang.IllegalStateException: the lock has already been acquired

and later

java.lang.IllegalStateException: the parent of ‘bibliothek.gui.dock.common.intern.DefaultCommonDockable@b64f2e’ is not ‘bibliothek.gui.dock.common.intern.EfficientControlFactory$3@fbd1fc’ but 'Root [id=1300266508146]

Is there a way to avoid these exceptions?

Thanks!
Maciej

package test;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
 
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.action.predefined.CBlank;
import bibliothek.gui.dock.common.event.CDockableStateListener;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.DefaultCDockable;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.station.LayoutLocked;
 
public class Dock38 {
    @SuppressWarnings("unchecked")
    public static void main( String[] args ){
        // setting up frame and controller
        JFrame frame = new JFrame();
        CControl control = new CControl( frame );
        control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);
       
        // a listener that removes the standard maximize action when externalizing
        // and adds it back when unexternalizing
        control.addStateListener(new WMSMultipleStateListener());
       
        // now access the DockStation which shows our detached (externalized) items
        CStation<ScreenDockStation> screen = (CStation<ScreenDockStation>) control.getStation( CControl.EXTERNALIZED_STATION_ID );
 
        // if a Dockable is added to that station...
        screen.getStation().addDockStationListener( new ScreenDockStationListener());
       
        // make sure a SplitDockStation with one child and a parent that is a ScreenDockStation does not get removed
        control.intern().getController().setSingleParentRemover( new CSingleParentRemover( control ){
            @Override
            protected boolean shouldTest( DockStation station ){
                if( station instanceof SplitDockStation ){
                    SplitDockStation split = (SplitDockStation)station;
                    if( split.getDockParent() instanceof ScreenDockStation ){
                        // but we want to remove the station if it does not have any children at all
                        return split.getDockableCount() == 0;
                    }
                }
                return super.shouldTest( station );
            }
        });
 
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        frame.setSize( 700, 700 );
 
        DefaultSingleCDockable red = create( control, "Red", Color.RED );
        DefaultSingleCDockable green = create( control, "Green", Color.GREEN );
        DefaultSingleCDockable blue = create( control, "Blue", Color.BLUE );
 
        CGrid grid = new CGrid( control );
        grid.add( 0, 0, 10, 10, red );
        grid.add( 10, 0, 5, 5, green );
        grid.add( 10, 5, 5, 5, blue );
 
        control.getContentArea().deploy( grid );
        frame.getContentPane().add( control.getContentArea(), BorderLayout.CENTER );
        frame.setSize( new Dimension( 600, 600 ) );
        frame.setVisible( true );
    }
 
    public static DefaultSingleCDockable create( CControl control, String title, Color color ){
        JPanel panel = new JPanel();
        panel.setOpaque( true );
        panel.setBackground( color );
 
        final DefaultSingleCDockable singleDockable = new DefaultSingleCDockable( title, title, panel );
 
        CPanelPopup popup = new CPanelPopup();
        JButton button = new JButton( "Button" );
        button.setPreferredSize( new Dimension( 400, 300 ) );
        popup.setContent( button );
 
        singleDockable.addAction( popup );
 
        return singleDockable;
    }
 
    public static class WMSMultipleStateListener implements CDockableStateListener
    {

 
      public void visibilityChanged(CDockable cd)
      {
        // ignore
      }

      public void extendedModeChanged(CDockable cd, ExtendedMode mode)
      {
        if ( cd instanceof DefaultCDockable )
        {
          DefaultCDockable dockable = (DefaultCDockable) cd;
          if ( mode.equals(ExtendedMode.EXTERNALIZED) )
          {
            dockable.putAction(CDockable.ACTION_KEY_MAXIMIZE, CBlank.BLANK);
          }
          else
          {
            dockable.putAction(CDockable.ACTION_KEY_MAXIMIZE, null);
          }
        }        
      }
    }

    @LayoutLocked(locked = false)
    public static class ScreenDockStationListener extends DockStationAdapter
    {
      public void dockableAdded( DockStation station, Dockable dockable ){
        // ... and the new child is not a SplitDockStation ...
        if( !(dockable instanceof SplitDockStation )){
            // .. then we just insert a SplitDockStation
            SplitDockStation split = new SplitDockStation();
           
            DockController controller = station.getController();
           
            try{
                // disable events while rearanging our layout
                controller.freezeLayout();
               
                station.replace( dockable, split );
                split.drop( dockable );
            }
            finally{
                // and enable events after we finished
                controller.meltLayout();
            }
        }
    }      
    }
}

Try adding “DockController.getHierarchyLock().setConcurrent(true)” and “setConcurrent(false)” before and after applying the layout change. That would be lines 137 and 145. I’m not 100% sure it will work afterwards, but currently I cannot test the solution.

It took care of the first exception.
The second one is still there:
java.lang.IllegalStateException: the parent of ‘bibliothek.gui.dock.common.intern.DefaultCommonDockable@88b858’ is not ‘bibliothek.gui.dock.common.intern.EfficientControlFactory$3@12dcb8c’ but ‘Root [id=1300268952103]
Leaf[ Blue, placeholders={}, id=1300268952105 ]’
I think it is because the DockHierarchyLock expects the dockable to become a child of
the ScreenDockStation but it is inserted to the SplitDockStation instead.

Yes, that would be a very good explanation. Add a call “DockHierarchyLock.setHardExceptions(false)” to your application. The framework will still print something about an exception, but not actually throw the exception. That way you can continue working on your application.

I’ll try to update the application, because the exception is correct: concurrent modification of the child-parent relationship of stations and dockables should not be done (other modules may get confused by events arriving in the wrong order). I should post a solution in the evening.

Ok, I first thought of some fancy solution. But instead, just delay the replacement. The user won’t notice, the framework should never intercept the call, and all the events are fired in the correct order.


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CPanelPopup;
import bibliothek.gui.dock.common.action.predefined.CBlank;
import bibliothek.gui.dock.common.event.CDockableStateListener;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.DefaultCDockable;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.station.LayoutLocked;

public class Dock58 {
	@SuppressWarnings("unchecked")
	public static void main( String[] args ){
		// setting up frame and controller
		JFrame frame = new JFrame();
		CControl control = new CControl( frame );
		control.setTheme( ThemeMap.KEY_ECLIPSE_THEME );

		// a listener that removes the standard maximize action when externalizing
		// and adds it back when unexternalizing
		control.addStateListener( new WMSMultipleStateListener() );

		// now access the DockStation which shows our detached (externalized) items
		CStation<ScreenDockStation> screen = (CStation<ScreenDockStation>) control.getStation( CControl.EXTERNALIZED_STATION_ID );

		// if a Dockable is added to that station...
		screen.getStation().addDockStationListener( new ScreenDockStationListener() );

		// make sure a SplitDockStation with one child and a parent that is a ScreenDockStation does not get removed
		control.intern().getController().setSingleParentRemover( new CSingleParentRemover( control ){
			@Override
			protected boolean shouldTest( DockStation station ){
				if( station instanceof SplitDockStation ) {
					SplitDockStation split = (SplitDockStation) station;
					if( split.getDockParent() instanceof ScreenDockStation ) {
						// but we want to remove the station if it does not have any children at all
						return split.getDockableCount() == 0;
					}
				}
				return super.shouldTest( station );
			}
		} );

		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setSize( 700, 700 );

		DefaultSingleCDockable red = create( control, "Red", Color.RED );
		DefaultSingleCDockable green = create( control, "Green", Color.GREEN );
		DefaultSingleCDockable blue = create( control, "Blue", Color.BLUE );

		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 10, 10, red );
		grid.add( 10, 0, 5, 5, green );
		grid.add( 10, 5, 5, 5, blue );

		control.getContentArea().deploy( grid );
		frame.getContentPane().add( control.getContentArea(), BorderLayout.CENTER );
		frame.setSize( new Dimension( 600, 600 ) );
		frame.setVisible( true );
	}

	public static DefaultSingleCDockable create( CControl control, String title, Color color ){
		JPanel panel = new JPanel();
		panel.setOpaque( true );
		panel.setBackground( color );

		final DefaultSingleCDockable singleDockable = new DefaultSingleCDockable( title, title, panel );

		CPanelPopup popup = new CPanelPopup();
		JButton button = new JButton( "Button" );
		button.setPreferredSize( new Dimension( 400, 300 ) );
		popup.setContent( button );

		singleDockable.addAction( popup );

		return singleDockable;
	}

	public static class WMSMultipleStateListener implements CDockableStateListener {

		public void visibilityChanged( CDockable cd ){
			// ignore
		}

		public void extendedModeChanged( CDockable cd, ExtendedMode mode ){
			if( cd instanceof DefaultCDockable ) {
				DefaultCDockable dockable = (DefaultCDockable) cd;
				if( mode.equals( ExtendedMode.EXTERNALIZED ) ) {
					dockable.putAction( CDockable.ACTION_KEY_MAXIMIZE, CBlank.BLANK );
				}
				else {
					dockable.putAction( CDockable.ACTION_KEY_MAXIMIZE, null );
				}
			}
		}
	}

	@LayoutLocked(locked = false)
	public static class ScreenDockStationListener extends DockStationAdapter {
		public void dockableAdded( DockStation station, final Dockable dockable ){
			// ... and the new child is not a SplitDockStation ...
			if( !(dockable instanceof SplitDockStation) ) {
				SwingUtilities.invokeLater( new Runnable(){
					public void run(){
						checkAndReplace( dockable );
					}
				} );
			}
		}
	};

	private static void checkAndReplace( Dockable dockable ){
		DockStation station = dockable.getDockParent();
		if( !(station instanceof ScreenDockStation) ) {
			// cancel
			return;
		}

		// .. then we just insert a SplitDockStation
		SplitDockStation split = new SplitDockStation();

		DockController controller = station.getController();

		try {
			// disable events while rearanging our layout
			controller.freezeLayout();

			station.replace( dockable, split );
			split.drop( dockable );
		}
		finally {
			// and enable events after we finished
			controller.meltLayout();
		}
	}
}```

Thanks, now it works fine!
I also tried with the new method from preview7:

        station.getController().getHierarchyLock().onRelease(new Runnable()
        {
          public void run()
          {
            checkAndReplace(dockable);
          }
        });

It is meant for this kind of situations, right?

Yes, but I did not try myself yet. Does it work?

Yes, it seems to work! :slight_smile: