/*
* 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
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockUI;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.accept.DockAcceptance;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.ListeningDockAction;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.component.DefaultDockStationComponentRootHandler;
import bibliothek.gui.dock.component.DockComponentRootHandler;
import bibliothek.gui.dock.displayer.DisplayerCombinerTarget;
import bibliothek.gui.dock.event.DoubleClickListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.station.AbstractDockStation;
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.PlaceholderMapping;
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.DockStationDropLayer;
import bibliothek.gui.dock.station.screen.BoundaryRestriction;
import bibliothek.gui.dock.station.screen.DefaultScreenDockFullscreenStrategy;
import bibliothek.gui.dock.station.screen.FullscreenActionSource;
import bibliothek.gui.dock.station.screen.ScreenDockFullscreenFilter;
import bibliothek.gui.dock.station.screen.ScreenDockFullscreenStrategy;
import bibliothek.gui.dock.station.screen.ScreenDockProperty;
import bibliothek.gui.dock.station.screen.ScreenDockStationExtension;
import bibliothek.gui.dock.station.screen.ScreenDockStationFactory;
import bibliothek.gui.dock.station.screen.ScreenDockStationListener;
import bibliothek.gui.dock.station.screen.ScreenDockWindow;
import bibliothek.gui.dock.station.screen.ScreenDockWindowConfiguration;
import bibliothek.gui.dock.station.screen.ScreenDockWindowFactory;
import bibliothek.gui.dock.station.screen.ScreenDockWindowListener;
import bibliothek.gui.dock.station.screen.ScreenDropSizeStrategy;
import bibliothek.gui.dock.station.screen.ScreenFullscreenAction;
import bibliothek.gui.dock.station.screen.layer.ScreenLayer;
import bibliothek.gui.dock.station.screen.layer.ScreenWindowLayer;
import bibliothek.gui.dock.station.screen.magnet.AttractorStrategy;
import bibliothek.gui.dock.station.screen.magnet.DefaultMagnetStrategy;
import bibliothek.gui.dock.station.screen.magnet.MagnetController;
import bibliothek.gui.dock.station.screen.magnet.MagnetStrategy;
import bibliothek.gui.dock.station.screen.magnet.MultiAttractorStrategy;
import bibliothek.gui.dock.station.screen.window.DefaultScreenDockWindowConfiguration;
import bibliothek.gui.dock.station.screen.window.DefaultScreenDockWindowFactory;
import bibliothek.gui.dock.station.screen.window.ScreenDockWindowClosingStrategy;
import bibliothek.gui.dock.station.screen.window.ScreenDockWindowHandle;
import bibliothek.gui.dock.station.screen.window.WindowConfiguration;
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.DockableShowingManager;
import bibliothek.gui.dock.station.support.Enforcement;
import bibliothek.gui.dock.station.support.PlaceholderList.Filter;
import bibliothek.gui.dock.station.support.PlaceholderList.Level;
import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter;
import bibliothek.gui.dock.station.support.PlaceholderListItemConverter;
import bibliothek.gui.dock.station.support.PlaceholderListMapping;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderMetaMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue;
import bibliothek.gui.dock.themes.DefaultStationPaintValue;
import bibliothek.gui.dock.themes.StationCombinerValue;
import bibliothek.gui.dock.themes.StationThemeItemValue;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.title.ControllerTitleFactory;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.DirectWindowProvider;
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.WindowProvider;
import bibliothek.gui.dock.util.WindowProviderListener;
import bibliothek.gui.dock.util.extension.Extension;
import bibliothek.gui.dock.util.extension.ExtensionName;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.gui.dock.util.property.DynamicPropertyFactory;
import bibliothek.gui.dock.util.property.PropertyFactory;
import bibliothek.util.Path;
import bibliothek.util.Todo;
import bibliothek.util.Todo.Compatibility;
import bibliothek.util.Todo.Priority;
import bibliothek.util.Todo.Version;
/**
* A {@link DockStation} which is the whole screen. Every child of this
* station is a window. These windows can be moved and resized by the user.<br>
* This station tries to register a {@link DockTitleVersion} with
* the key {@link #TITLE_ID}.
*
* @author Benjamin Sigg
*/
public class ScreenDockStation extends AbstractDockStation {
/** The key for the {@link DockTitleVersion} of this station */
public static final String TITLE_ID = "screen dock";
/** This id is forwarded to {@link Extension}s which load additional {@link DisplayerFactory}s */
public static final String DISPLAYER_ID = "screen";
/** Path of an {@link ExtensionName} for creating additional {@link AttractorStrategy} */
public static final Path ATTRACTOR_STRATEGY_EXTENSION = new Path( "dock.AttractorStrategy" );
/** Path of an {@link ExtensionName} for creating {@link ScreenDockStationExtension}s */
public static final Path STATION_EXTENSION = new Path( "dock.ScreenDockStation" );
/** Name of a parameter of an {@link ExtensionName} pointing to <code>this</code>. */
public static final String EXTENSION_PARAM = "station";
/** a key for a property telling which boundaries a {@link ScreenDockWindow} can have */
public static final PropertyKey<BoundaryRestriction> BOUNDARY_RESTRICTION =
new PropertyKey<BoundaryRestriction>( "ScreenDockStation.boundary_restriction",
new ConstantPropertyFactory<BoundaryRestriction>( BoundaryRestriction.MEDIUM ), true );
/** a key for a property telling how to create new windows */
public static final PropertyKey<ScreenDockWindowFactory> WINDOW_FACTORY =
new PropertyKey<ScreenDockWindowFactory>( "ScreenDockStation.window_factory",
new ConstantPropertyFactory<ScreenDockWindowFactory>( new DefaultScreenDockWindowFactory() ), true );
/** strategy for closing {@link ScreenDockWindow}s, default is <code>null</code> */
public static final PropertyKey<ScreenDockWindowClosingStrategy> WINDOW_CLOSING_STRATEGY =
new PropertyKey<ScreenDockWindowClosingStrategy>( "ScreenDockStation.window_closing" );
/**
* A key for a property telling how to configure new windows. Replacing the configuration always leads to closing
* and recreating all windows.
*/
public static final PropertyKey<ScreenDockWindowConfiguration> WINDOW_CONFIGURATION =
new PropertyKey<ScreenDockWindowConfiguration>( "ScreenDockStation.window_configuration",
new PropertyFactory<ScreenDockWindowConfiguration>(){
public ScreenDockWindowConfiguration getDefault( PropertyKey<ScreenDockWindowConfiguration> key, DockProperties properties ){
return new DefaultScreenDockWindowConfiguration( properties.getController() );
}
public ScreenDockWindowConfiguration getDefault( PropertyKey<ScreenDockWindowConfiguration> key ){
return new DefaultScreenDockWindowConfiguration( null );
}
}, true);
/** a key for a property telling how to handle fullscreen mode */
public static final PropertyKey<ScreenDockFullscreenStrategy> FULL_SCREEN_STRATEGY =
new PropertyKey<ScreenDockFullscreenStrategy>( "ScreenDockStation.full_screen_strategy",
new PropertyFactory<ScreenDockFullscreenStrategy>() {
public ScreenDockFullscreenStrategy getDefault( PropertyKey<ScreenDockFullscreenStrategy> key, DockProperties properties ) {
return new DefaultScreenDockFullscreenStrategy();
}
public ScreenDockFullscreenStrategy getDefault( PropertyKey<ScreenDockFullscreenStrategy> key ){
return new DefaultScreenDockFullscreenStrategy();
}
}, true );
/** global setting to change the effect happening on a double click */
public static final PropertyKey<Boolean> EXPAND_ON_DOUBLE_CLICK =
new PropertyKey<Boolean>( "ScreenDockStation.double_click_fullscreen", new ConstantPropertyFactory<Boolean>( true ), true );
/** time in milliseconds a {@link ScreenDockWindow} is prevented from stealing the focus after the {@link #getOwner() owner} of this station changed. A value
* of <code>null</code> disables the focus stealing prevention. */
public static final PropertyKey<Integer> PREVENT_FOCUS_STEALING_DELAY =
new PropertyKey<Integer>( "ScreenDockStation.prevent_focus_stealing_delay", new ConstantPropertyFactory<Integer>( 500 ), false );
/** the {@link MagnetStrategy} decides how two {@link ScreenDockWindow}s attract each other */
public static final PropertyKey<MagnetStrategy> MAGNET_STRATEGY =
new PropertyKey<MagnetStrategy>( "ScreenDockStation.magnet_strategy", new ConstantPropertyFactory<MagnetStrategy>( new DefaultMagnetStrategy() ){
public MagnetStrategy getDefault( PropertyKey<MagnetStrategy> key ){
return null;
};
}, true );
/** the {@link AttractorStrategy} that tells whether two {@link Dockable}s attract each other */
public static final PropertyKey<AttractorStrategy> ATTRACTOR_STRATEGY =
new PropertyKey<AttractorStrategy>( "ScreenDockStation.attractor_strategy", new DynamicPropertyFactory<AttractorStrategy>(){
public AttractorStrategy getDefault( PropertyKey<AttractorStrategy> key, DockProperties properties ){
ExtensionName<AttractorStrategy> name = new ExtensionName<AttractorStrategy>( ATTRACTOR_STRATEGY_EXTENSION, AttractorStrategy.class, null );
List<AttractorStrategy> extensions = properties.getController().getExtensions().load( name );
MultiAttractorStrategy strategy = new MultiAttractorStrategy();
for( AttractorStrategy extension : extensions ){
strategy.add( extension );
}
return strategy;
}
}, true );
/** key for the {@link ScreenDropSizeStrategy} that is used when dropping a {@link Dockable} onto this station */
public static final PropertyKey<ScreenDropSizeStrategy> DROP_SIZE_STRATEGY =
new PropertyKey<ScreenDropSizeStrategy>( "ScreendockStation.drop_size_strategy", new ConstantPropertyFactory<ScreenDropSizeStrategy>( ScreenDropSizeStrategy.CURRENT_SIZE ), true );
/** The visibility state of the windows */
private boolean showing = false;
/** A list of all windows that are used by this station */
private DockablePlaceholderList<ScreenDockWindowHandle> dockables = new DockablePlaceholderList<ScreenDockWindowHandle>();
/** All listeners that were added to this station */
private List<ScreenDockStationListener> screenDockStationListeners = new ArrayList<ScreenDockStationListener>();
/** The version of titles that are used */
private DockTitleVersion version;
/** Extensions to this station, these extensions are loaded with {@link #STATION_EXTENSION} */
private ScreenDockStationExtension[] extensions;
/** Combiner to merge some {@link Dockable Dockables} */
private StationCombinerValue combiner;
/** Information about the current movement of a {@link Dockable} */
private DropInfo dropInfo;
/** Information about the current removal of a {@link Dockable} */
private StationDragOperation dragInfo;
/** The {@link Window} that is used as parent for the windows */
private WindowProvider owner;
/** The paint used to draw information on this station */
private DefaultStationPaintValue stationPaint;
/** A factory to create new {@link DockableDisplayer}*/
private DefaultDisplayerFactoryValue displayerFactory;
/** The set of {@link DockableDisplayer} used on this station */
private DisplayerCollection displayers;
/** The window which has currently the focus */
private ScreenDockWindow frontWindow;
/** A manager for the visibility of the children */
private DockableShowingManager visibility;
/** An action to enable or disable fullscreen mode of some window */
private ListeningDockAction fullscreenAction;
/** tells how much two windows must overlap in order for them to be merged */
private double dropOverRatio = 0.75;
/** controls attraction between {@link ScreenDockWindow}s */
private MagnetController magnet;
/** the restrictions of the boundaries of this window*/
private PropertyValue<BoundaryRestriction> restriction =
new PropertyValue<BoundaryRestriction>( ScreenDockStation.BOUNDARY_RESTRICTION ){
@Override
protected void valueChanged( BoundaryRestriction oldValue, BoundaryRestriction newValue ) {
checkWindowBoundaries();
}
};
/** a factory used to create new windows for this station */
private PropertyValue<ScreenDockWindowFactory> windowFactory =
new PropertyValue<ScreenDockWindowFactory>( ScreenDockStation.WINDOW_FACTORY ){
@Override
protected void valueChanged( ScreenDockWindowFactory oldValue, ScreenDockWindowFactory newValue ) {
updateWindows( true );
}
};
/** a strategy for telling {@link #windowFactory} how to create new windows */
private PropertyValue<ScreenDockWindowConfiguration> windowConfiguration =
new PropertyValue<ScreenDockWindowConfiguration>( ScreenDockStation.WINDOW_CONFIGURATION ){
@Override
protected void valueChanged( ScreenDockWindowConfiguration oldValue, ScreenDockWindowConfiguration newValue ){
updateWindows( true );
}
};
/** the current fullscreen strategy */
private PropertyValue<ScreenDockFullscreenStrategy> fullscreenStrategy =
new PropertyValue<ScreenDockFullscreenStrategy>( ScreenDockStation.FULL_SCREEN_STRATEGY ) {
@Override
protected void valueChanged( ScreenDockFullscreenStrategy oldValue, ScreenDockFullscreenStrategy newValue ) {
List<ScreenDockWindow> fullscreenWindows = new ArrayList<ScreenDockWindow>();
for( ScreenDockWindowHandle handle : dockables.dockables() ){
ScreenDockWindow window = handle.getWindow();
if( window.isFullscreen() ){
fullscreenWindows.add( window );
window.setFullscreen( false );
}
}
if( oldValue != null ){
oldValue.uninstall( ScreenDockStation.this );
}
if( newValue != null ){
newValue.install( ScreenDockStation.this );
}
for( ScreenDockWindowHandle window : dockables.dockables() ){
window.getWindow().setFullscreenStrategy( newValue );
}
for( ScreenDockWindow window : fullscreenWindows ){
window.setFullscreen( true );
}
}
};
/** whether the children of this station expand on double click to fullscreen */
private PropertyValue<Boolean> expandOnDoubleClick =
new PropertyValue<Boolean>( EXPAND_ON_DOUBLE_CLICK ){
@Override
protected void valueChanged( Boolean oldValue, Boolean newValue ){
if( oldValue.booleanValue() != newValue.booleanValue() ){
DockController controller = getController();
if( controller != null ){
if( newValue ){
controller.getDoubleClickController().addListener( doubleClickListener );
}
else{
controller.getDoubleClickController().removeListener( doubleClickListener );
}
}
}
}
};
/** current {@link PlaceholderStrategy} */
private PropertyValue<PlaceholderStrategy> placeholderStrategy = new PropertyValue<PlaceholderStrategy>(PlaceholderStrategy.PLACEHOLDER_STRATEGY) {
@Override
protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){
dockables.setStrategy( newValue );
}
};
/** monitors the children of this station and reacts on double clicks by changing their fullscreen state */
private DoubleClickListener doubleClickListener = new DoubleClickListener() {
public DockElement getTreeLocation(){
return ScreenDockStation.this;
}
public boolean process( Dockable dockable, MouseEvent event ){
DockStation parent = dockable.getDockParent();
while( parent != null && parent != ScreenDockStation.this ){
dockable = parent.asDockable();
parent = dockable == null ? null : dockable.getDockParent();
}
if( parent == ScreenDockStation.this ){
for( ScreenDockFullscreenFilter filter : filters ){
if( !filter.isFullscreenEnabled( dockable )){
return false;
}
}
boolean state = isFullscreen( dockable );
setFullscreen( dockable, !state );
return true;
}
return false;
}
};
/** this strategy tells how to drop a {@link Dockable} onto this station */
private PropertyValue<ScreenDropSizeStrategy> dropSizeStrategy = new PropertyValue<ScreenDropSizeStrategy>( DROP_SIZE_STRATEGY ){
@Override
protected void valueChanged( ScreenDropSizeStrategy oldValue, ScreenDropSizeStrategy newValue ){
if( oldValue != null ){
oldValue.uninstall( ScreenDockStation.this );
}
if( newValue != null ){
newValue.install( ScreenDockStation.this );
}
}
};
/** a list of filters that can disable fullscreen mode for some windows */
private List<ScreenDockFullscreenFilter> filters = new ArrayList<ScreenDockFullscreenFilter>();
/** all the {@link FullscreenActionSource}s that are currently used */
private List<FullscreenActionSource> filterSources = new LinkedList<FullscreenActionSource>();
/**
* Constructs a new <code>ScreenDockStation</code>.
* @param owner the window which will be used as parent for the
* windows of this station, must not be <code>null</code>
*/
public ScreenDockStation( Window owner ){
if( owner == null )
throw new IllegalArgumentException( "Owner must not be null" );
init( new DirectWindowProvider( owner ));
}
/**
* Constructs a new <code>ScreenDockStation</code>.
* @param owner the window which will be used as parent for
* the windows of this station, must not be <code>null</code>
*/
public ScreenDockStation( WindowProvider owner ){
if( owner == null )
throw new IllegalArgumentException( "Owner must not be null" );
init( owner );
}
private void init( WindowProvider owner ){
visibility = new DockableShowingManager( listeners );
this.owner = owner;
displayerFactory = new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".screen", this );
combiner = new StationCombinerValue( ThemeManager.COMBINER + ".screen", this );
displayers = new DisplayerCollection( this, displayerFactory, DISPLAYER_ID );
fullscreenAction = createFullscreenAction();
stationPaint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".screen", this );
magnet = new MagnetController( this );
addScreenDockStationListener( new ScreenWindowListener() );
owner.addWindowProviderListener( new WindowProviderListener(){
public void visibilityChanged( WindowProvider provider, boolean showing ){
// ignore
}
public void windowChanged (WindowProvider provider, Window window ){
updateWindows();
}
});
}
protected DockComponentRootHandler createRootHandler() {
return new DefaultDockStationComponentRootHandler( this, displayers );
}
/**
* Creates an {@link DockAction action} which is added to all children
* of this station. The action allows the user to expand a child to
* fullscreen. The action is also added to subchildren, but the effect
* does only affect direct children of this station.
* @return the action or <code>null</code> if this feature should be
* disabled, or the action is {@link #setFullscreenAction(ListeningDockAction) set later}
*/
protected ListeningDockAction createFullscreenAction(){
return new ScreenFullscreenAction( this );
}
/**
* Adds <code>listener</code> to this station.
* @param listener the new listener
*/
public void addScreenDockStationListener( ScreenDockStationListener listener ){
screenDockStationListeners.add( listener );
}
/**
* Removes <code>listener</code> from this station.
* @param listener the listener to remove
*/
public void removeScreenDockStationListener( ScreenDockStationListener listener ){
screenDockStationListeners.remove( listener );
}
/**
* Gets all the {@link ScreenDockStationListener}s that were added to this station.
* @return all the listeners
*/
protected ScreenDockStationListener[] screenDockStationListeners(){
return screenDockStationListeners.toArray( new ScreenDockStationListener[ screenDockStationListeners.size() ] );
}
/**
* Sets an {@link DockAction action} which allows to expand children. This
* method can only be invoked if there is not already set an action. It is
* a condition that {@link #createFullscreenAction()} returns <code>null</code>
* @param fullScreenAction the new action
* @throws IllegalStateException if there is already an action present
*/
public void setFullscreenAction( ListeningDockAction fullScreenAction ) {
if( this.fullscreenAction != null )
throw new IllegalStateException( "The fullScreenAction can only be set once" );
this.fullscreenAction = fullScreenAction;
}
public DockActionSource getDirectActionOffers( Dockable dockable ) {
if( fullscreenAction == null )
return null;
else{
return createFullscreenSource( dockable, new LocationHint( LocationHint.DIRECT_ACTION, LocationHint.VERY_RIGHT ));
}
}
public DockActionSource getIndirectActionOffers( Dockable dockable ) {
if( fullscreenAction == null )
return null;
DockStation parent = dockable.getDockParent();
if( parent == null )
return null;
if( parent instanceof ScreenDockStation )
return null;
dockable = parent.asDockable();
if( dockable == null )
return null;
parent = dockable.getDockParent();
if( parent != this )
return null;
return createFullscreenSource( dockable, new LocationHint( LocationHint.INDIRECT_ACTION, LocationHint.VERY_RIGHT ));
}
private DockActionSource createFullscreenSource( final Dockable dockable, LocationHint hint ){
return new FullscreenActionSource( fullscreenAction, hint ){
private boolean listening;
protected boolean isFullscreenEnabled(){
for( ScreenDockFullscreenFilter filter : filters ){
if( !filter.isFullscreenEnabled( dockable )){
return false;
}
}
return true;
}
protected void listen( boolean listening ){
if( this.listening != listening ){
this.listening = listening;
if( listening ){
filterSources.add( this );
}
else{
filterSources.remove( this );
}
}
}
};
}
/**
* Gets the {@link DisplayerFactory} that is used by this station
* to create an underground for its children.
* @return the factory
* @see StationThemeItemValue#setDelegate(Object)
*/
public DefaultDisplayerFactoryValue getDisplayerFactory() {
return displayerFactory;
}
/**
* Gets the current set of {@link DockableDisplayer displayers} used
* on this station.
* @return the set of displayers
*/
public DisplayerCollection getDisplayers() {
return displayers;
}
/**
* Gets the {@link Combiner} that is used to merge two {@link Dockable Dockables}
* on this station.
* @return the combiner
* @see StationThemeItemValue#setDelegate(Object)
*/
public StationCombinerValue getCombiner() {
return combiner;
}
/**
* Gets the {@link StationPaint} for this station. The paint is needed to
* paint information on this station, when a {@link Dockable} is dragged
* or moved.
* @return the paint
* @see StationThemeItemValue#setDelegate(Object)
*/
public DefaultStationPaintValue getPaint() {
return stationPaint;
}
@Override
protected void callDockUiUpdateTheme() throws IOException {
DockUI.updateTheme( this, new ScreenDockStationFactory( owner ) );
}
@Override
public void setController( DockController controller ) {
DockController old = getController();
if( old != null ){
if( expandOnDoubleClick.getValue() ){
old.getDoubleClickController().removeListener( doubleClickListener );
}
dockables.unbind();
}
version = null;
super.setController( controller );
displayers.setController( controller );
if( controller != null ){
version = controller.getDockTitleManager().getVersion( TITLE_ID, ControllerTitleFactory.INSTANCE );
if( expandOnDoubleClick.getValue() ){
controller.getDoubleClickController().addListener( doubleClickListener );
}
dockables.bind();
List<ScreenDockStationExtension> list = controller.getExtensions().load( new ExtensionName<ScreenDockStationExtension>( STATION_EXTENSION, ScreenDockStationExtension.class, EXTENSION_PARAM, this ) );
extensions = list.toArray( new ScreenDockStationExtension[ list.size() ] );
}
else{
extensions = null;
}
stationPaint.setController( controller );
combiner.setController( controller );
displayerFactory.setController( controller );
restriction.setProperties( controller );
windowFactory.setProperties( controller );
windowConfiguration.setProperties( controller );
fullscreenStrategy.setProperties( controller );
placeholderStrategy.setProperties( controller );
magnet.setController( controller );
dropSizeStrategy.setProperties( controller );
if( fullscreenAction != null ){
fullscreenAction.setController( controller );
}
for( ScreenDockWindowHandle window : dockables.dockables() ){
window.getWindow().setController( controller );
}
}
public int getDockableCount() {
return dockables.dockables().size();
}
public Dockable getDockable( int index ) {
return dockables.dockables().get( index ).asDockable();
}
/**
* Gets the index of a {@link Dockable} that is shown on this
* station. A call to {@link #getDockable(int)} with the result of this
* method would return <code>dockable</code>, if <code>dockable</code>
* is on this station.
* @param dockable the item to search
* @return the index of the item or -1 if not found
*/
public int indexOf( Dockable dockable ){
Filter<ScreenDockWindowHandle> handles = dockables.dockables();
for( int i = 0, n = handles.size(); i<n; i++ ){
ScreenDockWindowHandle window = handles.get( i );
if( window.asDockable() == dockable )
return i;
}
return -1;
}
public PlaceholderMapping getPlaceholderMapping() {
return new PlaceholderListMapping( this, dockables ){
public DockableProperty getLocationAt( Path placeholder ) {
DockablePlaceholderList<ScreenDockWindowHandle>.Item item = dockables.getItem( placeholder );
if( item == null ){
return null;
}
ScreenDockWindowHandle handle = item.getDockable();
if( handle != null ){
Dockable dockable = handle.asDockable();
ScreenDockProperty property = getLocation( dockable, dockable );
property.setPlaceholder( placeholder );
return property;
}
else if( item.contains( "x", "y", "width", "height" )){
int x = item.getInt( "x" );
int y = item.getInt( "y" );
int width = item.getInt( "width" );
int height = item.getInt( "height" );
return new ScreenDockProperty( x, y, width, height, placeholder );
}
else{
return null;
}
}
};
}
public PlaceholderMap getPlaceholders(){
return dockables.toMap();
}
/**
* 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:
* <ul>
* <li>id: the integer from <code>children</code></li>
* <li>x, y, width, height: the location of the child if not in fullscreen mode</li>
* <li>fullscreen: whether the child is in fullscreen mode</li>
* <li>placeholder: the placeholder of the element, might not be written</li>
* </ul>
* @param children a unique identifier for each child of this station
* @return the map
*/
public PlaceholderMap getPlaceholders( final Map<Dockable, Integer> children ){
final PlaceholderStrategy strategy = getPlaceholderStrategy();
return dockables.toMap( new PlaceholderListItemAdapter<Dockable, ScreenDockWindowHandle>() {
@Override
public ConvertedPlaceholderListItem convert( int index, ScreenDockWindowHandle dockable ) {
Integer id = children.get( dockable.asDockable() );
if( id == null ){
return null;
}
saveLocation( index );
ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
Rectangle bounds = dockable.getBounds();
item.putInt( "id", id );
item.putInt( "x", bounds.x );
item.putInt( "y", bounds.y );
item.putInt( "width", bounds.width );
item.putInt( "height", bounds.height );
item.putBoolean( "fullscreen", dockable.getWindow().isFullscreen() );
if( strategy != null ){
Path placeholder = strategy.getPlaceholderFor( dockable.asDockable() );
if( placeholder != null ){
item.putString( "placeholder", placeholder.toString() );
item.setPlaceholder( placeholder );
}
}
return item;
}
});
}
public void setPlaceholders( PlaceholderMap placeholders ){
DockUtilities.checkLayoutLocked();
if( getDockableCount() > 0 ){
throw new IllegalStateException( "there are children on this station" );
}
try{
DockablePlaceholderList<ScreenDockWindowHandle> next = new DockablePlaceholderList<ScreenDockWindowHandle>( placeholders );
if( getController() != null ){
dockables.setStrategy( null );
dockables.unbind();
dockables = next;
dockables.bind();
dockables.setStrategy( getPlaceholderStrategy() );
}
else{
dockables = next;
}
}
catch( IllegalArgumentException ex ){
// ignore
}
}
/**
* Sets a new layout on this station, this method assumes that <code>map</code> 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<Integer, Dockable> children ){
DockUtilities.checkLayoutLocked();
if( getDockableCount() > 0 ){
throw new IllegalStateException( "must not have any children" );
}
DockController controller = getController();
try{
if( controller != null ){
controller.freezeLayout();
}
DockablePlaceholderList<ScreenDockWindowHandle> next = new DockablePlaceholderList<ScreenDockWindowHandle>();
if( controller != null ){
dockables.setStrategy( null );
dockables.unbind();
dockables = next;
dockables.bind();
dockables.setStrategy( getPlaceholderStrategy() );
}
else{
dockables = next;
}
next.read( map, new PlaceholderListItemAdapter<Dockable, ScreenDockWindowHandle>(){
private DockHierarchyLock.Token token;
@Override
public ScreenDockWindowHandle convert( ConvertedPlaceholderListItem item ){
int id = item.getInt( "id" );
Dockable dockable = children.get( id );
if( dockable != null ){
DockUtilities.ensureTreeValidity( ScreenDockStation.this, dockable );
token = DockHierarchyLock.acquireLinking( ScreenDockStation.this, dockable );
int x = item.getInt( "x" );
int y = item.getInt( "y" );
int width = item.getInt( "width" );
int height = item.getInt( "height" );
boolean fullscreen = item.getBoolean( "fullscreen" );
listeners.fireDockableAdding( dockable );
WindowConfiguration configuration = getConfiguration( dockable );
ScreenDockWindow window = createWindow( configuration );
ScreenDockWindowHandle handle = new ScreenDockWindowHandle( dockable, window, configuration );
window.setController( getController() );
window.setFullscreenStrategy( getFullscreenStrategy() );
window.setDockable( dockable );
window.setWindowBounds( new Rectangle( x, y, width, height ) );
window.setVisible( isShowing() );
window.validate();
window.setFullscreen( fullscreen );
return handle;
}
return null;
}
@Override
public void added( ScreenDockWindowHandle dockable ){
try{
dockable.asDockable().setDockParent( ScreenDockStation.this );
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.windowRegistering( ScreenDockStation.this, dockable.asDockable(), dockable.getWindow() );
}
listeners.fireDockableAdded( dockable.asDockable() );
}
finally{
token.release();
}
}
});
}
finally{
if( controller != null ){
controller.meltLayout();
}
}
}
/**
* 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 );
}
public Dockable getFrontDockable() {
if( frontWindow == null )
return null;
else
return frontWindow.getDockable();
}
public void setFrontDockable( Dockable dockable ) {
Dockable oldSelected = getFrontDockable();
frontWindow = getWindow( dockable );
if( frontWindow != null ){
frontWindow.toFront();
}
Dockable newSelected = getFrontDockable();
if( oldSelected != newSelected )
listeners.fireDockableSelected( oldSelected, newSelected );
}
public DockStationDropLayer[] getLayers() {
DockStationDropLayer[] result = new DockStationDropLayer[ getDockableCount()+1 ];
result[0] = new ScreenLayer( this );
for( int i = 1; i < result.length; i++ ){
result[i] = new ScreenWindowLayer( this, getWindow( i-1 ));
}
return result;
}
public StationDropOperation prepareDrop( StationDropItem item ){
return prepare( item, item.getDockable().getDockParent() != this );
}
public StationDragOperation prepareDrag( Dockable dockable ){
final ScreenDockWindow window = getWindow( dockable );
if( dragInfo != null ){
dragInfo.canceled();
}
if( window != null ){
window.setPaintRemoval( true );
dragInfo = new StationDragOperation(){
public void succeeded(){
window.setPaintRemoval( false );
dragInfo = null;
}
public void canceled(){
window.setPaintRemoval( false );
dragInfo = null;
}
};
}
return dragInfo;
}
public StationDropOperation prepare( StationDropItem item, boolean drop ) {
DropInfo dropInfo = new DropInfo();
dropInfo.x = item.getMouseX();
dropInfo.y = item.getMouseY();
dropInfo.titleX = item.getTitleX();
dropInfo.titleY = item.getTitleY();
dropInfo.dockable = item.getDockable();
dropInfo.move = !drop;
Enforcement force = Enforcement.HARD;
dropInfo.combine = searchCombineDockable( dropInfo.x, dropInfo.y, dropInfo.dockable, true );
if( dropInfo.combine == null ){
force = Enforcement.EXPECTED;
dropInfo.combine = searchCombineDockable( dropInfo.x, dropInfo.y, dropInfo.dockable, false );
}
if( dropInfo.combine != null && dropInfo.combine.getDockable() == dropInfo.dockable )
dropInfo.combine = null;
if( dropInfo.combine != null ){
dropInfo.combiner = combiner.prepare( dropInfo, force );
if( dropInfo.combiner == null ){
dropInfo.combine = null;
}
}
if( !checkDropInfo( dropInfo ) ){
dropInfo = null;
}
return dropInfo;
}
/**
* Ensures that the desired location where to insert the next child is valid.
* @param dropInfo information about the element to drop
* @return <code>true</code> if <code>dropInfo</code> is valid, <code>false</code> otherwise
*/
private boolean checkDropInfo( DropInfo dropInfo ){
if( dropInfo.combine != null ){
if( !accept( dropInfo.dockable ) ||
!dropInfo.dockable.accept( this, dropInfo.combine.getDockable() ) ||
!dropInfo.combine.getDockable().accept( this, dropInfo.dockable ) ||
!getController().getAcceptance().accept( this, dropInfo.combine.getDockable(), dropInfo.dockable )){
return false;
}
}
else{
if( !accept( dropInfo.dockable ) ||
!dropInfo.dockable.accept( this ) ||
!getController().getAcceptance().accept( this, dropInfo.dockable )){
return false;
}
}
return true;
}
/**
* Searches a window on the coordinates x/y which can be used to create
* a combination with <code>drop</code>.
* @param x the x-coordinate on the screen
* @param y die y-coordinate on the screen
* @param drop the {@link Dockable} which might be combined with a window
* @param combineArea whether the point <code>x/y</code> must be over the
* {@link ScreenDockWindow#inCombineArea(int, int) combine area} or just
* over the window.
* @return the window which might become the parent of <code>drop</code>.
*/
protected ScreenDockWindow searchCombineDockable( int x, int y, Dockable drop, boolean combineArea ){
for( ScreenDockWindowHandle handle : dockables.dockables() ){
ScreenDockWindow window = handle.getWindow();
boolean candidate;
if( combineArea ){
candidate = window.inCombineArea( x, y );
}
else{
candidate = window.contains( x, y );
}
if( candidate ){
Dockable child = window.getDockable();
if( DockUtilities.acceptable( this, child, drop ) ){
return window;
}
}
}
return null;
}
public void drop( Dockable dockable ) {
Window owner = getOwner();
int x = 30;
int y = 30;
if( owner != null ){
x += owner.getX();
y += owner.getY();
}
Dimension preferred = dropSizeStrategy.getValue().getAddSize( this, dockable );
int width = Math.max( preferred.width, 100 );
int height = Math.max( preferred.height, 100 );
if( !drop( dockable, new ScreenDockProperty( x, y, width, height ) ) ){
addDockable( dockable, new Rectangle( x, y, width, height ) );
}
}
public DockableProperty getDockableProperty( Dockable dockable, Dockable target ) {
return getLocation( dockable, target );
}
/**
* Gets the location of <code>dockable</code> and its current state.
* @param dockable some child of this station
* @param target the final element for which the location is needed
* @return the location, not <code>null</code>
*/
public ScreenDockProperty getLocation( Dockable dockable, Dockable target ){
int index = indexOf( dockable );
ScreenDockWindow window = getWindow( index );
if( window == null ){
throw new IllegalArgumentException( "dockable not child of this station" );
}
Rectangle bounds = null;
boolean fullscreen = window.isFullscreen();
if( fullscreen ){
bounds = window.getNormalBounds();
}
if( bounds == null ){
bounds = window.getWindowBounds();
}
PlaceholderStrategy strategy = getPlaceholderStrategy();
Path placeholder = null;
if( strategy != null ){
placeholder = strategy.getPlaceholderFor( target == null ? dockable : target );
if( placeholder != null ){
dockables.dockables().addPlaceholder( index, placeholder );
}
}
return new ScreenDockProperty( bounds.x, bounds.y, bounds.width, bounds.height, placeholder, fullscreen );
}
public void aside( AsideRequest request ){
DockableProperty location = request.getLocation();
if( location instanceof ScreenDockProperty ){
ScreenDockProperty screenLocation = (ScreenDockProperty)location;
DockablePlaceholderList<ScreenDockWindowHandle>.Item item = getItem( screenLocation );
if( item != null ){
delegate().combine( item, getCombiner(), request );
}
ScreenDockProperty copy = screenLocation.copy();
copy.setSuccessor( null );
copy.setPlaceholder( request.getPlaceholder() );
request.answer( copy );
}
}
private DockablePlaceholderList<ScreenDockWindowHandle>.Item getItem( ScreenDockProperty property ){
Path oldPlaceholder = property.getPlaceholder();
if( oldPlaceholder != null ){
DockablePlaceholderList<ScreenDockWindowHandle>.Item item = dockables.getItem( oldPlaceholder );
if( item != null ){
return item;
}
}
ScreenDockStationExtension.DropArguments args = new ScreenDockStationExtension.DropArguments();
args.setProperty( property );
args.setBoundsIncludeWindow( true );
windowAt( args );
ScreenDockWindow window = args.getWindow();
if( window != null ){
return dockables.getItem( window.getDockable() );
}
return null;
}
/**
* Searches the {@link ScreenDockWindow} which displays the <code>dockable</code>.
* @param dockable the {@link Dockable} to search
* @return the window or <code>null</code>
*/
public ScreenDockWindow getWindow( Dockable dockable ){
int index = indexOf( dockable );
if( index < 0 )
return null;
return getWindow( index );
}
/**
* Gets the <code>index</code>'th window of this station. The number
* of windows is identical to the {@link #getDockableCount() number of Dockables}.
* @param index the index of the window
* @return the window which shows the index'th Dockable.
*/
public ScreenDockWindow getWindow( int index ){
return getWindowHandle( index ).getWindow();
}
/**
* Gets the <code>index</code>'th window of this station.
* @param index the index of the window
* @return the handle for <code>index</code>
*/
private ScreenDockWindowHandle getWindowHandle( int index ){
return dockables.dockables().get( index );
}
/**
* Gets a list of all children of this station that are currently in fullscreen mode.
* @return a list of children, not <code>null</code>
*/
public Dockable[] getFullscreenChildren() {
List<Dockable> result = new ArrayList<Dockable>();
for( ScreenDockWindowHandle handle : dockables.dockables() ){
ScreenDockWindow window = handle.getWindow();
if( window.isFullscreen() ){
result.add( window.getDockable() );
}
}
return result.toArray( new Dockable[ result.size() ] );
}
/**
* Tells whether <code>dockable</code> is currently shown in fullscreen mode.
* @param dockable the element to check
* @return the mode
* @throws IllegalArgumentException if <code>dockable</code> is not known
*/
public boolean isFullscreen( Dockable dockable ){
ScreenDockWindow window = getWindow( dockable );
if( window == null ){
throw new IllegalArgumentException( "dockable is not known to this station" );
}
return window.isFullscreen();
}
/**
* Changes the fullscreen mode of <code>dockable</code>.
* @param dockable the element whose mode is to be changed
* @param fullscreen the new mode
* @throws IllegalArgumentException if <code>dockable</code> is not known to this station
*/
public void setFullscreen( Dockable dockable, boolean fullscreen ){
ScreenDockWindow window = getWindow( dockable );
if( window == null ){
throw new IllegalArgumentException( "dockable is not known to this station" );
}
window.setFullscreen( fullscreen );
}
/**
* Adds the new filter <code>filter</code> to this station. The filter can deny {@link Dockable}s the
* possibility of being in fullscreen mode.
* @param filter the new filter, not <code>null</code>
*/
public void addFullscreenFilter( ScreenDockFullscreenFilter filter ){
filters.add( filter );
filterChanged();
}
/**
* Removes <code>filter</code> from this station.
* @param filter the filter to remove
* @see #addFullscreenFilter(ScreenDockFullscreenFilter)
*/
public void removeFullscreenFilter( ScreenDockFullscreenFilter filter ){
filters.remove( filter );
filterChanged();
}
private void filterChanged(){
for( FullscreenActionSource source : filterSources ){
source.update();
}
}
/**
* Tells this station what to do on a double click on a child. If set
* to <code>true</code>, then the child's fullscreen mode gets changed.
* @param expand whether to react on double clicks
*/
public void setExpandOnDoubleClick( boolean expand ){
expandOnDoubleClick.setValue( expand );
}
/**
* Resets the expand-on-double-click property to its default value.
* @see #setExpandOnDoubleClick(boolean)
*/
public void clearExpandOnDoubleClick(){
expandOnDoubleClick.setValue( null );
}
/**
* Tells whether children change their fullscreen mode if
* the user double clicks on them.
* @return the state
*/
public boolean isExpandOnDoubleClick(){
return expandOnDoubleClick.getValue();
}
public void move( Dockable dockable, DockableProperty property ) {
DockUtilities.checkLayoutLocked();
if( property instanceof ScreenDockProperty ){
ScreenDockWindow window = getWindow( dockable );
if( window == null )
throw new IllegalArgumentException( "dockable not child of this station" );
ScreenDockProperty bounds = (ScreenDockProperty)property;
window.setWindowBounds( new Rectangle( bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight() ) );
}
}
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" );
removeDockable( dockable );
}
/**
* Adds a {@link Dockable} on a newly created {@link ScreenDockWindow} to
* the station. If the station {@link #isShowing() is visible}, the window
* will be made visible too.
* @param dockable the {@link Dockable} to show
* @param bounds the bounds that the window will have
*/
public void addDockable( Dockable dockable, Rectangle bounds ){
addDockable( dockable, bounds, true );
}
/**
* Adds a {@link Dockable} on a newly created {@link ScreenDockWindow} to
* the station. If the station {@link #isShowing() is visible}, the window
* will be made visible too.
* @param dockable the {@link Dockable} to show
* @param bounds the bounds that the window will have
* @param boundsIncludeWindow if <code>true</code>, the bounds describe the size
* of the resulting window. Otherwise the size of the window will be a bit larger
* such that the title can be shown in the new space
*/
public void addDockable( Dockable dockable, Rectangle bounds, boolean boundsIncludeWindow ){
addDockable( dockable, bounds, null, boundsIncludeWindow );
}
/**
* Adds a {@link Dockable} on a newly created {@link ScreenDockWindow} to
* the station. If the station {@link #isShowing() is visible}, the window
* will be made visible too.
* @param dockable the {@link Dockable} to show
* @param bounds the bounds that the window will have
* @param placeholder the name of <code>dockable</code>, used to associate a group of other dockables
* to <code>dockable</code>. Can be <code>null</code>.
* @param boundsIncludeWindow if <code>true</code>, the bounds describe the size
* of the resulting window. Otherwise the size of the window will be a bit larger
* such that the title can be shown in the new space
* @throws IllegalStateException if there is already a window associated with the group of <code>placeholder</code>
*/
protected void addDockable( Dockable dockable, Rectangle bounds, Path placeholder, boolean boundsIncludeWindow ){
DockUtilities.checkLayoutLocked();
DockUtilities.ensureTreeValidity( this, dockable );
DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable );
try{
if( bounds == null )
throw new IllegalArgumentException( "Bounds must not be null" );
listeners.fireDockableAdding( dockable );
WindowConfiguration configuration = getConfiguration( dockable );
ScreenDockWindow window = createWindow(configuration);
register( dockable, placeholder, window, configuration );
window.setDockable( dockable );
bounds = new Rectangle( bounds );
if( !boundsIncludeWindow ){
window.validate();
Insets estimate = window.getDockableInsets();
if( estimate != null ){
bounds.x -= estimate.left;
bounds.y -= estimate.top;
bounds.width += estimate.left + estimate.right;
bounds.height += estimate.top + estimate.bottom;
}
}
window.setWindowBounds( bounds );
window.validate();
if( !boundsIncludeWindow ){
window.validate();
Point offset = window.getOffsetDrop();
if( offset != null ){
Rectangle windowBounds = window.getWindowBounds();
windowBounds = new Rectangle( windowBounds.x + offset.x, windowBounds.y + offset.y, windowBounds.width, windowBounds.height );
window.setWindowBounds( windowBounds );
}
}
if( isShowing() )
window.setVisible( true );
dockable.setDockParent( this );
listeners.fireDockableAdded( dockable );
}
finally{
token.release();
}
}
public boolean drop( Dockable dockable, DockableProperty property ){
if( property instanceof ScreenDockProperty )
return drop( dockable, (ScreenDockProperty)property );
else
return false;
}
/**
* Tries to add the <code>dockable</code> to this station, and uses
* the <code>property</code> to determine its location. If the preferred
* location overlaps an existing window, then the {@link Dockable} may be
* added to a child-station of this station.
* @param dockable the new {@link Dockable}
* @param property the preferred location of the dockable
* @return <code>true</code> if the dockable could be added, <code>false</code>
* otherwise.
*/
public boolean drop( Dockable dockable, ScreenDockProperty property ){
return drop( dockable, property, true );
}
/**
* Tries to add the <code>dockable</code> to this station, and uses
* the <code>property</code> to determine its location. If the preferred
* location overlaps an existing window, then the {@link Dockable} may be
* added to a child-station of this station.
* @param dockable the new {@link Dockable}
* @param property the preferred location of the dockable
* @param boundsIncludeWindow if <code>true</code>, the bounds describe the size
* of the resulting window. Otherwise the size of the window will be a bit larger
* such that the title can be shown in the new space
* @return <code>true</code> if the dockable could be added, <code>false</code>
* otherwise.
*/
public boolean drop( Dockable dockable, ScreenDockProperty property, boolean boundsIncludeWindow ){
ScreenDockStationExtension.DropArguments args = new ScreenDockStationExtension.DropArguments();
args.setDockable( dockable );
args.setProperty( property );
args.setBoundsIncludeWindow( boundsIncludeWindow );
windowAt( args );
if( extensions != null ){
DockController controller = getController();
if( controller != null ){
controller.freezeLayout();
}
try{
for( ScreenDockStationExtension extension : extensions ){
extension.drop( this, args );
}
boolean result = executeDrop( args );
for( ScreenDockStationExtension extension : extensions ){
extension.dropped( this, args, result );
}
return result;
}
finally{
if( controller != null ){
controller.meltLayout();
}
}
}
else{
return executeDrop( args );
}
}
private void windowAt( ScreenDockStationExtension.DropArguments args ){
ScreenDockWindow best = null;
double bestRatio = 0.0;
ScreenDockProperty property = args.getProperty();
int x = property.getX();
int y = property.getY();
int width = property.getWidth();
int height = property.getHeight();
Path placeholder = property.getPlaceholder();
if( placeholder != null ){
ScreenDockWindowHandle handle = dockables.getDockableAt( placeholder );
if( handle != null ){
bestRatio = 1.0;
best = handle.getWindow();
}
else{
PlaceholderMetaMap meta = dockables.getMetaMap( placeholder );
if( meta != null ){
if( meta.contains( "x" ) ){
x = meta.getInt( "x" );
}
if( meta.contains( "y" ) ){
y = meta.getInt( "y" );
}
if( meta.contains( "width" ) ){
width = meta.getInt( "width" );
}
if( meta.contains( "height" ) ){
height = meta.getInt( "height" );
}
ScreenDockProperty replacement = new ScreenDockProperty( x, y, width, height, placeholder, property.isFullscreen() );
replacement.setSuccessor( property.getSuccessor() );
args.setProperty( replacement );
args.setBoundsIncludeWindow( true );
}
else{
placeholder = null;
}
}
}
if( bestRatio == 0.0 ){
double propertySize = width * height;
for( ScreenDockWindowHandle handle : dockables.dockables() ){
ScreenDockWindow window = handle.getWindow();
if( !window.isFullscreen() ){
Rectangle bounds = window.getWindowBounds();
double windowSize = bounds.width * bounds.height;
bounds = SwingUtilities.computeIntersection( x, y, width, height, bounds );
if( !(bounds.width == 0 || bounds.height == 0) ){
double size = bounds.width * bounds.height;
double max = Math.max( propertySize, windowSize );
double ratio = size / max;
if( ratio > bestRatio ){
bestRatio = ratio;
best = window;
}
}
}
}
}
if( bestRatio >= dropOverRatio ){
args.setWindow( best );
}
}
private boolean executeDrop( ScreenDockStationExtension.DropArguments args ){
DockUtilities.checkLayoutLocked();
DockUtilities.ensureTreeValidity( this, args.getDockable() );
DockController controller = getController();
DockAcceptance acceptance = controller == null ? null : controller.getAcceptance();
ScreenDockWindow best = args.getWindow();
boolean done = false;
Dockable dockable = args.getDockable();
ScreenDockProperty property = args.getProperty();
if( best != null && best.getDockable() != null ){
DockableProperty successor = property.getSuccessor();
Dockable dock = best.getDockable();
if( successor != null ){
DockStation station = dock.asDockStation();
if( station != null )
done = station.drop( dockable, successor );
}
if( !done ){
Dockable old = best.getDockable();
if( old.accept( this, dockable ) && dockable.accept( this, old ) && (acceptance == null || acceptance.accept( this, old, dockable ))){
combine( old, dockable, property.getSuccessor() );
done = true;
}
}
}
if( !done ){
boolean accept = accept( dockable ) && dockable.accept( this ) && (acceptance == null || acceptance.accept( this, dockable ));
if( accept ){
addDockable( dockable, new Rectangle( property.getX(), property.getY(), property.getWidth(), property.getHeight() ), property.getPlaceholder(), args.isBoundsIncludeWindow() );
done = true;
}
}
if( done && property.isFullscreen() ){
DockStation parent = dockable.getDockParent();
while( parent != null && parent != this ){
dockable = parent.asDockable();
parent = dockable == null ? null : dockable.getDockParent();
}
if( dockable != null ){
setFullscreen( dockable, true );
}
}
return done;
}
/**
* Drops <code>dockable</code> at the same coordinates as <code>location</code>, a
* direct child of this station.
* @param dockable a new dockable
* @param location a known dockable
* @return whether the operation completed
*/
public boolean drop( Dockable dockable, Dockable location ){
boolean accept = accept( dockable ) && dockable.accept( this );
if( !accept ){
return false;
}
ScreenDockWindow window = getWindow( location );
if( window == null ){
throw new IllegalArgumentException( "location is now known to this station" );
}
Rectangle bounds = null;
if( window.isFullscreen() ){
bounds = window.getNormalBounds();
}
if( bounds == null ){
bounds = window.getWindowBounds();
}
addDockable( dockable, bounds, true );
return true;
}
/**
* Combines the <code>lower</code> and the <code>upper</code> {@link Dockable}
* to one {@link Dockable}, and replaces the <code>lower</code> with
* this new Dockable. There are no checks whether this station
* {@link #accept(Dockable) accepts} the new child or the children
* can be combined. The creation of the new {@link Dockable} is done
* by the {@link #getCombiner() combiner}.
* @param lower a {@link Dockable} which must be child of this station
* @param upper a {@link Dockable} which may be child of this station
*/
public void combine( Dockable lower, Dockable upper ){
combine( lower, upper, null );
}
/**
* Combines the <code>lower</code> and the <code>upper</code> {@link Dockable}
* to one {@link Dockable}, and replaces the <code>lower</code> with
* this new Dockable. There are no checks whether this station
* {@link #accept(Dockable) accepts} the new child or the children
* can be combined. The creation of the new {@link Dockable} is done
* by the {@link #getCombiner() combiner}.
* @param lower a {@link Dockable} which must be child of this station
* @param upper a {@link Dockable} which may be child of this station
* @param property location information associated with <code>upper</code>, may be <code>null</code>
*/
public void combine( Dockable lower, Dockable upper, DockableProperty property ){
DropInfo info = new DropInfo();
info.dockable = upper;
info.combine = getWindow( lower );
if( info.combine == null ){
throw new IllegalArgumentException( "lower is not a child of this station" );
}
Component component = lower.getComponent();
Point middle = new Point( component.getWidth() / 2, component.getHeight() / 2 );
SwingUtilities.convertPointToScreen( middle, component );
info.x = middle.x;
info.y = middle.y;
info.titleX = info.x;
info.titleY = info.y;
info.combiner = combiner.prepare( info, Enforcement.HARD );
combine( info, info.combiner, property );
}
/**
* Uses the current {@link Combiner} to combine the {@link Dockable}s described
* in <code>source</code>.
* @param source the source {@link Dockable}s to combine
* @param target the target created by the {@link Combiner}
* @param property location information associated with the new {@link Dockable}, can be <code>null</code>
*/
private void combine( CombinerSource source, CombinerTarget target, DockableProperty property ){
DockUtilities.checkLayoutLocked();
Dockable lower = source.getOld();
Dockable upper = source.getNew();
int index = indexOf( lower );
if( index < 0 ){
throw new IllegalArgumentException( "old is not child of this station" );
}
ScreenDockWindowHandle window = getWindowHandle( index );
removeDockable( upper );
index = indexOf( lower );
final Dockable old = window.getWindow().getDockable();
int listIndex = dockables.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList<ScreenDockWindowHandle>.Item item = dockables.list().get( listIndex );
final PlaceholderMap map = item.getPlaceholderMap();
DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, lower );
try{
listeners.fireDockableRemoving( lower );
item.setPlaceholderMap( null );
window.setDockable( null );
lower.setDockParent( null );
}
finally{
token.release();
}
listeners.fireDockableRemoved( lower );
Dockable valid = combiner.combine( new CombinerSourceWrapper( source ){
@Override
public PlaceholderMap getPlaceholders(){
return map;
}
@Override
public Dockable getOld(){
return old;
}
}, target );
if( property != null ){
DockStation combined = valid.asDockStation();
if( combined != null && upper.getDockParent() == combined ){
combined.move( upper, property );
}
}
token = DockHierarchyLock.acquireLinking( this, valid );
try{
listeners.fireDockableAdding( valid );
window.setDockable( valid );
valid.setDockParent( this );
listeners.fireDockableAdded( valid );
}
finally{
token.release();
}
}
public boolean canReplace( Dockable old, Dockable next ) {
if( extensions != null ){
for( ScreenDockStationExtension extension : extensions ){
if( !extension.canReplace( this, old, next )){
return false;
}
}
}
return true;
}
public void replace( DockStation old, Dockable next ){
replace( old.asDockable(), next, true );
}
public void replace( Dockable current, Dockable other ){
replace( current, other, false );
}
public void replace( Dockable current, Dockable other, boolean station ){
DockUtilities.checkLayoutLocked();
int index = indexOf( current );
if( index < 0 ){
throw new IllegalArgumentException( "current not known to this station" );
}
DockUtilities.ensureTreeValidity( this, other );
ScreenDockWindowHandle window = getWindowHandle( index );
if( station ){
int listIndex = dockables.levelToBase( index, Level.DOCKABLE );
DockablePlaceholderList<ScreenDockWindowHandle>.Item item = dockables.list().get( listIndex );
item.setPlaceholderMap( current.asDockStation().getPlaceholders() );
}
DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, current );
try{
listeners.fireDockableRemoving( current );
window.setDockable( null );
current.setDockParent( null );
listeners.fireDockableRemoved( current );
}
finally{
token.release();
}
token = DockHierarchyLock.acquireLinking( this, other );
try{
listeners.fireDockableAdding( other );
window.setDockable( other );
other.setDockParent( this );
listeners.fireDockableAdded( other );
}
finally{
token.release();
}
}
/**
* Removes the <code>dockable</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 dockable the {@link Dockable} to remove
*/
public void removeDockable( Dockable dockable ){
int index = indexOf( dockable );
if( index >= 0 ){
removeDockable( index );
}
}
/**
* Removes the <code>index</code>'th {@link Dockable} of 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 {@link Dockable} to remove
*/
public void removeDockable( int index ){
DockUtilities.checkLayoutLocked();
ScreenDockWindowHandle handle = getWindowHandle( index );
ScreenDockWindow window = handle.getWindow();
Dockable dockable = window.getDockable();
DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable );
try{
listeners.fireDockableRemoving( dockable );
window.setVisible( false );
deregister( dockable, window );
handle.setDockable( null );
dockable.setDockParent( null );
listeners.fireDockableRemoved( dockable );
}
finally{
token.release();
}
}
/**
* Invoked after a new {@link ScreenDockWindow} has been created. This
* method adds some listeners to the window. If the method is overridden,
* it should be called from the subclass to ensure the correct function
* of this station.
* @param dockable the element for which <code>window</code> will be used
* @param placeholder the name of <code>dockable</code>, used to place the new
* {@link ScreenDockWindowHandle} at its correct position. Can be <code>null</code>.
* @param window the window which was newly created
* @param configuration the configuration that was used to create <code>window</code>
* @return the newly created handle for <code>window</code>
*/
protected ScreenDockWindowHandle register( Dockable dockable, Path placeholder, ScreenDockWindow window, WindowConfiguration configuration ){
ScreenDockWindowHandle handle = new ScreenDockWindowHandle( dockable, window, configuration );
if( placeholder != null ){
if( dockables.getDockableAt( placeholder ) != null ){
throw new IllegalStateException( "there is already a window in the group " + placeholder + ", add the element directly to that window or do not use a placeholder" );
}
if( dockables.put( placeholder, handle ) == -1 ){
dockables.dockables().add( handle );
}
}
else{
dockables.dockables().add( handle );
}
window.setController( getController() );
window.setFullscreenStrategy( getFullscreenStrategy() );
getRootHandler().addRoot( window.getComponent() );
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.windowRegistering( this, dockable, window );
}
return handle;
}
/**
* Invoked when a {@link ScreenDockWindow} is no longer needed. This
* method removes some listeners from the window. If overridden
* by a subclass, the subclass should ensure that this implementation
* is invoked too.
* @param dockable the element for which <code>window</code> was used
* @param window the old window
*/
protected void deregister( Dockable dockable, ScreenDockWindow window ){
if( frontWindow == window )
frontWindow = null;
int index = indexOf( window.getDockable() );
saveLocation( index );
dockables.remove( index );
getRootHandler().removeRoot( window.getComponent() );
window.setDockable( null );
window.setPaintCombining( null );
window.setController( null );
window.setFullscreenStrategy( null );
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.windowDeregistering( this, dockable, window );
}
window.destroy();
}
private void saveLocation( int index ){
ScreenDockWindow window = dockables.dockables().get( index ).getWindow();
PlaceholderMetaMap map = dockables.dockables().getMetaMap( index );
Rectangle bounds = null;
if( window.isFullscreen() ){
bounds = window.getNormalBounds();
}
if( bounds == null ){
bounds = window.getWindowBounds();
}
map.putInt( "x", bounds.x );
map.putInt( "y", bounds.y );
map.putInt( "width", bounds.width );
map.putInt( "height", bounds.height );
}
/**
* Gets the {@link WindowConfiguration} which should be used to create a new {@link ScreenDockWindow}
* for <code>dockable</code>.
* @param dockable the element that is going to be shown
* @return its configuration, not <code>null</code>
*/
protected WindowConfiguration getConfiguration( Dockable dockable ){
WindowConfiguration result = windowConfiguration.getValue().getConfiguration( this, dockable );
if( result == null ){
result = new WindowConfiguration();
}
return result;
}
/**
* Creates a new window which is associated with this station.
* @param configuration the configuration that should be used to set up the new window
* @return the new window
*/
protected ScreenDockWindow createWindow( WindowConfiguration configuration ){
return getWindowFactory().createWindow( this, configuration );
}
/**
* Called if {@link #getOwner()} changed. This method replaces existing {@link ScreenDockWindow}
* by new windows created by {@link ScreenDockWindowFactory#updateWindow(ScreenDockWindow, WindowConfiguration, ScreenDockStation)}.
*/
protected void updateWindows(){
updateWindows( false );
}
/**
* Update all windows either by calling {@link ScreenDockWindowFactory#updateWindow(ScreenDockWindow, WindowConfiguration, ScreenDockStation)}
* or by calling {@link ScreenDockWindowFactory#createWindow(ScreenDockStation, WindowConfiguration)}.
* @param force if <code>true</code>, then {@link ScreenDockWindowFactory#createWindow(ScreenDockStation, WindowConfiguration) createWindow}
* is used and all windows are replaced, if <code>false</code> the factory is allowed to do optimizations.
*/
protected void updateWindows( boolean force ){
ScreenDockWindowFactory factory = getWindowFactory();
Integer delay = PREVENT_FOCUS_STEALING_DELAY.getDefault( null );
DockController controller = getController();
if( controller != null ){
delay = controller.getProperties().get( PREVENT_FOCUS_STEALING_DELAY );
}
for( ScreenDockWindowHandle handle : dockables.dockables() ){
final ScreenDockWindow oldWindow = handle.getWindow();
final ScreenDockWindow newWindow;
final WindowConfiguration configuration;
if( force ){
configuration = getConfiguration( oldWindow.getDockable() );
newWindow = createWindow( configuration );
}
else{
configuration = handle.getConfiguration();
newWindow = factory.updateWindow( oldWindow, configuration, this );
}
if( newWindow != null && newWindow != oldWindow ){
Dockable dockable = oldWindow.getDockable();
Rectangle bounds = oldWindow.getNormalBounds();
if( bounds == null ){
bounds = oldWindow.getWindowBounds();
}
boolean fullscreen = oldWindow.isFullscreen();
boolean visible = oldWindow.isVisible();
oldWindow.setDockable( null );
oldWindow.setPaintCombining( null );
oldWindow.setController( null );
oldWindow.setFullscreenStrategy( null );
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.windowDeregistering( this, dockable, oldWindow );
}
oldWindow.destroy();
handle.setWindow( newWindow, configuration );
newWindow.setController( getController() );
newWindow.setFullscreenStrategy( getFullscreenStrategy() );
newWindow.setWindowBounds( bounds );
newWindow.setFullscreen( fullscreen );
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.windowRegistering( this, dockable, newWindow );
}
if( visible && isShowing() ){
if( delay == null || delay.intValue() <= 0 ){
newWindow.setVisible( true );
}
else{
newWindow.setPreventFocusStealing( true );
newWindow.setVisible( true );
Timer timer = new Timer( delay, new ActionListener(){
public void actionPerformed( ActionEvent e ){
newWindow.setPreventFocusStealing( false );
}
});
timer.setRepeats( false );
timer.start();
}
}
}
}
}
/**
* Gets the owner of this station. The owner is forwarded to some
* windows as their owner. So the windows will always remain in the
* foreground.
* @return the current owner
* @see #getProvider()
*/
public Window getOwner(){
return owner.searchWindow();
}
/**
* Gets the provider which delivers window owners for the windows of this
* station.
* @return the provider for windows
*/
public WindowProvider getProvider(){
return owner;
}
/**
* Gets the factory that is currently used to create new windows for this station.
* @return the factory, not <code>null</code>
*/
public ScreenDockWindowFactory getWindowFactory(){
return windowFactory.getValue();
}
/**
* Gets the property which represents the window factory.
* @return the property
*/
protected PropertyValue<ScreenDockWindowFactory> getWindowFactoryProperty(){
return windowFactory;
}
/**
* Sets the factory that will be used to create new windows for this station, Calling this
* method will result in closing all existing windows and creating new windows.
* @param factory the new factory, <code>null</code> to set the default
* value
*/
public void setWindowFactory( ScreenDockWindowFactory factory ){
windowFactory.setValue( factory );
}
/**
* Gets the configuration which is currently used to create new windows.
* @return the configuration, not <code>null</code>
*/
public ScreenDockWindowConfiguration getWindowConfiguration(){
return windowConfiguration.getValue();
}
/**
* Gets the property which represents the window configuration.
* @return the property, not <code>null</code>
*/
protected PropertyValue<ScreenDockWindowConfiguration> getWindowConfigurationProperty(){
return windowConfiguration;
}
/**
* Sets the configuration which should be used to create new windows. Calling this method
* results in closing all existing windows and creating new windows.
* @param configuration the new configuration or <code>null</code> to use the default configuration
*/
public void setWindowConfiguration( ScreenDockWindowConfiguration configuration ){
windowConfiguration.setValue( configuration );
}
/**
* Gets the current fullscreen strategy.
* @return the strategy, not <code>null</code>
*/
public ScreenDockFullscreenStrategy getFullscreenStrategy(){
return fullscreenStrategy.getValue();
}
/**
* Sets the strategy used to handle fullscreen mode.
* @param strategy the new strategy, <code>null</code> will reapply the default strategy
*/
public void setFullscreenStrategy( ScreenDockFullscreenStrategy strategy ){
fullscreenStrategy.setValue( strategy );
}
/**
* Tells whether this station shows its children or not.
* @return <code>true</code> if the windows are visible, <code>false</code>
* otherwise
* @see #setShowing(boolean)
*/
public boolean isShowing() {
return showing;
}
/**
* Sets the visibility of all windows of this station.
* @param showing <code>true</code> if all windows should be visible,
* <code>false</code> otherwise.
*/
public void setShowing( boolean showing ){
if( this.showing != showing ){
this.showing = showing;
for( ScreenDockWindowHandle window : dockables.dockables() ){
window.getWindow().setVisible( showing );
}
visibility.fire();
}
}
/**
* Tells whether this station shows its children. This method just calls
* {@link #isShowing()}.
* @return <code>true</code> if the windows are visible, <code>false</code>
* if not.
* @see #isShowing()
*/
public boolean isStationShowing(){
return isShowing();
}
@Deprecated
@Todo( compatibility=Compatibility.BREAK_MAJOR, priority=Priority.ENHANCEMENT, target=Version.VERSION_1_1_3, description="remove this method" )
public boolean isStationVisible(){
return isShowing();
}
public boolean isChildShowing( Dockable dockable ){
return isVisible( dockable );
}
@Deprecated
@Todo( compatibility=Compatibility.BREAK_MAJOR, priority=Priority.ENHANCEMENT, target=Version.VERSION_1_1_3, description="remove this method" )
public boolean isVisible( Dockable dockable ){
return isStationVisible();
}
public Rectangle getStationBounds() {
return null;
}
public Dockable asDockable() {
return null;
}
public DockStation asDockStation() {
return this;
}
public String getFactoryID() {
return ScreenDockStationFactory.ID;
}
/**
* Gets the {@link DockTitleVersion} used by this station to create
* new {@link DockTitle}s.
* @return the version, can be <code>null</code>
*/
public DockTitleVersion getTitleVersion(){
return version;
}
/**
* Gets the currently used {@link BoundaryRestriction}.
* @return the restriction
*/
public BoundaryRestriction getBoundaryRestriction(){
return restriction.getValue();
}
/**
* Changes the boundary restriction used to check the boundaries of
* the windows of this station.
* @param restriction the new restriction or <code>null</code> to reset
* the default value
*/
public void setBoundaryRestriction( BoundaryRestriction restriction ){
this.restriction.setValue( restriction );
}
/**
* Checks the boundaries of all windows of this station
*/
public void checkWindowBoundaries(){
for( ScreenDockWindowHandle window : dockables.dockables() )
window.getWindow().checkWindowBounds();
}
/**
* Gets the {@link MagnetController} of this station. The {@link MagnetController} controls the
* attraction between {@link ScreenDockWindow}s.
* @return the controller, never <code>null</code>
*/
public MagnetController getMagnetController(){
return magnet;
}
/**
* Tells the current overlapping two windows must have in order to be merged.
* @return the overlapping, a number between 0 and 1
* @see #setDropOverRatio(double)
*/
public double getDropOverRatio(){
return dropOverRatio;
}
/**
* Sets how much two windows must overlap in order to be merged. This property is only used when
* {@link #drop(Dockable, ScreenDockProperty, boolean) dropping} a {@link Dockable}. A value of 0 means that
* the windows don't have to overlap, a value of 1 indicates a perfect match. The default value is 0.75.
* @param dropOverRatio the new ratio, a value between 0 and 1 inclusive
*/
public void setDropOverRatio( double dropOverRatio ){
if( dropOverRatio < 0 || dropOverRatio > 1 ){
throw new IllegalArgumentException( "dropOverRatio must be between 0 and 1" );
}
this.dropOverRatio = dropOverRatio;
}
private ScreenDockWindowClosingStrategy getWindowClosingStrategy(){
DockController controller = getController();
if( controller == null ){
return null;
}
return controller.getProperties().get( WINDOW_CLOSING_STRATEGY );
}
/**
* Information where a {@link Dockable} will be dropped. This class
* is used only while a Dockable is dragged and this station has answered
* as possible parent.
*/
private class DropInfo implements CombinerSource, StationDropOperation{
/** The Dockable which is dragged */
public Dockable dockable;
/** Location of the mouse */
public int x, y;
/** Location of the title */
public int titleX, titleY;
/** Possible new parent */
public ScreenDockWindow combine;
/** Information about how to combine {@link #combine} with {@link #dockable} */
public CombinerTarget combiner;
/** whether this is a move operation or not */
public boolean move;
public Point getMousePosition(){
Point point = new Point( x, y );
SwingUtilities.convertPointFromScreen( point, combine.getDockable().getComponent() );
return point;
}
public void draw(){
dropInfo = this;
if( combine != null ){
combine.setPaintCombining( dropInfo.combiner );
}
}
public void destroy( StationDropOperation next ){
if( combine != null ){
combine.setPaintCombining( null );
}
if( dropInfo == this ){
dropInfo = null;
}
}
public DockStation getTarget(){
return ScreenDockStation.this;
}
public Dockable getItem(){
return dockable;
}
public CombinerTarget getCombination(){
return combiner;
}
public DisplayerCombinerTarget getDisplayerCombination(){
CombinerTarget target = getCombination();
if( target == null ){
return null;
}
return target.getDisplayerCombination();
}
public Dimension getSize(){
return combine.getDockable().getComponent().getSize();
}
public boolean isMouseOverTitle(){
return combine.inTitleArea( x, y );
}
public Dockable getNew(){
return dockable;
}
public Dockable getOld(){
return combine.getDockable();
}
public DockableDisplayer getOldDisplayer(){
return combine.getDockableDisplayer();
}
public DockStation getParent(){
return ScreenDockStation.this;
}
public PlaceholderMap getPlaceholders(){
for( DockablePlaceholderList<ScreenDockWindowHandle>.Item item : dockables.list() ){
ScreenDockWindowHandle handle = item.getDockable();
if( handle != null && handle.getWindow() == combine ){
return item.getPlaceholderMap();
}
}
return null;
}
public boolean isMove(){
return move;
}
public void execute(){
if( isMove() ){
move();
}
else{
drop();
}
}
private void move() {
DockUtilities.checkLayoutLocked();
if( combine != null ){
combine( dropInfo, combiner, null );
}
else{
ScreenDockWindow window = getWindow( dockable );
Point zero = window.getOffsetMove();
if( zero == null )
zero = new Point( 0, 0 );
Rectangle bounds = window.getWindowBounds();
bounds = new Rectangle( titleX - zero.x, titleY - zero.y, bounds.width, bounds.height );
window.setWindowBounds( bounds );
}
}
private void drop() {
if( combine != null ){
combine( dropInfo, combiner, null );
}
else{
Dimension size = dropSizeStrategy.getValue().getDropSize( ScreenDockStation.this, dockable );
ScreenDockProperty property = new ScreenDockProperty( titleX, titleY, size.width, size.height );
ScreenDockStation.this.drop( dockable, property, false );
}
}
}
/**
* A listener that adds itself to {@link ScreenDockWindow}s for monitoring their fullscreen state,
* their position, and calling the {@link ScreenDockWindowClosingStrategy} when necessary.
* @author Benjamin Sigg
*/
private class ScreenWindowListener implements ScreenDockStationListener, ScreenDockWindowListener{
public void fullscreenChanged( ScreenDockStation station, Dockable dockable ) {
listeners.fireDockablesRepositioned( dockable );
}
public void windowDeregistering( ScreenDockStation station, Dockable dockable, ScreenDockWindow window ) {
window.removeScreenDockWindowListener( this );
}
public void windowRegistering( ScreenDockStation station, Dockable dockable, ScreenDockWindow window ) {
window.addScreenDockWindowListener( this );
}
public void fullscreenStateChanged( ScreenDockWindow window ) {
Dockable dockable = window.getDockable();
if( dockable != null ){
for( ScreenDockStationListener listener : screenDockStationListeners() ){
listener.fullscreenChanged( ScreenDockStation.this, dockable );
}
}
}
public void shapeChanged( ScreenDockWindow window ) {
Dockable dockable = window.getDockable();
if( dockable != null ){
listeners.fireDockablesRepositioned( dockable );
}
}
public void visibilityChanged( ScreenDockWindow window ) {
// ignore
}
public void windowClosing( ScreenDockWindow window ){
ScreenDockWindowClosingStrategy strategy = getWindowClosingStrategy();
if( strategy != null ){
strategy.closing( window );
}
}
}
}