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

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

The newest version!
/*
 * 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.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.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.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.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.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.
* 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 this. */ public static final String EXTENSION_PARAM = "station"; /** a key for a property telling which boundaries a {@link ScreenDockWindow} can have */ public static final PropertyKey BOUNDARY_RESTRICTION = new PropertyKey( "ScreenDockStation.boundary_restriction", new ConstantPropertyFactory( BoundaryRestriction.MEDIUM ), true ); /** a key for a property telling how to create new windows */ public static final PropertyKey WINDOW_FACTORY = new PropertyKey( "ScreenDockStation.window_factory", new ConstantPropertyFactory( new DefaultScreenDockWindowFactory() ), true ); /** * 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 WINDOW_CONFIGURATION = new PropertyKey( "ScreenDockStation.window_configuration", new PropertyFactory(){ public ScreenDockWindowConfiguration getDefault( PropertyKey key, DockProperties properties ){ return new DefaultScreenDockWindowConfiguration( properties.getController() ); } public ScreenDockWindowConfiguration getDefault( PropertyKey key ){ return new DefaultScreenDockWindowConfiguration( null ); } }, true); /** a key for a property telling how to handle fullscreen mode */ public static final PropertyKey FULL_SCREEN_STRATEGY = new PropertyKey( "ScreenDockStation.full_screen_strategy", new PropertyFactory() { public ScreenDockFullscreenStrategy getDefault( PropertyKey key, DockProperties properties ) { return new DefaultScreenDockFullscreenStrategy(); } public ScreenDockFullscreenStrategy getDefault( PropertyKey key ){ return new DefaultScreenDockFullscreenStrategy(); } }, true ); /** global setting to change the effect happening on a double click */ public static final PropertyKey EXPAND_ON_DOUBLE_CLICK = new PropertyKey( "ScreenDockStation.double_click_fullscreen", new ConstantPropertyFactory( 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 null disables the focus stealing prevention. */ public static final PropertyKey PREVENT_FOCUS_STEALING_DELAY = new PropertyKey( "ScreenDockStation.prevent_focus_stealing_delay", new ConstantPropertyFactory( 500 ), false ); /** the {@link MagnetStrategy} decides how two {@link ScreenDockWindow}s attract each other */ public static final PropertyKey MAGNET_STRATEGY = new PropertyKey( "ScreenDockStation.magnet_strategy", new ConstantPropertyFactory( new DefaultMagnetStrategy() ){ public MagnetStrategy getDefault( PropertyKey key ){ return null; }; }, true ); /** the {@link AttractorStrategy} that tells whether two {@link Dockable}s attract each other */ public static final PropertyKey ATTRACTOR_STRATEGY = new PropertyKey( "ScreenDockStation.attractor_strategy", new DynamicPropertyFactory(){ public AttractorStrategy getDefault( PropertyKey key, DockProperties properties ){ ExtensionName name = new ExtensionName( ATTRACTOR_STRATEGY_EXTENSION, AttractorStrategy.class, null ); List 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 DROP_SIZE_STRATEGY = new PropertyKey( "ScreendockStation.drop_size_strategy", new ConstantPropertyFactory( 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 dockables = new DockablePlaceholderList(); /** All listeners that were added to this station */ private List screenDockStationListeners = new ArrayList(); /** 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; /** controlls attraction between {@link ScreenDockWindow}s */ private MagnetController magnet; /** the restrictions of the boundaries of this window*/ private PropertyValue restriction = new PropertyValue( ScreenDockStation.BOUNDARY_RESTRICTION ){ @Override protected void valueChanged( BoundaryRestriction oldValue, BoundaryRestriction newValue ) { checkWindowBoundaries(); } }; /** a factory used to create new windows for this station */ private PropertyValue windowFactory = new PropertyValue( 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 windowConfiguration = new PropertyValue( ScreenDockStation.WINDOW_CONFIGURATION ){ @Override protected void valueChanged( ScreenDockWindowConfiguration oldValue, ScreenDockWindowConfiguration newValue ){ updateWindows( true ); } }; /** the current fullscreen strategy */ private PropertyValue fullscreenStrategy = new PropertyValue( ScreenDockStation.FULL_SCREEN_STRATEGY ) { @Override protected void valueChanged( ScreenDockFullscreenStrategy oldValue, ScreenDockFullscreenStrategy newValue ) { List fullscreenWindows = new ArrayList(); 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 expandOnDoubleClick = new PropertyValue( 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 = new PropertyValue(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 dropSizeStrategy = new PropertyValue( 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 filters = new ArrayList(); /** all the {@link FullscreenActionSource}s that are currently used */ private List filterSources = new LinkedList(); /** * Constructs a new ScreenDockStation. * @param owner the window which will be used as parent for the * windows of this station, must not be null */ public ScreenDockStation( Window owner ){ if( owner == null ) throw new IllegalArgumentException( "Owner must not be null" ); init( new DirectWindowProvider( owner )); } /** * Constructs a new ScreenDockStation. * @param owner the window which will be used as parent for * the windows of this station, must not be null */ 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 FullscreenListener() ); owner.addWindowProviderListener( new WindowProviderListener(){ public void visibilityChanged( WindowProvider provider, boolean showing ){ // ignore } public void windowChanged (WindowProvider provider, Window window ){ updateWindows(); } }); } /** * 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 null if this feature should be * disabled, or the action is {@link #setFullscreenAction(ListeningDockAction) set later} */ protected ListeningDockAction createFullscreenAction(){ return new ScreenFullscreenAction( this ); } /** * Adds listener to this station. * @param listener the new listener */ public void addScreenDockStationListener( ScreenDockStationListener listener ){ screenDockStationListeners.add( listener ); } /** * Removes listener 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 null * @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 list = controller.getExtensions().load( new ExtensionName( 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 dockable, if dockable * 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 handles = dockables.dockables(); for( int i = 0, n = handles.size(); i *
  • id: the integer from children
  • *
  • x, y, width, height: the location of the child if not in fullscreen mode
  • *
  • fullscreen: whether the child is in fullscreen mode
  • *
  • 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 dockables.toMap( new PlaceholderListItemAdapter() { @Override public ConvertedPlaceholderListItem convert( int index, ScreenDockWindowHandle dockable ) { Integer id = children.get( dockable.asDockable() ); if( id == null ){ return null; } 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 next = new DockablePlaceholderList( 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 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(); if( controller != null ){ dockables.setStrategy( null ); dockables.unbind(); dockables = next; dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } else{ dockables = next; } next.read( map, new PlaceholderListItemAdapter(){ 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 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 ); } 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 true if dropInfo is valid, false 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 drop. * @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 x/y 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 drop. */ 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 dockable and its current state. * @param dockable some child of this station * @param target the final element for which the location is needd * @return the location, not null */ 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.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.Item getItem( ScreenDockProperty property ){ Path oldPlaceholder = property.getPlaceholder(); if( oldPlaceholder != null ){ DockablePlaceholderList.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 dockable. * @param dockable the {@link Dockable} to search * @return the window or null */ public ScreenDockWindow getWindow( Dockable dockable ){ int index = indexOf( dockable ); if( index < 0 ) return null; return getWindow( index ); } /** * Gets the index'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 index'th window of this station. * @param index the index of the window * @return the handle for index */ 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 null */ public Dockable[] getFullscreenChildren() { List result = new ArrayList(); 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 dockable is currently shown in fullscreen mode. * @param dockable the element to check * @return the mode * @throws IllegalArgumentException if dockable 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 dockable. * @param dockable the element whose mode is to be changed * @param fullscreen the new mode * @throws IllegalArgumentException if dockable 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 filter to this station. The filter can deny {@link Dockable}s the * possibility of being in fullscreen mode. * @param filter the new filter, not null */ public void addFullscreenFilter( ScreenDockFullscreenFilter filter ){ filters.add( filter ); filterChanged(); } /** * Removes filter 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 true, then the childs 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 true, 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 dockable, used to associate a group of other dockables * to dockable. Can be null. * @param boundsIncludeWindow if true, 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 placeholder */ 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 dockable to this station, and uses * the property 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 true if the dockable could be added, false * otherwise. */ public boolean drop( Dockable dockable, ScreenDockProperty property ){ return drop( dockable, property, true ); } /** * Tries to add the dockable to this station, and uses * the property 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 true, 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 true if the dockable could be added, false * 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 dockable at the same coordinates as location, 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 lower and the upper {@link Dockable} * to one {@link Dockable}, and replaces the lower 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 lower and the upper {@link Dockable} * to one {@link Dockable}, and replaces the lower 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 upper, may be null */ 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 source. * @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 null */ 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.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 ) { 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.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 dockable from this station.
    * Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure noone else adds or * removes Dockables. * @param dockable the {@link Dockable} to remove */ public void removeDockable( Dockable dockable ){ int index = indexOf( dockable ); if( index >= 0 ){ removeDockable( index ); } } /** * Removes the index'th {@link Dockable} of this station.
    * Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure noone else adds or * removes Dockables. * @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 window will be used * @param placeholder the name of dockable, used to place the new * {@link ScreenDockWindowHandle} at its correct position. Can be null. * @param window the window which was newly created * @param configuration the configuration that was used to create window * @return the newly created handle for window */ 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() ); 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 window was used * @param window the old window */ protected void deregister( Dockable dockable, ScreenDockWindow window ){ if( frontWindow == window ) frontWindow = null; int index = indexOf( window.getDockable() ); 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 ); dockables.remove( index ); window.setDockable( null ); window.setPaintCombining( null ); window.setController( null ); window.setFullscreenStrategy( null ); for( ScreenDockStationListener listener : screenDockStationListeners() ){ listener.windowDeregistering( this, dockable, window ); } window.destroy(); } /** * Gets the {@link WindowConfiguration} which should be used to create a new {@link ScreenDockWindow} * for dockable. * @param dockable the element that is going to be shown * @return its confugration, not null */ 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 true, then {@link ScreenDockWindowFactory#createWindow(ScreenDockStation, WindowConfiguration) createWindow} * is used and all windows are replaced, if false 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 null */ public ScreenDockWindowFactory getWindowFactory(){ return windowFactory.getValue(); } /** * Gets the property which represents the window factory. * @return the property */ protected PropertyValue 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, null 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 null */ public ScreenDockWindowConfiguration getWindowConfiguration(){ return windowConfiguration.getValue(); } /** * Gets the property which represents the window configuration. * @return the property, not null */ protected PropertyValue 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 null to use the default configuration */ public void setWindowConfiguration( ScreenDockWindowConfiguration configuration ){ windowConfiguration.setValue( configuration ); } /** * Gets the current fullscreen strategy. * @return the strategy, not null */ public ScreenDockFullscreenStrategy getFullscreenStrategy(){ return fullscreenStrategy.getValue(); } /** * Sets the strategy used to handle fullscreen mode. * @param strategy the new strategy, null will reapply the default strategy */ public void setFullscreenStrategy( ScreenDockFullscreenStrategy strategy ){ fullscreenStrategy.setValue( strategy ); } /** * Tells whether this station shows its children or not. * @return true if the windows are visible, false * otherwise * @see #setShowing(boolean) */ public boolean isShowing() { return showing; } /** * Sets the visibility of all windows of this station. * @param showing true if all windows should be visible, * false 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 true if the windows are visible, false * 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 null */ 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 null 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} controlls the * attraction between {@link ScreenDockWindow}s. * @return the controller, never null */ 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; } /** * 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.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 ); Rectangle bounds = new Rectangle( titleX, titleY, size.width, size.height ); addDockable( dockable, bounds, false ); } } } /** * A listener that adds itself to {@link ScreenDockWindow}s for monitoring their fullscreen state * and their position. * @author Benjamin Sigg */ private class FullscreenListener 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 } } }




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy