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

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

The newest version!
/*
 * Bibliothek - DockingFrames
 * Library built on Java/Swing, allows the user to "drag and drop"
 * panels containing any Swing-Component the developer likes to add.
 * 
 * Copyright (C) 2007 Benjamin Sigg
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Benjamin Sigg
 * [email protected]
 * CH - Switzerland
 */

package bibliothek.gui.dock;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.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.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.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.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.
* 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 MAXIMIZE_ACCELERATOR = new PropertyKey("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 LAYOUT_MANAGER = new PropertyKey("SplitDockStation layout manager", new ConstantPropertyFactory(new DefaultSplitLayoutManager()), true); /** * The algorithm that allows users to resize children of a {@link SplitDockStation} by * grabbing a gab between two children and moving that gap around. */ public static final PropertyKey DIVIDER_STRATEGY = new PropertyKey("SplitDockStation divider strategy", new DynamicPropertyFactory(){ public SplitDividerStrategy getDefault( PropertyKey key, DockProperties properties ){ return new DefaultSplitDividerStrategy(); } @Override public SplitDividerStrategy getDefault( PropertyKey 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 dockableListeners = new ArrayList(); /** 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 splitListeners = new ArrayList(); /** The handler for events and listeners concerning the visibility of children */ private DockableShowingManager visibility; /** the DockTitles which are bound to this dockable */ private List titles = new LinkedList(); /** the list of actions offered for this Dockable */ private HierarchyDockActionSource globalSource; /** * 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 titleText = new PropertyValue(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 titleToolTip = new PropertyValue(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 layoutManager = new PropertyValue(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 dividerStrategy = new PropertyValue(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 placeholderStrategyProperty = new PropertyValue(PlaceholderStrategy.PLACEHOLDER_STRATEGY){ @Override protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){ placeholderStrategy.setStrategy(newValue); } }; /** Access to the current {@link DisablingStrategy} */ private PropertyValue disablingStrategy = new PropertyValue( 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 true. */ 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 dockables = new ArrayList(); /** The {@link Dockable} which has the focus */ private Dockable frontDockable; /** The {@link Dockable} which is currently in fullscreen-mode. This value might be null */ private StationChildHandle fullScreenDockable; /** An action that is added to all children. The action changes the fullscreen-mode of the child. Can be null */ 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 true. */ 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 draged onto this station. */ private PutInfo putInfo; /** Information about the current {@link Span}s */ private SplitSpanStrategy spanStrategy; /** Information aboud 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 true, 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 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(); } } }); } /** * 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 this 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 null 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 null * @param fullScreenAction the new action * @throws IllegalStateException if there is already an action present */ public void setFullScreenAction( ListeningDockAction fullScreenAction ){ if( this.fullScreenAction != null ) throw new IllegalStateException("The fullScreenAction can only be set once"); this.fullScreenAction = fullScreenAction; } /** * Sets whether a double click on a child or its title can expand the child * to fullscreen or not. * @param expandOnDoubleclick true if the double click should * have an effect, false 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 true if a double click has an effect, false * 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 true. 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 dividiers and resize dockables in this way. * @return true 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 ){ 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); } hierarchyObserver.controllerChanged(controller); visibility.fire(); } } @Override public DockController getController(){ return controller; } 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 null */ 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 null */ 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 null 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 null * @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 null */ 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 null to install the default strategy */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ placeholderStrategyProperty.setValue(strategy); } /** * Every child has an invisible border whose size is determined by sideSnapSize. * 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 null 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 null */ 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 null */ @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 null */ @FrameworkOnly public SplitNodeFactory getNodeFactory(){ return nodeFactory; } /** * Sets whether the dockables should be resized while the split * is dragged, or not. * @param continousDisplay true 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 true 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 null */ 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 true 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 true 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 null */ 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 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 dockable. * The location is encoded as the path through the tree to get to dockable. * @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 placeholder. * @param placeholder the placeholder to search, not null * @return the path to placeholder or null if placeholder 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 dockable. * The location is encoded directly as the coordinates x,y,width and height * of the dockable. * @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 dockable, 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 null if the {@link #getPlaceholderStrategy() strategy} * did not assign a placeholder to dockable */ 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 true result implies that * {@link #getFullScreen()} returns not null. * @return true 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 null * @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 null. * @return true 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 null 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 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 null */ protected SplitPlaceholderConverter createPlaceholderConverter(){ return new SplitPlaceholderConverter( this ); } public DockStationDropLayer[] getLayers(){ 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 null */ 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 dockable described by putInfo 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 putInfo to this station. * @param token if null, 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 Dockable such that the boundaries given * by property are full filled. * @param dockable a new child of this station * @param property the preferred location of the child * @return true if the child could be added, false * if no location could be found */ public boolean drop( Dockable dockable, SplitDockProperty property ){ return drop(dockable, property, root()); } /** * Tries to add Dockable such that the boundaries given * by property 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 true if the child could be added, false * 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 property. * @param property the location whose neighbor is to be created * @param request information about the current location * @return true if the operation was a success, false 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 dockable such that its boundaries met * property. * @param property the location of dockable * @param dockable the element to place, can be null * @param root a node that must be part of the path for dockable * @return the best place for dockable */ 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 dockable at a location such that the path * to that location is the same as described in property. * @param dockable the element to insert * @param property the preferred path to the element * @return true 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 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 property. In the best case scenario this method directly * returns the {@link Leaf} to which property points.
* @param property a path in which a {@link SplitNode} is searched * @return The node closest to the leaf and in property, can be the {@link #root()}. And * the index of the node for calling {@link SplitDockPathProperty#getNode(int)}.s */ protected Tuple 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( index, start ); } /** * Drops dockable at the placeholder that is referenced by property. This * action removes the placeholder from the tree. * @param dockable the element to add * @param property the location of dockable * @return true if the the operation was a success, false if not */ public boolean drop( Dockable dockable, SplitDockPlaceholderProperty property ){ try{ access.arm(); DockUtilities.checkLayoutLocked(); validate(); return root().insert(property, dockable); } finally{ access.fire(); } } /** * Drops dockable on this station, may exchange the full screen element to ensure that * dockable is displayed. * @param dockable the element to drop * @param property the location of dockable * @return true if the operation was a success, false 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 leaf and dockable * to a new child of this station. No checks whether the two elements accepts * each other nor if the station accepts the new child dockable * are performed. * @param leaf the leaf which will be combined with dockable * @param dockable a {@link Dockable} which is dropped over leaf * @param source information about the combination, may be null * @param target information about the combination, may be null * @return true if the operation was successful, false * otherwise */ protected boolean dropOver( Leaf leaf, Dockable dockable, CombinerSource source, CombinerTarget target ){ return dropOver(leaf, dockable, null, source, target ); } /** * Combines the {@link Dockable} of leaf and dockable * to a new child of this station. No checks whether the two elements accepts * each other nor if the station accepts the new child dockable * are performed. * @param leaf the leaf which will be combined with dockable * @param dockable a {@link Dockable} which is dropped over leaf * @param property a hint at which position dockable should be * in the combination. * @param source information about the combination, may be null * @param target information about the combination, may be null * @return true if the operation was successful, false * 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( 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 dockable at the side put of * neighbor. The divider is set to the value of divider, * and if fire is activated, some events are fired. There are * no checks whether dockable accepts this station or anything * else. * @param neighbor The node which will be the neighbor of dockable * @param put The side on which dockable should be added in * respect to neighbor. * @param dockable the new child of this station * @param leaf the leaf which contains dockable, can be null * @param divider the divider-location, a value between 0 and 1 * @param token if null, then a token will be acquired by this method * and this method will fire events, otherwise this methods is executed silently * @return true if the operation was a success, false 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 neighbor and child as a children. * @param neighbor one of the new children * @param put the location of child relative to neighbor * @param child the other new child * @param divider how space is divided between neighbor and child */ 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 grid. Any changes to grid * afterwards will not affect this station, nor will changes on this station * affect grid. * @param grid the layout to drop, not null * @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 tree. Calling this method is equivalent * to dropTree( tree, true ); * @param tree the new set of children * @throws SplitDropTreeException If the tree is not acceptable. */ public void dropTree( SplitDockTree tree ){ dropTree(tree, true); } /** * Removes all children from this station and then adds the contents * that are stored in tree. * @param tree the new set of children * @param checkValidity whether to ensure that the new elements are * accepted or not. * @throws SplitDropTreeException if checkValidity is * set to true and the tree is not acceptable */ public void dropTree( SplitDockTree 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.Key rootKey = tree.getRoot(); if( rootKey != null ) { Map linksToSet = new HashMap(); root().evolve(rootKey, checkValidity, linksToSet); for( Map.Entry 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 factory. * @param factory the factory to write into */ public void createTree( SplitDockTreeFactory factory ){ root().submit(factory); } /** * Visits the internal structure of this station. * @param the type of result this method produces * @param factory a factory that will collect information * @return the result of factory */ public N visit( SplitTreeFactory 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 null * @param newDockable the new fullscreen-Dockable, can be null */ 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 title is no longer * considered to be a good title and should be exchanged. * @param title a title, can be null */ 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 * null 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 ); } } } } dividerStrategy.getValue().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 dockable 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 dockable to this station and fires events * only if fire is true. * @param dockable the new child of this station * @param token if null, 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 handle 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 null 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 displayer 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.
* Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure noone else adds or * removes Dockables. */ 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 dockable from this station. If * dockable is not a child of this station, nothing happens.
* Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure noone else adds or * removes Dockables. * @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 placeholder and * removes placeholder. 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 placeholders = new HashSet(); placeholders.add(placeholder); removePlaceholders(placeholders); } /** * Searches the entire tree for all occurences of all placeholders in placeholders. * All placeholders are removed and the tree shrinks where possible. * @param placeholders the placeholders to remove */ public void removePlaceholders( final Set placeholders ){ if( placeholders.isEmpty() ) return; final List nodesToDelete = new ArrayList(); 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 handle from this station. Unbinds its * {@link Dockable}. * @param handle the handle to remove * @param token if null, 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 null, 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.
* Note *
  • 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)}.
  • *
  • 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}.
  • *
* @return the root * @see #root() */ public Root getRoot(){ return root(); } /** * Searches the node whose {@link SplitNode#getId() id} equals id. * @param id the id to search * @return the node with the id id */ 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 dockable which has the * enclosing {@link SplitDockStation} as its direct parent. * @param dockable the root of the search * @return dockable, a parent of dockable * or null */ 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 repositioned = new HashSet(); 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 this 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 dockables = new ArrayList(); 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 ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy