/* * 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.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.Icon; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.MouseInputListener; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.DockTheme; import bibliothek.gui.DockUI; import bibliothek.gui.Dockable; import bibliothek.gui.dock.DockHierarchyLock.Token; import bibliothek.gui.dock.action.DefaultDockActionSource; import bibliothek.gui.dock.action.DockAction; import bibliothek.gui.dock.action.DockActionSource; import bibliothek.gui.dock.action.HierarchyDockActionSource; import bibliothek.gui.dock.action.ListeningDockAction; import bibliothek.gui.dock.action.LocationHint; import bibliothek.gui.dock.component.DefaultDockStationComponentRootHandler; import bibliothek.gui.dock.component.DockComponentConfiguration; import bibliothek.gui.dock.component.DockComponentRootHandler; import bibliothek.gui.dock.control.relocator.Merger; import bibliothek.gui.dock.disable.DisablingStrategy; import bibliothek.gui.dock.disable.DisablingStrategyListener; import bibliothek.gui.dock.displayer.DisplayerRequest; import bibliothek.gui.dock.displayer.DockableDisplayerHints; import bibliothek.gui.dock.dockable.DockHierarchyObserver; import bibliothek.gui.dock.dockable.DockableStateListener; import bibliothek.gui.dock.dockable.DockableStateListenerManager; import bibliothek.gui.dock.event.DockHierarchyEvent; import bibliothek.gui.dock.event.DockHierarchyListener; import bibliothek.gui.dock.event.DockStationAdapter; import bibliothek.gui.dock.event.DockStationListener; import bibliothek.gui.dock.event.DockableListener; import bibliothek.gui.dock.event.DoubleClickListener; import bibliothek.gui.dock.event.SplitDockListener; import bibliothek.gui.dock.layout.DockableProperty; import bibliothek.gui.dock.layout.location.AsideAnswer; import bibliothek.gui.dock.layout.location.AsideRequest; import bibliothek.gui.dock.security.SecureContainer; import bibliothek.gui.dock.station.Combiner; import bibliothek.gui.dock.station.DisplayerCollection; import bibliothek.gui.dock.station.DisplayerFactory; import bibliothek.gui.dock.station.DockStationIcon; import bibliothek.gui.dock.station.DockableDisplayer; import bibliothek.gui.dock.station.DockableDisplayerListener; import bibliothek.gui.dock.station.NoStationDropOperation; import bibliothek.gui.dock.station.PlaceholderMapping; import bibliothek.gui.dock.station.StationBackgroundComponent; import bibliothek.gui.dock.station.StationChildHandle; import bibliothek.gui.dock.station.StationDragOperation; import bibliothek.gui.dock.station.StationDropItem; import bibliothek.gui.dock.station.StationDropOperation; import bibliothek.gui.dock.station.StationPaint; import bibliothek.gui.dock.station.layer.DefaultDropLayer; import bibliothek.gui.dock.station.layer.DockStationDropLayer; import bibliothek.gui.dock.station.span.Span; import bibliothek.gui.dock.station.split.DefaultSplitDividerStrategy; import bibliothek.gui.dock.station.split.DefaultSplitLayoutManager; import bibliothek.gui.dock.station.split.DefaultSplitNodeFactory; import bibliothek.gui.dock.station.split.DockableSplitDockTree; import bibliothek.gui.dock.station.split.Leaf; import bibliothek.gui.dock.station.split.Node; import bibliothek.gui.dock.station.split.Placeholder; import bibliothek.gui.dock.station.split.PutInfo; import bibliothek.gui.dock.station.split.PutInfo.Put; import bibliothek.gui.dock.station.split.Root; import bibliothek.gui.dock.station.split.SplitDividerStrategy; import bibliothek.gui.dock.station.split.SplitDockAccess; import bibliothek.gui.dock.station.split.SplitDockCombinerSource; import bibliothek.gui.dock.station.split.SplitDockFullScreenProperty; import bibliothek.gui.dock.station.split.SplitDockGrid; import bibliothek.gui.dock.station.split.SplitDockPathProperty; import bibliothek.gui.dock.station.split.SplitDockPlaceholderMapping; import bibliothek.gui.dock.station.split.SplitDockPlaceholderProperty; import bibliothek.gui.dock.station.split.SplitDockProperty; import bibliothek.gui.dock.station.split.SplitDockStationFactory; import bibliothek.gui.dock.station.split.SplitDockTree; import bibliothek.gui.dock.station.split.SplitDockTreeFactory; import bibliothek.gui.dock.station.split.SplitDropOperation; import bibliothek.gui.dock.station.split.SplitDropTreeException; import bibliothek.gui.dock.station.split.SplitFullScreenAction; import bibliothek.gui.dock.station.split.SplitLayoutManager; import bibliothek.gui.dock.station.split.SplitNode; import bibliothek.gui.dock.station.split.SplitNodeFactory; import bibliothek.gui.dock.station.split.SplitNodeVisitor; import bibliothek.gui.dock.station.split.SplitPlaceholderConverter; import bibliothek.gui.dock.station.split.SplitPlaceholderSet; import bibliothek.gui.dock.station.split.SplitSpanStrategy; import bibliothek.gui.dock.station.split.SplitTreeFactory; import bibliothek.gui.dock.station.split.SplitTreePathFactory; import bibliothek.gui.dock.station.split.layer.SideSnapDropLayer; import bibliothek.gui.dock.station.split.layer.SplitOverrideDropLayer; import bibliothek.gui.dock.station.support.CombinerSource; import bibliothek.gui.dock.station.support.CombinerTarget; import bibliothek.gui.dock.station.support.ComponentDragOperation; import bibliothek.gui.dock.station.support.DockStationListenerManager; import bibliothek.gui.dock.station.support.DockableShowingManager; import bibliothek.gui.dock.station.support.Enforcement; import bibliothek.gui.dock.station.support.PlaceholderMap; import bibliothek.gui.dock.station.support.PlaceholderStrategy; import bibliothek.gui.dock.station.support.PlaceholderStrategyListener; import bibliothek.gui.dock.station.support.RootPlaceholderStrategy; import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue; import bibliothek.gui.dock.themes.DefaultStationPaintValue; import bibliothek.gui.dock.themes.StationCombinerValue; import bibliothek.gui.dock.themes.ThemeManager; import bibliothek.gui.dock.title.ActivityDockTitleEvent; import bibliothek.gui.dock.title.ControllerTitleFactory; import bibliothek.gui.dock.title.DockTitle; import bibliothek.gui.dock.title.DockTitleFactory; import bibliothek.gui.dock.title.DockTitleRequest; import bibliothek.gui.dock.title.DockTitleVersion; import bibliothek.gui.dock.util.BackgroundAlgorithm; import bibliothek.gui.dock.util.ConfiguredBackgroundPanel; 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.Transparency; import bibliothek.gui.dock.util.extension.Extension; import bibliothek.gui.dock.util.icon.DockIcon; import bibliothek.gui.dock.util.property.ConstantPropertyFactory; import bibliothek.gui.dock.util.property.DynamicPropertyFactory; import bibliothek.util.FrameworkOnly; import bibliothek.util.Path; import bibliothek.util.Todo; import bibliothek.util.Todo.Compatibility; import bibliothek.util.Todo.Priority; import bibliothek.util.Todo.Version; import bibliothek.util.container.Tuple; /** * This station shows all its children at once. The children are separated * by small gaps which can be moved by the user. It is possible to set * one child to {@link #setFullScreen(Dockable) fullscreen}, this child will * be shown above all other children. The user can double click on the title * of a child to change its fullscreen-mode.<br> * The station tries to register a {@link DockTitleFactory} with the * ID {@link #TITLE_ID}. * @author Benjamin Sigg */ public class SplitDockStation extends SecureContainer implements Dockable, DockStation { /** The ID under which this station tries to register a {@link DockTitleFactory} */ public static final String TITLE_ID = "split"; /** This id is forwarded to {@link Extension}s which load additional {@link DisplayerFactory}s */ public static final String DISPLAYER_ID = "split"; /** * Describes which {@link KeyEvent} will maximize/normalize the currently * selected {@link Dockable}. */ public static final PropertyKey<KeyStroke> MAXIMIZE_ACCELERATOR = new PropertyKey<KeyStroke>("SplitDockStation maximize accelerator"); /** * Defines the behavior of a {@link DockStation}, how to react on a * drop-event, how to react on resize and other things related * to the layout. */ public static final PropertyKey<SplitLayoutManager> LAYOUT_MANAGER = new PropertyKey<SplitLayoutManager>("SplitDockStation layout manager", new ConstantPropertyFactory<SplitLayoutManager>(new DefaultSplitLayoutManager()), true); /** * The algorithm that allows users to resize children of a {@link SplitDockStation} by * grabbing a gap between two children and moving that gap around. */ public static final PropertyKey<SplitDividerStrategy> DIVIDER_STRATEGY = new PropertyKey<SplitDividerStrategy>("SplitDockStation divider strategy", new DynamicPropertyFactory<SplitDividerStrategy>(){ public SplitDividerStrategy getDefault( PropertyKey<SplitDividerStrategy> key, DockProperties properties ){ return new DefaultSplitDividerStrategy(); } @Override public SplitDividerStrategy getDefault( PropertyKey<SplitDividerStrategy> key ){ return null; } }, true); /** The parent of this station */ private DockStation parent; /** Listener registered to the parent. When triggered it invokes other listeners */ private VisibleListener visibleListener = new VisibleListener(); /** The controller to which this station is registered */ private DockController controller; /** The theme of this station */ private DockTheme theme; /** Combiner to {@link #dropOver(Leaf, Dockable, CombinerSource, CombinerTarget) combine} some Dockables */ private StationCombinerValue combiner; /** The type of titles which are used for this station */ private DockTitleVersion title; /** A list of {@link DockableListener} which will be invoked when something noticable happens */ private List<DockableListener> dockableListeners = new ArrayList<DockableListener>(); /** All {@link DockableStateListener}s of this station */ private DockableStateListenerManager dockableStateListeners; /** an observer ensuring that the {@link DockHierarchyEvent}s are sent properly */ private DockHierarchyObserver hierarchyObserver; /** A list of {@link SplitDockListener} which will be invoked when something noticable happens */ private List<SplitDockListener> splitListeners = new ArrayList<SplitDockListener>(); /** The handler for events and listeners concerning the visibility of children */ private DockableShowingManager visibility; /** the DockTitles which are bound to this dockable */ private List<DockTitle> titles = new LinkedList<DockTitle>(); /** the list of actions offered for this Dockable */ private HierarchyDockActionSource globalSource; /** keeps track of all the {@link Component}s of this station */ private DockComponentRootHandler rootHandler; /** * The list of all registered {@link DockStationListener DockStationListeners}. * This list can be used to send events to all listeners. */ protected DockStationListenerManager dockStationListeners = new DockStationListenerManager(this); /** Optional text for this station */ private PropertyValue<String> titleText = new PropertyValue<String>(PropertyKey.DOCK_STATION_TITLE){ @Override protected void valueChanged( String oldValue, String newValue ){ if( oldValue == null ) oldValue = ""; if( newValue == null ) newValue = ""; for( DockableListener listener : dockableListeners.toArray(new DockableListener[dockableListeners.size()]) ) listener.titleTextChanged(SplitDockStation.this, oldValue, newValue); } }; /** Optional icon for this station */ private DockIcon titleIcon; /** Optional tooltip for this station */ private PropertyValue<String> titleToolTip = new PropertyValue<String>(PropertyKey.DOCK_STATION_TOOLTIP){ @Override protected void valueChanged( String oldValue, String newValue ){ for( DockableListener listener : dockableListeners.toArray(new DockableListener[dockableListeners.size()]) ) listener.titleToolTipChanged(SplitDockStation.this, oldValue, newValue); } }; /** the manager for detailed control of the behavior of this station */ private PropertyValue<SplitLayoutManager> layoutManager = new PropertyValue<SplitLayoutManager>(LAYOUT_MANAGER){ @Override protected void valueChanged( SplitLayoutManager oldValue, SplitLayoutManager newValue ){ if( oldValue != null ) oldValue.uninstall(SplitDockStation.this); if( newValue != null ) newValue.install(SplitDockStation.this); } }; /** the strategy responsible for resizing the children of this station when the user moves a gap between them */ private PropertyValue<SplitDividerStrategy> dividerStrategy = new PropertyValue<SplitDividerStrategy>(DIVIDER_STRATEGY){ @Override protected void valueChanged( SplitDividerStrategy oldValue, SplitDividerStrategy newValue ){ if( oldValue != null ){ oldValue.uninstall( SplitDockStation.this ); } if( newValue != null && content != null ){ newValue.install( SplitDockStation.this, getContentPane() ); } } }; private PropertyValue<PlaceholderStrategy> placeholderStrategyProperty = new PropertyValue<PlaceholderStrategy>(PlaceholderStrategy.PLACEHOLDER_STRATEGY){ @Override protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){ placeholderStrategy.setStrategy(newValue); } }; /** Access to the current {@link DisablingStrategy} */ private PropertyValue<DisablingStrategy> disablingStrategy = new PropertyValue<DisablingStrategy>( DisablingStrategy.STRATEGY ){ @Override protected void valueChanged( DisablingStrategy oldValue, DisablingStrategy newValue ){ if( oldValue != null ){ oldValue.removeDisablingStrategyListener( disablingStrategyListener ); } if( newValue != null ){ newValue.addDisablingStrategyListener( disablingStrategyListener ); setDisabled( newValue.isDisabled( SplitDockStation.this )); } else{ setDisabled( false ); } } }; /** observes the {@link #disablingStrategy} and closes the front dockable if necessary */ private DisablingStrategyListener disablingStrategyListener = new DisablingStrategyListener(){ public void changed( DockElement item ){ if( item == SplitDockStation.this ){ setDisabled( disablingStrategy.getValue().isDisabled( item )); } } }; /** strategy for managing placeholders */ private RootPlaceholderStrategy placeholderStrategy = new RootPlaceholderStrategy(this); /** Whether the user can double click on a child to expand it. Default is <code>true</code>. */ private boolean expandOnDoubleclick = true; /** expands a child of this station when the user clicks twice on the child */ private FullScreenListener fullScreenListener = new FullScreenListener(); /** The list of {@link Dockable Dockables} which are shown on this station */ private List<StationChildHandle> dockables = new ArrayList<StationChildHandle>(); /** The {@link Dockable} which has the focus */ private Dockable frontDockable; /** The {@link Dockable} which is currently in fullscreen-mode. This value might be <code>null</code> */ private StationChildHandle fullScreenDockable; /** An action that is added to all children. The action changes the fullscreen-mode of the child. Can be <code>null</code> */ private ListeningDockAction fullScreenAction; /** Size of the gap between two children in pixel */ private int dividerSize = 4; /** * Relative size of the border where a {@link Dockable} will be placed aside * another Dockable when dragging the new Dockable onto this station. Should * be between 0 and 0.25f. */ private float sideSnapSize = 1 / 4f; /** * Size of the border outside this station where a {@link Dockable} will still * be considered to be dropped onto this station. Measured in pixel. */ private int borderSideSnapSize = 25; /** * Whether the bounds of this station are slightly bigger than the station itself. * Used together with {@link #borderSideSnapSize} to grab Dockables "out of the sky". * The default is <code>true</code>. */ private boolean allowSideSnap = true; /** Access to the private and protected methods for some friends of this station */ private Access access = new Access(); /** The root of the tree which determines the structure of this station */ private Root root; /** The factory responsible for creating new {@link SplitNode}s */ private SplitNodeFactory nodeFactory = new DefaultSplitNodeFactory(); /** Ensures that no placeholder is used twice on this station */ private SplitPlaceholderSet placeholderSet; /** Whether nodes can automatically be removed from the tree or not */ private int treeLock = 0; /** Information about the {@link Dockable} which is currently dragged onto this station. */ private PutInfo putInfo; /** Information about the current {@link Span}s */ private SplitSpanStrategy spanStrategy; /** Information about the {@link Dockable} that is currently removed from this station */ private ComponentDragOperation dragInfo; /** A {@link StationPaint} to draw some markings onto this station */ private DefaultStationPaintValue paint; /** A {@link DisplayerFactory} used to create {@link DockableDisplayer} for the children of this station */ private DefaultDisplayerFactoryValue displayerFactory; /** The set of displayers currently used by this station */ private DisplayerCollection displayers; /** * Whether the user can resize the content. */ private boolean resizingEnabled = true; /** If <code>true</code>, the components are resized while the split is dragged */ private boolean continousDisplay = false; /** the configurable hints for the parent of this station */ private DockableDisplayerHints hints; /** the parent of all {@link DockableDisplayer}s */ private Content content; /** the background algorithm of this station */ private Background background = new Background(); /** the newest state issued by the {@link #disablingStrategy} */ private boolean disabled = false; /** the minimum size a {@link Leaf} can have, in pixels */ private Dimension minimumLeafSize = new Dimension( 20, 20 ); /** * Constructs a new {@link SplitDockStation}. */ public SplitDockStation(){ this( true ); } /** * Creates a new {@link SplitDockStation}. * @param createFullScreenAction whether {@link #createFullScreenAction()} should be called or not */ public SplitDockStation( boolean createFullScreenAction ){ content = new Content(); content.setBackground( background ); setBasePane( content ); hierarchyObserver = new DockHierarchyObserver(this); placeholderSet = new SplitPlaceholderSet(access); dockableStateListeners = new DockableStateListenerManager( this ); paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".split", this ); combiner = new StationCombinerValue( ThemeManager.COMBINER + ".split", this ); displayerFactory = new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".split", this ); displayers = new DisplayerCollection( this, displayerFactory, DISPLAYER_ID ); displayers.addDockableDisplayerListener(new DockableDisplayerListener(){ public void discard( DockableDisplayer displayer ){ SplitDockStation.this.discard(displayer); } public void moveableElementChanged( DockableDisplayer displayer ){ // ignore } }); if( createFullScreenAction ){ fullScreenAction = createFullScreenAction(); } visibility = new DockableShowingManager(dockStationListeners); SplitDividerStrategy strategy = dividerStrategy.getValue(); if( strategy != null ){ strategy.install( this, getContentPane() ); } globalSource = new HierarchyDockActionSource(this); globalSource.bind(); spanStrategy = new SplitSpanStrategy( this ); titleIcon = new DockStationIcon( "dockStation.default", this ){ protected void changed( Icon oldValue, Icon newValue ){ for( DockableListener listener : dockableListeners.toArray( new DockableListener[ dockableListeners.size()] )){ listener.titleIconChanged( SplitDockStation.this, oldValue, newValue ); } } }; addDockStationListener(new DockStationAdapter(){ @Override public void dockableAdded( DockStation station, Dockable dockable ){ updateConfigurableDisplayerHints(); } @Override public void dockableRemoved( DockStation station, Dockable dockable ){ updateConfigurableDisplayerHints(); } }); placeholderStrategy.addListener(new PlaceholderStrategyListener(){ public void placeholderInvalidated( Set<Path> placeholders ){ removePlaceholders(placeholders); } }); addHierarchyListener( new HierarchyListener(){ public void hierarchyChanged( HierarchyEvent e ){ if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 ){ if( getDockParent() == null ){ dockableStateListeners.checkShowing(); } visibility.fire(); } } }); } private DockComponentRootHandler getRootHandler(){ if( rootHandler == null ){ rootHandler = createRootHandler(); rootHandler.addRoot( getComponent() ); } return rootHandler; } /** * Creates a new {@link DockComponentRootHandler} for monitoring all the {@link Component}s of this station. * @return the new handler */ protected DockComponentRootHandler createRootHandler() { return new DefaultDockStationComponentRootHandler( this, displayers ); } /** * Gets the root of this station, creates a root if necessary. This * method cannot be overridden while {@link #getRoot()} can. This method * just returns the value of {@link #root}, makes it a read only variable. * @return the root * @see #getRoot() */ protected final Root root(){ if( root == null ) { root = access.createRoot( -1 ); } return root; } @Override public String toString(){ if( root == null ) { return super.toString(); } else { return root.toString(); } } @Override public Dimension getMinimumSize(){ Insets insets = getInsets(); Dimension base = getRoot().getMinimumSize(); if( insets != null ) { base = new Dimension(base.width + insets.left + insets.right, base.height + insets.top + insets.bottom); } return base; } @Override public Dimension getPreferredSize(){ Insets insets = getInsets(); Dimension base = getRoot().getPreferredSize(); if( insets != null ) { base = new Dimension(base.width + insets.left + insets.right, base.height + insets.top + insets.bottom); } return base; } public DockTheme getTheme(){ return theme; } public void updateTheme(){ DockController controller = getController(); if( controller != null ) { DockTheme newTheme = controller.getTheme(); if( newTheme != theme ) { theme = newTheme; try { callDockUiUpdateTheme(); } catch( IOException ex ) { throw new RuntimeException(ex); } } } } /** * Calls the method {@link DockUI#updateTheme(DockStation, DockFactory)} * with <code>this</code> as the first argument, and an appropriate factory * as the second argument. * @throws IOException if the DockUI throws an exception */ protected void callDockUiUpdateTheme() throws IOException{ DockUI.updateTheme(this, new SplitDockStationFactory()); } /** * 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 SplitFullScreenAction(this); } /** * 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; } /** * Sets whether a double click on a child or its title can expand the child * to fullscreen or not. * @param expandOnDoubleclick <code>true</code> if the double click should * have an effect, <code>false</code> if double clicks should be ignored. */ public void setExpandOnDoubleclick( boolean expandOnDoubleclick ){ this.expandOnDoubleclick = expandOnDoubleclick; } /** * Tells whether a child expands to fullscreen when double clicked or not. * @return <code>true</code> if a double click has an effect, <code>false</code> * otherwise * @see #setExpandOnDoubleclick(boolean) */ public boolean isExpandOnDoubleclick(){ return expandOnDoubleclick; } /** * Enables the user to resize the children of this station. The default value * of this property is <code>true</code>. Note that resizing is a core * functionality of this station, disabling it should be considered * carefully. * @param resizingEnabled whether resizing is enabled or not */ public void setResizingEnabled( boolean resizingEnabled ){ this.resizingEnabled = resizingEnabled; } /** * Tells whether the user can drag dividers and resize dockables in this way. * @return <code>true</code> if resizing is allowed */ public boolean isResizingEnabled(){ return resizingEnabled; } /** * Called by the current {@link DisablingStrategy} when this station changes its state. * @param disabled whether the station is enabled or not */ protected void setDisabled( boolean disabled ){ this.disabled = disabled; if( disabled ){ setCursor( null ); } } /** * Tells the result of the current {@link DisablingStrategy}. * @return whether this station is currently enabled or not */ public boolean isDisabled(){ return disabled; } public void setDockParent( DockStation station ){ if( this.parent != null ) this.parent.removeDockStationListener(visibleListener); parent = station; if( station != null ) station.addDockStationListener(visibleListener); hierarchyObserver.update(); } public DockStation getDockParent(){ return parent; } public void setController( DockController controller ){ getRootHandler().setController( null ); super.setController( controller ); if( this.controller != controller ) { if( this.controller != null ){ this.controller.getDoubleClickController().removeListener(fullScreenListener); } for( StationChildHandle handle : dockables ) { handle.setTitleRequest(null); } this.controller = controller; getDisplayers().setController(controller); if( fullScreenAction != null ) fullScreenAction.setController(controller); titleIcon.setController(controller); titleText.setProperties(controller); layoutManager.setProperties(controller); placeholderStrategyProperty.setProperties(controller); paint.setController( controller ); displayerFactory.setController( controller ); combiner.setController( controller ); background.setController( controller ); dividerStrategy.setProperties( controller ); disablingStrategy.setProperties( controller ); spanStrategy.setController( controller ); if( controller != null ) { title = controller.getDockTitleManager().getVersion(TITLE_ID, ControllerTitleFactory.INSTANCE); controller.getDoubleClickController().addListener(fullScreenListener); } else title = null; for( StationChildHandle handle : dockables ) { handle.setTitleRequest(title); } getRootHandler().setController( controller ); hierarchyObserver.controllerChanged(controller); visibility.fire(); } } @Override public DockController getController(){ return controller; } public DockComponentConfiguration getComponentConfiguration() { return getRootHandler().getConfiguration(); } public void setComponentConfiguration( DockComponentConfiguration configuration ) { getRootHandler().setConfiguration( configuration ); } public void addDockableListener( DockableListener listener ){ dockableListeners.add(listener); } public void removeDockableListener( DockableListener listener ){ dockableListeners.remove(listener); } public void addDockHierarchyListener( DockHierarchyListener listener ){ hierarchyObserver.addDockHierarchyListener(listener); } public void removeDockHierarchyListener( DockHierarchyListener listener ){ hierarchyObserver.removeDockHierarchyListener(listener); } public void addMouseInputListener( MouseInputListener listener ){ // ignore } public void removeMouseInputListener( MouseInputListener listener ){ // ignore } public boolean accept( DockStation station ){ return true; } public boolean accept( DockStation base, Dockable neighbour ){ return true; } public Component getComponent(){ return this; } public DockElement getElement(){ return this; } public boolean isUsedAsTitle(){ return false; } public boolean shouldFocus(){ return true; } public boolean shouldTransfersFocus(){ return false; } public Point getPopupLocation( Point click, boolean popupTrigger ){ if( popupTrigger ) return click; else return null; } public String getTitleText(){ String text = titleText.getValue(); if( text == null ) return ""; else return text; } /** * Sets the text of the title of this dockable. * @param titleText the text displayed in the title */ public void setTitleText( String titleText ){ this.titleText.setValue(titleText); } public String getTitleToolTip(){ return titleToolTip.getValue(); } /** * Sets the tooltip that should be shown on any title that is {@link #bind(DockTitle) bound} * to this dockable. * @param text the tooltip, can be <code>null</code> */ public void setTitleToolTip( String text ){ titleToolTip.setValue(text); } public Icon getTitleIcon(){ return titleIcon.value(); } /** * Sets an icon that is shown in the {@link DockTitle titles} of this {@link Dockable}. * @param titleIcon the icon or <code>null</code> */ public void setTitleIcon( Icon titleIcon ){ this.titleIcon.setValue( titleIcon, true ); } /** * Resets the icon of this {@link SplitDockStation}, the default icon is shown again. */ public void resetTitleIcon(){ this.titleIcon.setValue( null ); } /** * Sets a special {@link SplitLayoutManager} which this station has to use. * @param manager the manager or <code>null</code> to return to the * manager that is specified in the {@link DockProperties} by the key * {@link #LAYOUT_MANAGER}. */ public void setSplitLayoutManager( SplitLayoutManager manager ){ layoutManager.setValue(manager); } /** * Gets the layout manager which was explicitly set. * @return the manager or <code>null</code> * @see #setSplitLayoutManager(SplitLayoutManager) * @see #getCurrentSplitLayoutManager() */ public SplitLayoutManager getSplitLayoutManager(){ return layoutManager.getOwnValue(); } /** * Gets the {@link SplitLayoutManager} that is currently used by this station. * @return the currently used layout manager * @see #setSplitLayoutManager(SplitLayoutManager) */ public SplitLayoutManager getCurrentSplitLayoutManager(){ return layoutManager.getValue(); } /** * Gets the strategy for creating and storing placeholders. Note that this is not the same * value as was set to {@link #setPlaceholderStrategy(PlaceholderStrategy)} * @return the strategy, never <code>null</code> */ public RootPlaceholderStrategy getPlaceholderStrategy(){ return placeholderStrategy; } /** * Sets the strategy for selecting placeholders when removing {@link Dockable}s from this * station. * @param strategy the new strategy or <code>null</code> to install the default strategy */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ placeholderStrategyProperty.setValue(strategy); } /** * Every child has an invisible border whose size is determined by <code>sideSnapSize</code>. * If another {@link Dockable} is dragged into that border, it is added as neighbor. * Otherwise it is merged with the present child. * @param sideSnapSize the relative size of the border, should be between * 0 and 0.5f * @throws IllegalArgumentException if the size is less than 0 */ public void setSideSnapSize( float sideSnapSize ){ if( sideSnapSize < 0 ) throw new IllegalArgumentException("sideSnapSize must not be less than 0"); this.sideSnapSize = sideSnapSize; } /** * Gets the relative size of the invisible border of all children. * @return the size * @see #setSideSnapSize(float) */ public float getSideSnapSize(){ return sideSnapSize; } /** * There is an invisible border around the station. If a {@link Dockable} is * dragged inside this border, its considered to be on the station, but * will be dropped aside the station (like the whole station is a neighbor * of the Dockable). * @param borderSideSnapSize the size of the border in pixel * @throws IllegalArgumentException if the size is smaller than 0 */ public void setBorderSideSnapSize( int borderSideSnapSize ){ if( borderSideSnapSize < 0 ) throw new IllegalArgumentException("borderSideSnapeSize must not be less than 0"); this.borderSideSnapSize = borderSideSnapSize; } /** * Gets the size of the border around the station. * @return the size in pixel * @see #setBorderSideSnapSize(int) */ public int getBorderSideSnapSize(){ return borderSideSnapSize; } /** * Sets the size of the divider-gap between the children of this station. * @param dividerSize the size of the gap in pixel * @throws IllegalArgumentException if the size is less than 0. */ public void setDividerSize( int dividerSize ){ if( dividerSize < 0 ) throw new IllegalArgumentException("dividerSize must not be less than 0"); this.dividerSize = dividerSize; doLayout(); } /** * Gets the size of the divider-gap. * @return the size * @see #setDividerSize(int) */ public int getDividerSize(){ return dividerSize; } /** * Gets the {@link SplitDividerStrategy} that is used to handle the divider of this station. * @return the current strategy */ public SplitDividerStrategy getDividerStrategy(){ return dividerStrategy.getValue(); } /** * Sets the {@link SplitDividerStrategy} that should be used to handle the divider of this station. * @param strategy the new strategy or <code>null</code> to revert to the default value */ public void setDividerStrategy( SplitDividerStrategy strategy ){ dividerStrategy.setValue( strategy ); } /** * Gets read access to the strategy which is responsible for handling the {@link Span}s of * this station. * @return the object responsible for handling {@link Span}s, not <code>null</code> */ protected SplitSpanStrategy getSpanStrategy(){ return spanStrategy; } /** * Sets the factory which is responsible for creating new {@link SplitNode}s. Clients usually have no * need to change this property. * @param factory the new factory, must not be <code>null</code> */ @FrameworkOnly public void setNodeFactory( SplitNodeFactory factory ){ if( factory == null ){ throw new IllegalArgumentException( "factory must not be null" ); } this.nodeFactory = factory; } /** * Gets the factory which is responsible for creating new {@link SplitNode}s. Clients usually have no * need to access this property. * @return the current factory, not <code>null</code> */ @FrameworkOnly public SplitNodeFactory getNodeFactory(){ return nodeFactory; } /** * Sets whether the dockables should be resized while the split * is dragged, or not. * @param continousDisplay <code>true</code> if the dockables should * be resized */ public void setContinousDisplay( boolean continousDisplay ){ this.continousDisplay = continousDisplay; } /** * Tells whether the dockables are resized while the split is * dragged, or not. * @return <code>true</code> if the dockables are resized * @see #setContinousDisplay(boolean) */ public boolean isContinousDisplay(){ return continousDisplay; } /** * Sets the minimum size a {@link Leaf} can have. The default is 20/20. * @param minimumLeafSize the new minimum size in pixels, not <code>null</code> */ public void setMinimumLeafSize( Dimension minimumLeafSize ){ if( minimumLeafSize == null ){ throw new IllegalArgumentException( "minimumLeafSize must not be null" ); } this.minimumLeafSize = minimumLeafSize; revalidate(); } /** * Gets the minimum size a {@link Leaf} can have. * @return the minimum size */ public Dimension getMinimumLeafSize(){ return minimumLeafSize; } /** * Sets whether {@link Dockable Dockables} which are dragged near * the station are captured and added to this station. * @param allowSideSnap <code>true</code> if the station can * snap Dockables which are near. * @see #setBorderSideSnapSize(int) */ public void setAllowSideSnap( boolean allowSideSnap ){ this.allowSideSnap = allowSideSnap; } /** * Tells whether the station can grab Dockables which are dragged * near the station. * @return <code>true</code> if grabbing is allowed * @see #setAllowSideSnap(boolean) */ public boolean isAllowSideSnap(){ return allowSideSnap; } public void requestDockTitle( DockTitleRequest request ){ // ignore } public void requestDisplayer( DisplayerRequest request ){ // ignore } public void changed( Dockable dockable, DockTitle title, boolean active ){ title.changed(new ActivityDockTitleEvent(this, dockable, active)); } public void requestChildDockTitle( DockTitleRequest request ){ // ignore } public void requestChildDisplayer( DisplayerRequest request ){ // ignore } public void bind( DockTitle title ){ if( titles.contains(title) ) throw new IllegalArgumentException("Title is already bound"); titles.add(title); for( DockableListener listener : dockableListeners.toArray(new DockableListener[dockableListeners.size()]) ) listener.titleBound(this, title); } public void unbind( DockTitle title ){ if( !titles.contains(title) ) throw new IllegalArgumentException("Title is unknown"); titles.remove(title); for( DockableListener listener : dockableListeners.toArray(new DockableListener[dockableListeners.size()]) ) listener.titleUnbound(this, title); } public DockTitle[] listBoundTitles(){ return titles.toArray(new DockTitle[titles.size()]); } public DockActionSource getLocalActionOffers(){ return null; } public DockActionSource getGlobalActionOffers(){ return globalSource; } public void configureDisplayerHints( DockableDisplayerHints hints ){ this.hints = hints; updateConfigurableDisplayerHints(); } /** * Gets the argument that was last used for * {@link #configureDisplayerHints(DockableDisplayerHints)}. * @return the configurable hints or <code>null</code> */ protected DockableDisplayerHints getConfigurableDisplayerHints(){ return hints; } /** * Updates the {@link #getConfigurableDisplayerHints() current hints} * of this station. */ protected void updateConfigurableDisplayerHints(){ if( hints != null ) { if( getDockableCount() == 0 ) hints.setShowBorderHint(Boolean.TRUE); else hints.setShowBorderHint(Boolean.FALSE); } } public DockStation asDockStation(){ return this; } public DefaultDockActionSource getDirectActionOffers( Dockable dockable ){ if( fullScreenAction == null ) return null; else { DefaultDockActionSource source = new DefaultDockActionSource(new LocationHint(LocationHint.DIRECT_ACTION, LocationHint.VERY_RIGHT)); source.add(fullScreenAction); return source; } } public DockActionSource getIndirectActionOffers( Dockable dockable ){ if( fullScreenAction == null ) return null; DockStation parent = dockable.getDockParent(); if( parent == null ) return null; if( parent instanceof SplitDockStation ) return null; dockable = parent.asDockable(); if( dockable == null ) return null; parent = dockable.getDockParent(); if( parent != this ) return null; DefaultDockActionSource source = new DefaultDockActionSource(fullScreenAction); source.setHint(new LocationHint(LocationHint.INDIRECT_ACTION, LocationHint.VERY_RIGHT)); return source; } public void addDockStationListener( DockStationListener listener ){ dockStationListeners.addListener(listener); } public void removeDockStationListener( DockStationListener listener ){ dockStationListeners.removeListener(listener); } public void addDockableStateListener( DockableStateListener listener ){ dockableStateListeners.addListener( listener ); } public void removeDockableStateListener( DockableStateListener listener ){ dockableStateListeners.removeListener( listener ); } /** * Adds a listener to this station. The listener is informed some * settings only available to a {@link SplitDockStation} are changed. * @param listener the new listener */ public void addSplitDockStationListener( SplitDockListener listener ){ splitListeners.add(listener); } /** * Removes an earlier added listener. * @param listener The listener to remove */ public void removeSplitDockStationListener( SplitDockListener listener ){ splitListeners.remove(listener); } 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() && (!isFullScreen() || dockable == getFullScreen()); } public boolean isStationShowing(){ return isStationVisible(); } @Deprecated @Todo( compatibility=Compatibility.BREAK_MAJOR, priority=Priority.ENHANCEMENT, target=Version.VERSION_1_1_3, description="remove this method" ) public boolean isStationVisible(){ return isDockableVisible(); } public boolean isDockableShowing(){ return isDockableVisible(); } @Deprecated @Todo( compatibility=Compatibility.BREAK_MAJOR, priority=Priority.ENHANCEMENT, target=Version.VERSION_1_1_3, description="remove this method" ) public boolean isDockableVisible(){ DockController controller = getController(); if( controller == null ){ return false; } DockStation parent = getDockParent(); if( parent != null ){ return parent.isChildShowing( this ); } return isShowing(); } public int getDockableCount(){ return dockables.size(); } public Dockable getDockable( int index ){ return dockables.get(index).getDockable(); } public DockableProperty getDockableProperty( Dockable child, Dockable target ){ DockableProperty result = getDockablePlaceholderProperty(child, target); if( result == null ) { result = getDockablePathProperty(child); } return result; } public void aside( AsideRequest request ){ boolean result = false; if( request.getPlaceholder() != null ){ // clean up old traces of this placeholder removePlaceholder( request.getPlaceholder() ); } DockableProperty location = request.getLocation(); if( location instanceof SplitDockPlaceholderProperty ){ SplitDockPlaceholderProperty property = (SplitDockPlaceholderProperty)location; SplitNode node = root().getPlaceholderNode( property.getPlaceholder() ); if( node != null ){ node.aside( request ); result = true; } else{ location = property.getBackup(); } } if( location instanceof SplitDockPathProperty ){ SplitDockPathProperty property = (SplitDockPathProperty)location; Tuple<Integer, SplitNode> start = getLowestNode( property ); result = start.getB().aside( property, start.getA(), request ); if( !result ){ location = property.toLocation(); } } if( location instanceof SplitDockProperty ){ SplitDockProperty property = (SplitDockProperty)location; result = aside( property, request ); } if( location instanceof SplitDockFullScreenProperty ){ Dockable fullscreen = getFullScreen(); if( fullscreen != null ){ Leaf leaf = getRoot().getLeaf( fullscreen ); if( request.getPlaceholder() != null ){ leaf.addPlaceholder( request.getPlaceholder() ); } DockStation fullScreenStation = fullscreen.asDockStation(); AsideAnswer answer; if( fullScreenStation != null ){ answer = request.forward( fullScreenStation ); } else{ answer = request.forward( getCombiner(), leaf.getPlaceholderMap() ); if( !answer.isCanceled() ){ leaf.setPlaceholderMap( answer.getLayout() ); } } if( answer.isCanceled() ){ return; } } request.answer( new SplitDockFullScreenProperty() ); return; } if( result ){ if( request.getPlaceholder() == null ){ location = request.getLocation(); if( location instanceof SplitDockPlaceholderProperty ){ location = ((SplitDockPlaceholderProperty)location).getBackup(); } location = location.copy(); location.setSuccessor( null ); request.answer( location ); } else{ SplitDockPathProperty path = getDockablePathProperty( request.getPlaceholder() ); SplitDockPlaceholderProperty newLocation = new SplitDockPlaceholderProperty( request.getPlaceholder(), path ); request.answer( newLocation ); } } } /** * Creates a {@link DockableProperty} for the location of <code>dockable</code>. * The location is encoded as the path through the tree to get to <code>dockable</code>. * @param dockable the element whose location is searched * @return the location */ public SplitDockPathProperty getDockablePathProperty( final Dockable dockable ){ return root().submit( new SplitTreePathFactory(){ public SplitDockPathProperty leaf( Dockable check, long id, Path[] placeholders, PlaceholderMap placeholderMap ){ if( check == dockable ){ SplitDockPathProperty path = new SplitDockPathProperty(); path.setLeafId( id ); return path; } return null; } }); } /** * Creates a {@link DockableProperty} pointing to the leaf containing <code>placeholder</code>. * @param placeholder the placeholder to search, not <code>null</code> * @return the path to <code>placeholder</code> or <code>null</code> if <code>placeholder</code> was not found */ public SplitDockPathProperty getDockablePathProperty( final Path placeholder ){ return root().submit( new SplitTreePathFactory(){ public SplitDockPathProperty leaf( Dockable check, long id, Path[] placeholders, PlaceholderMap placeholderMap ){ return placeholder( id, placeholders, placeholderMap ); } public SplitDockPathProperty placeholder( long id, Path[] placeholders, PlaceholderMap placeholderMap ){ if( contains( placeholders )){ SplitDockPathProperty path = new SplitDockPathProperty(); path.setLeafId( id ); return path; } return null; } private boolean contains( Path[] placeholders ){ for( Path path : placeholders ){ if( path.equals( placeholder )){ return true; } } return false; } }); } /** * Creates a {@link DockableProperty} for the location of <code>dockable</code>. * The location is encoded directly as the coordinates x,y,width and height * of the <code>dockable</code>. * @param dockable the element whose location is searched * @return the location */ public SplitDockProperty getDockableLocationProperty( Dockable dockable ){ Leaf leaf = getRoot().getLeaf(dockable); return new SplitDockProperty(leaf.getX(), leaf.getY(), leaf.getWidth(), leaf.getHeight()); } /** * Creates a {@link SplitDockPlaceholderProperty} for <code>dockable</code>, may * insert an additional placeholder in the tree. * @param dockable the element whose location is searched * @param target hint required to find the placeholder * @return the placeholder or <code>null</code> if the {@link #getPlaceholderStrategy() strategy} * did not assign a placeholder to <code>dockable</code> */ public SplitDockPlaceholderProperty getDockablePlaceholderProperty( Dockable dockable, Dockable target ){ Leaf leaf = getRoot().getLeaf(dockable); if( leaf == null ) { throw new IllegalArgumentException("dockable not known to this station"); } Path placeholder = getPlaceholderStrategy().getPlaceholderFor(target == null ? dockable : target); if( placeholder == null ) { return null; } placeholderSet.set(leaf, placeholder); return new SplitDockPlaceholderProperty(placeholder, getDockablePathProperty(dockable)); } public Dockable getFrontDockable(){ if( isFullScreen() ) return getFullScreen(); if( frontDockable == null && dockables.size() > 0 ) frontDockable = dockables.get(0).getDockable(); return frontDockable; } public void setFrontDockable( Dockable dockable ){ Dockable old = getFrontDockable(); this.frontDockable = dockable; if( isFullScreen() && dockable != null ) setFullScreen(dockable); if( old != dockable ){ access.dockableSelected( old ); } } /** * Tells whether a {@link Dockable} is currently shown in fullscreen-mode * on this station. A <code>true</code> result implies that * {@link #getFullScreen()} returns not <code>null</code>. * @return <code>true</code> if a child is fullscreen. */ public boolean isFullScreen(){ return fullScreenDockable != null; } /** * Gets the {@link Dockable} which is in fullscreen-mode and covers all * other children of this station. * @return the child or <code>null</code> * @see #setFullScreen(Dockable) * @see #isFullScreen() */ public Dockable getFullScreen(){ return fullScreenDockable == null ? null : fullScreenDockable.getDockable(); } /** * Tells whether {@link #createFullScreenAction()} was called and returned a value other * than <code>null</code>. * @return <code>true</code> if this station shows a fullscreen-action */ public boolean hasFullScreenAction(){ return fullScreenAction != null; } /** * Sets one of the children of this station as the one child which covers * all other children. This child is in "fullscreen"-mode. * @param dockable a child of this station or <code>null</code> if * all children should be visible. * @see #isFullScreen() */ public void setFullScreen( Dockable dockable ){ try{ access.arm(); dockable = layoutManager.getValue().willMakeFullscreen(this, dockable); Dockable oldFullScreen = getFullScreen(); if( oldFullScreen != dockable ) { if( dockable != null ) { access.repositioned.add( dockable ); Leaf leaf = getRoot().getLeaf(dockable); if( leaf == null ) throw new IllegalArgumentException("Dockable not child of this station"); fullScreenDockable = leaf.getDockableHandle(); updateVisibility(); } else { fullScreenDockable = null; updateVisibility(); } if( oldFullScreen != null ){ access.repositioned.add( oldFullScreen ); } // doLayout(); revalidate(); fireFullScreenChanged(oldFullScreen, getFullScreen()); visibility.fire(); } } finally{ access.fire(); } } /** * Calls {@link Component#setVisible(boolean)} on all current children and updates the visibility. * depending on whether there is a {@link #setFullScreen(Dockable) fullscreen-dockable} or not. */ protected void updateVisibility(){ StationChildHandle fullscreenHandle = fullScreenDockable; if( fullscreenHandle == null ){ for( StationChildHandle handle : dockables ) { handle.getDisplayer().getComponent().setVisible( true ); } } else{ for( StationChildHandle handle : dockables ) { handle.getDisplayer().getComponent().setVisible( handle == fullscreenHandle ); } } } /** * Switches the child which is in fullscreen-mode. If there is no child, * nothing will happen. If there is only one child, it will be set to * fullscreen (if it is not already fullscreen). */ public void setNextFullScreen(){ if( dockables.size() > 0 ) { if( fullScreenDockable == null ) setFullScreen(getDockable(0)); else { int index = indexOfDockable(fullScreenDockable.getDockable()); index++; index %= getDockableCount(); setFullScreen(getDockable(index)); } } } public boolean accept( Dockable child ){ return true; } public PlaceholderMapping getPlaceholderMapping() { return new SplitDockPlaceholderMapping( this ); } public PlaceholderMap getPlaceholders(){ return createPlaceholderConverter().getPlaceholders(); } public void setPlaceholders( PlaceholderMap placeholders ){ createPlaceholderConverter().setPlaceholders( placeholders ); } /** * Creates the algorithm that is used by {@link #getPlaceholders()} and {@link #setPlaceholders(PlaceholderMap)}. * @return the algorithm to handle {@link PlaceholderMap}s, not <code>null</code> */ protected SplitPlaceholderConverter createPlaceholderConverter(){ return new SplitPlaceholderConverter( this ); } public DockStationDropLayer[] getLayers(){ // do not support drag and drop if the component is invisible Component component = getComponent(); if( component.getWidth() <= 0 || component.getHeight() <= 0 ){ return new DockStationDropLayer[]{}; } return new DockStationDropLayer[]{ new DefaultDropLayer( this ), new SplitOverrideDropLayer( this ), new SideSnapDropLayer( this ) }; } public StationDropOperation prepareDrop( StationDropItem item ){ PutInfo putInfo = null; boolean move = item.getDockable().getDockParent() == this; if( move ){ putInfo = layoutManager.getValue().prepareMove( this, item ); if( putInfo != null ){ if( putInfo.getNode() == null ){ return new NoStationDropOperation( this, item.getDockable() ); } prepareCombine( putInfo, move, item ); } } else{ if( SwingUtilities.isDescendingFrom(getComponent(), item.getDockable().getComponent()) ){ putInfo = null; } else{ putInfo = layoutManager.getValue().prepareDrop( this, item ); } if( putInfo != null ){ prepareCombine( putInfo, move, item ); } } if( putInfo == null ){ return null; } return new SplitDropOperation( access, putInfo, item, move ); } public StationDragOperation prepareDrag( Dockable dockable ){ dragInfo = new ComponentDragOperation( dockable, this ){ @Override protected void destroy(){ dragInfo = null; } }; return dragInfo; } /** * Gets the location where the currently dragged {@link Dockable} would be dropped. * @return a possible location, may be <code>null</code> */ public PutInfo getDropInfo(){ return putInfo; } /** * Sets the current information telling where and how an item is to be dropped. * @param putInfo the current drop information */ protected void setDropInfo( PutInfo putInfo ){ this.putInfo = putInfo; setPut( putInfo ); repaint(); } /** * Forward call to {@link SplitSpanStrategy#setPut(PutInfo)}. * @param putInfo the new information about where an item is about to be dropped */ protected void setPut( PutInfo putInfo ){ spanStrategy.setPut( putInfo ); } /** * Resets the information telling where and how an item is to be dropped. */ protected void unsetDropInfo(){ this.putInfo = null; unsetPut(); repaint(); } /** * Forward call to {@link SplitSpanStrategy#unsetPut()}. */ protected void unsetPut(){ spanStrategy.unsetPut(); } private void prepareCombine( PutInfo putInfo, boolean move, StationDropItem item ){ if( putInfo.getCombinerSource() == null && putInfo.getCombinerTarget() == null ){ if( putInfo.getNode() instanceof Leaf ){ Point mouseOnStation = new Point( item.getMouseX(), item.getMouseY() ); SwingUtilities.convertPointFromScreen( mouseOnStation, getComponent() ); SplitDockCombinerSource source = new SplitDockCombinerSource( putInfo, this, mouseOnStation ); Enforcement force; if( putInfo.getPut() == PutInfo.Put.CENTER ){ force = Enforcement.EXPECTED; } else if( putInfo.getPut() == PutInfo.Put.TITLE ){ force = Enforcement.HARD; } else{ force = Enforcement.WHISHED; } CombinerTarget target = getCombiner().prepare( source, force ); if( target == null && putInfo.isCombining() && putInfo.getDockable().asDockStation() != null ){ DockController controller = getController(); if( controller != null ){ Merger merger = controller.getRelocator().getMerger(); target = getCombiner().prepare( source, Enforcement.HARD ); putInfo.setCombination( source, target ); if( !merger.canMerge( new SplitDropOperation( access, putInfo, item, move ), this, putInfo.getDockable().asDockStation() ) ){ putInfo.setCombination( null, null ); } } } putInfo.setCombination( source, target ); } } } public void drop( Dockable dockable ){ addDockable( dockable, null ); } public boolean drop( Dockable dockable, DockableProperty property ){ if( property instanceof SplitDockProperty ) { return drop(dockable, (SplitDockProperty) property); } else if( property instanceof SplitDockPathProperty ) { return drop(dockable, (SplitDockPathProperty) property); } else if( property instanceof SplitDockPlaceholderProperty ) { return drop(dockable, (SplitDockPlaceholderProperty) property); } else if( property instanceof SplitDockFullScreenProperty ) { return drop(dockable, (SplitDockFullScreenProperty) property); } else { return false; } } /** * Moves the <code>dockable</code> described by <code>putInfo</code> at a new location * @param putInfo description of the new location * @param item more information about the drag and drop operation that is currently happening */ private void move( PutInfo putInfo, StationDropItem item ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); Root root = access.getOwner().getRoot(); Leaf leaf = root.getLeaf(putInfo.getDockable()); SplitNode parent = putInfo.getNode(); if( leaf.getParent() == parent ) { while( parent != null ){ if( parent == root ) { // no movement possible return; } else { Node node = (Node)parent; SplitNode next; if( node.getLeft() == leaf ){ next = node.getRight(); } else{ next = node.getLeft(); } if( next.isVisible() ){ putInfo.setNode( next ); break; } parent = parent.getParent(); } } } putInfo.setLeaf(leaf); if( putInfo.getPut() == Put.CENTER ) { leaf.placehold(false); } else { leaf.delete(true); } drop( DockHierarchyLock.acquireFake(), putInfo, item ); } finally{ access.fire(); } } /** * Adds the {@link Dockable} given by <code>putInfo</code> to this station. * @param token if <code>null</code>, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently * @param putInfo information about where to drop the new {@link Dockable} * @param item detailed information about the drag and drop operation that is going on */ private void drop( DockHierarchyLock.Token token, PutInfo putInfo, StationDropItem item ){ try{ boolean fire = token == null; access.arm(); DockUtilities.checkLayoutLocked(); if( putInfo.getNode() == null ) { if( fire ) { DockUtilities.ensureTreeValidity(SplitDockStation.this, putInfo.getDockable()); token = DockHierarchyLock.acquireLinking( SplitDockStation.this, putInfo.getDockable() ); } try{ if( fire ){ dockStationListeners.fireDockableAdding(putInfo.getDockable()); } addDockable( putInfo.getDockable(), token ); if( fire ) { dockStationListeners.fireDockableAdded(putInfo.getDockable()); } } finally{ if( fire ){ token.release(); } } } else { boolean finish = false; if( putInfo.getCombinerTarget() != null ) { if( putInfo.getNode() instanceof Leaf ) { if( putInfo.getLeaf() != null ) { if( fire ){ token = DockHierarchyLock.acquireUnlinking( SplitDockStation.this, putInfo.getLeaf().getDockable() ); } try{ putInfo.getLeaf().setDockable( null, token ); putInfo.setLeaf(null); } finally{ if( fire ){ token.release(); } } } if( dropOver((Leaf) putInfo.getNode(), putInfo.getDockable(), putInfo.getCombinerSource(), putInfo.getCombinerTarget() ) ) { finish = true; } } else { putInfo.setPut(PutInfo.Put.TOP); } } if( !finish ) { updateBounds(); layoutManager.getValue().calculateDivider( SplitDockStation.this, putInfo, root().getLeaf(putInfo.getDockable()), item ); dropAside( putInfo.getNode(), putInfo.getPut(), putInfo.getDockable(), putInfo.getLeaf(), putInfo.getDivider(), token ); } } revalidate(); } finally{ access.fire(); } } /** * Tries to add <code>Dockable</code> such that the boundaries given * by <code>property</code> are full filled. * @param dockable a new child of this station * @param property the preferred location of the child * @return <code>true</code> if the child could be added, <code>false</code> * if no location could be found */ public boolean drop( Dockable dockable, SplitDockProperty property ){ return drop(dockable, property, root()); } /** * Tries to add <code>Dockable</code> such that the boundaries given * by <code>property</code> are full filled. * @param dockable a new child of this station * @param property the preferred location of the child * @param root the root of all possible parents where the child could be inserted * @return <code>true</code> if the child could be added, <code>false</code> * if no location could be found */ private boolean drop( final Dockable dockable, final SplitDockProperty property, SplitNode root ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); if( getDockableCount() == 0 ) { if( !DockUtilities.acceptable( this, dockable ) ){ return false; } drop(dockable); return true; } updateBounds(); DropInfo info = getDropInfo( property, dockable, root ); if( info.bestLeaf != null ) { DockStation station = info.bestLeaf.getDockable().asDockStation(); DockableProperty successor = property.getSuccessor(); if( station != null && successor != null ) { if( station.drop(dockable, successor) ) { validate(); return true; } } if( info.bestLeafIntersection > 0.75 ) { if( station != null && DockUtilities.acceptable( station, dockable ) ) { station.drop(dockable); validate(); return true; } else { boolean result = dropOver(info.bestLeaf, dockable, property.getSuccessor(), null, null); validate(); return result; } } } if( info.bestNode != null ) { if( !DockUtilities.acceptable( this, dockable ) ){ return false; } double divider = info.getDivider( property ); return dropAside( info.bestNode, info.bestNodePut, dockable, null, divider, null ); } repaint(); return false; } finally{ access.fire(); } } /** * Tries to put a new location "aside" the one described by <code>property</code>. * @param property the location whose neighbor is to be created * @param request information about the current location * @return <code>true</code> if the operation was a success, <code>false</code> if not */ private boolean aside( SplitDockProperty property, AsideRequest request ){ if( request.getPlaceholder() == null ){ return false; } DropInfo info = getDropInfo( property, null, getRoot() ); if( info.bestLeaf != null ){ if( property.getSuccessor() != null && info.bestLeafIntersection > 0.75 ){ DockStation station = info.bestLeaf.getDockable().asDockStation(); AsideAnswer answer; if( station != null ){ answer = request.forward( station ); } else{ PlaceholderMap layout = info.bestLeaf.getPlaceholderMap(); answer = request.forward( getCombiner(), layout ); if( !answer.isCanceled() ){ info.bestLeaf.setPlaceholderMap( answer.getLayout() ); } } return !answer.isCanceled(); } } if( info.bestNode != null ){ double divider = info.getDivider( property ); Placeholder placeholder = new Placeholder( access, -1 ); placeholder.setPlaceholders( new Path[]{ request.getPlaceholder() } ); createAside( info.bestNode, info.bestNodePut, placeholder, divider ); AsideAnswer answer = request.forward( getCombiner(), placeholder.getPlaceholderMap() ); if( !answer.isCanceled() ){ info.bestLeaf.setPlaceholderMap( answer.getLayout() ); } return !answer.isCanceled(); } return false; } /** * Information about where to put a {@link Dockable} such that its boundaries match * some preset boundaries. * @author Benjamin Sigg */ private static class DropInfo { public Leaf bestLeaf; public double bestLeafIntersection; public SplitNode bestNode; public double bestNodeIntersection = Double.POSITIVE_INFINITY; public PutInfo.Put bestNodePut; public double getDivider( SplitDockProperty property ){ double divider = 0.5; if( bestNodePut == PutInfo.Put.LEFT ) { divider = property.getWidth() / bestNode.getWidth(); } else if( bestNodePut == PutInfo.Put.RIGHT ) { divider = 1 - property.getWidth() / bestNode.getWidth(); } else if( bestNodePut == PutInfo.Put.TOP ) { divider = property.getHeight() / bestNode.getHeight(); } else if( bestNodePut == PutInfo.Put.BOTTOM ) { divider = 1 - property.getHeight() / bestNode.getHeight(); } return Math.max(0, Math.min(1, divider)); } } /** * Searches for the best place for <code>dockable</code> such that its boundaries met * <code>property</code>. * @param property the location of <code>dockable</code> * @param dockable the element to place, can be <code>null</code> * @param root a node that must be part of the path for <code>dockable</code> * @return the best place for <code>dockable</code> */ protected DropInfo getDropInfo( final SplitDockProperty property, final Dockable dockable, SplitNode root ){ final DropInfo info = new DropInfo(); root.visit(new SplitNodeVisitor(){ public void handleLeaf( Leaf leaf ){ double intersection = leaf.intersection( property ); if( intersection > info.bestLeafIntersection ) { info.bestLeafIntersection = intersection; info.bestLeaf = leaf; } handleNeighbour(leaf); } public void handleNode( Node node ){ if( node.isVisible() ) { handleNeighbour(node); } } public void handleRoot( Root root ){ // do nothing } public void handlePlaceholder( Placeholder placeholder ){ // ignore } private void handleNeighbour( SplitNode node ){ if( dockable == null || DockUtilities.acceptable( SplitDockStation.this, dockable ) ){ double x = node.getX(); double y = node.getY(); double width = node.getWidth(); double height = node.getHeight(); double left = Math.abs(x - property.getX()); double right = Math.abs(x + width - property.getX() - property.getWidth()); double top = Math.abs(y - property.getY()); double bottom = Math.abs(y + height - property.getY() - property.getHeight()); double value = left + right + top + bottom; value -= Math.max(Math.max(left, right), Math.max(top, bottom)); double kx = property.getX() + property.getWidth() / 2; double ky = property.getY() + property.getHeight() / 2; PutInfo.Put put = node.relativeSidePut(kx, ky); double px, py; if( put == PutInfo.Put.TOP ) { px = x + 0.5 * width; py = y + 0.25 * height; } else if( put == PutInfo.Put.BOTTOM ) { px = x + 0.5 * width; py = y + 0.75 * height; } else if( put == PutInfo.Put.LEFT ) { px = x + 0.25 * width; py = y + 0.5 * height; } else { px = x + 0.5 * width; py = y + 0.75 * height; } double distance = Math.pow((kx - px) * (kx - px) + (ky - py) * (ky - py), 0.25); value *= distance; if( value < info.bestNodeIntersection ) { info.bestNodeIntersection = value; info.bestNode = node; info.bestNodePut = put; } } } }); return info; } /** * Tries to insert <code>dockable</code> at a location such that the path * to that location is the same as described in <code>property</code>. * @param dockable the element to insert * @param property the preferred path to the element * @return <code>true</code> if the element was successfully inserted */ public boolean drop( Dockable dockable, SplitDockPathProperty property ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); // use the ids of the topmost nodes in the path to find a node of this station Tuple<Integer, SplitNode> startNode = getLowestNode( property ); int index = startNode.getA(); SplitNode start = startNode.getB(); updateBounds(); boolean done = start.insert(property, index, dockable); if( done ) revalidate(); return done; } finally{ access.fire(); } } /** * Searches the lowest node whose {@link SplitNode#getId() identifier} is equal to * an identifier from <code>property</code>. In the best case scenario this method directly * returns the {@link Leaf} to which <code>property</code> points.<br> * @param property a path in which a {@link SplitNode} is searched * @return The node closest to the leaf and in <code>property</code>, can be the {@link #root()}. And * the index of the node for calling {@link SplitDockPathProperty#getNode(int)}.s */ protected Tuple<Integer, SplitNode> getLowestNode( SplitDockPathProperty property ){ SplitNode start = null; int index = 0; long leafId = property.getLeafId(); if( leafId != -1 ) { start = getNode(leafId); if( start != null ) { index = property.size(); } } if( start == null ) { for( index = property.size() - 1; index >= 0; index-- ) { SplitDockPathProperty.Node node = property.getNode(index); long id = node.getId(); if( id != -1 ) { start = getNode(id); if( start != null ){ break; } } } } if( start == null || index < 0 ) { start = root(); index = 0; } return new Tuple<Integer, SplitNode>( index, start ); } /** * Drops <code>dockable</code> at the placeholder that is referenced by <code>property</code>. This * action removes the placeholder from the tree. * @param dockable the element to add * @param property the location of <code>dockable</code> * @return <code>true</code> if the the operation was a success, <code>false</code> if not */ public boolean drop( Dockable dockable, SplitDockPlaceholderProperty property ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); validate(); return root().insert(property, dockable); } finally{ access.fire(); } } /** * Drops <code>dockable</code> on this station, may exchange the full screen element to ensure that * <code>dockable</code> is displayed. * @param dockable the element to drop * @param property the location of <code>dockable</code> * @return <code>true</code> if the operation was a success, <code>false</code> if not */ public boolean drop( Dockable dockable, SplitDockFullScreenProperty property ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); DockableProperty successor = property.getSuccessor(); if( dockable.getDockParent() == this ) { setFullScreen(dockable); return true; } Dockable currentFullScreen = getFullScreen(); if( currentFullScreen == null ) { return false; } DockStation currentFullScreenStation = currentFullScreen.asDockStation(); if( currentFullScreenStation != null ){ if( successor != null ){ if( currentFullScreenStation.drop( dockable, successor )){ return true; } } return false; } else{ Leaf leaf = getRoot().getLeaf(currentFullScreen); setFullScreen(null); if( !dropOver(leaf, dockable, successor, null, null) ){ return false; } Dockable last = dockable; while( dockable != null && dockable != this ){ last = dockable; DockStation station = dockable.getDockParent(); dockable = station == null ? null : station.asDockable(); } if( last != null ){ setFullScreen(last); } return true; } } finally{ access.fire(); } } /** * Combines the {@link Dockable} of <code>leaf</code> and <code>dockable</code> * to a new child of this station. No checks whether the two elements accepts * each other nor if the station accepts the new child <code>dockable</code> * are performed. * @param leaf the leaf which will be combined with <code>dockable</code> * @param dockable a {@link Dockable} which is dropped over <code>leaf</code> * @param source information about the combination, may be <code>null</code> * @param target information about the combination, may be <code>null</code> * @return <code>true</code> if the operation was successful, <code>false</code> * otherwise */ protected boolean dropOver( Leaf leaf, Dockable dockable, CombinerSource source, CombinerTarget target ){ return dropOver(leaf, dockable, null, source, target ); } /** * Combines the {@link Dockable} of <code>leaf</code> and <code>dockable</code> * to a new child of this station. No checks whether the two elements accepts * each other nor if the station accepts the new child <code>dockable</code> * are performed. * @param leaf the leaf which will be combined with <code>dockable</code> * @param dockable a {@link Dockable} which is dropped over <code>leaf</code> * @param property a hint at which position <code>dockable</code> should be * in the combination. * @param source information about the combination, may be <code>null</code> * @param target information about the combination, may be <code>null</code> * @return <code>true</code> if the operation was successful, <code>false</code> * otherwise */ protected boolean dropOver( Leaf leaf, Dockable dockable, DockableProperty property, CombinerSource source, CombinerTarget target ){ if( !DockUtilities.acceptable( this, leaf.getDockable(), dockable ) ){ return false; } try{ access.arm(); DockUtilities.checkLayoutLocked(); DockUtilities.ensureTreeValidity(this, dockable); if( source == null || target == null ){ PutInfo info = new PutInfo( leaf, Put.TITLE, dockable, true ); source = new SplitDockCombinerSource( info, this, null ); target = combiner.prepare( source, Enforcement.HARD ); } if( leaf.getDockable() != null ){ Dockable oldDockable = leaf.getDockable(); DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, oldDockable ); try{ dockStationListeners.fireDockableRemoving( oldDockable ); leaf.setDockable( null, token ); dockStationListeners.fireDockableRemoved( oldDockable ); } finally{ token.release(); } } Dockable combination = combiner.combine( source, target ); leaf.setPlaceholderMap(null); if( property != null ) { DockStation combinedStation = combination.asDockStation(); if( combinedStation != null && dockable.getDockParent() == combinedStation ) { if( source.getPlaceholders() == null || getPlaceholderStrategy().getPlaceholderFor( dockable ) == null ){ // if a placeholder is present, then the combiner should have set the dockable at the correct position combinedStation.move(dockable, property); } } } DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, combination ); try{ dockStationListeners.fireDockableAdding(combination); leaf.setDockable( combination, token ); dockStationListeners.fireDockableAdded(combination); } finally{ token.release(); } revalidate(); repaint(); return true; } finally{ access.fire(); } } /** * Adds <code>dockable</code> at the side <code>put</code> of * <code>neighbor</code>. The divider is set to the value of <code>divider</code>, * and if <code>fire</code> is activated, some events are fired. There are * no checks whether <code>dockable</code> accepts this station or anything * else. * @param neighbor The node which will be the neighbor of <code>dockable</code> * @param put The side on which <code>dockable</code> should be added in * respect to <code>neighbor</code>. * @param dockable the new child of this station * @param leaf the leaf which contains <code>dockable</code>, can be <code>null</code> * @param divider the divider-location, a value between 0 and 1 * @param token if <code>null</code>, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently * @return <code>true</code> if the operation was a success, <code>false</code> otherwise */ protected boolean dropAside( SplitNode neighbor, PutInfo.Put put, Dockable dockable, Leaf leaf, double divider, DockHierarchyLock.Token token ){ if( !DockUtilities.acceptable( this, dockable ) ){ return false; } try{ boolean fire = token == null; access.arm(); DockUtilities.checkLayoutLocked(); if( fire ) { DockUtilities.ensureTreeValidity(this, dockable); token = DockHierarchyLock.acquireLinking( this, dockable ); } try{ if( fire ){ dockStationListeners.fireDockableAdding(dockable); } boolean leafSet = false; if( leaf == null ) { leaf = access.createLeaf( -1 ); leafSet = true; } createAside( neighbor, put, leaf, divider ); if( leafSet ) { leaf.setDockable( dockable, token ); } if( fire ) { dockStationListeners.fireDockableAdded(dockable); } revalidate(); repaint(); } finally{ if( fire ){ token.release(); } } } finally{ access.fire(); } return true; } /** * Creates a new {@link Node} having <code>neighbor</code> and <code>child</code> as a children. * @param neighbor one of the new children * @param put the location of <code>child</code> relative to <code>neighbor</code> * @param child the other new child * @param divider how space is divided between <code>neighbor</code> and <code>child</code> */ private void createAside( SplitNode neighbor, PutInfo.Put put, SplitNode child, double divider ){ SplitNode parent = neighbor.getParent(); // Node herstellen Node node = null; updateBounds(); int location = parent.getChildLocation(neighbor); node = access.createNode( -1 ); if( put == PutInfo.Put.TOP ) { node.setLeft( child ); node.setRight( neighbor ); node.setOrientation( Orientation.VERTICAL ); } else if( put == PutInfo.Put.BOTTOM ) { node.setLeft( neighbor ); node.setRight( child ); node.setOrientation( Orientation.VERTICAL ); } else if( put == PutInfo.Put.LEFT ) { node.setLeft( child ); node.setRight( neighbor ); node.setOrientation( Orientation.HORIZONTAL ); } else { node.setLeft( neighbor ); node.setRight( child ); node.setOrientation( Orientation.HORIZONTAL ); } node.setDivider(divider); parent.setChild(node, location); invalidate(); } public void move( Dockable dockable, DockableProperty property ){ // do nothing } /** * Copies the layout of <code>grid</code>. Any changes to <code>grid</code> * afterwards will not affect this station, nor will changes on this station * affect <code>grid</code>. * @param grid the layout to drop, not <code>null</code> * @see #dropTree(SplitDockTree) */ public void dropGrid( SplitDockGrid grid ){ dropTree( grid.toTree() ); } /** * Removes all children from this station and then adds the contents * that are stored in <code>tree</code>. Calling this method is equivalent * to <code>dropTree( tree, true );</code> * @param tree the new set of children * @throws SplitDropTreeException If the tree is not acceptable. */ public void dropTree( SplitDockTree<Dockable> tree ){ dropTree(tree, true); } /** * Removes all children from this station and then adds the contents * that are stored in <code>tree</code>. * @param tree the new set of children * @param checkValidity whether to ensure that the new elements are * accepted or not. * @throws SplitDropTreeException if <code>checkValidity</code> is * set to <code>true</code> and the tree is not acceptable */ public void dropTree( SplitDockTree<Dockable> tree, boolean checkValidity ){ if( tree == null ) throw new IllegalArgumentException("Tree must not be null"); DockUtilities.checkLayoutLocked(); DockController controller = getController(); try { access.arm(); treeLock++; if( controller != null ){ controller.freezeLayout(); } setFullScreen(null); removeAllDockables(); // ensure valid tree for( Dockable dockable : tree.getDockables() ) { DockUtilities.ensureTreeValidity(this, dockable); } SplitDockTree<Dockable>.Key rootKey = tree.getRoot(); if( rootKey != null ) { Map<Leaf, Dockable> linksToSet = new HashMap<Leaf, Dockable>(); root().evolve(rootKey, checkValidity, linksToSet); for( Map.Entry<Leaf, Dockable> entry : linksToSet.entrySet() ) { entry.getKey().setDockable( entry.getValue(), null ); } updateBounds(); } } finally { treeLock--; if( controller != null ){ controller.meltLayout(); } access.fire(); } } /** * Gets the contents of this station as a {@link SplitDockTree}. * @return the tree */ public DockableSplitDockTree createTree(){ DockableSplitDockTree tree = new DockableSplitDockTree(); createTree(new SplitDockTreeFactory(tree)); return tree; } /** * Writes the contents of this station into <code>factory</code>. * @param factory the factory to write into */ public void createTree( SplitDockTreeFactory factory ){ root().submit(factory); } /** * Visits the internal structure of this station. * @param <N> the type of result this method produces * @param factory a factory that will collect information * @return the result of <code>factory</code> */ public <N> N visit( SplitTreeFactory<N> factory ){ return root().submit(factory); } public boolean canDrag( Dockable dockable ){ return true; } public void drag( Dockable dockable ){ if( dockable.getDockParent() != this ) throw new IllegalArgumentException("The dockable cannot be dragged, it is not child of this station."); removeDockable(dockable); } /** * Sends a message to all registered instances of {@link SplitDockListener}, * that the {@link Dockable} in fullscreen-mode has changed. * @param oldDockable the old fullscreen-Dockable, can be <code>null</code> * @param newDockable the new fullscreen-Dockable, can be <code>null</code> */ protected void fireFullScreenChanged( Dockable oldDockable, Dockable newDockable ){ for( SplitDockListener listener : splitListeners.toArray(new SplitDockListener[splitListeners.size()]) ) listener.fullScreenDockableChanged(this, oldDockable, newDockable); } /** * Informs all {@link DockableListener}s that <code>title</code> is no longer * considered to be a good title and should be exchanged. * @param title a title, can be <code>null</code> */ protected void fireTitleExchanged( DockTitle title ){ for( DockableListener listener : dockableListeners.toArray(new DockableListener[dockableListeners.size()]) ) listener.titleExchanged(this, title); } /** * Informs all {@link DockableListener}s that all bound titles and the * <code>null</code> title are no longer considered good titles and * should be replaced */ protected void fireTitleExchanged(){ DockTitle[] bound = listBoundTitles(); for( DockTitle title : bound ) fireTitleExchanged(title); fireTitleExchanged(null); } public Dockable asDockable(){ return this; } /** * Gets a {@link StationPaint} to paint markings on this station. * @return the paint */ public DefaultStationPaintValue getPaint(){ return paint; } /** * Gets a {@link DisplayerFactory} to create new {@link DockableDisplayer} * for this station. * @return the factory */ public DefaultDisplayerFactoryValue getDisplayerFactory(){ return displayerFactory; } /** * Gets the set of {@link DockableDisplayer displayers} that are currently * used by this station. * @return the set of displayers */ public DisplayerCollection getDisplayers(){ return displayers; } /** * Gets a {@link Combiner} to combine {@link Dockable Dockables} on * this station. * @return the combiner */ public StationCombinerValue getCombiner(){ return combiner; } @Override protected void paintOverlay( Graphics g ){ if( putInfo != null && !putInfo.willHaveNoEffect() ) { DefaultStationPaintValue paint = getPaint(); if( putInfo.getNode() == null ) { Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight()); paint.drawInsertion(g, bounds, bounds); } else { CombinerTarget target = putInfo.getCombinerTarget(); if( target == null ){ SplitNode node = putInfo.getNode(); if( node.getParent() == getRoot() ){ node = getRoot(); } Rectangle bounds = node.getBounds(); if( putInfo.getPut() == PutInfo.Put.LEFT ) { bounds.width = (int) (bounds.width * putInfo.getDivider() + 0.5); } else if( putInfo.getPut() == PutInfo.Put.RIGHT ) { int width = bounds.width; bounds.width = (int) (bounds.width * (1 - putInfo.getDivider()) + 0.5); bounds.x += width - bounds.width; } else if( putInfo.getPut() == PutInfo.Put.TOP ) { bounds.height = (int) (bounds.height * putInfo.getDivider() + 0.5); } else if( putInfo.getPut() == PutInfo.Put.BOTTOM ) { int height = bounds.height; bounds.height = (int) (bounds.height * (1 - putInfo.getDivider()) + 0.5); bounds.y += height - bounds.height; } paint.drawInsertion(g, putInfo.getNode().getBounds(), bounds); } else{ Rectangle bounds = putInfo.getNode().getBounds(); StationPaint stationPaint = paint.get(); if( stationPaint != null ){ target.paint( g, getComponent(), stationPaint, bounds, bounds ); } } } } SplitDividerStrategy dividerStrategy = this.dividerStrategy.getValue(); if( dividerStrategy != null ){ dividerStrategy.paint( this, g ); } if( dragInfo != null && dragInfo.getDockable() != null ){ Leaf leaf = getRoot().getLeaf( dragInfo.getDockable() ); StationPaint stationPaint = paint.get(); if( stationPaint != null && leaf != null ){ Rectangle bounds = leaf.getBounds(); stationPaint.drawRemoval( g, this, bounds, bounds ); } } } /** * Adds <code>dockable</code> to this station. * @param dockable A {@link Dockable} which must not be a child * of this station. */ public void addDockable( Dockable dockable ){ addDockable( dockable, null ); } /** * Adds <code>dockable</code> to this station and fires events * only if <code>fire</code> is <code>true</code>. * @param dockable the new child of this station * @param token if <code>null</code>, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently */ private void addDockable( Dockable dockable, DockHierarchyLock.Token token ){ try{ boolean fire = token == null; access.arm(); DockUtilities.checkLayoutLocked(); if( fire ){ DockUtilities.ensureTreeValidity(this, dockable); token = DockHierarchyLock.acquireLinking( this, dockable ); } try{ if( fire ){ dockStationListeners.fireDockableAdding(dockable); } Leaf leaf = access.createLeaf( -1 ); Root root = root(); if( root.getChild() == null ) { root.setChild(leaf); } else { SplitNode child = root.getChild(); root.setChild( null ); Node node = access.createNode( -1 ); node.setLeft( leaf ); node.setRight( child ); root.setChild( node ); } leaf.setDockable( dockable, token ); if( fire ) { dockStationListeners.fireDockableAdded(dockable); } revalidate(); } finally{ if( fire ){ token.release(); } } } finally{ access.fire(); } } 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 previous, Dockable next ){ replace(previous, next, false); } private void replace( Dockable previous, Dockable next, boolean station ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); if( previous == null ) throw new NullPointerException("previous must not be null"); if( next == null ) throw new NullPointerException("next must not be null"); if( previous != next ) { Leaf leaf = root().getLeaf(previous); if( leaf == null ) throw new IllegalArgumentException("Previous is not child of this station"); DockUtilities.ensureTreeValidity(this, next); boolean wasFullScreen = isFullScreen() && getFullScreen() == previous; leaf.setDockable(next, null, true, station); if( wasFullScreen ) setFullScreen(next); revalidate(); repaint(); } } finally{ access.fire(); } } /** * Adds <code>handle</code> to the list of dockables,also adds the {@link DockableDisplayer} of the new handle to this station. * @param handle the element to add * @param token tells whether the operation is monitored or not. If the value is <code>null</code> then this method * will acquire a token itself and fire events, otherwise this method will silently be executed. */ private void addHandle( StationChildHandle handle, DockHierarchyLock.Token token ){ Dockable dockable = handle.getDockable(); DockUtilities.ensureTreeValidity(this, dockable); boolean fire = token == null; if( fire ){ token = DockHierarchyLock.acquireLinking( this, dockable ); } try{ if( fire ){ dockStationListeners.fireDockableAdding(dockable); } dockables.add(handle); dockable.setDockParent(this); handle.updateDisplayer(); DockableDisplayer displayer = handle.getDisplayer(); getContentPane().add(displayer.getComponent()); displayer.getComponent().setVisible(!isFullScreen()); if( fire ){ dockStationListeners.fireDockableAdded(dockable); } } finally{ if( fire ){ token.release(); } } } /** * Removes <code>displayer</code> and creates a replacement. * @param displayer the displayer to replaces */ protected void discard( DockableDisplayer displayer ){ int index = indexOfDockable(displayer.getDockable()); if( index < 0 ) throw new IllegalArgumentException("displayer unknown to this station: " + displayer); Dockable dockable = displayer.getDockable(); boolean visible = displayer.getComponent().isVisible(); Leaf leaf = root().getLeaf(dockable); getContentPane().remove(displayer.getComponent()); StationChildHandle handle = leaf.getDockableHandle(); handle.updateDisplayer(); displayer = handle.getDisplayer(); getContentPane().add(displayer.getComponent()); displayer.getComponent().setVisible(visible); revalidate(); } /** * Gets the index of a child of this station. * @param dockable the child which is searched * @return the index or -1 if the child was not found */ public int indexOfDockable( Dockable dockable ){ for( int i = 0, n = dockables.size(); i < n; i++ ) if( dockables.get(i).getDockable() == dockable ) return i; return -1; } /** * Removes all children 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. */ public void removeAllDockables(){ DockController controller = getController(); try { access.arm(); DockUtilities.checkLayoutLocked(); if( controller != null ) controller.freezeLayout(); for( int i = getDockableCount() - 1; i >= 0; i-- ) removeDisplayer(i, null); root().setChild(null); } finally { if( controller != null ) controller.meltLayout(); access.fire(); } } /** * Removes <code>dockable</code> from this station. If * <code>dockable</code> is not a child of this station, nothing happens.<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 child to remove */ public void removeDockable( Dockable dockable ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); Leaf leaf = root().getLeaf(dockable); if( leaf != null ) { leaf.setDockable(null, null, true, dockable.asDockStation() != null); leaf.placehold(true); } } finally{ access.fire(); } } /** * Searches the entire tree for any occurence of <code>placeholder</code> and * removes <code>placeholder</code>. Also shrinks the tree if some nodes or leafs * are no longer required due to the removed placeholder * @param placeholder the placeholder to remove */ public void removePlaceholder( Path placeholder ){ Set<Path> placeholders = new HashSet<Path>(); placeholders.add(placeholder); removePlaceholders(placeholders); } /** * Searches the entire tree for all occurences of all placeholders in <code>placeholders</code>. * All placeholders are removed and the tree shrinks where possible. * @param placeholders the placeholders to remove */ public void removePlaceholders( final Set<Path> placeholders ){ if( placeholders.isEmpty() ) return; final List<SplitNode> nodesToDelete = new ArrayList<SplitNode>(); root().visit(new SplitNodeVisitor(){ public void handleRoot( Root root ){ handle(root); } public void handlePlaceholder( Placeholder placeholder ){ handle(placeholder); } public void handleNode( Node node ){ handle(node); } public void handleLeaf( Leaf leaf ){ handle(leaf); } private void handle( SplitNode node ){ node.removePlaceholders(placeholders); if( !node.isOfUse() ) { nodesToDelete.add(node); } } }); for( SplitNode node : nodesToDelete ) { node.delete(true); } } /** * Removes <code>handle</code> from this station. Unbinds its * {@link Dockable}. * @param handle the handle to remove * @param token if <code>null</code>, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently */ private void removeHandle( StationChildHandle handle, DockHierarchyLock.Token token ){ int index = dockables.indexOf( handle ); if( index >= 0 ) { removeDisplayer( index, token ); } } /** * Removes the index'th handle from this station * @param index the index of the handle to remove * @param token if <code>null</code>, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently */ private void removeDisplayer( int index, DockHierarchyLock.Token token ){ StationChildHandle handle = dockables.get(index); if( handle == fullScreenDockable ) { setNextFullScreen(); if( handle == fullScreenDockable ) setFullScreen(null); } Dockable dockable = handle.getDockable(); boolean fire = token == null; if( fire ){ token = DockHierarchyLock.acquireUnlinking( this, dockable ); } try{ if( fire ) dockStationListeners.fireDockableRemoving(dockable); dockables.remove(index); DockableDisplayer displayer = handle.getDisplayer(); displayer.getComponent().setVisible(true); getContentPane().remove(displayer.getComponent()); handle.destroy(); if( dockable == frontDockable ) { setFrontDockable(null); } dockable.setDockParent(null); if( fire ) dockStationListeners.fireDockableRemoved(dockable); } finally{ if( fire ){ token.release(); } } } /** * Gets the {@link Root} of the tree which stores all locations and sizes * of the children of this station. Clients can modify the contents of this * station directly by accessing this tree.<br> * <b>Note</b> * <ul><li>that removing or adding children to the tree does not automatically * remove or add new {@link Dockable}s, that has to be explicitly done through * {@link Leaf#setDockable(Dockable, bibliothek.gui.dock.DockHierarchyLock.Token)}.</li> * <li>The tree should never be invalid. That means that each {@link Node} * should have two children, and each {@link Leaf} should have * a {@link Dockable}.</li> * </ul> * @return the root * @see #root() */ public Root getRoot(){ return root(); } /** * Searches the node whose {@link SplitNode#getId() id} equals <code>id</code>. * @param id the id to search * @return the node with the id <code>id</code> */ public SplitNode getNode( final long id ){ class Visitor implements SplitNodeVisitor { private SplitNode result; public void handleRoot( Root root ){ if( root.getId() == id ) { result = root; } } public void handleLeaf( Leaf leaf ){ if( leaf.getId() == id ) { result = leaf; } } public void handlePlaceholder( Placeholder placeholder ){ if( placeholder.getId() == id ) { result = placeholder; } } public void handleNode( Node node ){ if( node.getId() == id ) { result = node; } } } ; if( root == null ) return null; Visitor visitor = new Visitor(); getRoot().visit(visitor); return visitor.result; } public String getFactoryID(){ return SplitDockStationFactory.ID; } /** * Updates all locations and sizes of the {@link Component Components} * which are in the structure of this tree. */ public void updateBounds(){ Insets insets = getContentPane().getInsets(); double factorW = getWidth() - insets.left - insets.right; double factorH = getHeight() - insets.top - insets.bottom; SplitLayoutManager manager = layoutManager.getValue(); if( factorW <= 0 || factorH <= 0 ) { manager.updateBounds(root(), 0, 0, 0, 00); } else { manager.updateBounds(root(), insets.left / factorW, insets.top / factorH, factorW, factorH); } } /** * Allows subclasses access to the internal working of this {@link SplitDockStation}. Subclasses * should be very careful when invoking methods of {@link Access}. * @return the internal API of this station */ protected Access getAccess(){ return access; } /** * The background algorithm of this {@link SplitDockStation}. * @author Benjamin Sigg */ private class Background extends BackgroundAlgorithm implements StationBackgroundComponent{ public Background(){ super( StationBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".station.split" ); } public Component getComponent(){ return SplitDockStation.this.getComponent(); } public DockStation getStation(){ return SplitDockStation.this; } } /** * The panel which will be the parent of all {@link DockableDisplayer displayers} * @author Benjamin Sigg */ private class Content extends ConfiguredBackgroundPanel { public Content(){ super( Transparency.DEFAULT ); } @Override public void doLayout(){ updateBounds(); Insets insets = getInsets(); if( fullScreenDockable != null ) { fullScreenDockable.getDisplayer().getComponent().setBounds(insets.left, insets.top, getWidth() - insets.left - insets.right, getHeight() - insets.bottom - insets.top); } } @Override public void setTransparency( Transparency transparency ){ super.setTransparency( transparency ); SplitDockStation.this.setSolid( transparency == Transparency.SOLID ); } } /** * Orientation how two {@link Dockable Dockables} are aligned. */ public enum Orientation { /** One {@link Dockable} is at the left, the other at the right */ HORIZONTAL, /** One {@link Dockable} is at the top, the other at the bottom */ VERTICAL }; /** * This listener is added to the parent of this station, and ensures * that the visibility-state of the children of this station is always * correct. * @author Benjamin Sigg */ private class VisibleListener extends DockStationAdapter { @Override public void dockableShowingChanged( DockStation station, Dockable dockable, boolean visible ){ visibility.fire(); } } /** * A listener that reacts on double clicks and can expand a child of * this station to fullscreen-mode. * @author Benjamin Sigg */ private class FullScreenListener implements DoubleClickListener { public DockElement getTreeLocation(){ return SplitDockStation.this; } public boolean process( Dockable dockable, MouseEvent event ){ if( event.isConsumed() || !isExpandOnDoubleclick() ) return false; else { if( dockable == SplitDockStation.this ) return false; dockable = unwrap(dockable); if( dockable != null ) { if( isFullScreen() ) { if( getFullScreen() == dockable ) { setFullScreen(null); event.consume(); } } else { setFullScreen(dockable); event.consume(); } return true; } return false; } } /** * Searches a parent of <code>dockable</code> which has the * enclosing {@link SplitDockStation} as its direct parent. * @param dockable the root of the search * @return <code>dockable</code>, a parent of <code>dockable</code> * or <code>null</code> */ private Dockable unwrap( Dockable dockable ){ while( dockable.getDockParent() != SplitDockStation.this ) { DockStation parent = dockable.getDockParent(); if( parent == null ) return null; dockable = parent.asDockable(); if( dockable == null ) return null; } return dockable; } } /** * Access to this {@link SplitDockStation}. * @author Benjamin Sigg */ protected class Access implements SplitDockAccess{ private long lastUniqueId = -1; private int repositionedArm = 0; private Set<Dockable> repositioned = new HashSet<Dockable>(); private Dockable dockableSelected = null; public StationChildHandle getFullScreenDockable(){ return fullScreenDockable; } public DockTitleVersion getTitleVersion(){ return title; } public SplitDockStation getOwner(){ return SplitDockStation.this; } public double validateDivider( double divider, Node node ){ return layoutManager.getValue().validateDivider(SplitDockStation.this, divider, node); } public StationChildHandle newHandle( Dockable dockable ){ return new StationChildHandle(SplitDockStation.this, getDisplayers(), dockable, title); } public void addHandle( StationChildHandle dockable, DockHierarchyLock.Token token ){ SplitDockStation.this.addHandle( dockable, token ); } public void removeHandle( StationChildHandle handle, DockHierarchyLock.Token token ){ SplitDockStation.this.removeHandle( handle, token ); } public boolean drop( Dockable dockable, SplitDockProperty property, SplitNode root ){ return SplitDockStation.this.drop(dockable, property, root); } public PutInfo validatePutInfo( PutInfo putInfo ){ return layoutManager.getValue().validatePutInfo(SplitDockStation.this, putInfo); } public void repositioned( SplitNode node ){ arm(); try{ node.visit( new SplitNodeVisitor(){ public void handleRoot( Root root ){ // ignore } public void handlePlaceholder( Placeholder placeholder ){ // ignore } public void handleNode( Node node ){ // ignore } public void handleLeaf( Leaf leaf ){ Dockable dockable = leaf.getDockable(); if( dockable != null ){ repositioned.add( dockable ); } } }); } finally{ fire(); } } /** * Tells whether this {@link Access} currently is withholding events. * @return whether events are currently disabled */ public boolean isArmed(){ return repositionedArm > 0; } public void dockableSelected( Dockable dockable ){ arm(); if( dockableSelected == null ){ dockableSelected = dockable; } fire(); } /** * Prepares <code>this</code> to fire an event to * {@link DockStationListener#dockablesRepositioned(DockStation, Dockable[])}. */ public void arm(){ repositionedArm++; } /** * Fires an event to {@link DockStationListener#dockablesRepositioned(DockStation, Dockable[])}. */ public void fire(){ repositionedArm--; if( repositionedArm == 0 ){ List<Dockable> dockables = new ArrayList<Dockable>(); for( Dockable dockable : repositioned ){ if( dockable.getDockParent() == SplitDockStation.this ){ dockables.add( dockable ); } } repositioned.clear(); if( dockables.size() > 0 ){ dockStationListeners.fireDockablesRepositioned( dockables.toArray( new Dockable[ dockables.size() ] ) ); } if( dockableSelected != null ){ Dockable newDockable = getFrontDockable(); if( dockableSelected != newDockable ){ dockStationListeners.fireDockableSelected( dockableSelected, newDockable ); } dockableSelected = null; } } } public long uniqueID(){ long id = System.currentTimeMillis(); if( id <= lastUniqueId ) { lastUniqueId++; id = lastUniqueId+1; } while( getNode(id) != null ) { id++; } lastUniqueId = id; return id; } public boolean isTreeAutoCleanupEnabled(){ return treeLock == 0; } public SplitPlaceholderSet getPlaceholderSet(){ return placeholderSet; } public SplitSpanStrategy getSpanStrategy(){ return spanStrategy; } public Leaf createLeaf( long id ){ return nodeFactory.createLeaf( this, id ); } public Node createNode( long id ){ return nodeFactory.createNode( this, id ); } public Placeholder createPlaceholder( long id ){ return nodeFactory.createPlaceholder( this, id ); } /** * Creates a new {@link Root}. * @param id the unique identifier of the new root * @return the new root */ public Root createRoot( long id ){ return nodeFactory.createRoot( this, id ); } public void setDropInfo( PutInfo putInfo ){ SplitDockStation.this.setDropInfo( putInfo ); } public void unsetDropInfo(){ SplitDockStation.this.unsetDropInfo(); } public void drop( Token token, PutInfo putInfo, StationDropItem item ){ SplitDockStation.this.drop( token, putInfo, item ); } public void move( PutInfo putInfo, StationDropItem item ){ SplitDockStation.this.move( putInfo, item ); } } }