/*
* 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
* rvguillaume@hotmail.com
* FR - France
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockUI;
import bibliothek.gui.Dockable;
import bibliothek.gui.Orientation;
import bibliothek.gui.Position;
import bibliothek.gui.dock.component.DefaultDockStationComponentRootHandler;
import bibliothek.gui.dock.component.DockComponentRootHandler;
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.station.AbstractDockableStation;
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.OrientationObserver;
import bibliothek.gui.dock.station.OrientedDockStation;
import bibliothek.gui.dock.station.OrientingDockStation;
import bibliothek.gui.dock.station.OrientingDockStationEvent;
import bibliothek.gui.dock.station.OrientingDockStationListener;
import bibliothek.gui.dock.station.OverpaintablePanel;
import bibliothek.gui.dock.station.PlaceholderMapping;
import bibliothek.gui.dock.station.StationBackgroundComponent;
import bibliothek.gui.dock.station.StationChildHandle;
import bibliothek.gui.dock.station.StationDragOperation;
import bibliothek.gui.dock.station.StationDropItem;
import bibliothek.gui.dock.station.StationDropOperation;
import bibliothek.gui.dock.station.StationPaint;
import bibliothek.gui.dock.station.layer.DefaultDropLayer;
import bibliothek.gui.dock.station.layer.DockStationDropLayer;
import bibliothek.gui.dock.station.span.Span;
import bibliothek.gui.dock.station.support.DockablePlaceholderList;
import bibliothek.gui.dock.station.support.DockableShowingManager;
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.DefaultToolbarContainerConverter;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerConverter;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerConverterCallback;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerDockStationFactory;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerDropInfo;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerLayoutManager;
import bibliothek.gui.dock.station.toolbar.ToolbarContainerProperty;
import bibliothek.gui.dock.station.toolbar.ToolbarStrategy;
import bibliothek.gui.dock.station.toolbar.layer.ToolbarContainerDropLayer;
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.themes.color.StationColor;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleFactory;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.BackgroundAlgorithm;
import bibliothek.gui.dock.util.ConfiguredBackgroundPanel;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.SilentPropertyValue;
import bibliothek.gui.dock.util.Transparency;
import bibliothek.gui.dock.util.color.DockColor;
import bibliothek.gui.dock.util.extension.Extension;
import bibliothek.util.Path;
/**
* A {@link Dockable} and a {@link DockStation} which stands for a group of
* {@link ToolbarGroupDockStation}. As dockable it can be put in every
* {@link DockStation}. As DockStation it accepts only
* {@link ToolbarElementInterface}s. When ToolbarElement are added, all the
* ComponentDockable extracted from the element are merged together and wrapped
* in a {@link ToolbarGroupDockStation} before to be added.
*
* @author Herve Guillaume
*/
public class ToolbarContainerDockStation extends AbstractDockableStation implements OrientingDockStation, OrientedDockStation {
/** the id of the {@link DockTitleFactory} used with this station */
public static final String TITLE_ID = "toolbar.container";
/**
* This id is forwarded to {@link Extension}s which load additional
* {@link DisplayerFactory}s
*/
public static final String DISPLAYER_ID = "toolbar.container";
public static final Orientation DEFAULT_ORIENTATION = Orientation.VERTICAL;
/** the orientation of the station */
private Orientation orientation = DEFAULT_ORIENTATION;
/** the number of dockables <code>this</code> station will accept */
private int dockablesMaxNumber = -1;
/** The containerPane */
private JPanel containerPanel;
/** The background of {@link #containerPanel} and in return of this entire station */
private Background background = new Background();
/**
* The graphical representation of this station: the pane which contains
* toolbars
*/
protected OverpaintablePanelBase mainPanel;
/** the background color of {@link #mainPanel} */
private BackgroundColor mainPanelBackground;
/** dockables associate with the container pane */
private DockablePlaceholderList<StationChildHandle> dockables = new DockablePlaceholderList<StationChildHandle>();
/** the {@link DockableDisplayer} shown */
private final DisplayerCollection displayers;
/** factory for {@link DockTitle}s used for the main panel */
private DockTitleVersion title;
/** factory for creating new {@link DockableDisplayer}s */
private final DefaultDisplayerFactoryValue displayerFactory;
/** A paint to draw lines */
private final DefaultStationPaintValue paint;
/** the index of the closest dockable above the mouse */
private int indexBeneathMouse = -1;
/** closest side of the the closest dockable above the mouse */
private Position sideAboveMouse = null;
/**
* Tells if this station is in prepareDrop state and should draw something
* accordingly
*/
boolean prepareDropDraw = false;
/** The dockable that is about to be dragged away from this station */
private Dockable removal = null;
/** all registered {@link OrientingDockStationListener}s. */
private final List<OrientingDockStationListener> orientingListeners = new ArrayList<OrientingDockStationListener>();
/** current {@link PlaceholderStrategy} */
private final PropertyValue<PlaceholderStrategy> placeholderStrategy = new PropertyValue<PlaceholderStrategy>( 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;
/** This {@link LayoutManager} is responsible for updating the boundaries of all {@link Dockable}s and keeping track of {@link Span}s */
private ToolbarContainerLayoutManager layoutManager;
/** the number of pixels outside this station where a drag and drop operation is still possible */
private int sideSnapSize = 10;
/**
* Constructs a new ContainerLineStation
*/
public ToolbarContainerDockStation( Orientation orientation ){
this( orientation, -1 );
}
/**
* Creates a new station
* @param orientation the orientation of the content
* @param maxNumberOfDockables the maximum number of children or -1
*/
public ToolbarContainerDockStation( Orientation orientation, int maxNumberOfDockables ){
this.orientation = orientation;
setDockablesMaxNumber( maxNumberOfDockables );
mainPanel = new OverpaintablePanelBase();
mainPanelBackground = new BackgroundColor();
paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".toolbar", this );
displayerFactory = new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".toolbar.container", this );
displayers = new DisplayerCollection( this, displayerFactory, DISPLAYER_ID );
final DockableDisplayerListener listener = new DockableDisplayerListener(){
@Override
public void discard( DockableDisplayer displayer ){
ToolbarContainerDockStation.this.discard( displayer );
}
@Override
public void moveableElementChanged( DockableDisplayer displayer ){
// ignore
}
};
displayers.addDockableDisplayerListener( listener );
setTitleIcon( null );
new OrientationObserver( this ){
@Override
protected void orientationChanged( Orientation current ){
if( current != null ) {
setOrientation( current );
}
}
};
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();
}
}
});
}
/**
* Create a pane for this dock station
*/
private JPanel createPanel(){
ConfiguredBackgroundPanel panel = new ConfiguredBackgroundPanel( Transparency.DEFAULT );
panel.setBackground( background );
layoutManager = new ToolbarContainerLayoutManager( panel, ToolbarContainerDockStation.this );
panel.setLayout( layoutManager );
panel.setBorder( new EmptyBorder( new Insets( 3, 3, 3, 3 ) ) );
return panel;
}
@Override
protected DockComponentRootHandler createRootHandler() {
return new DefaultDockStationComponentRootHandler( this, displayers );
}
@Override
public int getDockableCount(){
return dockables.dockables().size();
}
@Override
public Dockable getDockable( int index ){
return dockables.dockables().get( index ).getDockable();
}
@Override
public Dockable getFrontDockable(){
// there's no child which is more important than another
return null;
}
@Override
public void setFrontDockable( Dockable dockable ){
// there's no child which is more important than another
}
@Override
public PlaceholderMap getPlaceholders(){
return createConverter().getPlaceholders( this );
}
/**
* Gets the layout of this station encoded as {@link PlaceholderMap}.
*
* @param children
* identifiers for the children
* @return the encoded layout, not <code>null</code>
*/
public PlaceholderMap getPlaceholders( Map<Dockable, Integer> children ){
return createConverter().getPlaceholders( this, children );
}
@Override
public PlaceholderMapping getPlaceholderMapping() {
return new PlaceholderListMapping( this, dockables ){
@Override
public DockableProperty getLocationAt( Path placeholder ) {
int index = dockables.getDockableIndex( placeholder );
return new ToolbarContainerProperty( index, placeholder );
}
};
}
@Override
public void setPlaceholders( PlaceholderMap placeholders ){
createConverter().setPlaceholders( this, placeholders );
}
/**
* Sets the layout of this station using the encoded layout from
* <code>placeholders</code>
*
* @param placeholders
* the placeholders to read
* @param children
* the children to add
*/
public void setPlaceholders( PlaceholderMap placeholders, Map<Integer, Dockable> children ){
createConverter().setPlaceholders( this, new ToolbarContainerConverterCallback(){
int index = 0;
@Override
public StationChildHandle wrap( Dockable dockable ){
return new StationChildHandle( ToolbarContainerDockStation.this, displayers, dockable, title );
}
@Override
public void adding( StationChildHandle handle ){
listeners.fireDockableAdding( handle.getDockable() );
}
@Override
public void added( StationChildHandle handle ){
handle.updateDisplayer();
insertAt( handle, index++ );
handle.getDockable().setDockParent( ToolbarContainerDockStation.this );
listeners.fireDockableAdded( handle.getDockable() );
}
@Override
public void setDockables( DockablePlaceholderList<StationChildHandle> list ){
ToolbarContainerDockStation.this.setDockables( list, false );
}
@Override
public void finished( DockablePlaceholderList<StationChildHandle> list ){
if( getController() != null ) {
list.bind();
list.setStrategy( getPlaceholderStrategy() );
}
}
}, placeholders, children );
}
/**
* Creates a {@link ToolbarContainerConverter} which will be used for one
* call.
*
* @return the new converter, not <code>null</code>
*/
protected ToolbarContainerConverter createConverter(){
return new DefaultToolbarContainerConverter();
}
/**
* Gets the {@link PlaceholderStrategy} that is currently in use.
*
* @return the current strategy, may be <code>null</code>
*/
public PlaceholderStrategy getPlaceholderStrategy(){
return placeholderStrategy.getValue();
}
/**
* Sets the {@link PlaceholderStrategy} to use, <code>null</code> will set
* the default strategy.
*
* @param strategy
* the new strategy, can be <code>null</code>
*/
public void setPlaceholderStrategy( PlaceholderStrategy strategy ){
placeholderStrategy.setValue( strategy );
}
@Override
public DockableProperty getDockableProperty( Dockable child, Dockable target ){
final int index = indexOf( child );
Path placeholder = null;
PlaceholderStrategy strategy = getPlaceholderStrategy();
if( strategy != null ){
if( target != null ){
placeholder = strategy.getPlaceholderFor( target );
}
else{
placeholder = strategy.getPlaceholderFor( child );
}
if( placeholder != null ){
dockables.dockables().addPlaceholder( index, placeholder );
}
}
return new ToolbarContainerProperty( index, placeholder );
}
public void aside( AsideRequest request ){
DockableProperty location = request.getLocation();
int index;
Path newPlaceholder = request.getPlaceholder();
if( location instanceof ToolbarContainerProperty ){
ToolbarContainerProperty toolbarLocation = (ToolbarContainerProperty)location;
if( toolbarLocation.getSuccessor() == null ){
index = dockables.getNextListIndex( toolbarLocation.getIndex(), toolbarLocation.getPlaceholder() );
if( newPlaceholder != null ){
dockables.list().insertPlaceholder( index, newPlaceholder );
}
}
else{
index = dockables.getListIndex( toolbarLocation.getIndex(), toolbarLocation.getPlaceholder() );
if( newPlaceholder != null ){
dockables.list().addPlaceholder( index, newPlaceholder );
StationChildHandle handle = dockables.list().get( index ).getDockable();
if( handle != null ){
DockStation station = handle.asDockable().asDockStation();
if( station != null ){
AsideAnswer answer = request.forward( station );
if( answer.isCanceled() ){
return;
}
}
}
}
}
}
else {
index = dockables.dockables().size();
if( newPlaceholder != null ){
dockables.dockables().insertPlaceholder( index, newPlaceholder );
}
}
request.answer( new ToolbarContainerProperty( index, newPlaceholder ));
}
@Override
public StationDragOperation prepareDrag( Dockable dockable ){
removal = dockable;
getComponent().repaint();
return new StationDragOperation(){
@Override
public void succeeded(){
removal = null;
getComponent().repaint();
}
@Override
public void canceled(){
removal = null;
getComponent().repaint();
}
};
}
/**
* Sets the number of pixels outside the station where a drag and drop
* operation can still start.
* @param sideSnapSize the size in pixels
*/
public void setSideSnapSize( int sideSnapSize ){
this.sideSnapSize = sideSnapSize;
}
/**
* Gets the number of pixels outside the station where a drag and drop
* operation can still start
* @return the size in pixels
*/
public int getSideSnapSize(){
return sideSnapSize;
}
@Override
public DockStationDropLayer[] getLayers(){
return new DockStationDropLayer[]{
new DefaultDropLayer( this ){
@Override
public Component getComponent(){
return ToolbarContainerDockStation.this.getComponent();
}
},
new ToolbarContainerDropLayer( this )
};
}
@Override
public StationDropOperation prepareDrop( StationDropItem item ){
// System.out.println(this.toString() + "## prepareDrop(...) ##");
final DockController controller = getController();
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;
}
}
if( !getToolbarStrategy().isToolbarPart( dockable ) ) {
// only ToolbarElementInterface can be drop or move into this
return null;
}
final ToolbarContainerDropInfo result = new ToolbarContainerDropInfo( dockable, this, dockables, item.getMouseX(), item.getMouseY() ){
@Override
public void execute(){
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.setDrawing( null );
}
// without this line, nothing is displayed except if you
// drag another component
indexBeneathMouse = -1;
sideAboveMouse = null;
prepareDropDraw = false;
mainPanel.repaint();
}
@Override
public int getIndex(){
return indexOf( getDockableBeneathMouse() );
}
@Override
public void draw(){
boolean effect = true;
indexBeneathMouse = getIndex();
if( isMove() ){
int target = moveIndex( this, indexBeneathMouse );
int current = indexOf( getItem() );
effect = target != current && target != current-1;
}
if( effect ){
layoutManager.setDrawing( this );
prepareDropDraw = true;
}
else{
layoutManager.setDrawing( null );
prepareDropDraw = false;
}
sideAboveMouse = getSideDockableBeneathMouse();
mainPanel.repaint();
}
};
// System.out.println(result.toSummaryString());
return result;
}
else {
return null;
}
}
@Override
public void addOrientingDockStationListener( OrientingDockStationListener listener ){
orientingListeners.add( listener );
}
@Override
public void removeOrientingDockStationListener( OrientingDockStationListener listener ){
orientingListeners.remove( listener );
}
@Override
public Orientation getOrientationOf( Dockable child ){
return orientation;
}
/**
* Sets the number of dockables that this station will accept (max = -1
* indicates that there's no limit).
*
* @param max
* the number of dockables accepted
*/
public void setDockablesMaxNumber( int max ){
dockablesMaxNumber = max;
}
/**
* Gets the number of dockables that this station will accept.
*
* @return the number of dockables accepted (-1 if there is no maximum
* number)
*/
public int getDockablesMaxNumber(){
return dockablesMaxNumber;
}
/**
* Fires an {@link OrientingDockStationEvent}.
*
* @param dockables
* the items whose orientation changed
*/
protected void fireOrientingEvent(){
final OrientingDockStationEvent event = new OrientingDockStationEvent( this );
for( final OrientingDockStationListener listener : orientingListeners.toArray( new OrientingDockStationListener[orientingListeners.size()] ) ) {
listener.changed( event );
}
}
private void drop( ToolbarContainerDropInfo dropInfo ){
// Note: Computation of index to insert drag dockable is not the
// same between a move() and a drop(), because with a move() it is
// as if the drag dockable were remove first then added again in the
// list (Note: It's wird because in fact drag() is called after
// move()...)
// we check if there's dockable in this station
if( getDockables().dockables().size() == 0 ) {
// in this case, it's inevitably a drop() action
drop( dropInfo.getItem(), 0 );
}
// if the dockable has to be drop at the same place (centered with
// regards to itself): nothing to be done
if( dropInfo.getItemPositionVSBeneathDockable() != Position.CENTER ) {
final int indexBeneathMouse = indexOf( dropInfo.getDockableBeneathMouse() );
int dropIndex;
if( dropInfo.isMove() ) {
move( dropInfo.getItem(), moveIndex( dropInfo, indexBeneathMouse ));
}
else {
int increment = 0;
if( (dropInfo.getSideDockableBeneathMouse() == Position.SOUTH) || (dropInfo.getSideDockableBeneathMouse() == Position.EAST) ) {
increment++;
}
dropIndex = indexBeneathMouse + increment;
drop( dropInfo.getItem(), dropIndex );
}
}
}
private int moveIndex( ToolbarContainerDropInfo dropInfo, int indexBeneathMouse ){
switch( getOrientation() ){
case VERTICAL:
if( dropInfo.getItemPositionVSBeneathDockable() == Position.SOUTH ) {
if( dropInfo.getSideDockableBeneathMouse() == Position.SOUTH ) {
return indexBeneathMouse + 1;
}
else {
return indexBeneathMouse;
}
}
else {
if( dropInfo.getSideDockableBeneathMouse() == Position.SOUTH ) {
return indexBeneathMouse;
}
else {
return indexBeneathMouse - 1;
}
}
case HORIZONTAL:
if( dropInfo.getItemPositionVSBeneathDockable() == Position.EAST ) {
if( dropInfo.getSideDockableBeneathMouse() == Position.EAST ) {
return indexBeneathMouse + 1;
}
else {
return indexBeneathMouse;
}
}
else {
if( dropInfo.getSideDockableBeneathMouse() == Position.EAST ) {
return indexBeneathMouse;
}
else {
return indexBeneathMouse - 1;
}
}
default:
throw new IllegalStateException( "unknown orientation: " + getOrientation() );
}
}
@Override
public boolean drop( Dockable dockable, DockableProperty property ){
if( property instanceof ToolbarContainerProperty ) {
final ToolbarContainerProperty toolbar = (ToolbarContainerProperty) property;
Path placeholder = toolbar.getPlaceholder();
boolean hasPlaceholder = false;
int index = -1;
StationChildHandle presetHandle = null;
if( placeholder != null ){
hasPlaceholder = dockables.hasPlaceholder( placeholder );
if( hasPlaceholder ){
index = dockables.getDockableIndex( placeholder );
presetHandle = dockables.getDockableAt( placeholder );
}
}
if( index == -1 ){
index = toolbar.getIndex();
}
if( toolbar.getSuccessor() != null ) {
final DockablePlaceholderList<StationChildHandle> list = getDockables();
Dockable preset = null;
if( presetHandle != null ){
preset = presetHandle.asDockable();
}
else if( !hasPlaceholder && index >= 0 && index < list.dockables().size() ) {
preset = list.dockables().get( index ).getDockable();
}
if( (preset != null) && (preset.asDockStation() != null) ) {
return preset.asDockStation().drop( dockable, property.getSuccessor() );
}
}
final int max = getDockables().dockables().size();
if( hasPlaceholder && presetHandle == null && toolbar.getSuccessor() != null ){
Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable );
DockController controller = getController();
if( controller != null ){
controller.freezeLayout();
}
try{
add( replacement, -1, placeholder );
if( replacement != dockable ){
if( !replacement.asDockStation().drop( dockable, toolbar.getSuccessor() ) ){
replacement.asDockStation().drop( dockable );
}
}
return true;
}
finally{
if( controller != null ){
controller.meltLayout();
}
}
}
else{
return drop( dockable, Math.max( 0, Math.min( max, index )) );
}
}
return false;
}
/**
* Adds <code>dockable</code> to this station. The dockable must be a
* {@link ToolbarElementInterface} : if not, do nothing. The dockable is
* added at the last position.
*
* @param dockable
* a new child
*/
@Override
public void drop( Dockable dockable ){
// System.out.println(this.toString() +
// "## drop(Dockable dockable )##");
this.drop( dockable, getDockables().dockables().size() );
}
/**
* Inserts <code>dockable</code> to this station at the given index. The
* dockable must be a {@link ToolbarElementInterface}: if not, do nothing.
*
* @param dockable
* a new child
* @param index the group of the child
* @return <code>true</code> if dropping was successful
*/
private boolean drop( Dockable dockable, int index ){
// System.out.println(this.toString()
// + "## drop(Dockable dockable, int index )##");
return add( dockable, index );
}
private void move( Dockable dockable, int indexWhereInsert ){
// System.out.println(this.toString() + "## move() ## ==> ");
if( getToolbarStrategy().isToolbarPart( dockable ) ) {
final DockController controller = getController();
try {
if( controller != null ) {
controller.freezeLayout();
}
add( dockable, indexWhereInsert );
}
finally {
if( controller != null ) {
controller.meltLayout();
}
}
}
}
@Override
public void move( Dockable dockable, DockableProperty property ){
}
@Override
public boolean canDrag( Dockable dockable ){
// System.out.println(this.toString()
// + "## canDrag(Dockable dockable) ## " + this.toString());
return true;
}
@Override
public void drag( Dockable dockable ){
// System.out.println(this.toString() +
// "## drag(Dockable dockable) ##");
if( dockable.getDockParent() != this ) {
throw new IllegalArgumentException( "The dockable cannot be dragged, it is not child of this station." );
}
remove( dockable );
}
@Override
public boolean canReplace( Dockable old, Dockable next ){
if( old.getClass() == next.getClass() ) {
return true;
}
else {
return false;
}
}
@Override
public void replace( Dockable old, Dockable next ){
// System.out.println(this.toString()
// + "## replace(Dockable old, Dockable next) ## "
// + this.toString());
DockUtilities.checkLayoutLocked();
final DockController controller = getController();
if( controller != null ) {
controller.freezeLayout();
}
final int index = indexOf( old );
remove( old );
// the child is a TollbarGroupDockStation because canReplace()
// ensure it
add( next, index );
controller.meltLayout();
}
@Override
public void replace( DockStation old, Dockable next ){
// System.out.println(this.toString()
// + "## replace(DockStation old, Dockable next) ## "
// + this.toString());
replace( old.asDockable(), next );
}
@Override
public String getFactoryID(){
return ToolbarContainerDockStationFactory.ID;
}
@Override
public Component getComponent(){
return mainPanel;
}
@Override
protected void callDockUiUpdateTheme() throws IOException{
DockUI.updateTheme( this, new ToolbarContainerDockStationFactory() );
}
/**
* Gets the panel which contains dockables
*
* @return the panel which contains dockables
*/
public JPanel getContainerPanel(){
return containerPanel;
}
/**
* Gets the {@link ToolbarStrategy} that is currently used by this station.
*
* @return the strategy, never <code>null</code>
*/
public ToolbarStrategy getToolbarStrategy(){
final SilentPropertyValue<ToolbarStrategy> value = new SilentPropertyValue<ToolbarStrategy>( ToolbarStrategy.STRATEGY, getController() );
final ToolbarStrategy result = value.getValue();
value.setProperties( (DockController) null );
return result;
}
@Override
public boolean accept( Dockable child ){
if( dockablesMaxNumber == -1 ) {
return true;
}
else if( dockables.dockables().size() >= dockablesMaxNumber ) {
return false;
}
else {
return true;
}
}
@Override
public boolean accept( DockStation station ){
return true;
}
@Override
public String toString(){
return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() );
}
/**
* Updates one of the lists containing dockables.
*
* @param list
* the new list
* @param bind
* whether the new list should be
* {@link DockablePlaceholderList#bind() bound}
*/
private void setDockables( DockablePlaceholderList<StationChildHandle> list, boolean bind ){
if( getController() != null ) {
final DockablePlaceholderList<StationChildHandle> oldList = getDockables();
oldList.setStrategy( null );
oldList.unbind();
}
dockables = list;
if( (getController() != null) && bind ) {
list.bind();
list.setStrategy( getPlaceholderStrategy() );
}
}
/**
* Gets the dockables in the station
*
* @return the dockables associated with the station
*/
public DockablePlaceholderList<StationChildHandle> getDockables(){
return dockables;
}
/**
* Gets the orientation of dockables in the station
*
* @return the orientation
*/
@Override
public Orientation getOrientation(){
return orientation;
}
/**
* Sets the orientation of dockables in the station
*
* @param orientation
* the orientation
*/
@Override
public void setOrientation( Orientation orientation ){
this.orientation = orientation;
fireOrientingEvent();
}
/**
* Gets the index of a child.
*
* @param dockable
* the child which is searched
* @return the index of <code>dockable</code> or -1 if it was not found
*/
private int indexOf( Dockable dockable ){
for( int i = 0; i < dockables.dockables().size(); i++ ) {
if( dockables.dockables().get( i ).getDockable() == dockable ) {
return i;
}
}
return -1;
}
/**
* Removes the child with the given <code>index</code> from this station.<br>
* Note: clients may need to invoke {@link DockController#freezeLayout()}
* and {@link DockController#meltLayout()} to ensure no-one else adds or
* removes <code>Dockable</code>s.
*
* @param index
* the index of the child that will be removed
*/
private void remove( Dockable dockable ){
DockUtilities.checkLayoutLocked();
final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable );
try {
final int index = indexOf( dockable );
final DockablePlaceholderList.Filter<StationChildHandle> dockables = getDockables().dockables();
listeners.fireDockableRemoving( dockable );
dockable.setDockParent( null );
final StationChildHandle childHandle = dockables.get( index );
getDockables().remove( index );
getContainerPanel().remove( childHandle.getDisplayer().getComponent() );
childHandle.destroy();
mainPanel.getContentPane().revalidate();
mainPanel.getContentPane().repaint();
listeners.fireDockableRemoved( dockable );
fireDockablesRepositioned( index );
}
finally {
token.release();
}
}
/**
* Add one dockable at the index position. The dockable can be a
* {@link ToolbarItemDockable}, {@link ToolbarDockStation} or a
* {@link ToolbarGroupDockStation} (see method accept()). All the
* ComponentDockable extracted from the element are merged together and
* wrapped in a {@link ToolbarDockStation} before to be added at index
* position
*
* @param dockable
* Dockable to add
* @param index
* Index where add dockable
* @return <code>true</code> if dropping was successful
*/
protected boolean add( Dockable dockable, int index ){
return add( dockable, index, null );
}
protected boolean add( Dockable dockable, int index, Path placeholder ){
// System.out.println(this.toString()
// + "## add( Dockable dockable, int index ) ##");
DockUtilities.ensureTreeValidity( this, dockable );
DockUtilities.checkLayoutLocked();
final ToolbarStrategy strategy = getToolbarStrategy();
if( strategy.isToolbarPart( dockable ) ) {
Dockable replacement = strategy.ensureToolbarLayer( this, dockable );
if( replacement != dockable ){
replacement.setController( getController() );
replacement.asDockStation().drop( dockable );
replacement.setController( null );
dockable = replacement;
}
final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
try {
listeners.fireDockableAdding( dockable );
final DockablePlaceholderList.Filter<StationChildHandle> dockables = getDockables().dockables();
final StationChildHandle handle = new StationChildHandle( this, displayers, dockable, title );
if( placeholder != null ){
index = getDockables().put( placeholder, handle );
}
else{
dockables.add( index, handle );
}
handle.updateDisplayer();
insertAt( handle, index );
dockable.setDockParent( this );
listeners.fireDockableAdded( dockable );
fireDockablesRepositioned( index + 1 );
}
finally {
token.release();
}
mainPanel.revalidate();
mainPanel.repaint();
return true;
}
return false;
}
private void insertAt( StationChildHandle handle, int index ){
final Dockable dockable = handle.getDockable();
dockable.setDockParent( this );
getContainerPanel().add( handle.getDisplayer().getComponent(), index );
mainPanel.getContentPane().revalidate();
mainPanel.getContentPane().repaint();
}
/**
* Replaces <code>displayer</code> with a new {@link DockableDisplayer}.
*
* @param displayer
* the displayer to replace, must actually be shown on this
* station
*/
protected void discard( DockableDisplayer displayer ){
final int index = indexOf( displayer.getDockable() );
final StationChildHandle handle = getDockables().dockables().get( index );
getContainerPanel().remove( displayer.getComponent() );
handle.updateDisplayer();
insertAt( handle, index );
}
/**
* A {@link DockColor} used to change the background of {@link ToolbarContainerDockStation#mainPanel}
* @author Benjamin Sigg
*/
private class BackgroundColor extends StationColor{
public BackgroundColor(){
super( "toolbar.container.background", ToolbarContainerDockStation.this, null );
}
@Override
protected void changed( Color oldValue, Color newValue ){
mainPanel.setBackground( newValue );
}
}
/**
* 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 OverpaintablePanel {
/**
* Creates a new panel
*/
public OverpaintablePanelBase(){
containerPanel = createPanel();
// content.setBounds( 0, 0, content.getPreferredSize().width,
// content.getPreferredSize().height );
// this.setPreferredSize( new Dimension(
// content.getPreferredSize().width,
// content.getPreferredSize().height ) );
setBasePane( containerPanel );
setContentPane( containerPanel );
setSolid( true );
getContentPane().revalidate();
getContentPane().repaint();
}
@Override
protected void paintOverlay( Graphics g ){
paintRemoval( g );
final DefaultStationPaintValue paint = getPaint();
Rectangle rectangleAreaBeneathMouse;
if( prepareDropDraw ) {
if( indexBeneathMouse != -1 ) {
// WARNING: This rectangle stands for the component beneath
// mouse. His coordinates are in the frame of reference his
// direct parent: getPanel(areaBeneathMouse).
// So we need to translate this rectangle in the frame of
// reference of the overlay panel, which is the same that
// the base pane
final Rectangle rectComponentBeneathMouse = getDockables().dockables().get( indexBeneathMouse ).getDisplayer().getComponent().getBounds();
// this rectangle stands for the panel which holds the
// mouse.
// The return rectangle is in the frame of reference of his
// direct parent which is the content of the overlay pane
rectangleAreaBeneathMouse = getContainerPanel().getBounds();
// Translation
rectComponentBeneathMouse.translate( rectangleAreaBeneathMouse.x, rectangleAreaBeneathMouse.y );
switch( getOrientation() ){
case VERTICAL:
int y;
if( sideAboveMouse == Position.NORTH ) {
y = rectComponentBeneathMouse.y;
}
else {
y = rectComponentBeneathMouse.y + rectComponentBeneathMouse.height;
}
paint.drawInsertionLine( g, rectComponentBeneathMouse.x, y, rectComponentBeneathMouse.x + rectComponentBeneathMouse.width, y );
break;
case HORIZONTAL:
int x;
if( sideAboveMouse == Position.WEST ) {
x = rectComponentBeneathMouse.x;
}
else {
x = rectComponentBeneathMouse.x + rectComponentBeneathMouse.width;
}
paint.drawInsertionLine( g, x, rectComponentBeneathMouse.y, x, rectComponentBeneathMouse.y + rectComponentBeneathMouse.height );
}
}
else {
// the container pane is empty
paint.drawDivider( g, getContainerPanel().getBounds() );
}
}
}
private void paintRemoval( Graphics g ){
if( removal != null ){
for( StationChildHandle handle : dockables.dockables() ){
if( handle.getDockable() == removal ){
Rectangle bounds = handle.getDisplayer().getComponent().getBounds();
getPaint().drawRemoval( g, bounds, bounds );
break;
}
}
}
}
@Override
public String toString(){
return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() );
}
}
/**
* Gets a {@link StationPaint} which is used to paint some lines onto this
* station. Use a {@link DefaultStationPaintValue#setDelegate(StationPaint)
* delegate} to exchange the paint.
*
* @return the paint
*/
public DefaultStationPaintValue getPaint(){
return paint;
}
@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 ) {
unbind( dockables );
}
super.setController( controller );
paint.setController( controller );
// we catch the DockTitleManager (one by controller)
// effect of getVersion(...): we catch the DockTitleVersion
// associated
// with TITLE_ID. If none exist: a new one is created and registered
// in DockTitleManager. If no default factory is registered in the
// version TITLE_ID, so an new one is registered (in our case a
// BasicDockTitle.FACTORY).
displayerFactory.setController( controller );
layoutManager.setController( controller );
background.setController( controller );
mainPanelBackground.connect( controller );
if( controller == null ) {
title = null;
}
else {
title = controller.getDockTitleManager().getVersion( TITLE_ID, BasicDockTitleFactory.FACTORY );
}
displayers.setController( controller );
placeholderStrategy.setProperties( controller );
if( controller != null ) {
bind( dockables, title );
}
visibility.fire();
}
}
private void unbind( DockablePlaceholderList<StationChildHandle> list ){
list.unbind();
for( final StationChildHandle handle : list.dockables() ) {
handle.setTitleRequest( null );
}
}
private void bind( DockablePlaceholderList<StationChildHandle> list, DockTitleVersion title ){
list.bind();
for( final StationChildHandle handle : list.dockables() ) {
handle.setTitleRequest( title, true );
}
}
/**
* The background algorithm of this {@link ToolbarContainerDockStation}.
* @author Benjamin Sigg
*/
private class Background extends BackgroundAlgorithm implements StationBackgroundComponent{
public Background(){
super( StationBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".station.toolbar.container" );
}
@Override
public Component getComponent(){
return ToolbarContainerDockStation.this.getComponent();
}
@Override
public DockStation getStation(){
return ToolbarContainerDockStation.this;
}
}
/**
* 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 == ToolbarContainerDockStation.this ){
visibility.fire();
}
}
}
}