All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.ToolbarGroupDockStation Maven / Gradle / Ivy

/*
 * Bibliothek - DockingFrames
 * Library built on Java/Swing, allows the user to "drag and drop"
 * panels containing any Swing-Component the developer likes to add.
 * 
 * Copyright (C) 2012 Herve Guillaume, Benjamin Sigg
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Herve Guillaume
 * [email protected]
 * FR - France
 *
 * Benjamin Sigg
 * [email protected]
 * CH - Switzerland
 */

package bibliothek.gui.dock;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockUI;
import bibliothek.gui.Dockable;
import bibliothek.gui.Orientation;
import bibliothek.gui.dock.component.DefaultDockStationComponentRootHandler;
import bibliothek.gui.dock.component.DockComponentRootHandler;
import bibliothek.gui.dock.event.DockStationListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.layout.location.AsideAnswer;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.security.SecureContainer;
import bibliothek.gui.dock.station.DisplayerCollection;
import bibliothek.gui.dock.station.DisplayerFactory;
import bibliothek.gui.dock.station.DockableDisplayer;
import bibliothek.gui.dock.station.DockableDisplayerListener;
import bibliothek.gui.dock.station.PlaceholderMapping;
import bibliothek.gui.dock.station.StationChildHandle;
import bibliothek.gui.dock.station.StationDropItem;
import bibliothek.gui.dock.station.StationDropOperation;
import bibliothek.gui.dock.station.layer.DockStationDropLayer;
import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem;
import bibliothek.gui.dock.station.support.PlaceholderList.Level;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.toolbar.ToolbarGroupDockStationFactory;
import bibliothek.gui.dock.station.toolbar.ToolbarGroupDockStationLayout;
import bibliothek.gui.dock.station.toolbar.ToolbarStrategy;
import bibliothek.gui.dock.station.toolbar.group.ColumnScrollBar;
import bibliothek.gui.dock.station.toolbar.group.ColumnScrollBarFactory;
import bibliothek.gui.dock.station.toolbar.group.DefaultToolbarGroupDividierStrategy;
import bibliothek.gui.dock.station.toolbar.group.SlimScrollbar;
import bibliothek.gui.dock.station.toolbar.group.ToolbarColumn;
import bibliothek.gui.dock.station.toolbar.group.ToolbarColumnModel;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupDividerStrategy;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupDividerStrategyFactory;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupDropInfo;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupExpander;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupHeader;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupHeaderFactory;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupPlaceholderMapping;
import bibliothek.gui.dock.station.toolbar.group.ToolbarGroupProperty;
import bibliothek.gui.dock.station.toolbar.layer.ToolbarGroupInnerLayer;
import bibliothek.gui.dock.station.toolbar.layer.ToolbarGroupOuterLayer;
import bibliothek.gui.dock.station.toolbar.layout.DockablePlaceholderToolbarGrid;
import bibliothek.gui.dock.station.toolbar.layout.PlaceholderToolbarGridConverter;
import bibliothek.gui.dock.station.toolbar.layout.ToolbarGridLayoutManager;
import bibliothek.gui.dock.station.toolbar.title.ColumnDockActionSource;
import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue;
import bibliothek.gui.dock.themes.DefaultStationPaintValue;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.themes.basic.BasicDockTitleFactory;
import bibliothek.gui.dock.title.DockTitleFactory;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.toolbar.expand.ExpandedState;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.SilentPropertyValue;
import bibliothek.gui.dock.util.extension.Extension;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.util.Path;
import bibliothek.util.Todo;
import bibliothek.util.Todo.Compatibility;
import bibliothek.util.Todo.Priority;
import bibliothek.util.Todo.Version;

/**
 * A {@link Dockable} and a {@link DockStation} which stands a group of
 * {@link ToolbarDockStation}. As Dockable it can be put in
 * DockStation which implements marker interface
 * {@link ToolbarInterface} or in {@link ScreenDockStation}, so that a
 * ToolbarDockStation can be floattable. As
 * DockStation, it accepts a {@link ToolbarElementInterface}. If
 * the element is not a ToolbarElementInterface, it is wrapped in a
 * ToolbarDockStation before to be added.
 * 
 * @author Herve Guillaume
 */
public class ToolbarGroupDockStation extends AbstractToolbarDockStation {

	/** the id of the {@link DockTitleFactory} which is used by this station */
	public static final String TITLE_ID = "toolbar.group";
	/**
	 * This id is forwarded to {@link Extension}s which load additional
	 * {@link DisplayerFactory}s
	 */
	public static final String DISPLAYER_ID = "toolbar.group";

	/** Key for the factory that creates new {@link ColumnScrollBar}s */
	public static final PropertyKey SCROLLBAR_FACTORY = new PropertyKey( "dock.scrollbarFactory", new ConstantPropertyFactory( SlimScrollbar.FACTORY ), true );

	/** Key for a factory that creates new {@link ToolbarGroupHeader}s */
	public static final PropertyKey HEADER_FACTORY = new PropertyKey( "dock.toolbarGroupHeaderFactory" );

	/** Key for a strategy for painting borders between the {@link Dockable}s */
	public static final PropertyKey DIVIDER_STRATEGY_FACTORY = new PropertyKey( "dock.toolbarGroupDividerStrategy", new ConstantPropertyFactory( DefaultToolbarGroupDividierStrategy.FACTORY ), true );
	
	/** A list of all children organized in columns and lines */
	private final DockablePlaceholderToolbarGrid dockables = new DockablePlaceholderToolbarGrid();

	/** Responsible for managing the {@link ExpandedState} of the children */
	private ToolbarGroupExpander expander;

	/** The {@link PlaceholderStrategy} that is used by {@link #dockables} */
	private final PropertyValue placeholderStrategy = new PropertyValue( PlaceholderStrategy.PLACEHOLDER_STRATEGY ){
		@Override
		protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){
			dockables.setStrategy( newValue );
		}
	};

	/** factory creating the current divider strategy */
	private PropertyValue dividerStrategyFactory = new PropertyValue( DIVIDER_STRATEGY_FACTORY ){
		@Override
		protected void valueChanged( ToolbarGroupDividerStrategyFactory oldValue, ToolbarGroupDividerStrategyFactory newValue ){
			if( newValue == null ){
				setDividerStrategy( null );
			}
			else{
				setDividerStrategy( newValue.create( ToolbarGroupDockStation.this ));
			}
		}
	};
	
	/** Responsible for painting a border between the {@link Dockable}s, can be null */
	private ToolbarGroupDividerStrategy dividerStrategy;

	/**
	 * The graphical representation of this station: the pane which contains
	 * component
	 */
	private OverpaintablePanelBase mainPanel;

	/**
	 * Size of the border outside this station where a {@link Dockable} will
	 * still be considered to be dropped onto this station. Measured in pixel.
	 */
	private int borderSideSnapSize = 10;
	/**
	 * Whether the bounds of this station are slightly bigger than the station
	 * itself. Used together with {@link #borderSideSnapSize} to grab Dockables
	 * "out of the sky". The default is true.
	 */
	private boolean allowSideSnap = true;

	/**
	 * The {@link LayoutManager} that is currently used to set the location and
	 * size of all children. Can be null if no
	 * {@link #setOrientation(Orientation) orientation} is set.
	 */
	private ToolbarGridLayoutManager layoutManager;

	/**
	 * Information about the drop operation that is currently in progress
	 */
	private ToolbarGroupDropInfo dropInfo;

	/** the factory that creates new scrollbars */
	private PropertyValue scrollbarFactory = new PropertyValue( SCROLLBAR_FACTORY ){
		@Override
		protected void valueChanged( ColumnScrollBarFactory oldValue, ColumnScrollBarFactory newValue ){
			resetScrollbars();
		}
	};

	/** the scrollbars that are currently shown on this station */
	private Map scrollbars = new HashMap();

	/** this listener is added to all {@link ColumnScrollBar}s and ensures an update of positions if the bar changes its value */
	private AdjustmentListener adjustmentListener = new AdjustmentListener(){
		@Override
		public void adjustmentValueChanged( AdjustmentEvent e ){
			mainPanel.dockablePane.revalidate();
		}
	};

	public ToolbarGridLayoutManager getLayoutManager(){
		return layoutManager;
	}

	/** the factory used to create new headers for this station */
	private PropertyValue headerFactory = new PropertyValue( HEADER_FACTORY ){
		@Override
		protected void valueChanged( ToolbarGroupHeaderFactory oldValue, ToolbarGroupHeaderFactory newValue ){
			ToolbarGroupHeader header = null;
			if( newValue != null ) {
				header = newValue.create( ToolbarGroupDockStation.this );
			}

			if( groupHeader != null ) {
				mainPanel.removeHeaderCopmonent( groupHeader.getComponent() );
				groupHeader.destroy();
			}
			groupHeader = header;
			if( groupHeader != null ) {
				groupHeader.setOrientation( getOrientation() );
				mainPanel.addHeaderComponent( groupHeader.getComponent() );
			}
		}
	};

	/** the current component at the top end of this station */
	private ToolbarGroupHeader groupHeader;

	// ########################################################
	// ############ Initialization Managing ###################
	// ########################################################

	/**
	 * Creates a new {@link ToolbarGroupDockStation}.
	 */
	public ToolbarGroupDockStation(){
		init();
	}

	protected void init(){
		init( ThemeManager.BACKGROUND_PAINT + ".station.toolbar.group" );
		mainPanel = new OverpaintablePanelBase();
		paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".toolbar.group", this );
		setOrientation( getOrientation() );
		displayerFactory = createDisplayerFactory();
		displayers = new DisplayerCollection( this, displayerFactory, getDisplayerId() );
		displayers.addDockableDisplayerListener( new DockableDisplayerListener(){
			@Override
			public void discard( DockableDisplayer displayer ){
				ToolbarGroupDockStation.this.discard( displayer );
			}
			@Override
			public void moveableElementChanged( DockableDisplayer displayer ){
				// ignore	
			}
		} );

		setTitleIcon( null );
		expander = new ToolbarGroupExpander( this );
		setDividerStrategy( dividerStrategyFactory.getValue().create( this ) );
	}
	
	@Override
	protected DockComponentRootHandler createRootHandler() {
		return new DefaultDockStationComponentRootHandler( this, displayers );
	}

	// ########################################################
	// ################### Class Utilities ####################
	// ########################################################

	/**
	 * Gets access to a simplified view of the contents of this station.
	 * 
	 * @return a model describing all the columns that are shown on this station
	 */
	public ToolbarColumnModel getColumnModel(){
		return dockables.getModel();
	}

	/**
	 * Gets the column location of the dockable.
	 * 
	 * @param dockable
	 *            the {@link Dockable} to search
	 * @return the column location or -1 if the child was not found
	 */
	public int column( Dockable dockable ){
		return dockables.getColumn( dockable );
	}

	/**
	 * Gets the line location of the dockable.
	 * 
	 * @param dockable
	 *            the {@link Dockable} to search
	 * @return the line location or -1 if the child was not found
	 */
	public int line( Dockable dockable ){
		return dockables.getLine( dockable );
	}

	/**
	 * Gets the number of column of this.
	 * 
	 * @return the number of column
	 */
	public int columnCount(){
		return dockables.getColumnCount(); // column(getDockable(getDockableCount()
											// - 1)) + 1;
	}

	/**
	 * Gets the number of lines in column.
	 * 
	 * @param column
	 *            the column
	 * @return the number of lines in column
	 */
	public int lineCount( int column ){
		return dockables.getLineCount( column );
	}

	/**
	 * Gets the dockable at the specified column and
	 * line.
	 * 
	 * @param columnIndex
	 *            the column index
	 * @param line
	 *            the line index
	 * @return the dockable or null if there's no dockable at the
	 *         specified indexes.
	 */
	public Dockable getDockable( int columnIndex, int line ){
		StationChildHandle handle = getHandle( columnIndex, line );
		if( handle == null ) {
			return null;
		}
		return handle.asDockable();
	}

	/**
	 * Gets the {@link StationChildHandle} which is used at the given position.
	 * 
	 * @param columnIndex
	 *            the column in which to search
	 * @param line
	 *            the line in the column
	 * @return the item or null if the indices are out of bounds
	 */
	public StationChildHandle getHandle( int columnIndex, int line ){
		ToolbarColumnModel model = getColumnModel();
		if( columnIndex < 0 || columnIndex >= model.getColumnCount() ) {
			return null;
		}
		ToolbarColumn column = model.getColumn( columnIndex );
		if( line < 0 || line >= column.getDockableCount() ) {
			return null;
		}
		return column.getItem( line );
	}

	/**
	 * Gets the {@link StationChildHandle} which displays dockable.
	 * @param dockable the item to search
	 * @return the handle showing dockable or null if not found
	 */
	public StationChildHandle getHandle( Dockable dockable ){
		ToolbarColumn column = getColumnModel().getColumn( dockable );
		if( column == null ) {
			return null;
		}
		for( int i = 0, n = column.getDockableCount(); i < n; i++ ) {
			StationChildHandle handle = column.getItem( i );
			if( handle.getDockable() == dockable ) {
				return handle;
			}
		}
		return null;
	}

	/**
	 * Tells if dockable is the last dockable in its column.
	 * 
	 * @param dockable
	 *            the dockable
	 * @return true if the dockable it's the last in its column, false if it's
	 *         not or if it doesn't belong to this dockstation.
	 */
	public boolean isLastOfColumn( Dockable dockable ){
		int index = indexOf( dockable );
		int column = column( dockable );
		if( index == (getDockableCount() - 1) ) {
			return true;
		}
		else if( column( getDockable( index + 1 ) ) != column ) {
			return true;
		}
		else {
			return false;
		}
	}

	// ########################################################
	// ############ General DockStation Managing ##############
	// ########################################################

	/**
	 * Gets the {@link ToolbarStrategy} that is currently used by this station.
	 * 
	 * @return the strategy, never null
	 */
	@Override
	public ToolbarStrategy getToolbarStrategy(){
		DockController controller = getController();
		DockStation parent = getDockParent();
		while( controller == null && parent != null ){
			controller = parent.getController();
			Dockable parentDockable = parent.asDockable();
			if( parentDockable == null ){
				parent = null;
			}
			else{
				parent = parentDockable.getDockParent();
			}
		}
		
		final SilentPropertyValue value = new SilentPropertyValue( ToolbarStrategy.STRATEGY, controller );
		final ToolbarStrategy result = value.getValue();
		value.setProperties( (DockController) null );
		return result;
	}

	@Override
	public Component getComponent(){
		return mainPanel;
	}

	@Override
	public int getDockableCount(){
		return dockables.size();
	}

	@Override
	public Dockable getDockable( int index ){
		return dockables.get( index ).asDockable();
	}

	@Override
	public String getFactoryID(){
		return ToolbarGroupDockStationFactory.ID;
	}

	/**
	 * Gets a unique identifier used to get the {@link DisplayerFactory} for this station.
	 * @return the unique identifier, not null
	 */
	protected String getDisplayerId(){
		return DISPLAYER_ID;
	}

	/**
	 * Sets whether {@link Dockable Dockables} which are dragged near the
	 * station are captured and added to this station.
	 * 
	 * @param allowSideSnap
	 *            true if the station can snap Dockables which are
	 *            near.
	 * @see #setBorderSideSnapSize(int)
	 */
	public void setAllowSideSnap( boolean allowSideSnap ){
		this.allowSideSnap = allowSideSnap;
	}

	/**
	 * Tells whether the station can grab Dockables which are dragged near the
	 * station.
	 * 
	 * @return true if grabbing is allowed
	 * @see #setAllowSideSnap(boolean)
	 */
	public boolean isAllowSideSnap(){
		return allowSideSnap;
	}

	/**
	 * There is an invisible border around the station. If a {@link Dockable} is
	 * dragged inside this border, its considered to be on the station and will
	 * be dropped into.
	 * 
	 * @param borderSideSnapSize
	 *            the size of the border in pixel
	 * @throws IllegalArgumentException
	 *             if the size is smaller than 0
	 */
	public void setBorderSideSnapSize( int borderSideSnapSize ){
		if( borderSideSnapSize < 0 ) {
			throw new IllegalArgumentException( "borderSideSnapeSize must not be less than 0" );
		}

		this.borderSideSnapSize = borderSideSnapSize;
	}

	/**
	 * Gets the size of the invisible border around the station where a dockable
	 * can be dropped.
	 * 
	 * @return the size in pixel
	 * @see #setBorderSideSnapSize(int)
	 */
	public int getBorderSideSnapSize(){
		return borderSideSnapSize;
	}

	@Override
	public void setController( DockController controller ){
		if( getController() != controller ) {
			if( getController() != null ) {
				dockables.unbind();
			}
			Iterator iter = dockables.items();
			while( iter.hasNext() ) {
				iter.next().setTitleRequest( null );
			}

			super.setController( controller );
			// if not set controller of the DefaultStationPaintValue, call to
			// DefaultStationPaintValue do nothing

			if( controller == null ) {
				title = null;
			}
			else {
				title = registerTitle( controller );
			}

			paint.setController( controller );
			expander.setController( controller );
			placeholderStrategy.setProperties( controller );
			displayerFactory.setController( controller );
			displayers.setController( controller );
			mainPanel.setController( controller );
			layoutManager.setController( controller );
			scrollbarFactory.setProperties( controller );
			headerFactory.setProperties( controller );
			dividerStrategyFactory.setProperties( controller );

			if( getController() != null ) {
				dockables.bind();
			}

			iter = dockables.items();
			while( iter.hasNext() ) {
				iter.next().setTitleRequest( title, true );
			}
		}
	}

	// ########################################################
	// ############ Orientation Managing ######################
	// ########################################################

	@Override
	public void setOrientation( Orientation orientation ){
		if( orientation == null ) {
			throw new IllegalArgumentException( "orientation must not be null" );
		}

		// it's very important to change position and orientation of inside
		// dockables first, else doLayout() is done on wrong inside information
		this.orientation = orientation;
		fireOrientingEvent();
		for( ColumnScrollBar scrollbar : scrollbars.values() ) {
			scrollbar.setOrientation( orientation );
		}
		if( groupHeader != null ) {
			groupHeader.setOrientation( orientation );
			mainPanel.removeHeaderCopmonent( groupHeader.getComponent() );
			mainPanel.addHeaderComponent( groupHeader.getComponent() );
		}
		mainPanel.updateAlignment();
		mainPanel.revalidate();
	}

	// ########################################################
	// ############### Drop/Move Managing #####################
	// ########################################################

	@Override
	public DockStationDropLayer[] getLayers(){
		return new DockStationDropLayer[]{
				new ToolbarGroupInnerLayer( this, mainPanel.dockablePane ), 
				new ToolbarGroupOuterLayer( this, mainPanel.dockablePane ) };
	}

	@Override
	public boolean accept( Dockable child ){
		return getToolbarStrategy().isToolbarGroupPart( child );
	}

	@Override
	public boolean accept( DockStation station ){
		return getToolbarStrategy().isToolbarGroupPartParent( station, this, false );
	}

	public boolean accept( DockStation base, Dockable neighbor ){
		return false;
	}

	@Override
	public StationDropOperation prepareDrop( StationDropItem item ){
		// System.out.println(this.toString() + "## prepareDrop(...) ##");
		final DockController controller = getController();

		if( getExpandedState() == ExpandedState.EXPANDED ) {
			return null;
		}

		Dockable dockable = item.getDockable();

		// check if the dockable and the station accept each other
		if( this.accept( dockable ) && dockable.accept( this ) ) {
			// check if controller exists and if the controller accepts that
			// the dockable becomes a child of this station
			if( controller != null ) {
				if( !controller.getAcceptance().accept( this, dockable ) ) {
					return null;
				}
			}

			Point mouse = new Point( item.getMouseX(), item.getMouseY() );
			SwingUtilities.convertPointFromScreen( mouse, mainPanel.dockablePane );

			int column = getColumnAt( mouse );
			int line = -1;
			if( column >= 0 && column < columnCount() ) {
				Rectangle columnBounds = layoutManager.getBounds( column );

				if( getOrientation() == Orientation.VERTICAL ) {
					int x = columnBounds.x;
					int width = columnBounds.width;

					if( x + width / 5 <= mouse.x && mouse.x <= x + width * 4 / 5 ) {
						line = layoutManager.getInsertionLineAt( column, mouse.y );
					}
					else if( mouse.x >= x + width * 4 / 5 ) {
						column++;
					}
				}
				else {
					int y = columnBounds.y;
					int height = columnBounds.height;
					if( y + height / 5 <= mouse.y && mouse.y <= y + height * 4 / 5 ) {
						line = layoutManager.getInsertionLineAt( column, mouse.x );
					}
					else if( mouse.y >= y + height * 4 / 5 ) {
						column++;
					}
				}
			}
			if( column == -1 ) {
				column = 0;
			}
			
			boolean effect = true;
			if( dockable.getDockParent() == this ){
				int currentColumn = column( dockable );
				int currentLine = line( dockable );
				effect = currentColumn != column || (currentLine != line && currentLine != line-1 );
				effect = effect && !(line == -1 && lineCount( currentColumn ) == 1 && (currentColumn == column || currentColumn == column-1));
			}

			return new ToolbarGroupDropInfo( dockable, ToolbarGroupDockStation.this, column, line, effect ){
				@Override
				public void execute(){
					dropInfo = null;
					drop( this );
				}

				// Note: draw() is called first by the Controller. It seems
				// destroy() is called after, after a new StationDropOperation
				// is created

				@Override
				public void destroy( StationDropOperation next ){
					if( next == null || next.getTarget() != getTarget() ) {
						layoutManager.mutate();
					}
					dropInfo = null;
					mainPanel.repaint();
				}

				@Override
				public void draw(){
					dropInfo = this;
					int column = getColumn();
					int line = getLine();
					
					if( hasEffect() ){
						layoutManager.mutate( column, line );
					}
					else{
						layoutManager.mutate();
					}
					mainPanel.repaint();
				}
			};
		}
		else {
			return null;
		}
	}

	/**
	 * Tells which column covers the point location. If location is outside
	 * this station, then a non-existing column -1 or {@link #columnCount()} is returned.
	 * @param location some point on this {@link Component}
	 * @return the column covering location, can be -1 or {@link #columnCount()}
	 */
	public int getColumnAt( Point location ){
		int pos;
		int max;
		if( getOrientation() == Orientation.VERTICAL ) {
			pos = location.x;
			max = mainPanel.getWidth();
		}
		else {
			pos = location.y;
			max = mainPanel.getHeight();
		}

		if( pos < 0 ) {
			return -1;
		}
		if( pos >= max ) {
			return columnCount();
		}

		return layoutManager.getColumnAt( pos );
	}

	/**
	 * Drops thanks to information collect by dropInfo.
	 * 
	 * @param dropInfoGroup
	 */
	protected void drop( ToolbarGroupDropInfo dropInfoGroup ){
		int line = dropInfoGroup.getLine();
		if( line == -1 ) {
			drop( dropInfoGroup.getItem(), dropInfoGroup.getColumn() );
		}
		else {
			drop( dropInfoGroup.getItem(), dropInfoGroup.getColumn(), line );
		}
	}

	@Override
	public void drop( Dockable dockable ){
		drop( dockable, 0, 0 );
	}

	/**
	 * Drops the dockable at the specified line and column.
	 * 
	 * @param dockable
	 *            the dockable to insert
	 * @param column
	 *            the column where insert
	 * @param line
	 *            the line where insert
	 * @return true if the dockable has been inserted, false otherwise
	 */
	public boolean drop( Dockable dockable, int column, int line ){
		return drop( dockable, column, line, false );
	}

	public boolean drop( Dockable dockable, int column, int line, boolean force ){
		if( force || this.accept( dockable ) ) {
			if( !force ) {
				Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
				if( replacement == null ) {
					return false;
				}
				if( replacement != dockable ) {
					replacement.asDockStation().drop( dockable );
					dockable = replacement;
				}
			}
			add( dockable, column, line );
			return true;
		}
		return false;
	}

	private void add( Dockable dockable, int column, int line ){
		DockUtilities.ensureTreeValidity( this, dockable );
		DockUtilities.checkLayoutLocked();
		Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
		if( replacement != dockable ) {
			replacement.asDockStation().drop( dockable );
			dockable = replacement;
		}

		final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
		try {
			listeners.fireDockableAdding( dockable );

			dockable.setDockParent( this );
			final StationChildHandle handle = createHandle( dockable );
			// add in the list of dockable
			final int before = dockables.getColumnCount();
			dockables.insert( column, line, handle );
			// add in the main panel
			addComponent( handle );
			listeners.fireDockableAdded( dockable );
			fireDockablesRepositioned( dockable, before != dockables.getColumnCount() );
		}
		finally {
			token.release();
		}
	}

	/**
	 * Creates a new {@link StationChildHandle} that wraps around
	 * dockable. This method does not add the handle to any list or
	 * fire any events, that is the callers responsibility. Callers should also
	 * call {@link #addComponent(StationChildHandle)} with the new handle.
	 * @param dockable the element that is to be wrapped
	 * @return a new {@link StationChildHandle} for the element
	 */
	private StationChildHandle createHandle( Dockable dockable ){
		final StationChildHandle handle = new StationChildHandle( this, displayers, dockable, title );
		handle.updateDisplayer();
		return handle;
	}

	/**
	 * Adds handle to the {@link #mainPanel} of this station. Note
	 * that this method only cares about the {@link Component}-{@link Container}
	 * relationship, it does not store handle in the
	 * {@link #dockables} list.
	 * @param handle the handle to add
	 */
	private void addComponent( StationChildHandle handle ){
		mainPanel.dockablePane.add( handle.getDisplayer().getComponent() );
		mainPanel.getContentPane().revalidate();
	}

	/**
	 * Removes handle of the {@link #mainPanel} of this station.
	 * Note that this method only cares about the {@link Component}-
	 * {@link Container} relationship, it does not remove handle of
	 * the {@link #dockables} list.
	 * 
	 * @param handle
	 *            the handle to remove
	 */
	private void removeComponent( StationChildHandle handle ){
		mainPanel.dockablePane.remove( handle.getDisplayer().getComponent() );
		mainPanel.getContentPane().revalidate();
	}

	/**
	 * Drops the dockable in a new column.
	 * 
	 * @param dockable
	 *            the dockable to insert
	 * @param column
	 *            the column index to create
	 * @return true if the dockable has been inserted, false otherwise
	 */
	public boolean drop( Dockable dockable, int column ){
		return drop( dockable, column, false );
	}

	public boolean drop( Dockable dockable, int column, boolean force ){
		if( force || this.accept( dockable ) ) {
			if( !force ) {
				Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
				if( replacement == null ) {
					return false;
				}
				if( replacement != dockable ) {
					replacement.asDockStation().drop( dockable );
					dockable = replacement;
				}
			}
			add( dockable, column );
			return true;
		}
		return false;
	}

	private void add( Dockable dockable, int column ){
		DockUtilities.ensureTreeValidity( this, dockable );
		DockUtilities.checkLayoutLocked();
		Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
		if( replacement != dockable ) {
			replacement.asDockStation().drop( dockable );
			dockable = replacement;
		}
		final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
		try {
			listeners.fireDockableAdding( dockable );
			dockable.setDockParent( this );
			final StationChildHandle handle = createHandle( dockable );
			// add in the list of dockable
			final int before = dockables.getColumnCount();
			dockables.insert( column, handle, false );
			// add in the main panel
			addComponent( handle );
			listeners.fireDockableAdded( dockable );
			fireDockablesRepositioned( dockable, before != dockables.getColumnCount() );
		}
		finally {
			token.release();
		}
	}

	@Override
	public void drag( Dockable dockable ){
		if( dockable.getDockParent() != this ) {
			throw new IllegalArgumentException( "not a child of this station: " + dockable );
		}
		remove( dockable );
	}

	@Override
	protected void remove( Dockable dockable ){
		DockUtilities.checkLayoutLocked();
		final int column = column( dockable );
		final int before = dockables.getColumnCount();

		final StationChildHandle handle = dockables.get( dockable );
		final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable );
		try {
			listeners.fireDockableRemoving( dockable );

			dockables.remove( handle );
			removeComponent( handle );
			handle.destroy();
			dockable.setDockParent( null );

			listeners.fireDockableRemoved( dockable );
			fireColumnRepositioned( column, before != dockables.getColumnCount() );
			mainPanel.repaint(); // if we don't call repaint, the station is
			// not repaint when we remove a ToolbarDockStation in a column and
			// that
			// it doesn't affect the number of column
		}
		finally {
			token.release();
		}
	}

	@Override
	public void replace( Dockable old, Dockable next ){
		// TODO Auto-generated method stub
		DockUtilities.checkLayoutLocked();
		final DockController controller = getController();
		if( controller != null ) {
			controller.freezeLayout();
		}
		final int column = dockables.getColumn( old );
		final int line = dockables.getLine( old );
		final int beforeCount = dockables.getColumnCount();
		remove( old );
		// if remove the old dockable delete a column we have to recreate it
		if( beforeCount != dockables.getColumnCount() ) {
			add( next, column );
		}
		else {
			add( next, column, line );
		}
		controller.meltLayout();
	}

	@Override
	public boolean canReplace( Dockable old, Dockable next ){
		return acceptable( next ) && getToolbarStrategy().isToolbarGroupPartParent( this, next, true );
	}

	/**
	 * Fires
	 * {@link DockStationListener#dockablesRepositioned(DockStation, Dockable[])}
	 * for all {@link Dockable}s that are in the same column as
	 * dockable, including dockable.
	 * 
	 * @param dockable
	 *            some item from a column that changed
	 * @param all
	 *            whether there should be an event for all the columns after
	 *            dockable as well
	 */
	protected void fireDockablesRepositioned( Dockable dockable ){
		fireDockablesRepositioned( dockable, false );
	}

	/**
	 * Fires
	 * {@link DockStationListener#dockablesRepositioned(DockStation, Dockable[])}
	 * for all {@link Dockable}s that are in the same column as
	 * dockable, including dockable.
	 * 
	 * @param dockable
	 *            some item from a column that changed
	 * @param all
	 *            whether there should be an event for all the columns after the
	 *            column of the dockable as well
	 */
	protected void fireDockablesRepositioned( Dockable dockable, boolean all ){
		fireColumnRepositioned( column( dockable ), all );
	}

	/**
	 * Fires
	 * {@link DockStationListener#dockablesRepositioned(DockStation, Dockable[])}
	 * for all {@link Dockable}s in the given column.
	 * 
	 * @param column
	 *            the column for which to fire an event
	 * @param all
	 *            whether there should be an event for all the columns after
	 *            column as well
	 */
	protected void fireColumnRepositioned( int column, boolean all ){
		final List list = new ArrayList();
		final int end = all ? dockables.getColumnCount() : column + 1;

		for( int i = column; i < end; i++ ) {
			final Iterator items = dockables.getColumnContent( i );
			while( items.hasNext() ) {
				list.add( items.next().getDockable() );
			}
		}

		if( list.size() > 0 ) {
			listeners.fireDockablesRepositioned( list.toArray( new Dockable[list.size()] ) );
		}
	}

	// ########################################################
	// ###################### UI Managing #####################
	// ########################################################

	/**
	 * Gets a {@link ColumnDockActionSource} which allows to modify the
	 * {@link ExpandedState} of the children of this station.
	 * 
	 * @return the source, not null
	 */
	public ColumnDockActionSource getExpandActionSource(){
		return expander.getActions();
	}

	/**
	 * Gets the factory for the {@link #getHeaderComponent() header component} which is currently
	 * in use.
	 * @return the current factory, can be null
	 * @see #HEADER_FACTORY
	 */
	public ToolbarGroupHeaderFactory getHeaderComponentFactory(){
		return headerFactory.getValue();
	}

	/**
	 * Sets the factory for the {@link #getHeaderComponent() header component}. A value of null
	 * will be interpreted as a request to reinstall the default factory.
	 * @param factory the new factory, can be null in order to reinstall the default factory
	 * @see #HEADER_FACTORY
	 */
	public void setHeaderComponentFactory( ToolbarGroupHeaderFactory factory ){
		headerFactory.setValue( factory );
	}

	/**
	 * Gets the {@link Component} which is currently shown at the top of this station.
	 * @return the component or null
	 * @see #HEADER_FACTORY
	 */
	public ToolbarGroupHeader getHeaderComponent(){
		return groupHeader;
	}

	@Override
	protected void callDockUiUpdateTheme() throws IOException{
		DockUI.updateTheme( this, new ToolbarGroupDockStationFactory() );
	}

	@Override
	protected DefaultDisplayerFactoryValue createDisplayerFactory(){
		return new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".toolbar.group", this );
	}

	@Override
	protected DockTitleVersion registerTitle( DockController controller ){
		return controller.getDockTitleManager().getVersion( TITLE_ID, BasicDockTitleFactory.FACTORY );
	}

	/**
	 * Replaces displayer with a new {@link DockableDisplayer}.
	 * 
	 * @param displayer
	 *            the displayer to replace
	 * @throws IllegalArgumentException
	 *             if displayer is not a child of this station
	 */
	@Override
	protected void discard( DockableDisplayer displayer ){
		final Dockable dockable = displayer.getDockable();

		final StationChildHandle handle = dockables.get( dockable );
		if( handle == null ) {
			throw new IllegalArgumentException( "displayer is not child of this station: " + displayer );
		}

		removeComponent( handle );
		handle.updateDisplayer();
		addComponent( handle );
	}

	private void resetScrollbars(){
		for( Map.Entry entry : scrollbars.entrySet() ) {
			ColumnScrollBar replacement = scrollbarFactory.getValue().create( this );
			mainPanel.dockablePane.remove( entry.getValue().getComponent() );
			entry.setValue( replacement );
			mainPanel.dockablePane.add( entry.getValue().getComponent() );
		}
	}

	private void setDividerStrategy( ToolbarGroupDividerStrategy dividerStrategy ){
		this.dividerStrategy = dividerStrategy;
		layoutManager.setDividerStrategy( dividerStrategy );
		mainPanel.revalidate();
	}
	
	public Rectangle getDropGapBoundaries(){
		if( dropInfo == null ){
			return null;
		}
		
		Component dockablePane = mainPanel.dockablePane;
		Point zero = new Point( 0, 0 );
		zero = SwingUtilities.convertPoint( dockablePane, zero, mainPanel );

		Rectangle gapBounds;

		if( dropInfo.getLine() == -1 ) {
			gapBounds = layoutManager.getGapBounds( dropInfo.getColumn(), false );
		}
		else {
			gapBounds = layoutManager.getGapBounds( dropInfo.getColumn(), dropInfo.getLine() );
		}

		gapBounds.x += zero.x;
		gapBounds.y += zero.y;
	
		return gapBounds;
	}
	
	/**
	 * This panel is used as base of the station. All children of the station
	 * have this panel as parent too. It allows to draw arbitrary figures over
	 * the base panel
	 * 
	 * @author Herve Guillaume
	 */
	protected class OverpaintablePanelBase extends SecureContainer {

		/**
		 * Generated serial number
		 */
		private static final long serialVersionUID = -4399008463139189130L;

		/**
		 * The direct parent {@link Container} of {@link Dockable}s.
		 */
		private final JPanel dockablePane = new JPanel(){
			protected void paintComponent( Graphics g ){
				super.paintComponent( g );
				if( dividerStrategy != null ){
					dividerStrategy.paint( this, g, layoutManager );
				}
			}
		};

		/**
		 * The parent of {@link #dockablePane}, adds insets, borders and other
		 * decorations.
		 */
		private JPanel decorationPane = createBackgroundPanel();

		/**
		 * Creates a new panel
		 */
		public OverpaintablePanelBase(){
			setBasePane( decorationPane );
			setSolid( false );
			decorationPane.setOpaque( false );
			dockablePane.setOpaque( false );

			decorationPane.setLayout( new BorderLayout() );
			decorationPane.add( dockablePane, BorderLayout.CENTER );
		}

		public void addHeaderComponent( Component header ){
			if( getOrientation() == Orientation.HORIZONTAL ) {
				decorationPane.add( header, BorderLayout.WEST );
			}
			else {
				decorationPane.add( header, BorderLayout.NORTH );
			}
		}

		public void removeHeaderCopmonent( Component header ){
			decorationPane.remove( header );
		}

		@Override
		public Dimension getPreferredSize(){
			// this is a workaround because the boundaries of the children are required to
			// compute the preferred size of this components. It is not pretty...
			if( dockablePane != null && !dockablePane.isValid() ){
				dockablePane.doLayout();
			}
			
			return getBasePane().getPreferredSize();
		}

		@Override
		public Dimension getMinimumSize(){
			return getBasePane().getPreferredSize();
		}

		@Override
		public Dimension getMaximumSize(){
			return getBasePane().getPreferredSize();
		}

		/**
		 * Update alignment with regards to the current orientation of this
		 * {@link ToolbarGroupDockStation}
		 */
		public void updateAlignment(){
			final Orientation orientation = getOrientation();
			if( orientation != null ) {
				if( layoutManager != null ) {
					layoutManager.setController( null );
				}

				layoutManager = new ToolbarGridLayoutManager( dockablePane, orientation, dockables, ToolbarGroupDockStation.this ){
					@Override
					protected Component toComponent( StationChildHandle item ){
						return item.getDisplayer().getComponent();
					}

					@Override
					protected void setShowScrollbar( int column, boolean show ){
						boolean change = false;

						if( show ) {
							if( !scrollbars.containsKey( column ) ) {
								ColumnScrollBar bar = scrollbarFactory.getValue().create( ToolbarGroupDockStation.this );
								bar.setOrientation( getOrientation() );
								scrollbars.put( column, bar );
								dockablePane.add( bar.getComponent() );
								bar.addAdjustmentListener( adjustmentListener );
								change = true;
							}
						}
						else {
							ColumnScrollBar bar = scrollbars.remove( column );
							if( bar != null ) {
								dockablePane.remove( bar.getComponent() );
								bar.removeAdjustmentListener( adjustmentListener );
								change = true;
							}
						}
						if( change ) {
							revalidateLater();
						}
					}

					@Override
					protected int getScrollbarValue( int column, int required, int available ){
						ColumnScrollBar scrollbar = scrollbars.get( column );
						if( scrollbar == null ) {
							return 0;
						}
						scrollbar.setValues( required, available );
						return scrollbar.getValue();
					}

					@Override
					protected Component getScrollbar( int column ){
						ColumnScrollBar scrollbar = scrollbars.get( column );
						if( scrollbar == null ) {
							return null;
						}
						return scrollbar.getComponent();
					}

				};
				dockablePane.setLayout( layoutManager );
				layoutManager.setDividerStrategy( dividerStrategy );
				layoutManager.setController( getController() );
			}
			mainPanel.revalidate();
		}

		/**
		 * Asynchronously revalidates {@link #decorationPane}, but only if its size did not change
		 */
		private void revalidateLater(){
			if( orientation == Orientation.VERTICAL ) {
				final int height = decorationPane.getHeight();
				EventQueue.invokeLater( new Runnable(){
					@Override
					public void run(){
						if( height == decorationPane.getHeight() ) {
							decorationPane.revalidate();
						}
					}
				} );
			}
			else {
				final int width = decorationPane.getWidth();
				EventQueue.invokeLater( new Runnable(){
					@Override
					public void run(){
						if( width == decorationPane.getWidth() ) {
							decorationPane.revalidate();
						}
					}
				} );
			}
		}

		@Override
		protected void paintOverlay( Graphics g ){
			final Graphics2D g2D = (Graphics2D) g;

			paintDrag( g );

			if( dropInfo != null && dropInfo.hasEffect() ) {
				Point zero = new Point( 0, 0 );
				zero = SwingUtilities.convertPoint( dockablePane, zero, this );
				
				Rectangle gapBounds = getDropGapBoundaries();
				paint.drawInsertion( g2D, new Rectangle( zero.x, zero.y, dockablePane.getWidth(), dockablePane.getHeight() ), gapBounds );
			}
		}

		private void paintDrag( Graphics g ){
			Dockable removal = getRemoval();
			if( removal != null ) {
				StationChildHandle handle = getHandle( removal );
				if( handle != null ) {
					Rectangle bounds = handle.getDisplayer().getComponent().getBounds();

					Component dockablePane = mainPanel.dockablePane;
					Point zero = new Point( 0, 0 );
					zero = SwingUtilities.convertPoint( dockablePane, zero, this );
					bounds.x += zero.x;
					bounds.y += zero.y;

					getPaint().drawRemoval( g, bounds, bounds );
				}
			}
		}

		@Override
		public String toString(){
			return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() );
		}

	}

	// ########################################################
	// ############### PlaceHolder Managing ###################
	// ########################################################

	@Override
	public PlaceholderMap getPlaceholders(){
		PlaceholderMap map = dockables.toMap();
		writeLayoutArguments( map );
		return map;
	}

	/**
	 * Converts this station into a {@link PlaceholderMap} using
	 * identifiers to remember which {@link Dockable} was at which
	 * location.
	 * 
	 * @param identifiers
	 *            the identifiers to apply
	 * @return this as map
	 */
	public PlaceholderMap getPlaceholders( Map identifiers ){
		PlaceholderMap map = dockables.toMap( identifiers );
		writeLayoutArguments( map );
		return map;
	}

	@Override
	public PlaceholderMapping getPlaceholderMapping() {
		return new ToolbarGroupPlaceholderMapping( this, dockables );
	}
	
	@Override
	public void setPlaceholders( PlaceholderMap placeholders ){
		dockables.fromMap( placeholders );
		readLayoutArguments( placeholders );
	}

	public void setPlaceholders( PlaceholderMap placeholders, Map children ){
		DockUtilities.checkLayoutLocked();
		if( getDockableCount() > 0 ) {
			throw new IllegalStateException( "this station still has children" );
		}

		final DockController controller = getController();
		readLayoutArguments( placeholders );

		try {
			if( controller != null ) {
				controller.freezeLayout();
			}

			dockables.setStrategy( null );
			dockables.unbind();

			dockables.fromMap( placeholders, children, new PlaceholderToolbarGridConverter(){
				@Override
				public StationChildHandle convert( Dockable dockable, ConvertedPlaceholderListItem item ){
					listeners.fireDockableAdding( dockable );

					dockable.setDockParent( ToolbarGroupDockStation.this );
					final StationChildHandle handle = createHandle( dockable );
					addComponent( handle );

					return handle;
				}

				@Override
				public void added( StationChildHandle item ){
					listeners.fireDockableAdded( item.getDockable() );
				}
			} );

			if( controller != null ) {
				dockables.bind();
			}
			dockables.setStrategy( placeholderStrategy.getValue() );
		}
		finally {
			if( controller != null ) {
				controller.meltLayout();
			}
		}
	}

	private void writeLayoutArguments( PlaceholderMap map ){
		ToolbarGroupDockStationLayout.writeOrientation( map, getOrientation() );
	}

	private void readLayoutArguments( PlaceholderMap map ){
		Orientation orientation = ToolbarGroupDockStationLayout.readOrientation( map );
		if( orientation != null ){
			setOrientation( orientation );
		}
	}

	@Override
	public DockableProperty getDockableProperty( Dockable child, Dockable target ){
		final int column = column( child );
		final int line = line( child );

		if( target == null ) {
			target = child;
		}

		final PlaceholderStrategy strategy = placeholderStrategy.getValue();
		Path placeholder = null;
		if( strategy != null ) {
			placeholder = strategy.getPlaceholderFor( target );
			if( (placeholder != null) && (column >= 0) && (line >= 0) ) {
				dockables.addPlaceholder( column, line, placeholder );
			}
		}

		return new ToolbarGroupProperty( column, line, placeholder );
	}
	
	@Override
	@Todo( compatibility=Compatibility.COMPATIBLE, priority=Priority.ENHANCEMENT, target=Version.VERSION_1_1_2,
		description="Implement this feature")
	public void aside( AsideRequest request ){
		DockableProperty location = request.getLocation();
		int column = -1;
		int line = -1;
		Path newPlaceholder = request.getPlaceholder();
		
		if( location instanceof ToolbarGroupProperty ){
			ToolbarGroupProperty groupLocation = (ToolbarGroupProperty)location;
			
			Path placeholder = groupLocation.getPlaceholder();
			if( placeholder != null ){
				column = dockables.getColumn( placeholder );
			}
			if( column == -1 ){
				placeholder = null;
				column = groupLocation.getColumn();
			}
			
			int totalColumnCount = dockables.getTotalColumnCount();
			column = Math.max( 0, Math.min( column, totalColumnCount ) );
			
			if( placeholder != null && column < totalColumnCount ){
				line = dockables.getLine( column, placeholder );
			}
			else if( column == totalColumnCount ){
				line = 0;
			}
			
			if( line == -1 ){
				line = groupLocation.getLine();
				line = Math.max( 0, Math.min( line, dockables.getLineCount( column ) ) );
			}
			
			if( groupLocation.getSuccessor() == null ){
				// insert
				if( newPlaceholder != null ){
					dockables.insertPlaceholder( column, line, newPlaceholder );
				}
			}
			else{
				// add to existing location
				if( newPlaceholder != null ){
					dockables.addPlaceholder( column, line, newPlaceholder );
					Dockable existing = dockables.getModel().getColumn( column ).getDockable( line );
					if( existing.asDockStation() != null ){
						AsideAnswer answer = request.forward( existing.asDockStation() );
						if( answer.isCanceled() ){
							return;
						}
					}
				}
			}
		}
		else {
			column = 0;
			if( dockables.getColumnCount() > 0 ){
				line = dockables.getLineCount( column );
			}
			else{
				line = 0;
			}
		}
		
		request.answer( new ToolbarGroupProperty( column, line, newPlaceholder ) );
	}

	@Override
	public boolean drop( Dockable dockable, DockableProperty property ){
		if( property instanceof ToolbarGroupProperty ) {
			return drop( dockable, (ToolbarGroupProperty) property );
		}
		return false;
	}

	/**
	 * Tries to drop dockable at property.
	 * 
	 * @param dockable
	 *            the element to drop
	 * @param property
	 *            the location of dockable
	 * @return true if dropping was successful, false
	 *         otherwise
	 */
	public boolean drop( Dockable dockable, ToolbarGroupProperty property ){
		final Path placeholder = property.getPlaceholder();

		int column = property.getColumn();
		int line = property.getLine();

		if( placeholder != null ) {
			if( dockables.hasPlaceholder( placeholder ) ) {
				final StationChildHandle child = dockables.get( placeholder );
				if( child == null ) {
					if( acceptable( dockable ) ) {
						Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
						if( replacement == null ) {
							return false;
						}

						DockController controller = getController();
						if( controller != null ) {
							controller.freezeLayout();
						}
						try {
							dropAt( replacement, placeholder );

							if( replacement != dockable ) {
								if( property.getSuccessor() != null ) {
									if( !replacement.asDockStation().drop( dockable, property.getSuccessor() ) ) {
										replacement.asDockStation().drop( dockable );
									}
								}
								else {
									replacement.asDockStation().drop( dockable );
								}
							}
						}
						finally {
							if( controller != null ) {
								controller.meltLayout();
							}
						}

						return true;
					}
				}
				else {
					if( drop( child, dockable, property ) ) {
						return true;
					}

					column = dockables.getColumn( child.getDockable() );
					line = dockables.getLine( column, child.getDockable() ) + 1;
				}
			}
		}
		else {
			if( column >= 0 && column < columnCount() ) {
				if( line >= 0 && line < lineCount( column ) ) {
					DockStation child = getDockable( column, line ).asDockStation();
					if( child != null && child.drop( dockable, property.getSuccessor() ) ) {
						return true;
					}
				}
			}
		}

		if( !acceptable( dockable ) ) {
			return false;
		}

		line = Math.max( 0, line );
		// column = Math.max( 0, column );
		return drop( dockable, column, line );
	}

	/**
	 * Drops dockable at the location described by
	 * placeholder. This method must only be called if
	 * placeholder points to a valid location.
	 * 
	 * @param dockable
	 *            the dockable to drop
	 * @param placeholder
	 *            the location of dockable, must be valid
	 */
	private void dropAt( Dockable dockable, Path placeholder ){

		DockUtilities.checkLayoutLocked();
		final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
		try {
			DockUtilities.ensureTreeValidity( this, dockable );
			listeners.fireDockableAdding( dockable );
			final int before = dockables.getColumnCount();

			dockable.setDockParent( this );
			final StationChildHandle handle = createHandle( dockable );
			dockables.put( placeholder, handle );
			addComponent( handle );

			listeners.fireDockableAdded( dockable );
			fireDockablesRepositioned( dockable, before != dockables.getColumnCount() );
		}
		finally {
			token.release();
		}
	}

	@SuppressWarnings("static-method")
	private boolean drop( StationChildHandle parent, Dockable child, ToolbarGroupProperty property ){
		if( property.getSuccessor() == null ) {
			return false;
		}

		final DockStation station = parent.getDockable().asDockStation();
		if( station == null ) {
			return false;
		}

		return station.drop( child, property.getSuccessor() );
	}

	@Override
	public void move( Dockable dockable, DockableProperty property ){
		if( property instanceof ToolbarGroupProperty ) {
			move( dockable, (ToolbarGroupProperty) property );
		}
	}

	private void move( Dockable dockable, ToolbarGroupProperty property ){
		final int sourceColumn = column( dockable );
		final int sourceLine = line( dockable );

		boolean empty = false;
		int destinationColumn = property.getColumn();
		int destinationLine = property.getLine();

		final Path placeholder = property.getPlaceholder();
		if( placeholder != null ) {
			final int column = dockables.getColumn( placeholder );
			if( column != -1 ) {
				final int line = dockables.getLine( column, placeholder );
				if( line != -1 ) {
					empty = true;
					destinationColumn = column;
					destinationLine = line;
				}
			}
		}

		if( !empty ) {
			// ensure destination valid
			destinationColumn = Math.min( destinationColumn, dockables.getColumnCount() );
			if( (destinationColumn == dockables.getColumnCount()) || (destinationColumn == -1) ) {
				destinationLine = 0;
			}
			else {
				destinationLine = Math.min( destinationLine, dockables.getLineCount( destinationColumn ) );
			}
		}

		Level level;
		if( empty ) {
			level = Level.BASE;
		}
		else {
			level = Level.DOCKABLE;
		}
		dockables.move( sourceColumn, sourceLine, destinationColumn, destinationLine, level );
		mainPanel.getContentPane().revalidate();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy