CWorkingArea

If you have downloaded 1.1.0p7 (which is not online yet) and are wondering why your application does no longer compile: I had to modify some interfaces. Sorry for that, there was no other way. The changes are small, but the CStackDockStation now has to implement CommonDockable. The example below shows how it has to be done now.


import java.awt.Color;

import tutorial.support.ColorSingleCDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;

@Tutorial( id="NewCStation", title="Custom CStation")
public class NewCStationExample {
	/* This example shows how a StackDockStation is converted into a dockable CStation that behaves like
	 * a working-area.
	 * 
	 * This is only a skeleton example, in a real application some additional tweaking will
	 * be necessary to get everything running smoothly. */
	public static void main( String[] args ){
		/* Setting up a frame */
		JTutorialFrame frame = new JTutorialFrame( NewCStationExample.class );
		CControl control = new CControl( frame );
		frame.destroyOnClose( control );
		
		/* We do not want to move around the entire stack, so we change the group behavior */
		control.setGroupBehavior( CGroupBehavior.TOPMOST );
		
		/* Usually a CWorkingArea is not used alone but on top of a CContentArea, so we do the same
		 * in this example. */
		CContentArea contentArea = control.getContentArea();
		frame.add( contentArea );
		
		/* We create our station and register it as root station */
		CStack stack = new CStack( "stack" );
		control.addStation( stack, true );
		
		/* Now we set up some Dockable to play around with. The first three will be children of
		 * our station, the other three will represent "free" dockables. */
		ColorSingleCDockable white = new ColorSingleCDockable( "White", Color.WHITE );
		ColorSingleCDockable gray = new ColorSingleCDockable( "Gray", Color.GRAY );
		ColorSingleCDockable black = new ColorSingleCDockable( "Black", Color.BLACK );
		
		ColorSingleCDockable red = new ColorSingleCDockable( "Red", Color.RED );
		ColorSingleCDockable green = new ColorSingleCDockable( "Green", Color.GREEN );
		ColorSingleCDockable blue = new ColorSingleCDockable( "Blue", Color.BLUE );
		
		/* We now put three Dockables and our custom station onto the CContentArea of our application. */
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 0.25, red );
		grid.add( 0, 0.25, 0.25, 0.25, green );
		grid.add( 0, 0.5, 0.25, 0.5, blue );
		grid.add( 0.25, 0.25, 0.75, 0.75, stack );
		contentArea.deploy( grid );
		
		
		/* Now we need to set up the remaining three Dockables. First we register them at the CControl. */
		control.addDockable( white );
		control.addDockable( gray );
		control.addDockable( black );
		
		/* Then we make sure, that they always stay on our custom station */
		white.setWorkingArea( stack );
		gray.setWorkingArea( stack );
		black.setWorkingArea( stack );
		
		/* We fall back to the Core API to make the Dockables visible. */
		stack.getStation().add( white.intern(), 0 );
		stack.getStation().add( gray.intern(), 1 );
		stack.getStation().add( black.intern(), 2 );
		
		frame.setVisible( true );
	}

	/* This is our custom CStation that is build upon a StackDockStation. By implementing
	 * CNormalModeArea we can register this station as an area with children in the "normal" mode. This is 
	 * necessary in order for the CLocationManager to accept this station as a proper root-station. 
	 * 
	 * Our implementation is distributed over two classes: the CStack is a wrapper implementing CDockable and
	 * CStation. The CStackDockStation is the wrapped class that allows access to the API of Core.
	 * 
	 * To make our life easier we extend from AbstractCDockableStation which already implements many methods 
	 * of CDockable and CStation. */
	private static class CStack extends AbstractDockableCStation<StackDockStation> implements CNormalModeArea{
		public CStack( String id ){
			CStackDockStation delegate = new CStackDockStation( this );

			CLocation stationLocation = new CLocation(){
				@Override
				public CLocation getParent(){
					return null;
				}
				
				@Override
				public String findRoot(){
					return getUniqueId();
				}
				
				@Override
				public DockableProperty findProperty( DockableProperty successor ){
					return successor;
				}
				
				@Override
				public ExtendedMode findMode(){
					return ExtendedMode.NORMALIZED;
				}
				
				@Override
				public CLocation aside(){
					return this;
				}
			}; 
			
			init( delegate, id, stationLocation, delegate );
			
		}

		/* This method is called by the CControl and allows access to some inner API that is
		 * hidden from normal clients. */
		protected void install( CControlAccess access ){
			access.getLocationManager().getNormalMode().add( this );
		}

		
		protected void uninstall( CControlAccess access ){
			access.getLocationManager().getNormalMode().remove( getUniqueId() );
		}

		public CStationPerspective createPerspective(){
			/* For this example we do not support perspectives. */
			throw new IllegalStateException( "not implemented" );
		}

		public boolean isNormalModeChild( Dockable dockable ){
			return isChild( dockable );
		}

		public DockableProperty getLocation( Dockable child ){
			return DockUtilities.getPropertyChain( getStation(), child );
		}

		public void setLocation( Dockable dockable, DockableProperty location, AffectedSet set ){
			set.add( dockable );
			
			if( isChild( dockable )){
				getStation().move( dockable, location );
			}
			else{
				if( !getStation().drop( dockable, location )){
					getStation().drop( dockable );
				}
			}
		}

		public void addModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public Path getTypeId(){
			// not required
			return null;
		}
		
		public boolean autoDefaultArea(){
			return true;
		}

		public boolean isChild( Dockable dockable ){
			return dockable.getDockParent() == getStation();
		}

		public void removeModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public void setController( DockController controller ){
			// ignore
		}

		public void setMode( LocationMode mode ){
			// ignore
		}

		public CLocation getCLocation( Dockable dockable ){
			DockableProperty property = DockUtilities.getPropertyChain( getStation(), dockable );
			return getStationLocation().expandProperty( property );
		}

		public CLocation getCLocation( Dockable dockable, Location location ){
			DockableProperty property = location.getLocation();
			if( property == null ){
				return getStationLocation();
			}
			
			return getStationLocation().expandProperty( property );
		}

		public boolean respectWorkingAreas(){
			return true;
		}

		public boolean isCloseable(){
			return false;
		}

		public boolean isExternalizable(){
			return false;
		}

		public boolean isMaximizable(){
			return false;
		}

		public boolean isMinimizable(){
			return false;
		}

		public boolean isStackable(){
			return false;
		}
		
		public boolean isWorkingArea(){
			return true;
		}
		
		public DockActionSource[] getSources(){
			return new DockActionSource[]{ getClose() };
		}
	}
	
	/* This is our Core DockStation. It needs to implement CommonDockable and CommonStation, that way the framework
	 * recognizes this object as beeing wrapped by some CDockable or CStation. */
	private static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation,CStackDockStation>, CommonDockable{
		private CStack delegate;
		
		public CStackDockStation( CStack stack ){
			this.delegate = stack;
		}
		
		public CDockable getDockable(){
			return delegate;
		}

		public DockActionSource[] getSources(){
			return delegate.getSources();
		}

		public CStation<StackDockStation> getStation(){
			return delegate;
		}
		
		public StackDockStation getDockStation(){
			return this;
		}
		
		@Override
		public CStackDockStation asDockStation(){
			return this;
		}
		
		@Override
		public CommonDockable asDockable(){
			return this;
		}
	}
}

Hi,

Thanks for your notification…

Question:
I want for some CStack station to hide them after minimizing all their dockables [Same as the console log in Eclipse IDE]. Could you please advice?

Thank you

I think the easiest solution is to misuse the SingleParentRemover for this. Some listener on the StackDockStation should also work nicely (but the SingleParentRemover has the additional bonus of not running into concurrent modification issues).

In the example below:

  • There is a CustomSingleParentRemover which removes empty CStacks.
  • CStack implements now SingleCDockable, otherwise its location could not be stored.
  • If you un-minimize one of the children the CStack is made visible again.

import java.awt.Color;

import tutorial.support.ColorSingleCDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;

@Tutorial( id="NewCStation", title="Custom CStation")
public class Dock43 {
	/* This example shows how a StackDockStation is converted into a dockable CStation that behaves like
	 * a working-area.
	 * 
	 * This is only a skeleton example, in a real application some additional tweaking will
	 * be necessary to get everything running smoothly. */
	public static void main( String[] args ){
		/* Setting up a frame */
		JTutorialFrame frame = new JTutorialFrame( Dock43.class );
		CControl control = new CControl( frame );
		frame.destroyOnClose( control );
		
		// <<<<<<<<<<<< new line
		control.getController().setSingleParentRemover( new CustomSingleParentRemover( control ) );
		// >>>>>>>>>>>> new line
		
		/* We do not want to move around the entire stack, so we change the group behavior */
		control.setGroupBehavior( CGroupBehavior.TOPMOST );
		
		/* Usually a CWorkingArea is not used alone but on top of a CContentArea, so we do the same
		 * in this example. */
		CContentArea contentArea = control.getContentArea();
		frame.add( contentArea );
		
		/* We create our station and register it as root station */
		CStack stack = new CStack( "stack" );
		control.addStation( stack, true );
		
		/* Now we set up some Dockable to play around with. The first three will be children of
		 * our station, the other three will represent "free" dockables. */
		ColorSingleCDockable white = new ColorSingleCDockable( "White", Color.WHITE );
		ColorSingleCDockable gray = new ColorSingleCDockable( "Gray", Color.GRAY );
		ColorSingleCDockable black = new ColorSingleCDockable( "Black", Color.BLACK );
		
		ColorSingleCDockable red = new ColorSingleCDockable( "Red", Color.RED );
		ColorSingleCDockable green = new ColorSingleCDockable( "Green", Color.GREEN );
		ColorSingleCDockable blue = new ColorSingleCDockable( "Blue", Color.BLUE );
		
		/* Now we need to set up the remaining three Dockables. First we register them at the CControl. */
		control.addDockable( white );
		control.addDockable( gray );
		control.addDockable( black );
		
		/* Then we make sure, that they always stay on our custom station */
		white.setWorkingArea( stack );
		gray.setWorkingArea( stack );
		black.setWorkingArea( stack );
		
		/* We fall back to the Core API to make the Dockables visible. */
		stack.getStation().add( white.intern(), 0 );
		stack.getStation().add( gray.intern(), 1 );
		stack.getStation().add( black.intern(), 2 );
		
		
		/* We now put three Dockables and our custom station onto the CContentArea of our application. */
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 0.25, red );
		grid.add( 0, 0.25, 0.25, 0.25, green );
		grid.add( 0, 0.5, 0.25, 0.5, blue );
		grid.add( 0.25, 0.25, 0.75, 0.75, stack );
		contentArea.deploy( grid );
		
		frame.setVisible( true );
	}
	
	private static class CustomSingleParentRemover extends CSingleParentRemover{
		public CustomSingleParentRemover( CControl control ){
			super( control );
		}

		@Override
		protected boolean shouldTest( DockStation station ){
			if( isCStack( station )){
				return true;
			}
			return super.shouldTest( station );
		}
		
		@Override
		protected boolean test( DockStation station ){
			if( isCStack( station )){
				if( station.getDockableCount() > 0 ){
					return false;
				}
				else{
					CStack stack  = (CStack)((CommonDockStation<?,?>)station).getStation();
					stack.setVisible( false );
					return false; // because CStack is a root station
				}
			}
			
			return super.test( station );
		}
		
		private boolean isCStack( DockStation station ){
			if( station instanceof CommonDockStation<?, ?>){
				if( ((CommonDockStation<?, ?>)station).getStation() instanceof CStack ){
					return true;
				}
			}
			return false;
		}
	}

	/* This is our custom CStation that is build upon a StackDockStation. By implementing
	 * CNormalModeArea we can register this station as an area with children in the "normal" mode. This is 
	 * necessary in order for the CLocationManager to accept this station as a proper root-station. 
	 * 
	 * Our implementation is distributed over two classes: the CStack is a wrapper implementing CDockable and
	 * CStation. The CStackDockStation is the wrapped class that allows access to the API of Core.
	 * 
	 * To make our life easier we extend from AbstractCDockableStation which already implements many methods 
	 * of CDockable and CStation. */
	private static class CStack extends AbstractDockableCStation<StackDockStation> implements CNormalModeArea, SingleCDockable{
		public CStack( String id ){
			CStackDockStation delegate = new CStackDockStation( this );

			CLocation stationLocation = new CLocation(){
				@Override
				public CLocation getParent(){
					return null;
				}
				
				@Override
				public String findRoot(){
					return getUniqueId();
				}
				
				@Override
				public DockableProperty findProperty( DockableProperty successor ){
					return successor;
				}
				
				@Override
				public ExtendedMode findMode(){
					return ExtendedMode.NORMALIZED;
				}
				
				@Override
				public CLocation aside(){
					return this;
				}
			}; 
			
			init( delegate, id, stationLocation, delegate );
			
		}

		/* This method is called by the CControl and allows access to some inner API that is
		 * hidden from normal clients. */
		protected void install( CControlAccess access ){
			access.getLocationManager().getNormalMode().add( this );
		}

		
		protected void uninstall( CControlAccess access ){
			access.getLocationManager().getNormalMode().remove( getUniqueId() );
		}

		public CStationPerspective createPerspective(){
			/* For this example we do not support perspectives. */
			throw new IllegalStateException( "not implemented" );
		}

		public boolean isNormalModeChild( Dockable dockable ){
			return isChild( dockable );
		}

		public DockableProperty getLocation( Dockable child ){
			return DockUtilities.getPropertyChain( getStation(), child );
		}

		public void setLocation( Dockable dockable, DockableProperty location, AffectedSet set ){
			set.add( dockable );
			
			if( isChild( dockable )){
				getStation().move( dockable, location );
			}
			else{
				if( !getStation().drop( dockable, location )){
					getStation().drop( dockable );
				}
			}
		}

		public void addModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public Path getTypeId(){
			// not required
			return null;
		}
		
		public boolean autoDefaultArea(){
			return true;
		}

		public boolean isChild( Dockable dockable ){
			return dockable.getDockParent() == getStation();
		}

		public void removeModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public void setController( DockController controller ){
			// ignore
		}

		public void setMode( LocationMode mode ){
			// ignore
		}

		public CLocation getCLocation( Dockable dockable ){
			DockableProperty property = DockUtilities.getPropertyChain( getStation(), dockable );
			return getStationLocation().expandProperty( property );
		}

		public CLocation getCLocation( Dockable dockable, Location location ){
			DockableProperty property = location.getLocation();
			if( property == null ){
				return getStationLocation();
			}
			
			return getStationLocation().expandProperty( property );
		}

		public boolean respectWorkingAreas(){
			return true;
		}

		public boolean isCloseable(){
			return false;
		}

		public boolean isExternalizable(){
			return false;
		}

		public boolean isMaximizable(){
			return false;
		}

		public boolean isMinimizable(){
			return false;
		}

		public boolean isStackable(){
			return false;
		}
		
		public boolean isWorkingArea(){
			return true;
		}
		
		public DockActionSource[] getSources(){
			return new DockActionSource[]{ getClose() };
		}
	}
	
	/* This is our Core DockStation. It needs to implement CommonDockable and CommonStation, that way the framework
	 * recognizes this object as beeing wrapped by some CDockable or CStation. */
	private static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation,CStackDockStation>, CommonDockable{
		private CStack delegate;
		
		public CStackDockStation(  CStack stack ){
			this.delegate = stack;
			
			final Runnable makeVisible = new Runnable() {
				public void run(){
					delegate.setVisible( true );
				}
			};
			DockUtilities.disableCheckLayoutLocked();
			addDockStationListener( new DockStationAdapter() {
				@Override
				public void dockableAdded( DockStation station, Dockable dockable ){
					DockController controller = delegate.getControl().getOwner().getController();
					controller.getHierarchyLock().onRelease( makeVisible );
				}
			});
		}
		
		public CDockable getDockable(){
			return delegate;
		}

		public DockActionSource[] getSources(){
			return delegate.getSources();
		}

		public CStation<StackDockStation> getStation(){
			return delegate;
		}
		
		public StackDockStation getDockStation(){
			return this;
		}
		
		@Override
		public CStackDockStation asDockStation(){
			return this;
		}
		
		@Override
		public CommonDockable asDockable(){
			return this;
		}
	}
}

Hi,

Thank you :slight_smile:

  1. Is it possible to add or [update/override the already existing] popup menu on the CStack, that appears eveywhere [on the CStack title and the CStack children dockables] on the context of the CStack?

  2. In my real application, I’m getting the following exception when double clicking on the tab title oer when clicking on maximize. Note that in the standalone example I’m not getting it :frowning: Any idea that could help me?

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
        at bibliothek.gui.dock.common.mode.station.CSplitDockStationHandle$Maximal.getCLocation(CSplitDockStationHandle.java:509)
        at bibliothek.gui.dock.common.mode.CMaximizedMode.getCLocation(CMaximizedMode.java:59)
        at bibliothek.gui.dock.common.mode.CLocationModeManager.getLocation(CLocationModeManager.java:323)
        at bibliothek.gui.dock.common.intern.AbstractCDockable.getBaseLocation(AbstractCDockable.java:330)
        at bibliothek.gui.dock.common.intern.CDockableLocationListenerManager.checkNow(CDockableLocationListenerManager.java:107)
        at bibliothek.gui.dock.common.intern.CDockableLocationListenerManager.access$2(CDockableLocationListenerManager.java:105)
        at bibliothek.gui.dock.common.intern.CDockableLocationListenerManager$2.run(CDockableLocationListenerManager.java:99)
        at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
        at java.awt.EventQueue.dispatchEvent(EventQueue.java:597)
        at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:269)
        at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
        at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:174)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
        at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
        at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
  1. I’m working on that problem and will (try to) fix it in 1.1.0p7b.

  2. I have to postpone that question, I have a bit too much work now.

  1. should be fixed, at least if it was the bug I suspect you found.

  2. The items you see in the popup menu are the DockActions that belong to the Dockable over which the mouse currently is. These are the same actions that appear on the title (or tabs, if using the EclipseTheme). At the moment you can add additional actions to CDockables using “DefaultCDockable.add/insertAction”. You can override the frameworks action by using “AbstractCDockable.putAction”, or you can go down to the Core and do wild things with ActionGuards and ActionOffers (the later gives you total control over the actions).

However, there is currently no possibility to change the mapping: e.g. clicking on a tab will always lead to a popup showing actions that belong to the Dockable of tab. And it is also not possible to disable the popup menu.

Does that help, or are you searching for something else?

Yes, this was what I’m looking for

I wanted to customize the menu on each dockable and I don’t want each menu to be shown as an action in the action section.
And ideally, I was expecting to be able to specify the menu once at the level of the stationand expecting that all its children will inherit this menu

Thank you

You will have to use the API from Core to accomplish that.

Action only in menu: Extend some DockAction, override „createView“ and return „null“ for all „ViewTargets“ that are not „ViewTarget.MENU“. It’s not documented, but the framework should not throw any NPE and just hide the missing views (if you get a NullPointerException let me know).

Inheritable Action: Implement an ActionGuard, register your class with „DockController.addActionGuard“. The ActionGuard is asked to provide a set of actions for various Dockables. To be more exact: whenever the location of a Dockable changed the ActionGuard is asked to provide a new set of actions. Provide a set if the parent of a Dockable is the stack, and don’t provide one if the parent is not the stack…

If you’d like an example, that could be arranged :slight_smile:

Thank you for your answer

An example will be great :slight_smile:

Hi,

Any news about the example

Thank you

Hi,

I noticed some problems after downloading the version 1.1.0p7b.

In the example NewCStationExample, I tried the following scenraio :

a. Double click on the header of one dockable --> The dockable gets maximized

b. Double click again on the header of the same dockable in order to restore it to it’s initial position ==>Doesn’t work

Also, In my application, minimize restore looses the sizes of the dockables.

Note that everything was working fine with the version 1.1.0p7, except that I was having the above exception when trying to maximize a dockable.

Could you please advice?

Thank you

Sorry, I completely forgot, good thing you asked again. Here, this is a simple example. You’ll only have to adapt the “react” method a bit to your needs.


import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;

import javax.swing.Icon;
import javax.swing.JFrame;

import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.ActionGuard;
import bibliothek.gui.dock.action.DefaultDockActionSource;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.action.actions.SimpleButtonAction;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;

public class Dock63 {
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		CControl control = new CControl( frame );
		
		// here we install our custom ActionGuard
		control.getController().addActionGuard( new CustomActionGuard() );
		
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 500, 400 );
		CContentArea area = control.getContentArea();
		frame.add( area );
		
		DefaultSingleCDockable dockableA = new DefaultSingleCDockable( "test a", "Test A" );
		DefaultSingleCDockable dockableB = new DefaultSingleCDockable( "test b", "Test B" );
		DefaultSingleCDockable dockableC = new DefaultSingleCDockable( "test c", "Test C" );
		
		control.addDockable( dockableA );
		control.addDockable( dockableB );
		control.addDockable( dockableC );
		
		dockableA.setVisible( true );
		dockableB.setLocation( dockableA.getBaseLocation().aside() );
		dockableB.setVisible( true );
		dockableC.setLocation( CLocation.base().minimalEast() );
		dockableC.setVisible( true );
		
		frame.setVisible( true );
	}
	
	private static class CustomActionGuard implements ActionGuard{
		// this is the set of actions we want to forward
		private DefaultDockActionSource actions;
		
		public CustomActionGuard(){
			// setting up our list of actions
			LocationHint hint = new LocationHint( LocationHint.ACTION_GUARD, LocationHint.LEFT );
			actions = new DefaultDockActionSource( hint );
			actions.add( new CustomAction() );
		}
		
		@Override
		public boolean react( Dockable dockable ){
			// We can define any rule we want to decide which dockable should receive
			// our actions and which should not

			// in this case we make sure only the dockable "dockableA/B/C" from the 
			// main-method receive the actions
			if( !(dockable instanceof CommonDockable) ){
				return false;
			}
			CDockable cdockable = ((CommonDockable)dockable).getDockable();
			if( !(cdockable instanceof DefaultSingleCDockable)){
				return false;
			}
			
			DockStation parent = dockable.getDockParent();
			
			// and in your code this would be the class "CStackDockStation" and not
			// yust "StackDockStation"
			return parent instanceof StackDockStation;
		}
		
		@Override
		public DockActionSource getSource( Dockable dockable ){
			return actions; // you could create new actions and DockActionSources here
		}
	}
	
	// there are a number of different actions available, but the button-action is the
	// most basic.
	private static class CustomAction extends SimpleButtonAction{
		public CustomAction(){
			setIcon( new RectIcon() );
			setText( "Hello" );
		}
		
		@Override
		public void action( Dockable dockable ){
			// note that these casts will always work because of the checks our CustomActionGuard performs
			DefaultSingleCDockable cdockable = (DefaultSingleCDockable)((CommonDockable)dockable).getDockable();
			System.out.println( "Ping: " + cdockable.getTitleText() );
		}
	}
	
	// just add something to look at :-)
	private static class RectIcon implements Icon{
		public int getIconHeight(){
			return 16;
		}

		public int getIconWidth(){
			return 16;
		}

		public void paintIcon( Component c, Graphics g, int x, int y ){
			g.setColor( Color.GREEN );
			g.fillRect( x+1, y+1, 14, 14 );
		}
	}
}```

[QUOTE=saba]Hi,

I noticed some problems after downloading the version 1.1.0p7b.

In the example NewCStationExample, I tried the following scenraio :

a. Double click on the header of one dockable --> The dockable gets maximized

b. Double click again on the header of the same dockable in order to restore it to it’s initial position ==>Doesn’t work

Also, In my application, minimize restore looses the sizes of the dockables.

Note that everything was working fine with the version 1.1.0p7, except that I was having the above exception when trying to maximize a dockable.

Could you please advice?

Thank you[/QUOTE]

I can’t confirm this bug neither in the example nor in any other application. Which version of the example are you running exactly? If possible just copy the entire source code, that’s the fastest solution.

Hi,

This the example I’m running. Double click on the black dockable --> It will maxmize --> Double click again to restore it --> ?


import tutorial.support.ColorSingleCDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.Path;

@Tutorial( id="NewCStation", title="Custom CStation")
public class NewCStationExample {
	/* This example shows how a StackDockStation is converted into a dockable CStation that behaves like
	 * a working-area.
	 * 
	 * This is only a skeleton example, in a real application some additional tweaking will
	 * be necessary to get everything running smoothly. */
	public static void main( String[] args ){
		/* Setting up a frame */
		JTutorialFrame frame = new JTutorialFrame( NewCStationExample.class );
		CControl control = new CControl( frame );
		control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);
		frame.destroyOnClose( control );
		
		/* We do not want to move around the entire stack, so we change the group behavior */
//		control.setGroupBehavior( CGroupBehavior.TOPMOST );
		
		/* Usually a CWorkingArea is not used alone but on top of a CContentArea, so we do the same
		 * in this example. */
		CContentArea contentArea = control.getContentArea();
		frame.add( contentArea );
		
		/* We create our station and register it as root station */
		CStack stack = new CStack( "stack" );
		control.addStation( stack, true );
		
		/* Now we set up some Dockable to play around with. The first three will be children of
		 * our station, the other three will represent "free" dockables. */
		ColorSingleCDockable white = new ColorSingleCDockable( "White", Color.WHITE );
		ColorSingleCDockable gray = new ColorSingleCDockable( "Gray", Color.GRAY );
		ColorSingleCDockable black = new ColorSingleCDockable( "Black", Color.BLACK );
		
		ColorSingleCDockable red = new ColorSingleCDockable( "Red", Color.RED );
		ColorSingleCDockable green = new ColorSingleCDockable( "Green", Color.GREEN );
		ColorSingleCDockable blue = new ColorSingleCDockable( "Blue", Color.BLUE );
		
		/* We now put three Dockables and our custom station onto the CContentArea of our application. */
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 0.25, red );
		grid.add( 0, 0.25, 0.25, 0.25, green );
		grid.add( 0, 0.5, 0.25, 0.5, blue );
		grid.add( 0.25, 0.25, 0.75, 0.75, stack );
		contentArea.deploy( grid );
		
		
		/* Now we need to set up the remaining three Dockables. First we register them at the CControl. */
		control.addDockable( white );
		control.addDockable( gray );
		control.addDockable( black );
		
		/* Then we make sure, that they always stay on our custom station */
		white.setWorkingArea( stack );
		gray.setWorkingArea( stack );
		black.setWorkingArea( stack );
		
		/* We fall back to the Core API to make the Dockables visible. */
		stack.getStation().add( white.intern(), 0 );
		stack.getStation().add( gray.intern(), 1 );
		stack.getStation().add( black.intern(), 2 );
		
		frame.setVisible( true );
	}

	/* This is our custom CStation that is build upon a StackDockStation. By implementing
	 * CNormalModeArea we can register this station as an area with children in the "normal" mode. This is 
	 * necessary in order for the CLocationManager to accept this station as a proper root-station. 
	 * 
	 * Our implementation is distributed over two classes: the CStack is a wrapper implementing CDockable and
	 * CStation. The CStackDockStation is the wrapped class that allows access to the API of Core.
	 * 
	 * To make our life easier we extend from AbstractCDockableStation which already implements many methods 
	 * of CDockable and CStation. */
	private static class CStack extends AbstractDockableCStation<StackDockStation> implements CNormalModeArea{
		public CStack( String id ){
			CStackDockStation delegate = new CStackDockStation( this );

			CLocation stationLocation = new CLocation(){
				@Override
				public CLocation getParent(){
					return null;
				}
				
				@Override
				public String findRoot(){
					return getUniqueId();
				}
				
				@Override
				public DockableProperty findProperty( DockableProperty successor ){
					return successor;
				}
				
				@Override
				public ExtendedMode findMode(){
					return ExtendedMode.NORMALIZED;
				}
				
				@Override
				public CLocation aside(){
					return this;
				}
			}; 
			
			init( delegate, id, stationLocation, delegate );
			
		}

		/* This method is called by the CControl and allows access to some inner API that is
		 * hidden from normal clients. */
		protected void install( CControlAccess access ){
			access.getLocationManager().getNormalMode().add( this );
		}

		
		protected void uninstall( CControlAccess access ){
			access.getLocationManager().getNormalMode().remove( getUniqueId() );
		}

		public CStationPerspective createPerspective(){
			/* For this example we do not support perspectives. */
			throw new IllegalStateException( "not implemented" );
		}

		public boolean isNormalModeChild( Dockable dockable ){
			return isChild( dockable );
		}

		public DockableProperty getLocation( Dockable child ){
			return DockUtilities.getPropertyChain( getStation(), child );
		}

		public void setLocation( Dockable dockable, DockableProperty location, AffectedSet set ){
			set.add( dockable );
			
			if( isChild( dockable )){
				getStation().move( dockable, location );
			}
			else{
				if( !getStation().drop( dockable, location )){
					getStation().drop( dockable );
				}
			}
		}

		public void addModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public Path getTypeId(){
			// not required
			return null;
		}
		
		public boolean autoDefaultArea(){
			return true;
		}

		public boolean isChild( Dockable dockable ){
			return dockable.getDockParent() == getStation();
		}

		public void removeModeAreaListener( ModeAreaListener listener ){
			// not required
		}

		public void setController( DockController controller ){
			// ignore
		}

		public void setMode( LocationMode mode ){
			// ignore
		}

		public CLocation getCLocation( Dockable dockable ){
			DockableProperty property = DockUtilities.getPropertyChain( getStation(), dockable );
			return getStationLocation().expandProperty( property );
		}

		public CLocation getCLocation( Dockable dockable, Location location ){
			DockableProperty property = location.getLocation();
			if( property == null ){
				return getStationLocation();
			}
			
			return getStationLocation().expandProperty( property );
		}

		public boolean respectWorkingAreas(){
			return true;
		}

		public boolean isCloseable(){
			return false;
		}

		public boolean isExternalizable(){
			return false;
		}

		public boolean isMaximizable(){
			return false;
		}

		public boolean isMinimizable(){
			return false;
		}

		public boolean isStackable(){
			return false;
		}
		
		public boolean isWorkingArea(){
			return true;
		}
		
		public DockActionSource[] getSources(){
			return new DockActionSource[]{ getClose() };
		}
	}
	
	/* This is our Core DockStation. It needs to implement CommonDockable and CommonStation, that way the framework
	 * recognizes this object as beeing wrapped by some CDockable or CStation. */
	private static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation,CStackDockStation>, CommonDockable{
		private CStack delegate;
		
		public CStackDockStation( CStack stack ){
			this.delegate = stack;
		}
		
		public CDockable getDockable(){
			return delegate;
		}

		public DockActionSource[] getSources(){
			return delegate.getSources();
		}

		public CStation<StackDockStation> getStation(){
			return delegate;
		}
		
		public StackDockStation getDockStation(){
			return this;
		}
		
		@Override
		public CStackDockStation asDockStation(){
			return this;
		}
		
		@Override
		public CommonDockable asDockable(){
			return this;
		}
	}
}

Ah, the not-existence of this line causes the problem:
// control.setGroupBehavior( CGroupBehavior.TOPMOST );

This is a tricky problem. Because the entire stack is now maximized the framework does not realize that the children are maximized. It still treats them as normalized, and now you can’t “normalize” them because they are already “normalized”.

I’m not yet sure how to work around the issue, I’ll write back if I have a solution. Inserting this line again would temporarily solve the problem.

There are some code modifications within the framework needed to properly catch and handle the custom StackDockStation. I’ll have to make sure the tweak does not break something else, but I can upload a new version during the weekend. For now just uncomment the line from the last post to continue your work.

The example will need a little update as well, the method on line 155 will afterwards look like this:

			return isChild( dockable ) && getExtendedMode() == ExtendedMode.NORMALIZED;
		}```

Hi Beni,

I have a framework which build component dynamically based on some hints

For example if the user choose a panel + dockable --> I’ll build a Dockable
if the user choose a tabbed pane + dockable --> I’ll build a CStack

The below code are as generated by my fraework

Problems:

  1. When using control.getController().setSingleParentRemover(new CustomSingleParentRemover(control));, I have a problem if the CStack is initially empty.
    In this case, the CStack is hidden (which is very good), after adding one child to this CStack the stack is not beiing shown again.

  2. If I double click on the title of the log, it’ll disappear

  3. If I comment control.getController().setSingleParentRemover(new CustomSingleParentRemover(control)); and I try to double click on the “log”, it will maximize correctly, trying to restore it again, dockable sizes are corrupted

Same issue, if you click on the “Open Screen” button and then you try to maximize the new added dockable then restoring it again

Could you please advice?

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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


import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DirectWindowProvider;
import bibliothek.gui.dock.util.DockUtilities;

import bibliothek.util.Path;

import tutorial.support.ColorSingleCDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;


@Tutorial(id = "NewCStation", title = "Custom CStation")
public class NewCStationExample {
    public NewCStationExample() {
    }


    /* This example shows how a StackDockStation is converted into a dockable CStation that behaves like
     * a working-area.
     *
     * This is only a skeleton example, in a real application some additional tweaking will
     * be necessary to get everything running smoothly. */
    public static void main(String[] args) {
        /* Setting up a frame */
        JTutorialFrame frame = new JTutorialFrame(NewCStationExample.class);

        final CControl control = new CControl();
        control.setGroupBehavior(CGroupBehavior.TOPMOST);
        CContentArea contentArea = control.getContentArea();
        final CGrid grid = new CGrid(control);
        
        control.getController().setSingleParentRemover(new CustomSingleParentRemover(control));
//      control.getController().getExtensions().add(new GlassCustomExtension());
//      control.putProperty(CombinedMenuContent.MENU_CONTENT, new SwingFilteredListMenuContent());
        control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);

        control.setRootWindow(new DirectWindowProvider(frame));

        CStack consolestack = new CStack("Navigation");
        consolestack.setHideIfEmpty(true);

        control.addStation(consolestack, true);

        ColorSingleCDockable dockable = new ColorSingleCDockable("Log", Color.LIGHT_GRAY);
//      dockable.putAction(CDockable.ACTION_KEY_CLOSE, new CustomCloseAction(control));
        dockable.setCloseable(true);
        // Set the title of a dockable
        dockable.setTitleText("Log");
        control.addDockable(dockable);
        consolestack.getStation().add(dockable.intern(), 0);
        dockable.setVisible(true);
        grid.add(0, 50, 50, 15, consolestack);
        control.getContentArea().deploy(grid);

        NaviagtionCDockable navigationDockable = new NaviagtionCDockable("Tree");
        grid.add(0, 11, 2, 40, navigationDockable);
        navigationDockable.setTitleText("Navigation");
        control.getContentArea().deploy(grid);

        final CStack screensstack = new CStack("screens");
        control.addStation(screensstack, true);
        grid.add(40, 11, 48, 40, screensstack);
        control.getContentArea().deploy(grid);
        
        navigationDockable.getOpenScreen().addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				ColorSingleCDockable dockable = new ColorSingleCDockable("Title " + screensstack.getStation().getDockableCount(), Color.WHITE);
				control.addDockable(dockable);
				screensstack.getStation().add(dockable.intern(), 0);
		        dockable.setVisible(true);
				
			}
		});
        frame.add(contentArea);
        frame.setVisible(true);
    }
    
    /* This is our custom CStation that is build upon a StackDockStation. By implementing
     * CNormalModeArea we can register this station as an area with children in the "normal" mode. This is
     * necessary in order for the CLocationManager to accept this station as a proper root-station.
     *
     * Our implementation is distributed over two classes: the CStack is a wrapper implementing CDockable and
     * CStation. The CStackDockStation is the wrapped class that allows access to the API of Core.
     *
     * To make our life easier we extend from AbstractCDockableStation which already implements many methods
     * of CDockable and CStation. */
    private static class CStack extends AbstractDockableCStation<StackDockStation> implements CNormalModeArea, SingleCDockable {
        private boolean hideIfEmpty;


        public CStack(String id) {
            CStackDockStation delegate = new CStackDockStation(this);
            CLocation stationLocation = new CLocation() {
                @Override
                public CLocation getParent() {
                    return null;
                }

                @Override
                public String findRoot() {
                    return getUniqueId();
                }

                @Override
                public DockableProperty findProperty(DockableProperty successor) {
                    return successor;
                }

                @Override
                public ExtendedMode findMode() {
                    return ExtendedMode.NORMALIZED;
                }

                @Override
                public CLocation aside() {
                    return this;
                }
            };
            init(delegate, id, stationLocation, delegate);
        }


        public void setHideIfEmpty(boolean hideIfEmpty) {
            this.hideIfEmpty = hideIfEmpty;
        }

        @Override
        public CStationPerspective createPerspective() {
            /* For this example we do not support perspectives. */
            throw new IllegalStateException("not implemented");
        }

        @Override
        public boolean isNormalModeChild(Dockable dockable) {
            return isChild(dockable);
        }

        @Override
        public DockableProperty getLocation(Dockable child) {
            return DockUtilities.getPropertyChain(getStation(), child);
        }

        @Override
        public void setLocation(Dockable dockable, DockableProperty location, AffectedSet set) {
            set.add(dockable);
            if (isChild(dockable)) {
                getStation().move(dockable, location);
            } else {
                if (!getStation().drop(dockable, location)) {
                    getStation().drop(dockable);
                }
            }
        }

        @Override
        public void addModeAreaListener(ModeAreaListener listener) {
            // not required
        }

        @Override
        public Path getTypeId() {
            // not required
            return null;
        }

        @Override
        public boolean autoDefaultArea() {
            return true;
        }

        @Override
        public boolean isChild(Dockable dockable) {
            return dockable.getDockParent() == getStation();
        }

        @Override
        public void removeModeAreaListener(ModeAreaListener listener) { // not required

        }

        @Override
        public void setController(DockController controller) { // ignore

        }

        @Override
        public void setMode(LocationMode mode) { // ignore

        }

        @Override
        public CLocation getCLocation(Dockable dockable) {
            DockableProperty property = DockUtilities.getPropertyChain(getStation(), dockable);
            return getStationLocation().expandProperty(property);
        }

        @Override
        public CLocation getCLocation(Dockable dockable, Location location) {
            DockableProperty property = location.getLocation();
            if (property == null) {
                return getStationLocation();
            }
            return getStationLocation().expandProperty(property);
        }

        @Override
        public boolean respectWorkingAreas() {
            return true;
        }

        @Override
        public boolean isCloseable() {
            return false;
        }

        @Override
        public boolean isExternalizable() {
            return false;
        }

        @Override
        public boolean isMaximizable() {
            return false;
        }

        @Override
        public boolean isMinimizable() {
            return false;
        }

        @Override
        public boolean isStackable() {
            return false;
        }

        @Override
        public boolean isWorkingArea() {
            return true;
        }

        public boolean hideIfEmpty() {
            return hideIfEmpty;
        }

        public DockActionSource[] getSources() {
            return new DockActionSource[] { getClose() };
        }

        /* This method is called by the CControl and allows access to some inner API that is
         * * hidden from normal clients. */
        @Override
        protected void install(CControlAccess access) {
            access.getLocationManager().getNormalMode().add(this);
        }

        @Override
        protected void uninstall(CControlAccess access) {
            access.getLocationManager().getNormalMode().remove(getUniqueId());
        }
    }

    /* This is our Core DockStation. It needs to implement CommonDockable and CommonStation, that way the framework
     * recognizes this object as beeing wrapped by some CDockable or CStation. */
    private static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation, CStackDockStation>, CommonDockable {
        private CStack delegate;


        public CStackDockStation(CStack stack) {
            this.delegate = stack;
            final Runnable makeVisible = new Runnable() {
                @Override
                public void run() {
                    delegate.setVisible(true);
                }
            };
            DockUtilities.disableCheckLayoutLocked();
            addDockStationListener(new DockStationAdapter() {
                    @Override
                    public void dockableAdded(DockStation station, Dockable dockable) {
                        DockController controller = delegate.getControl().getOwner().getController();
                        controller.getHierarchyLock().onRelease(makeVisible);
                    }
                });
        }


        @Override
        public CDockable getDockable() {
            return delegate;
        }

        @Override
        public DockActionSource[] getSources() {
            return delegate.getSources();
        }

        @Override
        public CStation<StackDockStation> getStation() {
            return delegate;
        }

        @Override
        public StackDockStation getDockStation() {
            return this;
        }

        @Override
        public CStackDockStation asDockStation() {
            return this;
        }

        @Override
        public CommonDockable asDockable() {
            return this;
        }
    }
    
    private static class NaviagtionCDockable extends DefaultSingleCDockable {
    	private JPanel panel = new JPanel();
    	JButton openScreenBtn = new JButton();
    	
    	
    	public JButton getOpenScreen() {
			return openScreenBtn;
		}


		public NaviagtionCDockable( String title ){
    		super( title );
    		setTitleText( title );
       		panel = new JPanel();
    		panel.setOpaque( true );
    		add( panel );
    		openScreenBtn = new JButton("Open screen");
    		panel.add(openScreenBtn);
    	}
    }
    
    private static class CustomSingleParentRemover extends CSingleParentRemover {
        public CustomSingleParentRemover(CControl control) {
            super(control);
        }


        @Override
        protected boolean shouldTest(DockStation station) {
            if (isCStack(station)) {
                return true;
            }

            return super.shouldTest(station);
        }

        @Override
        protected boolean test(DockStation station) {
            if (isCStack(station)) {
                if (station.getDockableCount() > 0) {
                    return false;
                } else {
                    CStack stack = (CStack) ((CommonDockStation<?, ?>) station).getStation();
                    stack.setVisible(false);
                    return false; // because CStack is a root station
                }
            }
            return super.test(station);
        }

        private boolean isCStack(DockStation station) {
            if (station instanceof CommonDockStation<?, ?>) {
                if (((CommonDockStation<?, ?>) station).getStation() instanceof CStack) {
                	return true;
                }
            }
            return false;
        }
    }


}

Thank you

Seems you uncovered some design flaws. The “visibility” state for the CStack is ambiguous: because it is a root-station it is visible, but because it does not have a parent it is not visible. I’ll clean up these ambiguities, and I think we can get the example to work.

This is the first part of my answer, these are the updated libraries (which I do not yet want to release). You’ll need them for the application to work again.

And this is the second part. The SingleParentRemover needs to work a bit different, just calling “CStack.setVisible(false)” would first unmaximize the maximized element… which then dissapears.

And as long as the CStack is a working-area you have to inform the CDockables about it. Lines like “dockable.setWorkingArea(consolestack );” are required for that (or you let “isWorkingArea” return false).

Using the modified libraries from the first part, this should now work properly. I would be happy if you can test this as well.


import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

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


import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.common.CContentArea;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.SingleCDockable;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.intern.AbstractDockableCStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.mode.CNormalModeArea;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.ModeAreaListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.util.DirectWindowProvider;
import bibliothek.gui.dock.util.DockUtilities;

import bibliothek.util.Path;

import tutorial.support.ColorSingleCDockable;
import tutorial.support.JTutorialFrame;
import tutorial.support.Tutorial;


@Tutorial(id = "NewCStation", title = "Custom CStation")
public class Dock65 {
    

    /* This example shows how a StackDockStation is converted into a dockable CStation that behaves like
     * a working-area.
     *
     * This is only a skeleton example, in a real application some additional tweaking will
     * be necessary to get everything running smoothly. */
    public static void main(String[] args) {
        /* Setting up a frame */
        JTutorialFrame frame = new JTutorialFrame(NewCStationExample.class);

        final CControl control = new CControl();
        control.setGroupBehavior(CGroupBehavior.TOPMOST);
        CContentArea contentArea = control.getContentArea();
        final CGrid grid = new CGrid(control);
        
        control.getController().setSingleParentRemover(new CustomSingleParentRemover(control));
//      control.getController().getExtensions().add(new GlassCustomExtension());
//      control.putProperty(CombinedMenuContent.MENU_CONTENT, new SwingFilteredListMenuContent());
        control.setTheme(ThemeMap.KEY_ECLIPSE_THEME);

        control.setRootWindow(new DirectWindowProvider(frame));

        CStack consolestack = new CStack("Navigation");
        consolestack.setHideIfEmpty(true);

        control.addStation(consolestack, true);

        ColorSingleCDockable dockable = new ColorSingleCDockable("Log", Color.LIGHT_GRAY);
//      dockable.putAction(CDockable.ACTION_KEY_CLOSE, new CustomCloseAction(control));
        dockable.setCloseable(true);
        // Set the title of a dockable
        dockable.setTitleText("Log");
        control.addDockable(dockable);
        consolestack.getStation().add(dockable.intern(), 0);
        dockable.setWorkingArea( consolestack );
        dockable.setVisible(true);
        grid.add(0, 50, 50, 15, consolestack);
        control.getContentArea().deploy(grid);

        NaviagtionCDockable navigationDockable = new NaviagtionCDockable("Tree");
        grid.add(0, 11, 2, 40, navigationDockable);
        navigationDockable.setTitleText("Navigation");
        control.getContentArea().deploy(grid);

        final CStack screensstack = new CStack("screens");
        control.addStation(screensstack, true);
        grid.add(40, 11, 48, 40, screensstack);
        control.getContentArea().deploy(grid);
        
        navigationDockable.getOpenScreen().addActionListener(new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				ColorSingleCDockable dockable = new ColorSingleCDockable("Title " + screensstack.getStation().getDockableCount(), Color.WHITE);
				control.addDockable(dockable);
				screensstack.getStation().add(dockable.intern(), 0);
				dockable.setWorkingArea( screensstack );
		        dockable.setVisible(true);
			}
		});
        frame.add(contentArea);
        frame.setVisible(true);
    }
    
    /* This is our custom CStation that is build upon a StackDockStation. By implementing
     * CNormalModeArea we can register this station as an area with children in the "normal" mode. This is
     * necessary in order for the CLocationManager to accept this station as a proper root-station.
     *
     * Our implementation is distributed over two classes: the CStack is a wrapper implementing CDockable and
     * CStation. The CStackDockStation is the wrapped class that allows access to the API of Core.
     *
     * To make our life easier we extend from AbstractCDockableStation which already implements many methods
     * of CDockable and CStation. */
    private static class CStack extends AbstractDockableCStation<StackDockStation> implements CNormalModeArea, SingleCDockable {
        private boolean hideIfEmpty;


        public CStack(String id) {
            CStackDockStation delegate = new CStackDockStation(this);
            CLocation stationLocation = new CLocation() {
                @Override
                public CLocation getParent() {
                    return null;
                }

                @Override
                public String findRoot() {
                    return getUniqueId();
                }

                @Override
                public DockableProperty findProperty(DockableProperty successor) {
                    return successor;
                }

                @Override
                public ExtendedMode findMode() {
                    return ExtendedMode.NORMALIZED;
                }

                @Override
                public CLocation aside() {
                    return this;
                }
            };
            init(delegate, id, stationLocation, delegate);
        }


        public void setHideIfEmpty(boolean hideIfEmpty) {
            this.hideIfEmpty = hideIfEmpty;
        }

        @Override
        public CStationPerspective createPerspective() {
            /* For this example we do not support perspectives. */
            throw new IllegalStateException("not implemented");
        }

        @Override
        public boolean isNormalModeChild(Dockable dockable) {
            return isChild(dockable);
        }

        @Override
        public DockableProperty getLocation(Dockable child) {
            return DockUtilities.getPropertyChain(getStation(), child);
        }

        @Override
        public void setLocation(Dockable dockable, DockableProperty location, AffectedSet set) {
            set.add(dockable);
            if (isChild(dockable)) {
                getStation().move(dockable, location);
            } else {
                if (!getStation().drop(dockable, location)) {
                    getStation().drop(dockable);
                }
            }
        }

        @Override
        public void addModeAreaListener(ModeAreaListener listener) {
            // not required
        }

        @Override
        public Path getTypeId() {
            // not required
            return null;
        }

        @Override
        public boolean autoDefaultArea() {
            return true;
        }

        @Override
        public boolean isChild(Dockable dockable) {
            return dockable.getDockParent() == getStation();
        }

        @Override
        public void removeModeAreaListener(ModeAreaListener listener) { // not required

        }

        @Override
        public void setController(DockController controller) { // ignore

        }

        @Override
        public void setMode(LocationMode mode) { // ignore

        }

        @Override
        public CLocation getCLocation(Dockable dockable) {
            DockableProperty property = DockUtilities.getPropertyChain(getStation(), dockable);
            return getStationLocation().expandProperty(property);
        }

        @Override
        public CLocation getCLocation(Dockable dockable, Location location) {
            DockableProperty property = location.getLocation();
            if (property == null) {
                return getStationLocation();
            }
            return getStationLocation().expandProperty(property);
        }

        @Override
        public boolean respectWorkingAreas() {
            return true;
        }

        @Override
        public boolean isCloseable() {
            return false;
        }

        @Override
        public boolean isExternalizable() {
            return false;
        }

        @Override
        public boolean isMaximizable() {
            return false;
        }

        @Override
        public boolean isMinimizable() {
            return false;
        }

        @Override
        public boolean isStackable() {
            return false;
        }

        @Override
        public boolean isWorkingArea() {
            return true;
        }

        public boolean hideIfEmpty() {
            return hideIfEmpty;
        }

        public DockActionSource[] getSources() {
            return new DockActionSource[] { getClose() };
        }

        /* This method is called by the CControl and allows access to some inner API that is
         * * hidden from normal clients. */
        @Override
        protected void install(CControlAccess access) {
            access.getLocationManager().getNormalMode().add(this);
        }

        @Override
        protected void uninstall(CControlAccess access) {
            access.getLocationManager().getNormalMode().remove(getUniqueId());
        }
    }

    /* This is our Core DockStation. It needs to implement CommonDockable and CommonStation, that way the framework
     * recognizes this object as beeing wrapped by some CDockable or CStation. */
    private static class CStackDockStation extends StackDockStation implements CommonDockStation<StackDockStation, CStackDockStation>, CommonDockable {
        private CStack delegate;


        public CStackDockStation(CStack stack) {
            this.delegate = stack;
            final Runnable makeVisible = new Runnable() {
                @Override
                public void run() {
                    delegate.setVisible(true);
                }
            };
            DockUtilities.disableCheckLayoutLocked();
            addDockStationListener(new DockStationAdapter() {
                    @Override
                    public void dockableAdded(DockStation station, Dockable dockable) {
                        DockController controller = delegate.getControl().getOwner().getController();
                        controller.getHierarchyLock().onRelease(makeVisible);
                    }
                });
        }


        @Override
        public CDockable getDockable() {
            return delegate;
        }

        @Override
        public DockActionSource[] getSources() {
            return delegate.getSources();
        }

        @Override
        public CStation<StackDockStation> getStation() {
            return delegate;
        }

        @Override
        public StackDockStation getDockStation() {
            return this;
        }

        @Override
        public CStackDockStation asDockStation() {
            return this;
        }

        @Override
        public CommonDockable asDockable() {
            return this;
        }
    }
    
    private static class NaviagtionCDockable extends DefaultSingleCDockable {
    	private JPanel panel = new JPanel();
    	JButton openScreenBtn = new JButton();
    	
    	
    	public JButton getOpenScreen() {
			return openScreenBtn;
		}


		public NaviagtionCDockable( String title ){
    		super( title );
    		setTitleText( title );
       		panel = new JPanel();
    		panel.setOpaque( true );
    		add( panel );
    		openScreenBtn = new JButton("Open screen");
    		panel.add(openScreenBtn);
    	}
    }
    
    private static class CustomSingleParentRemover extends CSingleParentRemover {
        public CustomSingleParentRemover(CControl control) {
            super(control);
        }


        @Override
        protected boolean shouldTest(DockStation station) {
            if (isCStack(station)) {
                return true;
            }

            return super.shouldTest(station);
        }

        @Override
        protected boolean test(DockStation station) {
            if (isCStack(station)) {
                if (station.getDockableCount() > 0 || station.asDockable().getDockParent() == null ) {
                    return false;
                } else {
                    CStack stack = (CStack) ((CommonDockStation<?, ?>) station).getStation();
                    
                    // stack.setVisible(false);  this line caused problems, let's do this manually
                    Dockable dockable = station.asDockable();
                    
                    stack.getControl().getLocationManager().store( dockable );
                    stack.getControl().getOwner().intern().hide( dockable );
                    
                    return false; // because CStack is a root station
                }
            }
            return super.test(station);
        }

        private boolean isCStack(DockStation station) {
            if (station instanceof CommonDockStation<?, ?>) {
                if (((CommonDockStation<?, ?>) station).getStation() instanceof CStack) {
                	return true;
                }
            }
            return false;
        }
    }


}```