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

bibliothek.gui.dock.ToolbarDockStation 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.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.io.IOException;
import java.util.Map;

import javax.swing.JComponent;
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.displayer.DockableDisplayerHints;
import bibliothek.gui.dock.event.DockStationAdapter;
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.OverpaintablePanel;
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.stack.StackDockProperty;
import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem;
import bibliothek.gui.dock.station.support.DockablePlaceholderList;
import bibliothek.gui.dock.station.support.DockableShowingManager;
import bibliothek.gui.dock.station.support.PlaceholderList;
import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter;
import bibliothek.gui.dock.station.support.PlaceholderListItemConverter;
import bibliothek.gui.dock.station.support.PlaceholderListMapping;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.toolbar.SpanToolbarLayoutManager;
import bibliothek.gui.dock.station.toolbar.ToolbarDockStationFactory;
import bibliothek.gui.dock.station.toolbar.ToolbarDropInfo;
import bibliothek.gui.dock.station.toolbar.ToolbarProperty;
import bibliothek.gui.dock.station.toolbar.layer.ToolbarSlimDropLayer;
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.ConfiguredBackgroundPanel;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.Transparency;
import bibliothek.gui.dock.util.extension.Extension;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.util.Path;

/**
 * A {@link Dockable} and a {@link DockStation} which stands for a group of
 * {@link ToolbarItemDockable}. As dockable it can be put in {@link DockStation}
 * which implements marker interface {@link ToolbarInterface}. As DockStation it
 * accept a {@link ToolbarItemDockable} or a {@link ToolbarDockStation}
 * 
 * @author Herve Guillaume
 */
public class ToolbarDockStation extends AbstractToolbarDockStation {

	/** the id of the {@link DockTitleFactory} which is used by this station */
	public static final String TITLE_ID = "toolbar";
	/**
	 * This id is forwarded to {@link Extension}s which load additional
	 * {@link DisplayerFactory}s
	 */
	public static final String DISPLAYER_ID = "toolbar";
	
	/** Key for setting the size of the gap between the children of a {@link ToolbarDockStation}. */
	public static final PropertyKey GAP = new PropertyKey( "dock.toolbar.gap", new ConstantPropertyFactory( Integer.valueOf( 2 ) ), true );
	
	/** Key for setting the size of the gap between the children of a station and the border of the station */
	public static final PropertyKey SIDE_GAP = new PropertyKey( "dock.toolbar.sidegap", new ConstantPropertyFactory( Integer.valueOf( 4 ) ), true );

	/** A list of all children */
	protected DockablePlaceholderList dockables = new DockablePlaceholderList();

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

	/**
	 * Size of the lateral zone where no drop action can be done (Measured in
	 * pixel).
	 */
	private int lateralNodropZoneSize = 2;

	/** current {@link PlaceholderStrategy} */
	private final PropertyValue placeholderStrategy = new PropertyValue( PlaceholderStrategy.PLACEHOLDER_STRATEGY ){
		@Override
		protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){
			dockables.setStrategy( newValue );
		}
	};

	/** a manager to inform listeners about changes in the visibility state */
	private DockableShowingManager visibility;

	/** added to the current parent of this dockable */
	private VisibleListener visibleListener;

	/** the {@link LayoutManager} positioning the children of this station */
	private SpanToolbarLayoutManager layoutManager;

	/** information about the {@link Dockable} that is currently dropped */
	private DropInfo dropInfo;
	
	/** size of the gap between children */
	private PropertyValue gap = new PropertyValue( GAP ){
		@Override
		protected void valueChanged( Integer oldValue, Integer newValue ){
			layoutManager.setGap( newValue.intValue() );
		}
	};
	
	/** size of the gap between children and border */
	private PropertyValue sideGap = new PropertyValue( SIDE_GAP ){
		@Override
		protected void valueChanged( Integer oldValue, Integer newValue ){
			layoutManager.setSideGap( newValue.intValue() );
		}
	};

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

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

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

		setTitleIcon( null );

		visibility = new DockableShowingManager( listeners );
		visibleListener = new VisibleListener();

		getComponent().addHierarchyListener( new HierarchyListener(){
			public void hierarchyChanged( HierarchyEvent e ){
				if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 ) {
					if( getDockParent() == null ) {
						getDockableStateListeners().checkShowing();
					}

					visibility.fire();
				}
			}
		} );
	}
	
	@Override
	protected DockComponentRootHandler createRootHandler() {
		return new DefaultDockStationComponentRootHandler( this, displayers );
	}

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

	@Override
	public Component getComponent(){
		return mainPanel;
	}
	
	@Override
	public void configureDisplayerHints( DockableDisplayerHints hints ){
		super.configureDisplayerHints( hints );
		if( hints != null ){
			if( hints.getStation() instanceof ScreenDockStation ){
				hints.setShowBorderHint( Boolean.TRUE );
			}
		}
	}

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

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

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

	/**
	 * Sets the size of the two lateral zones where no drop action can be done
	 * (Measured in pixel).
	 * 
	 * @param lateralNodropZoneSize
	 *            the size of the rectangular lateral zones (in pixel)
	 * @throws IllegalArgumentException
	 *             if the size is smaller than 0
	 */
	public void setLateralNodropZoneSize( int lateralNodropZoneSize ){
		if( lateralNodropZoneSize < 0 ) {
			throw new IllegalArgumentException( "borderSideSnapeSize must not be less than 0" );
		}
		this.lateralNodropZoneSize = lateralNodropZoneSize;
	}

	/**
	 * Gets the size of the two lateral zones where no drop action can be done
	 * (Measured in pixel).
	 * 
	 * @return the size of the rectangular lateral zones (in pixel)
	 */
	public int getLateralNodropZoneSize(){
		return lateralNodropZoneSize;
	}

	@Override
	public void setDockParent( DockStation station ){
		DockStation old = getDockParent();
		if( old != null )
			old.removeDockStationListener( visibleListener );

		super.setDockParent( station );

		if( station != null )
			station.addDockStationListener( visibleListener );

		visibility.fire();
	}

	@Override
	public void setController( DockController controller ){
		if( getController() != controller ) {
			if( getController() != null ) {
				dockables.unbind();
			}
			for( final StationChildHandle handle : dockables.dockables() ) {
				handle.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 );
			placeholderStrategy.setProperties( controller );
			displayerFactory.setController( controller );
			displayers.setController( controller );
			mainPanel.setController( controller );
			layoutManager.setController( controller );
			gap.setProperties( controller );
			sideGap.setProperties( controller );

			if( controller != null ) {
				dockables.bind();
			}
			for( final StationChildHandle handle : dockables.dockables() ) {
				handle.setTitleRequest( title, true );
			}

			visibility.fire();
		}
	}

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

	@Override
	public void setOrientation( Orientation orientation ){
		// 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();
		mainPanel.revalidate();
	}

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

	@Override
	public DockStationDropLayer[] getLayers(){
		return new DockStationDropLayer[]{ new ToolbarSlimDropLayer( this ) };
	}

	@Override
	public boolean accept( Dockable child ){
		return getToolbarStrategy().isToolbarPart( 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 exist and if the controller accept that
			// the dockable become 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.getDockablePane() );
			int index = layoutManager.getInsertionIndex( mouse.x, mouse.y );
			DropInfo info = new DropInfo( dockable, index );
			if( info.hasNoEffect() ) {
				return null;
			}
			return info;
		}
		else {
			return null;
		}
	}

	private class DropInfo extends ToolbarDropInfo {
		public DropInfo( Dockable dockable, int index ){
			super( dockable, ToolbarDockStation.this, index );
		}

		@Override
		public void execute(){
			dropInfo = null;
			layoutManager.setExpandedSpan( -1, false );

			if( isMove() ) {
				move( getItem(), getIndex() );
			}
			else {
				drop( getItem(), getIndex() );
			}
		}

		@Override
		public void destroy( StationDropOperation next ){
			if( dropInfo == this ) {
				dropInfo = null;
			}
			if( next == null || next.getTarget() != getTarget() ) {
				layoutManager.setExpandedSpan( -1, true );
			}
			mainPanel.repaint();
		}

		@Override
		public void draw(){
			dropInfo = this;
			layoutManager.setSpanSize( getItem() );
			layoutManager.setExpandedSpan( getIndex(), true );
			mainPanel.repaint();
		}
	}

	@Override
	public void drop( Dockable dockable ){
		// System.out.println(this.toString() + "## drop(Dockable dockable)##");
		this.drop( dockable, getDockableCount(), true );
	}

	/**
	 * Drops dockable at location index.
	 * 
	 * @param dockable
	 *            the element to add
	 * @param index
	 *            the location of dockable
	 * @return whether the operation was successful or not
	 */
	public boolean drop( Dockable dockable, int index ){
		return drop( dockable, index, false );
	}

	protected boolean drop( Dockable dockable, int index, boolean force ){
		// note: merging of two ToolbarGroupDockStations is done by the
		// ToolbarGroupDockStationMerger
		// System.out.println(this.toString()
		// + "## drop(Dockable dockable, int index)##");
		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, index );
			return true;
		}
		return false;
	}

	/**
	 * Adds dockable at the specified index. This method should be called in
	 * case of move action (it means when the dockable to insert already belongs
	 * to this station), because in this case this dockable was removed first.
	 * 
	 * @param dockable
	 *            the dockable to insert
	 * @param index
	 *            the index where insert the dockable
	 */
	protected void move( Dockable dockable, int index ){
		final DockController controller = getController();
		try {
			if( controller != null ) {
				controller.freezeLayout();
			}
			int current = indexOf( dockable );
			if( current == -1 ) {
				throw new IllegalArgumentException( "dockable is not known to this station" );
			}
			if( current < index ) {
				index--;
			}
			if( current != index ) {
				this.add( dockable, index );
			}
		}
		finally {
			if( controller != null ) {
				controller.meltLayout();
			}
		}
	}

	protected void add( Dockable dockable, int index ){
		add( dockable, index, null );
	}

	protected void add( Dockable dockable, int index, Path placeholder ){
		DockUtilities.ensureTreeValidity( this, dockable );
		DockUtilities.checkLayoutLocked();
		// Case where dockable is instance of ToolbarDockStation is handled by
		// the "ToolbarDockStationMerger"
		// Case where dockable is instance of ToolbarGroupDockStation is handled
		// by the "ToolbarStrategy.ensureToolbarLayer" method
		Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
		if( replacement != dockable ) {
			replacement.asDockStation().drop( dockable );
			dockable = replacement;
		}
		
		if( getExpandedState() == ExpandedState.EXPANDED && getDockableCount() == 1){
			DockStation stack = getDockable( 0 ).asDockStation();
			stack.drop( dockable, new StackDockProperty( index, placeholder ) );
		}
		else{
			final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
			try {
				listeners.fireDockableAdding( dockable );
				int inserted = -1;
	
				final StationChildHandle handle = new StationChildHandle( this, displayers, dockable, title );
				handle.updateDisplayer();
	
				if( (placeholder != null) && (dockables.getDockableAt( placeholder ) == null) ) {
					inserted = dockables.put( placeholder, handle );
				}
				else if( placeholder != null ) {
					index = dockables.getDockableIndex( placeholder );
				}
	
				if( inserted == -1 ) {
					getDockables().add( index, handle );
				}
				else {
					index = inserted;
				}
	
				insertAt( handle, index );
				listeners.fireDockableAdded( dockable );
				fireDockablesRepositioned( index + 1 );
			}
			finally {
				token.release();
			}
		}
	}

	protected void insertAt( StationChildHandle handle, int index ){
		final Dockable dockable = handle.getDockable();

		dockable.setDockParent( this );

		mainPanel.getDockablePane().add( handle.getDisplayer().getComponent(), index );
		mainPanel.getDockablePane().invalidate();

		mainPanel.revalidate();
		mainPanel.getContentPane().repaint();
	}

	@Override
	public void drag( Dockable dockable ){
		if( dockable.getDockParent() != this ) {
			throw new IllegalArgumentException( "The dockable cannot be dragged, it is not child of this station." );
		}
		this.remove( dockable );
	}

	/**
	 * Removes dockable from this station.
* Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure none else adds or * removes Dockables. * * @param dockable * the child to remove */ @Override protected void remove( Dockable dockable ){ DockUtilities.checkLayoutLocked(); final int index = indexOf( dockable ); final StationChildHandle handle = dockables.dockables().get( index ); if( getFrontDockable() == dockable ) { setFrontDockable( null ); } final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable ); try { listeners.fireDockableRemoving( dockable ); dockable.setDockParent( null ); dockables.remove( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); mainPanel.doLayout(); mainPanel.revalidate(); mainPanel.repaint(); handle.destroy(); listeners.fireDockableRemoved( dockable ); fireDockablesRepositioned( index ); } finally { token.release(); } } /** * Removes the child with the given index from this station.
* Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure no-one else adds or * removes Dockables. * * @param index * the index of the child that will be removed */ protected void remove( int index ){ DockUtilities.checkLayoutLocked(); final StationChildHandle handle = dockables.dockables().get( index ); final Dockable dockable = getDockable( index ); if( getFrontDockable() == dockable ) { setFrontDockable( null ); } final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable ); try { listeners.fireDockableRemoving( dockable ); dockable.setDockParent( null ); dockables.remove( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); mainPanel.doLayout(); mainPanel.getContentPane().revalidate(); mainPanel.getContentPane().repaint(); handle.destroy(); listeners.fireDockableRemoved( dockable ); fireDockablesRepositioned( index ); } finally { token.release(); } } @Override public void replace( Dockable old, Dockable next ){ DockUtilities.checkLayoutLocked(); final DockController controller = getController(); if( controller != null ) { controller.freezeLayout(); } final int index = indexOf( old ); remove( old ); // the child is a ToolbarGroupDockStation because canReplace() // ensure it add( next, index ); controller.meltLayout(); } // ######################################################## // ###################### UI Managing ##################### // ######################################################## @Override protected void callDockUiUpdateTheme() throws IOException{ DockUI.updateTheme( this, new ToolbarDockStationFactory() ); } @Override protected DefaultDisplayerFactoryValue createDisplayerFactory(){ return new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".toolbar", this ); } /** * 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; } @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 int index = indexOf( dockable ); if( index < 0 ) { throw new IllegalArgumentException( "displayer is not a child of this station: " + displayer ); } final StationChildHandle handle = dockables.dockables().get( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); handle.updateDisplayer(); insertAt( handle, index ); } /** * A panel with a fixed size (minimum, maximum and preferred size have * same values). * * @author Herve Guillaume * */ @SuppressWarnings("serial") protected class SizeFixedPanel extends ConfiguredBackgroundPanel { public SizeFixedPanel(){ super( Transparency.SOLID ); setBackground( ToolbarDockStation.this.getBackgroundAlgorithm() ); } @Override public Dimension getPreferredSize(){ final Dimension pref = super.getPreferredSize(); // Insets insets = getInsets(); // pref.height += insets.top + insets.bottom; // pref.width += insets.left + insets.right; return pref; } @Override public Dimension getMaximumSize(){ return getPreferredSize(); } @Override public Dimension getMinimumSize(){ return getPreferredSize(); } } /** * Called by the constructor, this method creates the main component of this station. * @return the main component, must not be null */ protected OverpaintablePanelBase createMainPanel(){ return new OverpaintablePanelBase(); } /** * Creates the parent {@link JComponent} of the {@link Dockable}s that are shown in this * station. The default behavior is to create a new {@link SizeFixedPanel}, using * {@link #getBackgroundAlgorithm()} for managing painting. * @return the new content pane */ @Override protected JPanel createBackgroundPanel(){ return new SizeFixedPanel(); } /** * 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 content Pane of this {@link OverpaintablePanel} (with a * BoxLayout) */ private JComponent dockablePane; /** * Creates a new panel */ public OverpaintablePanelBase(){ setSolid( false ); } /** * Initializes this panel with the default layout, namely exactly one child which is the * {@link #setDockablePane(JComponent) dockable pane}. */ public void setupLayout(){ setDockablePane( createBackgroundPanel() ); setBasePane( dockablePane ); } public void setDockablePane(JComponent pane){ if(dockablePane != null){ throw new IllegalStateException( "dockablePane is already set" ); } dockablePane = pane; layoutManager = new SpanToolbarLayoutManager( ToolbarDockStation.this, dockablePane ){ @Override protected void revalidate(){ dockablePane.revalidate(); } }; dockablePane.setLayout( layoutManager ); } public JComponent getDockablePane(){ return dockablePane; } @Override public Dimension getPreferredSize(){ return getBasePane().getPreferredSize(); } @Override public Dimension getMinimumSize(){ return getPreferredSize(); } @Override public Dimension getMaximumSize(){ return getPreferredSize(); } private Insets subtractComponent( JComponent component, Insets insets ){ Point topLeft = new Point( 0, 0 ); topLeft = SwingUtilities.convertPoint( component, topLeft, this ); insets.left += topLeft.x; insets.top += topLeft.y; insets.right += (getWidth() - component.getWidth()) - topLeft.x; insets.bottom += (getHeight() - component.getHeight()) - topLeft.y; return insets; } @Override protected void paintOverlay( Graphics g ){ final Graphics2D g2D = (Graphics2D) g; paintRemoval( g ); if( dropInfo != null ) { Insets insets = dockablePane.getInsets(); if( insets == null ){ insets = new Insets( 0, 0, 0, 0 ); } insets = subtractComponent( dockablePane, insets ); int index = dropInfo.getIndex(); int x, y, width, height; if( getOrientation() == Orientation.HORIZONTAL ) { if( index == 0 ) { x = insets.left; } else { x = dockablePane.getComponent( index - 1 ).getX() + dockablePane.getComponent( index - 1 ).getWidth(); } if( index == dockablePane.getComponentCount() ) { width = getWidth() - x - insets.right; } else { width = dockablePane.getComponent( index ).getX() - x; } y = insets.top; height = getHeight() - insets.top - insets.bottom; } else { if( index == 0 ) { y = insets.top; } else { y = dockablePane.getComponent( index - 1 ).getY() + dockablePane.getComponent( index - 1 ).getHeight(); } if( index == dockablePane.getComponentCount() ) { height = getHeight() - y - insets.top; } else { height = dockablePane.getComponent( index ).getY() - y; } x = insets.left; width = getWidth() - insets.left - insets.right; } if( width > 0 && height > 0 ){ Rectangle stationBounds = new Rectangle( 0, 0, getWidth(), getHeight() ); paint.drawInsertion( g2D, stationBounds, new Rectangle( x, y, width, height ) ); } } } private void paintRemoval( Graphics g ){ Dockable removal = getRemoval(); if( removal != null ) { for( StationChildHandle handle : dockables.dockables() ) { if( handle.getDockable() == removal ) { Dimension size = handle.getDisplayer().getComponent().getSize(); Point location = new Point(0, 0); location = SwingUtilities.convertPoint( handle.getDisplayer().getComponent(), location, this ); Rectangle bounds = new Rectangle( location, size ); paint.drawRemoval( g, bounds, bounds ); break; } } } } @Override public String toString(){ return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() ); } } // ######################################################## // ############### PlaceHolder Managing ################### // ######################################################## // TODO don't use ToolbarProperty but a custom class, would be much safer // if the layout is screwed up /** * Creates a new {@link DockableProperty} describing the location of * child on this station. This method is called by * {@link #getDockableProperty(Dockable, Dockable)} once the location and * placeholder of child or target have been * calculated * * @param child * a child of this station * @param target * the item whose position is searched * @param index * the location of child * @param placeholder * the placeholder for target or child * @return a new {@link DockableProperty} that stores index, * placeholder and any other information a subclass * deems necessary to store */ protected DockableProperty getDockableProperty( Dockable child, Dockable target, int index, Path placeholder ){ return new ToolbarProperty( index, placeholder ); } /** * Tells whether the subclass knows how to handle property. * This means that the type of property is the same type as the * result of {@link #getDockableProperty(Dockable, Dockable, int, Path)} * * @param property * the property to check * @return true if this subclass knows how to handle the type * of property */ protected boolean isValidProperty( DockableProperty property ){ return property instanceof ToolbarProperty; } /** * Gets the location of a {@link Dockable} on this station. Called only if * property passed {@link #isValidProperty(DockableProperty)}. * * @param property * some property created by * {@link #getDockableProperty(Dockable, Dockable, int, Path)} * @return the index parameter */ protected int getIndex( DockableProperty property ){ return ((ToolbarProperty) property).getIndex(); } protected Path getPlaceholder( DockableProperty property ){ return ((ToolbarProperty) property).getPlaceholder(); } /** * Grants direct access to the list of {@link Dockable}s, subclasses should * not modify the list unless the fire the appropriate events. * * @return the list of dockables */ protected PlaceholderList.Filter getDockables(){ return dockables.dockables(); } /** * Gets the placeholders of this station using a * {@link PlaceholderListItemConverter} to encode the children. The * converter puts the following parameters for each {@link Dockable} into * the map: *
    *
  • id: the integer from children
  • *
  • index: the location of the element in the dockables-list
  • *
  • placeholder: the placeholder of the element, might be missing
  • *
* * @param children * a unique identifier for each child of this station * @return the map */ public PlaceholderMap getPlaceholders( final Map children ){ final PlaceholderStrategy strategy = getPlaceholderStrategy(); return dockables.toMap( new PlaceholderListItemAdapter(){ @Override public ConvertedPlaceholderListItem convert( int index, StationChildHandle handle ){ final Dockable dockable = handle.getDockable(); final Integer id = children.get( dockable ); if( id == null ) { return null; } final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem(); item.putInt( "id", id ); item.putInt( "index", index ); if( strategy != null ) { final Path placeholder = strategy.getPlaceholderFor( dockable ); if( placeholder != null ) { item.putString( "placeholder", placeholder.toString() ); item.setPlaceholder( placeholder ); } } return item; } } ); } /** * Sets a new layout on this station, this method assumes that * map was created by the method {@link #getPlaceholders(Map)}. * * @param map * the map to read * @param children * the new children of this station * @throws IllegalStateException * if there are children left on this station */ public void setPlaceholders( PlaceholderMap map, final Map children ){ DockUtilities.checkLayoutLocked(); if( getDockableCount() > 0 ) { throw new IllegalStateException( "must not have any children" ); } final DockController controller = getController(); try { if( controller != null ) { controller.freezeLayout(); } final DockablePlaceholderList next = new DockablePlaceholderList(); if( getController() != null ) { dockables.setStrategy( null ); dockables.unbind(); dockables = next; } else { dockables = next; } next.read( map, new PlaceholderListItemAdapter(){ private DockHierarchyLock.Token token; private int index = 0; @Override public StationChildHandle convert( ConvertedPlaceholderListItem item ){ final int id = item.getInt( "id" ); final Dockable dockable = children.get( id ); if( dockable != null ) { DockUtilities.ensureTreeValidity( ToolbarDockStation.this, dockable ); token = DockHierarchyLock.acquireLinking( ToolbarDockStation.this, dockable ); listeners.fireDockableAdding( dockable ); return new StationChildHandle( ToolbarDockStation.this, displayers, dockable, title ); } return null; } @Override public void added( StationChildHandle handle ){ try { handle.updateDisplayer(); insertAt( handle, index++ ); listeners.fireDockableAdded( handle.getDockable() ); } finally { token.release(); } } } ); if( getController() != null ) { dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } } finally { if( controller != null ) { controller.meltLayout(); } } } @Override public PlaceholderMap getPlaceholders(){ return dockables.toMap(); } @Override public PlaceholderMapping getPlaceholderMapping() { return new PlaceholderListMapping( this, dockables ){ @Override public DockableProperty getLocationAt( Path placeholder ) { int index = dockables.getDockableIndex( placeholder ); return new ToolbarProperty( index, placeholder ); } }; } @Override public void setPlaceholders( PlaceholderMap placeholders ){ if( getDockableCount() > 0 ) { throw new IllegalStateException( "only allowed if there are not children present" ); } try { final DockablePlaceholderList next = new DockablePlaceholderList( placeholders ); if( getController() != null ) { dockables.setStrategy( null ); dockables.unbind(); dockables = next; dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } else { dockables = next; } } catch( final IllegalArgumentException ex ) { // silent } } /** * Gets the {@link PlaceholderStrategy} that is currently in use. * * @return the current strategy, may be null */ public PlaceholderStrategy getPlaceholderStrategy(){ return placeholderStrategy.getValue(); } /** * Sets the {@link PlaceholderStrategy} to use, null will set * the default strategy. * * @param strategy * the new strategy, can be null */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ placeholderStrategy.setValue( strategy ); } @Override public DockableProperty getDockableProperty( Dockable child, Dockable target ){ final int index = indexOf( child ); Path placeholder = null; final PlaceholderStrategy strategy = getPlaceholderStrategy(); if( strategy != null ) { placeholder = strategy.getPlaceholderFor( target == null ? child : target ); if( placeholder != null ) { dockables.dockables().addPlaceholder( index, placeholder ); } } return getDockableProperty( child, target, index, placeholder ); } public void aside( AsideRequest request ){ int index = -1; int resultIndex = -1; if( getExpandedState() == ExpandedState.EXPANDED && getDockableCount() == 1 ){ DockStation stack = getDockable( 0 ).asDockStation(); AsideAnswer answer = request.forward( stack ); if( answer.isCanceled() ){ return; } DockableProperty answerLocation = answer.getLocation(); if( answerLocation instanceof StackDockProperty ){ resultIndex = ((StackDockProperty) answerLocation).getIndex(); } } DockableProperty location = request.getLocation(); Path newPlaceholder = request.getPlaceholder(); if( location instanceof ToolbarProperty ){ ToolbarProperty toolbarLocation = (ToolbarProperty)location; index = dockables.getNextListIndex( toolbarLocation.getIndex(), toolbarLocation.getPlaceholder() ); if( newPlaceholder != null ){ dockables.list().insertPlaceholder( index, newPlaceholder ); } } else{ index = dockables.dockables().size(); if( newPlaceholder != null ){ dockables.dockables().insertPlaceholder( index, newPlaceholder ); } } if( resultIndex == -1 ){ resultIndex = index; } request.answer( new ToolbarProperty( index, newPlaceholder )); } @Override public boolean drop( Dockable dockable, DockableProperty property ){ if( isValidProperty( property ) ) { final boolean acceptable = acceptable( dockable ); boolean result = false; final int index = Math.min( getDockableCount(), getIndex( property ) ); final Path placeholder = getPlaceholder( property ); if( (placeholder != null) && (property.getSuccessor() != null) ) { final StationChildHandle preset = dockables.getDockableAt( placeholder ); if( preset != null ) { final DockStation station = preset.getDockable().asDockStation(); if( station != null ) { if( station.drop( dockable, property.getSuccessor() ) ) { dockables.removeAll( placeholder ); result = true; } } } } if( !result && (placeholder != null) ) { if( acceptable && dockables.hasPlaceholder( placeholder ) ) { add( dockable, index, placeholder ); result = true; } } if( !result && (dockables.dockables().size() == 0) ) { if( acceptable ) { drop( dockable ); result = true; } } if( !result ) { if( (index < dockables.dockables().size()) && (property.getSuccessor() != null) ) { final DockStation child = getDockable( index ).asDockStation(); if( child != null ) { result = child.drop( dockable, property.getSuccessor() ); } } } if( !result && acceptable ) { result = drop( dockable, index ); } return result; } return false; } @Override public void move( Dockable dockable, DockableProperty property ){ // TODO pending } /** * This listener is added to the parent of this station and will forward an * event to {@link ToolbarContainerDockStation#visibility} if the visibility * of the station changes. * * @author Benjamin Sigg */ private class VisibleListener extends DockStationAdapter { @Override public void dockableShowingChanged( DockStation station, Dockable dockable, boolean visible ){ if( dockable == ToolbarDockStation.this ) { visibility.fire(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy