bibliothek.gui.dock.FlapDockStation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docking-frames-core Show documentation
Show all versions of docking-frames-core Show documentation
${project.name} is base or core library
/*
* 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) 2007 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
*
* Benjamin Sigg
* [email protected]
* CH - Switzerland
*/
package bibliothek.gui.dock;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.HierarchyBoundsListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockTheme;
import bibliothek.gui.DockUI;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.accept.DockAcceptance;
import bibliothek.gui.dock.action.DefaultDockActionSource;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.ListeningDockAction;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.control.focus.FocusController;
import bibliothek.gui.dock.control.focus.MouseFocusObserver;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.event.DockTitleEvent;
import bibliothek.gui.dock.event.DockableAdapter;
import bibliothek.gui.dock.event.DockableFocusEvent;
import bibliothek.gui.dock.event.DockableFocusListener;
import bibliothek.gui.dock.event.FlapDockListener;
import bibliothek.gui.dock.event.FocusVetoListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.station.AbstractDockableStation;
import bibliothek.gui.dock.station.Combiner;
import bibliothek.gui.dock.station.DisplayerCollection;
import bibliothek.gui.dock.station.DisplayerFactory;
import bibliothek.gui.dock.station.DockableDisplayer;
import bibliothek.gui.dock.station.StationBackgroundComponent;
import bibliothek.gui.dock.station.StationPaint;
import bibliothek.gui.dock.station.flap.ButtonPane;
import bibliothek.gui.dock.station.flap.DefaultFlapLayoutManager;
import bibliothek.gui.dock.station.flap.DefaultFlapWindowFactory;
import bibliothek.gui.dock.station.flap.FlapDockHoldToggle;
import bibliothek.gui.dock.station.flap.FlapDockProperty;
import bibliothek.gui.dock.station.flap.FlapDockStationFactory;
import bibliothek.gui.dock.station.flap.FlapDropInfo;
import bibliothek.gui.dock.station.flap.FlapLayoutManager;
import bibliothek.gui.dock.station.flap.FlapWindow;
import bibliothek.gui.dock.station.flap.FlapWindowFactory;
import bibliothek.gui.dock.station.flap.button.ButtonContent;
import bibliothek.gui.dock.station.flap.button.ButtonContentFilter;
import bibliothek.gui.dock.station.flap.button.DefaultButtonContentFilter;
import bibliothek.gui.dock.station.support.CombinerSource;
import bibliothek.gui.dock.station.support.CombinerSourceWrapper;
import bibliothek.gui.dock.station.support.CombinerTarget;
import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem;
import bibliothek.gui.dock.station.support.DockablePlaceholderList;
import bibliothek.gui.dock.station.support.DockableVisibilityManager;
import bibliothek.gui.dock.station.support.PlaceholderList;
import bibliothek.gui.dock.station.support.PlaceholderListItem;
import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter;
import bibliothek.gui.dock.station.support.PlaceholderListItemConverter;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.support.PlaceholderList.Level;
import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue;
import bibliothek.gui.dock.themes.DefaultStationPaintValue;
import bibliothek.gui.dock.themes.StationCombinerValue;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.themes.basic.BasicButtonTitleFactory;
import bibliothek.gui.dock.title.ControllerTitleFactory;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleRequest;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.BackgroundAlgorithm;
import bibliothek.gui.dock.util.DockProperties;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.gui.dock.util.property.DynamicPropertyFactory;
import bibliothek.util.Path;
import bibliothek.util.Todo;
import bibliothek.util.Todo.Compatibility;
import bibliothek.util.Todo.Priority;
import bibliothek.util.Todo.Version;
/**
* This {@link DockStation} shows only a title for each of it's children.
* If the user clicks on one of the titles, a window will popup. The {@link Dockable}
* which owns the clicked title is shown in this window.
* @author Benjamin Sigg
*/
@Todo(compatibility=Compatibility.COMPATIBLE, priority=Priority.MINOR, target=Version.VERSION_1_1_0,
description="Allow clients to tell whether a Dockable should be opened if dropped on this station")
public class FlapDockStation extends AbstractDockableStation {
/**
* The direction in which the window with the Dockable
will popup,
* in respect to the location of this station.
*/
public static enum Direction{ NORTH, WEST, SOUTH, EAST };
/**
* This id is used to get a {@link DockTitleVersion} from the
* {@link DockController} which owns this station. The titles that are
* created for this version are used on the popup-window.
*/
public static final String WINDOW_TITLE_ID = "flap window";
/**
* This id is used to get a {@link DockTitleVersion} from the
* {@link DockController} which owns this station. The titles that are
* created for this version are used as buttons on this station.
*/
public static final String BUTTON_TITLE_ID = "flap button";
/**
* Key for the {@link FlapLayoutManager} that is used by all {@link FlapDockStation}s.
*/
public static final PropertyKey LAYOUT_MANAGER = new PropertyKey(
"flap dock station layout manager",
new DynamicPropertyFactory(){
public FlapLayoutManager getDefault(
PropertyKey key,
DockProperties properties ){
return new DefaultFlapLayoutManager();
}
}, true );
/**
* Key for all {@link DockTheme}s, tells the theme what content on the buttons
* should be visible. Note that some themes might ignore that setting. Changing this property will call
* {@link #recreateTitles()}, meaning all {@link DockTitle}s are removed and recreated.
*/
public static final PropertyKey BUTTON_CONTENT = new PropertyKey(
"flap dock station button content", new ConstantPropertyFactory( ButtonContent.THEME_DEPENDENT ), true );
/**
* Key for all elements that depend from {@link #BUTTON_CONTENT}, adds additional information to the {@link ButtonContent}.
*/
public static final PropertyKey BUTTON_CONTENT_FILTER = new PropertyKey(
"flap dock station button content connector", new ConstantPropertyFactory( new DefaultButtonContentFilter() ), true );
/**
* Key for the minimum size of all {@link FlapDockStation}s.
*/
public static final PropertyKey MINIMUM_SIZE = new PropertyKey( "flap dock station empty size",
new ConstantPropertyFactory( new Dimension( 10, 10 ) ), true );
/**
* Key for a factory that creates the windows of this station.
*/
public static final PropertyKey WINDOW_FACTORY = new PropertyKey("flap dock station window factory",
new ConstantPropertyFactory( new DefaultFlapWindowFactory() ), true );
/**
* The layoutManager which is responsible to layout this station
*/
private PropertyValue layoutManager = new PropertyValue( LAYOUT_MANAGER ){
@Override
protected void valueChanged( FlapLayoutManager oldValue, FlapLayoutManager newValue ) {
if( oldValue != null )
oldValue.uninstall( FlapDockStation.this );
if( newValue != null )
newValue.install( FlapDockStation.this );
}
};
/** current {@link PlaceholderStrategy} */
private PropertyValue placeholderStrategy = new PropertyValue(PlaceholderStrategy.PLACEHOLDER_STRATEGY) {
@Override
protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){
handles.setStrategy( newValue );
}
};
/**
* How to layout the buttons on this station
*/
private PropertyValue buttonContent = new PropertyValue( BUTTON_CONTENT ){
@Override
protected void valueChanged( ButtonContent oldValue, ButtonContent newValue ){
if( oldValue != newValue ){
recreateTitles();
}
}
};
/** the minimum size this station has */
private PropertyValue minimumSize = new PropertyValue( MINIMUM_SIZE ) {
protected void valueChanged( Dimension oldValue, Dimension newValue ){
buttonPane.revalidate();
}
};
/** the factory creating {@link FlapWindow}s for this station */
private PropertyValue windowFactory = new PropertyValue( WINDOW_FACTORY ){
protected void valueChanged( FlapWindowFactory oldValue, FlapWindowFactory newValue ){
if( oldValue != null ){
oldValue.uninstall( FlapDockStation.this );
}
if( newValue != null ){
newValue.install( FlapDockStation.this );
}
updateWindow( getFrontDockable(), true );
}
};
/** The direction in which the popup-window is, in respect to this station */
private Direction direction = Direction.SOUTH;
/**
* This property tells this station whether the station can change the
* {@link #direction} property automatically or not
*/
private boolean autoDirection = true;
/** The popup-window */
private FlapWindow window;
/** The size of the border, which can be grabbed by ther user, of the popup-window */
private int windowBorder = 3;
/** The minimal size of the popup-window */
private int windowMinSize = 25;
/** The initial size of windows, can be overridden by the layout manager */
private int defaultWindowSize = 400;
/**
* This variable is set when the front-dockable is removed, because
* the {@link DockController} is removed. If the controller is added
* again, then the frond-dockable can be restored with the value of
* this variable.
*/
private Dockable oldFrontDockable;
/** A list of all {@link Dockable Dockables} registered on this station */
private DockablePlaceholderList handles = new DockablePlaceholderList();
/** a listener for all {@link Dockable}s of this station */
private Listener dockableListener = new Listener();
/** The component on which all "buttons" are shown (the titles created with the id {@link #BUTTON_TITLE_ID}) */
private ButtonPane buttonPane;
/** This version is obtained by using {@link #BUTTON_TITLE_ID} */
private DockTitleVersion buttonVersion;
/** This version is obtained by using {@link #WINDOW_TITLE_ID} */
private DockTitleVersion titleVersion;
/** The {@link StationPaint} used to paint on this station */
private DefaultStationPaintValue paint;
/** The {@link Combiner} user to combine {@link Dockable Dockables}*/
private StationCombinerValue combiner;
/** The {@link DisplayerFactory} used to create displayers*/
private DefaultDisplayerFactoryValue displayerFactory;
/** Collection used to handle the {@link DockableDisplayer} */
private DisplayerCollection displayers;
/**
* Temporary information needed when a {@link Dockable} is moved
* over this station.
*/
private FlapDropInfo dropInfo;
/** A listener added to the {@link MouseFocusObserver} */
private ControllerListener controllerListener = new ControllerListener();
/**
* The button-titles are organized in a way that does not need much
* space if this property is true
*/
private boolean smallButtons = true;
/**
* An action that will be added to all children of this station.
*/
private ListeningDockAction holdAction;
/** A listener that is added to the parent of this dockable station. */
private VisibleListener visibleListener = new VisibleListener();
/** the last checked state of {@link #isDockableVisible()} */
private boolean lastVisible = false;
/** A list of listeners that were added to this station */
private List flapDockListeners = new ArrayList();
/** Manager for the visibility of the children of this station */
private DockableVisibilityManager visibility;
/** the background algorithm of this component */
private Background background = new Background();
/**
* Defaultconstructor of a {@link FlapDockStation}
*/
public FlapDockStation(){
init();
}
/**
* Creates a new {@link FlapDockStation}.
* @param init true
if the fields of this station should
* be initialized, false
otherwise. If false
, then
* {@link #init()} must be called by a subclass.
*/
protected FlapDockStation( boolean init ){
if( init ){
init();
}
}
/**
* Initializes the fields of this station, hast to be called exactly once
*/
protected void init(){
visibility = new DockableVisibilityManager( listeners );
buttonPane = createButtonPane();
buttonPane.setBackground( background );
buttonPane.setController( getController() );
setDirection( Direction.SOUTH );
displayerFactory = new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".flap", this );
displayers = new DisplayerCollection( this, displayerFactory );
paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".flap", this );
combiner = new StationCombinerValue( ThemeManager.COMBINER + ".flap", this );
buttonPane.addComponentListener( new ComponentAdapter(){
@Override
public void componentResized( ComponentEvent e ) {
if( autoDirection )
selfSetDirection();
else
updateWindowBounds();
}
});
buttonPane.addHierarchyBoundsListener( new HierarchyBoundsListener(){
public void ancestorMoved( HierarchyEvent e ) {
if( autoDirection )
selfSetDirection();
else
updateWindowBounds();
}
public void ancestorResized( HierarchyEvent e ) {
if( autoDirection )
selfSetDirection();
else
updateWindowBounds();
}
});
buttonPane.addHierarchyListener( new HierarchyListener(){
public void hierarchyChanged( HierarchyEvent e ){
if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 ){
if( getDockParent() == null ){
getDockableStateListeners().checkVisibility();
}
checkVisibility();
}
}
});
holdAction = createHoldAction();
}
/**
* Creates the panel which will show buttons for the children of this station.
* @return the new panel
*/
protected ButtonPane createButtonPane(){
return new ButtonPane( this );
}
/**
* Creates a {@link DockAction} that is added to all children
* of this station. The action should change the hold
* state of the associated {@link Dockable}, this can be done
* through the method {@link #setHold(Dockable, boolean)}.
* @return The action, or null
if no action should
* be added to the children
*/
protected ListeningDockAction createHoldAction(){
return new FlapDockHoldToggle( this );
}
@Override
public void setDockParent( DockStation station ) {
if( getDockParent() != null ){
getDockParent().removeDockStationListener( visibleListener );
}
super.setDockParent(station);
if( station != null ){
station.addDockStationListener( visibleListener );
}
}
@Override
public void setController( DockController controller ) {
if( getController() != controller ){
boolean remove = getController() != null;
if( remove ){
handles.unbind();
getController().removeDockableFocusListener( controllerListener );
getController().getFocusController().removeVetoListener( controllerListener );
oldFrontDockable = getFrontDockable();
setFrontDockable( null );
for( DockableHandle dockable : handles.dockables() ){
if( dockable != null ){
dockable.setTitle( null );
}
}
if( window != null ){
window.setDockTitle( null );
}
titleVersion = null;
buttonVersion = null;
}
super.setController(controller);
placeholderStrategy.setProperties( controller );
displayers.setController( controller );
paint.setController( controller );
displayerFactory.setController( controller );
combiner.setController( controller );
background.setController( controller );
if( window != null ){
window.setController( controller );
}
buttonPane.setController( controller );
FlapLayoutManager oldLayoutManager = layoutManager.getValue();
layoutManager.setProperties( controller );
FlapLayoutManager newLayoutManager = layoutManager.getValue();
if( oldLayoutManager == newLayoutManager ){
if( controller == null ){
if( oldLayoutManager != null ){
oldLayoutManager.uninstall( this );
}
}
else{
if( newLayoutManager != null ){
newLayoutManager.install( this );
}
}
}
buttonContent.setProperties( controller );
minimumSize.setProperties( controller );
if( holdAction != null )
holdAction.setController( controller );
if( controller != null ){
handles.bind();
titleVersion = controller.getDockTitleManager().getVersion( WINDOW_TITLE_ID, ControllerTitleFactory.INSTANCE );
buttonVersion = controller.getDockTitleManager().getVersion( BUTTON_TITLE_ID, BasicButtonTitleFactory.FACTORY );
for( DockableHandle dockable : handles.dockables() ){
if( dockable != null ){
dockable.setTitle( buttonVersion );
}
}
if( window != null ){
window.setDockTitle( titleVersion );
}
controller.addDockableFocusListener( controllerListener );
controller.getFocusController().addVetoListener( controllerListener );
if( isStationVisible() )
setFrontDockable( oldFrontDockable );
}
windowFactory.setProperties( controller );
buttonPane.resetTitles();
visibility.fire();
}
}
@Override
protected void callDockUiUpdateTheme() throws IOException {
DockUI.updateTheme( this, new FlapDockStationFactory());
}
/**
* Gets the direction in which the popup-window is currently opened.
* @return The direction
*/
public Direction getDirection() {
return direction;
}
/**
* Sets the direction in which the popup-window points. The direction
* may be overridden sone, if the property {@link #isAutoDirection() autoDirection}
* is set to true
.
* @param direction The direction of the popup-window
*/
public void setDirection( Direction direction ) {
if( direction == null )
throw new IllegalArgumentException();
this.direction = direction;
DockTitle.Orientation orientation = orientation( direction );
for( DockableHandle dockable : handles.dockables() ){
DockTitle title = dockable.getTitle();
if( title != null ){
title.setOrientation( orientation );
}
}
buttonPane.resetTitles();
updateWindowBounds();
buttonPane.revalidate();
}
/**
* Determines the orientation of the {@link DockTitle DockTitles} on this
* station.
* @param direction the direction in which the flap opens
* @return the orientation of the titles
*/
protected DockTitle.Orientation orientation( Direction direction ){
switch( direction ){
case NORTH:
return DockTitle.Orientation.SOUTH_SIDED;
case SOUTH:
return DockTitle.Orientation.NORTH_SIDED;
case EAST:
return DockTitle.Orientation.WEST_SIDED;
case WEST:
return DockTitle.Orientation.EAST_SIDED;
}
return null;
}
/**
* Recalculates the size and the location of the popup-window, if
* there is a window.
*/
protected void updateWindowBounds(){
if( window != null )
window.updateBounds();
}
/**
* Gets the minimum size this station should have.
* @return the minimum size, never null
*/
public Dimension getMinimumSize(){
return minimumSize.getValue();
}
/**
* Sets the minimum size this station should have. A value of null
* is valid and will let this station use the property {@link #MINIMUM_SIZE}.
* @param size the new minimum size or null
*/
public void setMinimumSize( Dimension size ){
minimumSize.setValue( size );
}
/**
* Gets the factory to create new {@link DockableDisplayer}.
* @return the factory
*/
public DefaultDisplayerFactoryValue getDisplayerFactory() {
return displayerFactory;
}
/**
* Gets the set of displayers currently used on this station.
* @return the set of displayers
*/
public DisplayerCollection getDisplayers() {
return displayers;
}
/**
* Gets the {@link Combiner} to merge {@link Dockable Dockables}
* @return the combiner
*/
public StationCombinerValue getCombiner() {
return combiner;
}
/**
* Gets the {@link StationPaint} to paint on this station.
* @return The paint
*/
public DefaultStationPaintValue getPaint() {
return paint;
}
/**
* Gets the rectangle to which a flap-window will be attached. The default
* is a rectangle that lies exactly over this component. The coordinates
* of the result are relative to the component of this station.
* @return the free area near a window
*/
public Rectangle getExpansionBounds(){
Component component = getComponent();
return new Rectangle( 0, 0, component.getWidth(), component.getHeight() );
}
/**
* Tells whether this station can change the
* {@link #setDirection(bibliothek.gui.dock.FlapDockStation.Direction) direction}
* itself, or if only the user can change the direction.
* @return true
if the station chooses the direction itself
* @see #setAutoDirection(boolean)
*/
public boolean isAutoDirection() {
return autoDirection;
}
/**
* Tells this station whether it can choose the
* {@link #setDirection(bibliothek.gui.dock.FlapDockStation.Direction) direction}
* of the popup-window itself, or if the direction remains always the
* same.
* @param autoDirection true
if the station can choose the
* direction itself, false
otherwise
*/
public void setAutoDirection( boolean autoDirection ) {
this.autoDirection = autoDirection;
if( autoDirection )
selfSetDirection();
}
/**
* Calculates the best
* {@link #setDirection(bibliothek.gui.dock.FlapDockStation.Direction) direction}
* for the popup-window of this station.
*/
public void selfSetDirection(){
Component c = getComponent();
Point center = new Point( c.getWidth()/2, c.getHeight()/2 );
SwingUtilities.convertPointToScreen( center, c );
Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
Direction direction;
if( c.getWidth() > c.getHeight() ){
if( center.y < size.height/2 ){
direction = Direction.SOUTH;
}
else{
direction = Direction.NORTH;
}
}
else{
if( center.x < size.width/2 ){
direction = Direction.EAST;
}
else{
direction = Direction.WEST;
}
}
if( direction != this.direction )
setDirection( direction );
else
updateWindowBounds();
}
public Dockable getFrontDockable() {
if( window == null ) //|| !window.isVisible() )
return null;
else
return window.getDockable();
}
public void setFrontDockable( Dockable dockable ) {
Dockable oldFrontDockable = getFrontDockable();
if( oldFrontDockable == dockable ){
return;
}
updateWindow( dockable, false );
if( getController() != null ){
if( oldFrontDockable != null ){
DockTitle[] titles = oldFrontDockable.listBoundTitles();
boolean active = getController().isFocused( oldFrontDockable );
for( DockTitle title : titles )
changed( oldFrontDockable, title, active );
}
}
if( window != null ){
if( window.getDockable() == null )
window.setWindowVisible( false );
else
window.repaint();
}
if( getController() != null ){
if( dockable != null ){
DockTitle[] titles = dockable.listBoundTitles();
boolean active = getController().isFocused( dockable );
for( DockTitle title : titles )
changed( dockable, title, active );
}
}
visibility.fire();
listeners.fireDockableSelected( oldFrontDockable, dockable );
}
/**
* Makes sure that dockable
is shown on the current {@link FlapWindow}. May replace
* the current window if necessary.
* @param dockable the item to show, can be null
* @param forceReplace whether the window should be replaced anyway
*/
private void updateWindow( Dockable dockable, boolean forceReplace ){
if( dockable == null ){
if( window != null ){
window.setDockable( null );
if( forceReplace ){
setFlapWindow( null );
}
}
}
else{
Window owner = SwingUtilities.getWindowAncestor( getComponent() );
if( window == null || forceReplace || !windowFactory.getValue().isValid(window, this) ){
if( window != null ){
window.setDockable( null );
}
FlapWindow window = createFlapWindow( buttonPane );
if( window != null )
setFlapWindow( window );
}
if( window != null && owner != null ){
window.setDockable( dockable );
if( owner.isVisible() )
window.setWindowVisible( true );
updateWindowBounds();
}
}
}
/**
* Creates a window for this station.
* @param buttonPane the panel needed to calculate the size of the window
* @return the window or null
if no window could be created
*/
protected FlapWindow createFlapWindow( ButtonPane buttonPane ){
FlapWindow window = windowFactory.getValue().create(this, buttonPane);
if( window != null ){
window.setDockTitle( titleVersion );
}
return window;
}
/**
* Tells the hold
=property of dockable
.
* @param dockable the {@link Dockable} whose property is asked
* @return the current state
* @see #setHold(Dockable, boolean)
*/
public boolean isHold( Dockable dockable ) {
FlapLayoutManager manager = layoutManager.getValue();
if( manager == null )
return false;
return manager.isHold( this, dockable );
}
/**
* Tells whether the station should close the popup when the
* {@link Dockable} looses the focus, or if the popup should
* remain open until the user closes the popup. The value is forwarded
* to the {@link FlapLayoutManager layout manager} of this station, the
* layout manager can then decide if and how it would like to react.
* @param dockable the {@link Dockable} whose settings should change
* @param hold true
if the popup should remain open,
* false
if it should close
*/
public void setHold( Dockable dockable, boolean hold ) {
FlapLayoutManager manager = layoutManager.getValue();
if( manager != null ){
boolean old = manager.isHold( this, dockable );
manager.setHold( this, dockable, hold );
hold = manager.isHold( this, dockable );
if( old != hold )
updateHold( dockable );
}
}
/**
* Updates the hold property of dockable
.
* The new value is provided by the {@link FlapLayoutManager layout manager}.
* @param dockable the element whose property is updated
*/
public void updateHold( Dockable dockable ){
FlapLayoutManager manager = layoutManager.getValue();
if( manager != null ){
boolean hold = manager.isHold( this, dockable );
fireHoldChanged( dockable, hold );
if( !hold && getController() != null && getFrontDockable() == dockable ){
if( !getController().isFocused( dockable ))
setFrontDockable( null );
}
}
}
/**
* How the buttons are organized.
* @return true
if the buttons are layout in a way that
* needs not much space.
* @see #setSmallButtons(boolean)
*/
public boolean isSmallButtons() {
return smallButtons;
}
/**
* Sets how the buttons are layout. If true
, then the buttons
* have their preferred size. If false
the buttons take
* all available space of this station.
* @param smallButtons true
if the buttons should be small
*/
public void setSmallButtons( boolean smallButtons ) {
this.smallButtons = smallButtons;
}
/**
* Gets the {@link DockTitleVersion} that is used to create titles
* for the popup-window.
* @return the version of titles for the popup, can be null
*/
public DockTitleVersion getTitleVersion() {
return titleVersion;
}
/**
* Gets the {@link DockTitleVersion} that is used to create titles
* for the button-panel.
* @return the version of titles for buttons, can be null
*/
public DockTitleVersion getButtonVersion() {
return buttonVersion;
}
/**
* Gets the size of the border of the popup-window, where the user
* can change the size of the window itself.
* @return the popup-size
* @see #setWindowBorder(int)
*/
public int getWindowBorder() {
return windowBorder;
}
/**
* Sets the size of the draggable area on the popup-window, that is used
* to change the size of the window.
* @param windowBorder the border, at least 0
*/
public void setWindowBorder( int windowBorder ) {
if( windowBorder < 0 )
throw new IllegalArgumentException( "Border must not be less than 0" );
this.windowBorder = windowBorder;
updateWindowBounds();
}
/**
* Gets the minimal size the popup-window can have.
* @return the minimal size
* @see #setWindowMinSize(int)
*/
public int getWindowMinSize() {
return windowMinSize;
}
/**
* Sets the minimal size which the popup-window can have.
* @param windowMinSize the minimal size
*/
public void setWindowMinSize( int windowMinSize ) {
if( windowMinSize < 0 )
throw new IllegalArgumentException( "Min size must not be smaller than 0" );
this.windowMinSize = windowMinSize;
updateWindowBounds();
}
/**
* Gets the current size of the popup-window
* @param dockable the element for which the size should be returned
* @return the current size
*/
public int getWindowSize( Dockable dockable ){
FlapLayoutManager manager = layoutManager.getValue();
if( manager == null )
return 0;
return manager.getSize( this, dockable );
}
/**
* Sets the size of the popup-window for dockable
. The
* value will be forwarded to the {@link FlapLayoutManager layout manager}
* of this station, the layout manager can decide if and how the new size
* is to be stored.
* @param dockable the element for which the size should be set
* @param size the size, at least 0
*/
public void setWindowSize( Dockable dockable, int size ){
if( size < 0 )
throw new IllegalArgumentException( "Size must at least be 0" );
FlapLayoutManager manager = layoutManager.getValue();
if( manager != null ){
manager.setSize( this, dockable, size );
updateWindowSize( dockable );
}
}
/**
* Updates the size of the window if dockable
is currently
* shown. The new size is provided by the {@link FlapLayoutManager layout manager}.
* @param dockable the element whose size should be updated
*/
public void updateWindowSize( Dockable dockable ){
if( getFrontDockable() == dockable ){
updateWindowBounds();
}
}
/**
* Sets the default size a window should have. This property might be
* overridden by the {@link FlapLayoutManager layout manager}.
* @param defaultWindowSize the default size of windows
*/
public void setDefaultWindowSize( int defaultWindowSize ) {
this.defaultWindowSize = defaultWindowSize;
}
/**
* Gets the default size of a new window.
* @return the default size
*/
public int getDefaultWindowSize() {
return defaultWindowSize;
}
/**
* Sets the layout manager which should be used by this station. The
* manager can be changed on a global level using {@link #LAYOUT_MANAGER}.
* @param manager the manager or null
when a default
* manager should be used
*/
public void setFlapLayoutManager( FlapLayoutManager manager ){
layoutManager.setValue( manager );
}
/**
* Gets the layout manager which was explicitly set by {@link #setFlapLayoutManager(FlapLayoutManager)}.
* @return the manager or null
*/
public FlapLayoutManager getFlapLayoutManager(){
return layoutManager.getOwnValue();
}
/**
* 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 );
}
/**
* Adds a listener to this station. The listener will be invoked when
* some properties of this station change.
* @param listener the new listener
*/
public void addFlapDockStationListener( FlapDockListener listener ){
flapDockListeners.add( listener );
}
/**
* Removes an earlier added listener from this station.
* @param listener the listener to remove
*/
public void removeFlapDockStationListener( FlapDockListener listener ){
flapDockListeners.remove( listener );
}
/**
* Informs all registered {@link FlapDockListener FlapDockListeners}
* that the hold-property of a {@link Dockable} has been changed.
* @param dockable the Dockable
whose property is changed
* @param value the new value of the property
*/
protected void fireHoldChanged( Dockable dockable, boolean value ){
for( FlapDockListener listener : flapDockListeners.toArray( new FlapDockListener[ flapDockListeners.size() ] ))
listener.holdChanged( this, dockable, value );
}
@Override
public DefaultDockActionSource getDirectActionOffers( Dockable dockable ) {
if( holdAction == null )
return null;
else{
DefaultDockActionSource source = new DefaultDockActionSource(new LocationHint( LocationHint.DIRECT_ACTION, LocationHint.LITTLE_LEFT ));
source.add( holdAction );
return source;
}
}
@Override
public void changed( Dockable dockable, DockTitle title, boolean active ) {
DockTitleEvent event = new DockTitleEvent( this, dockable, active );
event.setPreferred( dockable == getFrontDockable() );
title.changed( event );
}
@Override
public Rectangle getStationBounds() {
Point point = new Point( 0, 0 );
SwingUtilities.convertPointToScreen( point, getComponent() );
Rectangle result = new Rectangle( point.x, point.y, getComponent().getWidth(), getComponent().getHeight() );
if( window != null && window.isWindowVisible() ){
Rectangle bounds = window.getWindowBounds();
result = SwingUtilities.computeUnion( bounds.x, bounds.y, bounds.width, bounds.height, result );
}
return result;
}
/**
* Sets the current drop-information. The information is forwarded
* to the popup-window and the button-panel (if they exist).
* @param info the new information, or null
*/
private void setDropInfo( FlapDropInfo info ){
this.dropInfo = info;
if( window != null )
window.setDropInfo( info );
if( buttonPane != null )
buttonPane.setDropInfo( info );
}
/**
* Sets the popup-window that will be used in the future. The popup-window
* can be replaced by another window if the root window of the tree in which
* this {@link Component} is changes.
* @param window the new window, can be null
*/
private void setFlapWindow( FlapWindow window ){
if( this.window != null ){
this.window.setController( null );
this.window.destroy();
}
this.window = window;
if( window != null ){
window.setController( getController() );
window.setDropInfo( dropInfo );
}
}
/**
* Checks whether the currently used {@link FlapWindow} equals
* window
.
* @param window a window
* @return true
if window
is currently used
* by this station
*/
public boolean isFlapWindow( FlapWindow window ){
return this.window == window;
}
public PlaceholderMap getPlaceholders(){
return handles.toMap();
}
public void setPlaceholders( PlaceholderMap placeholders ){
if( getDockableCount() > 0 ){
throw new IllegalStateException( "only allowed if there are not children present" );
}
try{
DockablePlaceholderList next = new DockablePlaceholderList( placeholders );
if( getController() != null ){
handles.setStrategy( null );
handles.unbind();
handles = next;
handles.bind();
handles.setStrategy( getPlaceholderStrategy() );
}
else{
handles = next;
}
}
catch( IllegalArgumentException ex ){
// silent
}
}
/**
* Gets the placeholders of this station using a {@link PlaceholderListItemConverter} to
* encode the children of this station. To be exact, 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
* - hold: the return value of {@link #isHold(Dockable)}
* - size: the return value of {@link #getWindowSize(Dockable)}
* - placeholder: the placeholder of the element, might not be written
*
* @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 handles.toMap( new PlaceholderListItemAdapter() {
@Override
public ConvertedPlaceholderListItem convert( int index, DockableHandle dockable ){
Integer id = children.get( dockable.getDockable() );
if( id == null ){
return null;
}
ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
item.putInt( "id", id );
item.putInt( "index", index );
item.putBoolean( "hold", isHold( dockable.getDockable() ));
item.putInt( "size", getWindowSize( dockable.getDockable() ) );
if( strategy != null ){
Path placeholder = strategy.getPlaceholderFor( dockable.getDockable() );
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
* using {@link #getPlaceholders(Map)}.
* @param map the map to read
* @param children the new children of this stations
* @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" );
}
DockController controller = getController();
try{
if( controller != null ){
controller.freezeLayout();
}
DockablePlaceholderList next = new DockablePlaceholderList( map, new PlaceholderListItemAdapter(){
private DockHierarchyLock.Token token;
@Override
public DockableHandle convert( ConvertedPlaceholderListItem item ){
int id = item.getInt( "id" );
Dockable dockable = children.get( id );
if( dockable != null ){
DockUtilities.ensureTreeValidity( FlapDockStation.this, dockable );
token = DockHierarchyLock.acquireLinking( FlapDockStation.this, dockable );
boolean hold = item.getBoolean( "hold" );
int size = item.getInt( "size" );
listeners.fireDockableAdding( dockable );
DockableHandle handle = link( dockable );;
setHold( dockable, hold );
setWindowSize( dockable, size );
return handle;
}
return null;
}
@Override
public void added( DockableHandle dockable ){
try{
dockable.getDockable().setDockParent( FlapDockStation.this );
listeners.fireDockableAdded( dockable.getDockable() );
}
finally{
token.releaseNoCheck();
}
}
});
if( getController() != null ){
handles.setStrategy( null );
handles.unbind();
handles = next;
handles.bind();
handles.setStrategy( getPlaceholderStrategy() );
}
else{
handles = next;
}
}
finally{
if( controller != null ){
controller.meltLayout();
}
}
buttonPane.resetTitles();
}
public boolean prepareDrop( int mouseX, int mouseY, int titleX, int titleY, boolean checkOverrideZone, Dockable dockable ) {
if( SwingUtilities.isDescendingFrom( getComponent(), dockable.getComponent() )){
setDropInfo( null );
return false;
}
Point mouse = new Point( mouseX, mouseY );
SwingUtilities.convertPointFromScreen( mouse, buttonPane );
FlapDropInfo dropInfo = null;
boolean strong = buttonPane.titleContains( mouse.x, mouse.y );
DockAcceptance acceptance = getController().getAcceptance();
// if mouse over window title: force combination
if( !strong && window != null && window.isWindowVisible() ){
DockTitle title = window.getDockTitle();
if( title != null ){
Component c = title.getComponent();
Point point = new Point( mouseX, mouseY );
SwingUtilities.convertPointFromScreen( point, c );
Dockable child = window.getDockable();
boolean combine = c.contains( point ) &&
dockable.accept( this, child ) &&
child.accept( this, dockable ) &&
acceptance.accept( this, child, dockable );
if( combine ){
dropInfo = prepareCombine( dockable, window, new Point( mouseX, mouseY ), combine, true );
}
}
}
// maybe a parent station wants to catch the event
if( !strong && dropInfo == null ){
DockStation parent = getDockParent();
if( parent != null ){
if( checkOverrideZone && parent.isInOverrideZone( mouseX, mouseY, this, dockable ))
return false;
}
}
// if mouse over window: force combination
if( window != null && window.isWindowVisible() && dropInfo == null ){
Point point = new Point( mouseX, mouseY );
Dockable child = window.getDockable();
boolean combine = window.containsScreenPoint(point) &&
dockable.accept( this, child) &&
child.accept( this, dockable ) &&
acceptance.accept( this, child, dockable );
if( combine ){
dropInfo = prepareCombine( dockable, window, point, false, true );
}
}
if( dropInfo != null && dockable == getFrontDockable() )
return false;
if( dropInfo == null ){
if( dockable.accept( this ) &&
accept( dockable ) &&
acceptance.accept( this, dockable )){
dropInfo = new FlapDropInfo( this, dockable ){
public Point getMousePosition(){
return null;
}
public Dockable getOld(){
return null;
}
public PlaceholderMap getPlaceholders(){
return null;
}
public Dimension getSize(){
return null;
}
public boolean isMouseOverTitle(){
return false;
}
};
dropInfo.setIndex( buttonPane.indexAt( mouse.x, mouse.y ) );
}
}
setDropInfo( dropInfo );
return dropInfo != null;
}
/**
* Prepares a combination of dockable
and window
.
* @param dockable the element that is going to be dropped
* @param window the visible window under the mouse
* @param mouseOnScreen the location of the mouse on the screen
* @param mouseOverTitle whether the mouse is currently over a title
* @param force whether a combination must happen or not
* @return the combination, null
if a combination is not desired for the given arguments
*/
private FlapDropInfo prepareCombine( Dockable dockable, FlapWindow window, final Point mouseOnScreen, final boolean mouseOverTitle, boolean force ){
final Dockable child = window.getDockable();
FlapDropInfo info = new FlapDropInfo( this, dockable ){
public boolean isMouseOverTitle(){
return mouseOverTitle;
}
public Dimension getSize(){
return child.getComponent().getSize();
}
public PlaceholderMap getPlaceholders(){
for( DockablePlaceholderList.Item item : handles.list() ){
DockableHandle handle = item.getDockable();
if( handle != null && handle.getDockable() == child ){
return item.getPlaceholderMap();
}
}
return null;
}
public Point getMousePosition(){
Point mouse = new Point( mouseOnScreen );
SwingUtilities.convertPointFromScreen( mouse, getOld().getComponent() );
return mouse;
}
public Dockable getOld(){
return child;
}
};
CombinerTarget target = combiner.prepare( info, force );
if( target == null ){
return null;
}
info.setCombineTarget( target );
return info;
}
public void drop(){
if( dropInfo.getCombineTarget() != null ){
combine( dropInfo, dropInfo.getCombineTarget(), null );
}
else{
add( dropInfo.getDockable(), dropInfo.getIndex() );
}
}
public void drop( Dockable dockable ) {
add( dockable );
}
public boolean drop( Dockable dockable, DockableProperty property ) {
if( property instanceof FlapDockProperty )
return drop( dockable, (FlapDockProperty)property );
return false;
}
/**
* Adds the {@link Dockable} dockable
to this station or
* to a child of this station, according to the contents of
* property
.
* @param dockable the new child
* @param property the location of the new child
* @return true
if the new child could be added,
* false
if the child has been rejected
*/
public boolean drop( final Dockable dockable, FlapDockProperty property ) {
DockUtilities.checkLayoutLocked();
boolean result = false;
final Path placeholder = property.getPlaceholder();
DockableProperty successor = property.getSuccessor();
int index = property.getIndex();
boolean acceptable = acceptable( dockable );
if( placeholder != null && successor != null ){
DockableHandle current = handles.getDockableAt( placeholder );
if( current != null ){
final Dockable oldDockable = current.getDockable();
DockStation station = oldDockable.asDockStation();
if( station != null ){
if( station.drop( dockable, successor )){
result = true;
handles.removeAll( placeholder );
}
}
else{
result = combine( current.getDockable(), dockable, successor );
}
}
}
if( placeholder != null && !result ){
int listIndex = handles.getListIndex( placeholder );
if( listIndex >= 0 ){
add( dockable, property.getIndex(), listIndex );
setHold( dockable, property.isHolding() );
int size = property.getSize();
if( size >= getWindowMinSize() )
setWindowSize( dockable, size );
result = true;
}
else{
index = handles.getDockableIndex( placeholder );
if( index == -1 ){
index = property.getIndex();
}
}
}
if( !result && index >= getDockableCount() && acceptable ){
add( dockable );
setHold( dockable, property.isHolding() );
int size = property.getSize();
if( size >= getWindowMinSize() )
setWindowSize( dockable, size );
result = true;
}
if( !result && successor != null ){
DockStation previous = getDockable( index ).asDockStation();
if( previous != null ){
if( previous.drop( dockable, successor )){
result = true;
}
}
else{
result = combine( getDockable( index ), dockable, successor );
}
}
if( !result && acceptable ){
add( dockable, index );
setHold( dockable, property.isHolding() );
int size = property.getSize();
if( size >= getWindowMinSize() )
setWindowSize( dockable, size );
result = true;
}
return result;
}
public DockableProperty getDockableProperty( Dockable dockable, Dockable target ) {
int index = indexOf( dockable );
boolean holding = isHold( dockable );
int size = getWindowSize( dockable );
PlaceholderStrategy strategy = getPlaceholderStrategy();
Path placeholder = null;
if( strategy != null ){
placeholder = strategy.getPlaceholderFor( target == null ? dockable : target );
if( placeholder != null ){
handles.dockables().addPlaceholder( index, placeholder );
}
}
return new FlapDockProperty( index, holding, size, placeholder );
}
public boolean prepareMove( int mouseX, int mouseY, int titleX, int titleY,
boolean checkOverrideZone, Dockable dockable ){
return prepareDrop( mouseX, mouseY, titleX, titleY, checkOverrideZone, dockable );
}
public void move() {
if( dropInfo.getCombineTarget() != null ){
remove( dropInfo.getDockable() );
combine( dropInfo, dropInfo.getCombineTarget(), null );
}
else{
int index = indexOf( dropInfo.getDockable() );
if( index < dropInfo.getIndex() ){
dropInfo.setIndex( dropInfo.getIndex()-1 );
}
handles.dockables().move( index, dropInfo.getIndex() );
buttonPane.resetTitles();
fireDockablesRepositioned( Math.min( index, dropInfo.getIndex() ), Math.max( index, dropInfo.getIndex() ) );
}
}
public void move( Dockable dockable, DockableProperty property ) {
DockUtilities.checkLayoutLocked();
if( property instanceof FlapDockProperty ){
int index = indexOf( dockable );
if( index < 0 )
throw new IllegalArgumentException( "dockable is not child of this station" );
int destination = ((FlapDockProperty)property).getIndex();
destination = Math.min( destination, handles.dockables().size()-1 );
destination = Math.max( 0, destination );
if( destination != index ){
handles.dockables().move( index, destination );
buttonPane.resetTitles();
fireDockablesRepositioned( Math.min( index, destination ), Math.max( index, destination ) );
}
}
}
public void draw() {
if( dropInfo != null )
dropInfo.setDraw( true );
buttonPane.repaint();
if( window != null )
window.repaint();
}
public void forget() {
setDropInfo( null );
buttonPane.repaint();
}
public boolean isInOverrideZone( int x, int y, D invoker, Dockable drop ) {
Point mouse = new Point( x, y );
SwingUtilities.convertPointFromScreen( mouse, buttonPane );
if( buttonPane.contains( mouse ) && accept( drop ) && drop.accept( this ))
return true;
DockStation parent = getDockParent();
if( parent != null )
return parent.isInOverrideZone( x, y, invoker, drop );
return false;
}
public boolean canDrag( Dockable dockable ) {
return true;
}
public void drag( Dockable dockable ) {
if( dockable.getDockParent() != this )
throw new IllegalArgumentException( "The dockable can't be dragged, it is not child of this station" );
remove( dockable );
}
public String getFactoryID() {
return FlapDockStationFactory.ID;
}
public Component getComponent() {
return buttonPane;
}
public int getDockableCount() {
return handles.dockables().size();
}
public Dockable getDockable( int index ) {
return handles.dockables().get( index ).getDockable();
}
/**
* Gets the title which is used as button for the index
'th dockable.
* Clients should not modify the result of this method.
* @param index the index of a {@link Dockable}
* @return the title or null
*/
public DockTitle getButton( int index ){
return handles.dockables().get( index ).getTitle();
}
@Override
public boolean isVisible( Dockable dockable ) {
return isStationVisible() && (getFrontDockable() == dockable);
}
/**
* Deletes all titles of the button pane and then recreates them.
*/
protected void recreateTitles(){
for( DockableHandle handle : handles.dockables() ){
handle.setTitle( buttonVersion );
}
}
/**
* Removes dockable
from this station.
* Note: clients may need to invoke {@link DockController#freezeLayout()}
* and {@link DockController#meltLayout()} to ensure noone else adds or
* removes Dockable
s.
* @param dockable the child to remove
*/
public void remove( Dockable dockable ){
int index = indexOf( dockable );
if( index >= 0 )
remove( index );
}
/**
* 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 noone else adds or
* removes Dockable
s.
* @param index the index of the child that will be removed
*/
public void remove( int index ){
DockUtilities.checkLayoutLocked();
Dockable dockable = getDockable( index );
if( getFrontDockable() == dockable )
setFrontDockable( null );
if( oldFrontDockable == dockable )
oldFrontDockable = null;
DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable );
try{
listeners.fireDockableRemoving( dockable );
dockable.setDockParent( null );
DockableHandle handle = handles.dockables().get( index );
handles.remove( index );
handle.setTitle( null );
dockable.removeDockableListener( dockableListener );
// race condition, only required if not called from the EDT
buttonPane.resetTitles();
listeners.fireDockableRemoved( dockable );
}
finally{
token.release();
}
fireDockablesRepositioned( index );
}
/**
* Adds dockable
as new child to this station. The child
* is added at the end of all children.
* @param dockable the new child
*/
public void add( Dockable dockable ){
add( dockable, getDockableCount() );
}
/**
* Inserts dockable
as new child in the list of
* children.
* @param dockable the new child
* @param index the location in the button-panel of the child
*/
public void add( Dockable dockable, int index ){
add( dockable, index, -1 );
}
private void add( Dockable dockable, int index, int listIndex ){
DockUtilities.checkLayoutLocked();
DockUtilities.ensureTreeValidity( this, dockable );
DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
try{
listeners.fireDockableAdding( dockable );
DockableHandle handle = link( dockable );
if( listIndex == -1 || handles.list().get( listIndex ).getDockable() != null ){
handles.dockables().add( index, handle );
}
else if( handles.list().get( listIndex ).getDockable() == null ){
handles.list().get( listIndex ).setDockable( handle );
}
dockable.setDockParent( this );
buttonPane.resetTitles(); // race condition, only required if not called from the EDT
listeners.fireDockableAdded( dockable );
fireDockablesRepositioned( index+1 );
}
finally{
token.release();
}
}
private DockableHandle link( Dockable dockable ){
DockableHandle handle = new DockableHandle( dockable );
handle.setTitle( buttonVersion );
dockable.addDockableListener( dockableListener );
return handle;
}
/**
* Creates a combination out of child
, which must be a
* child of this station, and append
which must not be
* a child of this station.
* @param child a child of this station
* @param append a {@link Dockable} that is not a child of this station
* @return true
if the combination was successful,
* false
otherwise (the child
will remain
* on this station)
*/
public boolean combine( Dockable child, Dockable append ){
return combine( child, append, null );
}
/**
* Creates a combination out of child
, which must be a
* child of this station, and append
which must not be
* a child of this station.
* @param child a child of this station
* @param append a {@link Dockable} that is not a child of this station
* @param property location information associated with append
* @return true
if the combination was successful,
* false
otherwise (the child
will remain
* on this station)
*/
public boolean combine( final Dockable child, Dockable append, DockableProperty property ){
DockUtilities.checkLayoutLocked();
int index = indexOf( child );
if( index < 0 )
throw new IllegalArgumentException( "Child must be a child of this station" );
int listIndex = handles.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList.Item oldItem = handles.list().get( listIndex );
final PlaceholderMap placeholders = oldItem.getPlaceholderMap();
FlapDropInfo info = new FlapDropInfo( this, append ){
public boolean isMouseOverTitle(){
return true;
}
public Dimension getSize(){
return null;
}
public PlaceholderMap getPlaceholders(){
return placeholders;
}
public Dockable getOld(){
return child;
}
public Point getMousePosition(){
return null;
}
};
CombinerTarget target = combiner.prepare( info, true );
return combine( info, target, property );
}
private boolean combine( CombinerSource source, CombinerTarget target, DockableProperty property ){
DockUtilities.checkLayoutLocked();
DockController controller = getController();
Dockable child = source.getOld();
Dockable append = source.getNew();
DockUtilities.ensureTreeValidity( this, append );
try{
if( controller != null )
controller.freezeLayout();
int index = indexOf( child );
if( index < 0 )
throw new IllegalArgumentException( "old dockable must be a child of this station" );
if( append.getDockParent() != null )
append.getDockParent().drag( append );
boolean hold = isHold( child );
int listIndex = handles.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList.Item oldItem = handles.list().get( listIndex );
final PlaceholderMap placeholders = oldItem.getPlaceholderMap();
oldItem.setPlaceholderMap( null );
remove( index );
int other = indexOf( append );
if( other >= 0 ){
remove( other );
if( other < index )
index--;
}
index = Math.min( index, getDockableCount());
Dockable combination = combiner.combine( new CombinerSourceWrapper( source ){
@Override
public PlaceholderMap getPlaceholders(){
return placeholders;
}
}, target );
if( property != null ){
DockStation combined = combination.asDockStation();
if( combined != null && append.getDockParent() == combined ){
combined.move( append, property );
}
}
add( combination, index );
listIndex = handles.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList.Item newItem = handles.list().get( listIndex );
newItem.setPlaceholderSet( newItem.getPlaceholderSet() );
setHold( combination, hold );
return true;
}
finally{
if( controller != null )
controller.meltLayout();
}
}
public boolean canReplace( Dockable old, Dockable next ) {
return true;
}
public void replace( DockStation old, Dockable next ){
replace( old.asDockable(), next, true );
}
public void replace( Dockable child, Dockable append ){
replace( child, append, false );
}
private void replace( Dockable child, Dockable append, boolean station ){
DockUtilities.checkLayoutLocked();
DockController controller = getController();
try{
if( controller != null )
controller.freezeLayout();
int index = indexOf( child );
if( index < 0 )
throw new IllegalArgumentException( "Child must be a child of this station" );
boolean hold = isHold( child );
boolean open = getFrontDockable() == child;
int listIndex = handles.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList.Item oldItem = handles.list().get( listIndex );
remove( index );
handles.list().remove( oldItem );
add( append, index );
DockablePlaceholderList.Item newItem = handles.list().get( listIndex );
if( station ){
newItem.setPlaceholderMap( child.asDockStation().getPlaceholders() );
}
else{
newItem.setPlaceholderMap( oldItem.getPlaceholderMap() );
}
newItem.setPlaceholderSet( oldItem.getPlaceholderSet() );
setHold( append, hold );
if( open )
setFrontDockable( append );
}
finally{
if( controller != null )
controller.meltLayout();
}
}
/**
* Gets the location of dockable
in the button-panel.
* @param dockable the {@link Dockable} to search
* @return the location or -1 if the child was not found
*/
public int indexOf( Dockable dockable ){
PlaceholderList.Filter list = handles.dockables();
int index = 0;
for( DockableHandle handle : list ){
if( handle.getDockable() == dockable ){
return index;
}
index++;
}
return -1;
}
private void checkVisibility(){
boolean visible = isDockableVisible();
if( visible != lastVisible ){
lastVisible = visible;
if( visible ){
if( oldFrontDockable != null )
setFrontDockable( oldFrontDockable );
}
else{
oldFrontDockable = getFrontDockable();
setFrontDockable( null );
if( !isHold( oldFrontDockable ))
oldFrontDockable = null;
}
visibility.fire();
}
}
/**
* This listener is added to the direct parent of the enclosing
* {@link FlapDockListener}. The listener fires events if the visibility
* changes, and the listener can remove the popup-window if the station
* looses its visibility.
* @author Benjamin Sigg
*/
private class VisibleListener extends DockStationAdapter{
@Override
public void dockableVisibiltySet( DockStation station, Dockable dockable, boolean visible ) {
if( dockable == FlapDockStation.this ){
checkVisibility();
}
}
}
/**
* Listener added to the {@link Dockable}s of the enclosing
* {@link FlapDockStation}, reacts on changes of the {@link DockTitle}.
* @author Benjamin Sigg
*/
private class Listener extends DockableAdapter{
@Override
public void titleExchanged( Dockable dockable, DockTitle title ) {
int index = indexOf( dockable );
if( index < 0 )
return;
DockableHandle handle = handles.dockables().get( index );
if( handle.getTitle() == title ){
handle.setTitle( buttonVersion );
}
}
}
/**
* Handles title and listeners that are associated with a {@link Dockable}.
* @author Benjamin Sigg
*/
private class DockableHandle implements PlaceholderListItem{
/** the element that is handled by this handler */
private Dockable dockable;
/** the title used */
private DockTitleRequest title;
/** the listener that gets added to the title of this handle */
private ButtonListener buttonListener;
public DockableHandle( Dockable dockable ){
this.dockable = dockable;
buttonListener = new ButtonListener( dockable );
}
public Dockable getDockable(){
return dockable;
}
public Dockable asDockable(){
return getDockable();
}
public DockTitle getTitle(){
if( title == null )
return null;
return title.getAnswer();
}
public void setTitle( DockTitleVersion version ){
if( title != null ){
DockTitle answer = title.getAnswer();
if( answer != null ){
answer.removeMouseInputListener( buttonListener );
dockable.unbind( answer );
buttonPane.resetTitles();
}
title.uninstall();
title = null;
}
if( version != null ){
title = new DockTitleRequest( FlapDockStation.this, dockable, version ) {
@Override
protected void answer( DockTitle previous, DockTitle title ){
if( previous != null ){
previous.removeMouseInputListener( buttonListener );
dockable.unbind( previous );
}
if( title != null ){
title.addMouseInputListener( buttonListener );
title.setOrientation( orientation( direction ) );
dockable.bind( title );
}
buttonPane.resetTitles();
}
};
title.install();
title.request();
}
}
}
private class ControllerListener implements FocusVetoListener, DockableFocusListener{
public FocusVeto vetoFocus( FocusController controller, Dockable dockable ){
return FocusVeto.NONE;
}
public FocusVeto vetoFocus( FocusController controller, DockTitle title ){
for( DockableHandle handle : handles.dockables() ){
if( handle.getTitle() == title ){
return FocusVeto.VETO_NO_CONSUME;
}
}
return FocusVeto.NONE;
}
public void dockableFocused( DockableFocusEvent event ) {
Dockable front = getFrontDockable();
if( isStationVisible() ){
if( front == null || (front != null && isHold( front )))
return;
DockController controller = event.getController();
Dockable dockable = event.getNewFocusOwner();
if( controller.isFocused( FlapDockStation.this ))
return;
if( dockable == null || !DockUtilities.isAncestor( FlapDockStation.this, dockable ) ){
setFrontDockable( null );
}
}
}
}
/**
* Listens to the buttons. If one button is pressed, the popup-window
* will be made visible.
*/
private class ButtonListener extends MouseInputAdapter{
/**
* The Dockable
whose button is observed by this
* listener.
*/
private Dockable dockable;
/**
* Constructs a new listener.
* @param dockable the owner of the observed button
*/
public ButtonListener( Dockable dockable ){
this.dockable = dockable;
}
@Override
public void mouseReleased( MouseEvent e ){
if( dockable.getDockParent() == FlapDockStation.this ){
final int MASK = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK;
if( e.getButton() == MouseEvent.BUTTON1 && (e.getModifiersEx() & MASK ) == 0 ){
int index = indexOf( dockable );
if( index < 0 )
return;
DockableHandle handle = handles.dockables().get( index );
DockTitle title = handle.getTitle();
if( getFrontDockable() == dockable && title.isActive() ){
getController().setFocusedDockable( FlapDockStation.this, null, true );
setFrontDockable( null );
}
else
getController().setFocusedDockable( dockable, null, true );
}
}
}
}
/**
* The background algorithm of this {@link FlapDockStation}.
* @author Benjamin Sigg
*/
private class Background extends BackgroundAlgorithm implements StationBackgroundComponent{
/**
* Creates a new algorithm
*/
public Background(){
super( StationBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".station.flap" );
}
public DockStation getStation(){
return FlapDockStation.this;
}
public Component getComponent(){
return FlapDockStation.this.getComponent();
}
}
}