Using SplitDockStation in a ScreeenDockWindow?

Hi. I’m looking at using DockingFrames for an open source application our team is developing. So far it looks really nice. Thanks for sharing it.

Using the core classes I was able to create new windows that use a SplitDockStation. However if I then drag a dockable out of my window the new window created by the ScreenDockStation (using the ScreenDockWindowFactory) uses a StackDockStation.

The ScreenDockWindow interface has the methods get/setDockable that seem to assume that a single dockable is displayed in the window. Is that correct? Could that dockable be a SplitDockStation? Is there some easy way to write my own ScreenDockWindowFactory that uses a SplitDockStation? Or do I also need to create a new class that extends ScreenDockStation?

thanks,
Ron

Hm, that is tricky. There is a solution that will work most times. The algorithm that combines several Dockable’s to one can be exchanged.

This solution will however fail if a user drag & drops a whole StackDockStation because then there is no need to create any new elements.

But to modify the ScreenDockStation or the factory itself… I’m really not sure how that would work.

If the solution below is not enough for your needs then let me know. I might be able to modify the framework a bit, such that new stations can slip in while a drag & drop operation is executed.


import javax.swing.JFrame;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DefaultDockable;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.station.Combiner;
import bibliothek.gui.dock.station.split.SplitDockGrid;
import bibliothek.gui.dock.station.split.SplitDockTree;
import bibliothek.gui.dock.themes.BasicTheme;
import bibliothek.gui.dock.themes.NoStackTheme;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleVersion;

public class Dock1 {
	public static void main( String[] args ){
		JFrame frame = new JFrame();

		// setup a controller and a few dockables
		DockController controller = new DockController();
		controller.setTheme( new NoStackTheme( new BasicTheme() ) );
		SplitDockStation center = new SplitDockStation();
		controller.add( center );
		frame.add( center.getComponent() );

		SplitDockGrid layout = new SplitDockGrid();
		int index = 0;
		for( int i = 0; i < 3; i++ ){
			for( int j = 0; j < 3; j++ ){
				layout.addDockable( i, j, 1, 1, new DefaultDockable( String.valueOf( index++ ) ));
			}
		}
		center.dropTree( layout.toTree() );

		// setup the special ScreenDockStation
		ScreenDockStation screen = new ScreenDockStation( frame );

		// ** This is the new and important line **
		screen.getCombiner().setDelegate( new SplitCombiner() );

		controller.add( screen );

		frame.setBounds( 20, 20, 400, 500 );
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

		frame.setVisible( true );
		screen.setShowing( true );
	}

	/**
	 * Ensures the combination of Dockables becomes a SplitDockStation
	 */
	private static class SplitCombiner implements Combiner{
		public Dockable combine( Dockable old, Dockable drop, DockStation parent ){
			SplitDockStation combination = new SplitDockStation(){
				@Override
				public DockTitle getDockTitle( DockTitleVersion version ){
					// don't show a title if it does not display anything meaningful or
					// is necessary in any other way
					if( ScreenDockStation.TITLE_ID.equals( version.getID() ) || 
							StackDockStation.TITLE_ID.equals( version.getID() ) ||
							SplitDockStation.TITLE_ID.equals( version.getID() ) ||
							FlapDockStation.WINDOW_TITLE_ID.equals( version.getID() ))
						return null;

					return super.getDockTitle( version );
				}
			};

			SplitDockTree layout = new SplitDockTree();
			layout.root( layout.horizontal( old, drop, 0.5 ) );
			combination.dropTree( layout );

			return combination;
		}
	}
}

Beni, thanks very much. Using a SplitCombiner works good enough for now. I’d still like to be able to let the user dragging the dockable decide whether to use a vertical or horizontal orientation, but they can just drag it a second time after the SplitDockStation has been created.

By the way the current implementation requires dragging the mouse to the DockTitle of a Dockable. Is there anyway to make it so that the entire area of the Dockable can be the target of the drop? To clarify, once a SplitDockStation is in use as soon as you drag a dockable anywhere over it you can drop it, but for a normal dockable only dropping on the DockTitle will cause a new StackDockStation (or with the SplitCombiner a SplitDockStation) to be created.

thanks,
Ron

Override the method “searchCombineDockable” of ScreenDockStation. Like this:

    	public TakeAllScreenDockStation( JFrame frame ){
    		super( frame );
    	}
    	
    	protected ScreenDockWindow searchCombineDockable( int x, int y, Dockable drop ){
            DockAcceptance acceptance = getController() == null ? null : getController().getAcceptance();
            
            for( int i = 0, n = getDockableCount(); i<n; i++ ){
            	ScreenDockWindow window = getWindow( i );
            	if( window.getWindowBounds().contains( x, y ) ){
            	    Dockable child = window.getDockable();
                    
                    if( acceptance == null || acceptance.accept( this, child, drop )){
                        if( drop.accept( this, child ) && child.accept( this, drop )){
                            return window;
                        }
                    }
                }
            }
            
            return null;
        }
    }```

That works great! Thanks.

– Ron –

Hello Beni.
I’ve recently started to use DockingFrames and it’s really an excellent framework!
Since there is newer version of framework, some of code doesn’t work now. Could you please write some sample about this mentioned problem? Thanks in advance. :slight_smile:

Could you please be a bit more specific. Are you referring to the example in this thread, or something else? What is the issue you want to solve, and why do you want to solve it? I preferre first to find the requirements, then write the sample code :wink:

Sorry…
Yes, I’m referring to the example in this thread! Since I use the newest version, the example in this thread doesn’t work (some classes missing, different parameters passing, etc.), so I couldn’t find solution. I want to place for example 2 or more Dockables in 1 ScreenDockStation. And one more question is this possible with using Common library?
Thanks.

Sorry again… ‘I want to place for example 2 or more Dockables in 1 ScreenDockStation’ but to be in SplitDockStation not StackDockStation…

Ok, I’ll write you back during the weekend. I plan to write a little tutorial that does not outdate as fast as the last one, and that will need some time.

Here is the updated example. It will also be included in the tutorial project.

I’m using the Common API, because no sane person would want to use the Core API for his applications :wink:


import java.awt.BorderLayout;

import javax.swing.JFrame;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockHierarchyLock;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.accept.DockAcceptance;
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.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.event.DockStationAdapter;
import bibliothek.gui.dock.station.LayoutLocked;
import bibliothek.gui.dock.title.NullTitleFactory;

// @Tutorial(title="Splitting externalized Dockables", id="SplittingExternalized")
public class SplittingExternalizedDockablesExample {
	public static void main( String[] args ){
		/* In this example we will replace the default behavior of externalized CDockables: instead of being stacked,
		 * we will allow them to be split (as if they would be children of a CGridArea or CWorkingArea).
		 * 
		 * This will be a deep intrusion into the inner workings of the framework. We will not implement all features
		 * that would be required to fully support this kind of behavior, but it will be enough for more simple
		 * applications.
		 * 
		 * Be aware that this example reaches into places of the framework that may suddenly change their API, we are
		 * getting far away from the nice, public API. */
		
		/* Like in every example, we need a JFrame... */
		// JTutorialFrame frame = new JTutorialFrame( SplittingExternalizedDockablesExample.class );
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 400, 400 );
		
		/* ... and a controller */
		CControl control = new CControl( frame );
		
		/* We are going to make sure, that any Dockable that is externalized (= put onto a ScreenDockStation) is
		 * actually put onto a SplitDockStation that we create on the fly.
		 * 
		 * Usually the framework would throw away any DockStation that has 0 or 1 children. Here we are replacing
		 * this behavior with an algorithm that does not remove SplitDockStations that have 1 child and that were
		 * put on a ScreenDockStation. */
		control.getController().setSingleParentRemover( new SplitAwareSingleParentRemover( control ) );

		/* We are now accessing the ScreenDockStation itself (it always has the same unique identifier), and add
		 * a DockStationListener to it. The listener will be informed whenever a Dockable is put onto the 
		 * station, and we can replace the Dockable with a SplitDockStation. */
		CStation<?> screen = control.getStation( CControl.EXTERNALIZED_STATION_ID );
		screen.getStation().addDockStationListener( new SplitInserter( control ) );
		
		/* The last thing we want is a user stacking a Dockable with our floating SplitDockStations. 
		 * With this DockAcceptance we instruct the framework to never allow such an action to happen. */
		control.getController().addAcceptance( new CombiningPreventer() );
		
		/* A SplitDockStation usually does automatically create a "maximize" button, but CDockables already have on.
		 * We are hiding the "maximize" button of the CDockable if we are on a floating SplitDockStation. */
		control.addStateListener( new MaximizeButtonDisabler() );

		/* Finally we do not want the floating SplitDockStations to have a title. Since a ScreenDockStation does only
		 * have floating SplitDockStations as children, we can safely disable all titles. */
		control.getController().getDockTitleManager().registerClient( ScreenDockStation.TITLE_ID, new NullTitleFactory() );
		
		/* And the remaining part of the initialization is like in most of the other examples */
//		frame.destroyOnClose( control );
		frame.add( control.getContentArea(), BorderLayout.CENTER );
		
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 1, new DefaultSingleCDockable( "red", "Red" ) );
		grid.add( 1, 0, 1, 1, new DefaultSingleCDockable( "green", "Green" ) );
		grid.add( 0, 1, 1, 1, new DefaultSingleCDockable( "blue", "Blue" ) );
		grid.add( 1, 1, 1, 1, new DefaultSingleCDockable( "yellow", "Yellow" ) );
		control.getContentArea().deploy( grid );
		
		frame.setVisible( true );
	}
	
	/* This algorithm decides which DockStations should be automatically removed by the framework */
	private static class SplitAwareSingleParentRemover extends CSingleParentRemover{
		public SplitAwareSingleParentRemover( CControl control ){
			super( control );
		}

		@Override
		protected boolean shouldTest( DockStation station ){
			if( station instanceof SplitDockStation ){
				if( station.getDockableCount() == 1 ){
					if( station.asDockable().getDockParent() instanceof ScreenDockStation ){
						/* prevents removal of floating SplitDockStations that have only one child */
						return false;
					}
				}
			}
			return super.shouldTest( station );
		}
	}
	
	/* This listener replaces any Dockable that is added to a ScreenDockStation with a new SplitDockStation.
	 * 
	 * The framework has some built in sanity checks, warning developers when their application tries to do 
	 * bad stuff. One of the checks is to ensure, that the tree of Dockables and DockStations is never modified
	 * concurrently. And modifying the layout from within a DockStationListener usually is a sign that an application
	 * violates that condition.
	 * With @LayoutLocked we inform the framework that we know exactly what we are doing, and disable the warning. */
	@LayoutLocked(locked=false)
	public static class SplitInserter extends DockStationAdapter {
		private CControl control;
		
		public SplitInserter( CControl control ){
			this.control = control;
		}
		
		public void dockableAdded( DockStation station, final Dockable dockable ){
			if( !(dockable instanceof SplitDockStation) ) {
				DockHierarchyLock lock = control.getController().getHierarchyLock();

				/* This method is called while the tree is modified, if we would try to replace the Dockable right
				 * now the framework would raise an exception. 
				 * Instead we instruct the framework to replace the Dockable at the next possible opportunity. */
				lock.onRelease( new Runnable(){
					public void run(){
						checkAndReplace( dockable );
					}
				});
			}
		}
		
		private void checkAndReplace( Dockable dockable ){
			DockStation station = dockable.getDockParent();
			if( !(station instanceof ScreenDockStation) ) {
				// just some sanity checks - we do not expect this piece of code to be ever executed.
				return;
			}
			
			/* And now we just replace the new Dockable "dockable" with "split" */
			SplitDockStation split = new SplitDockStation();
			
			DockController controller = control.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();
			}
		}
	};
	
	/* A DockAcceptance instructs the framework to ignore certain drag and drop operations... */
	private static class CombiningPreventer implements DockAcceptance{
		public boolean accept( DockStation parent, Dockable child ){
			return true;
		}
		
		public boolean accept( DockStation parent, Dockable child, Dockable next ){
			/* ... in this case we ignore any drag and drop operation that would put a floating
			 * SplitDockStation into a stack. */
			return !(parent instanceof ScreenDockStation);
		}
	}
	
	/* If a CDockable is in the externalized mode, then we hide the default "maximize" button. */
	public static class MaximizeButtonDisabler 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 );
				}
			}
		}
	}
}

Thanks! Really helps! :wink:

Sorry for bothering. One more thing I’m interested in… Can we leave title of SplitDockStation as ScreenDockStation and have maximize button?
I tried to comment that line of code, but double click on title bar throws exception for maximization. Also when remove MaximizeDisabler there is maximize button on title bar of splitdockstation and also added maximize button on dockable, I understood that MaximizeDisabler is used for removing that maximize button, but is it possible to have maximization for splitdockstation?

Hm, this request really pushes the boundaries of what is possible with the framework. I’ll have to dig a bit deeper into the code (and change some stuff) to get this working. This will need some time, I’ll write back maybe in a week or so…

I’ve decided to integrate this feature directly into the framework, you can now activate it with one line of code.

  • The SplitDockStation itself can be maximized (it has a button)
  • The children can be maximized inside the SplitDockStation

The feature is not completely finished:

  • The order of the buttons is not yet correct (that bug is surprisingly though to solve)
  • Perspectives will not yet work
  • There are some issues with layout information that is lost when all the externalized CDockables are closed

I’ll continue working on the issues, but for now you have version 1.1.2p6 to play around with the new feature.

Here an example how to use it:


import java.awt.BorderLayout;

import javax.swing.JFrame;

import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGrid;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.behavior.ExternalizingCGridAreaConfiguration;

public class SplittingExternalizedDockablesExample {
	public static void main( String[] args ){
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		frame.setBounds( 20, 20, 400, 400 );
		
		final CControl control = new CControl( frame );
		
		/* We are now accessing the ScreenDockStation itself (it always has the same unique identifier), and add
		 * a DockStationListener to it. The listener will be informed whenever a Dockable is put onto the 
		 * station, and we can replace the Dockable with a SplitDockStation. */
		ExternalizingCGridAreaConfiguration.installOn( control );
		
		frame.add( control.getContentArea(), BorderLayout.CENTER );
		
		CGrid grid = new CGrid( control );
		grid.add( 0, 0, 1, 1, new DefaultSingleCDockable( "red", "Red" ) );
		grid.add( 1, 0, 1, 1, new DefaultSingleCDockable( "green", "Green" ) );
		grid.add( 0, 1, 1, 1, new DefaultSingleCDockable( "blue", "Blue" ) );
		grid.add( 1, 1, 1, 1, new DefaultSingleCDockable( "yellow", "Yellow" ) );
		control.getContentArea().deploy( grid );
		
		frame.setVisible( true );
	}
}