/* * 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) 2012 Herve Guillaume, 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 * * Herve Guillaume * rvguillaume@hotmail.com * FR - France * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.io.IOException; import java.util.Map; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.SwingUtilities; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.DockUI; import bibliothek.gui.Dockable; import bibliothek.gui.Orientation; import bibliothek.gui.dock.component.DefaultDockStationComponentRootHandler; import bibliothek.gui.dock.component.DockComponentRootHandler; import bibliothek.gui.dock.displayer.DockableDisplayerHints; import bibliothek.gui.dock.event.DockStationAdapter; 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.DisplayerCollection; import bibliothek.gui.dock.station.DisplayerFactory; import bibliothek.gui.dock.station.DockableDisplayer; import bibliothek.gui.dock.station.DockableDisplayerListener; import bibliothek.gui.dock.station.OverpaintablePanel; import bibliothek.gui.dock.station.PlaceholderMapping; import bibliothek.gui.dock.station.StationChildHandle; import bibliothek.gui.dock.station.StationDropItem; import bibliothek.gui.dock.station.StationDropOperation; import bibliothek.gui.dock.station.layer.DockStationDropLayer; import bibliothek.gui.dock.station.stack.StackDockProperty; import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem; import bibliothek.gui.dock.station.support.DockablePlaceholderList; import bibliothek.gui.dock.station.support.DockableShowingManager; import bibliothek.gui.dock.station.support.PlaceholderList; import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter; import bibliothek.gui.dock.station.support.PlaceholderListItemConverter; import bibliothek.gui.dock.station.support.PlaceholderListMapping; import bibliothek.gui.dock.station.support.PlaceholderMap; import bibliothek.gui.dock.station.support.PlaceholderStrategy; import bibliothek.gui.dock.station.toolbar.SpanToolbarLayoutManager; import bibliothek.gui.dock.station.toolbar.ToolbarDockStationFactory; import bibliothek.gui.dock.station.toolbar.ToolbarDropInfo; import bibliothek.gui.dock.station.toolbar.ToolbarProperty; import bibliothek.gui.dock.station.toolbar.layer.ToolbarSlimDropLayer; import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue; import bibliothek.gui.dock.themes.DefaultStationPaintValue; import bibliothek.gui.dock.themes.ThemeManager; import bibliothek.gui.dock.themes.basic.BasicDockTitleFactory; import bibliothek.gui.dock.title.DockTitleFactory; import bibliothek.gui.dock.title.DockTitleVersion; import bibliothek.gui.dock.toolbar.expand.ExpandedState; import bibliothek.gui.dock.util.ConfiguredBackgroundPanel; 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.property.ConstantPropertyFactory; import bibliothek.util.Path; /** * A {@link Dockable} and a {@link DockStation} which stands for a group of * {@link ToolbarItemDockable}. As dockable it can be put in {@link DockStation} * which implements marker interface {@link ToolbarInterface}. As DockStation it * accept a {@link ToolbarItemDockable} or a {@link ToolbarDockStation} * * @author Herve Guillaume */ public class ToolbarDockStation extends AbstractToolbarDockStation { /** the id of the {@link DockTitleFactory} which is used by this station */ public static final String TITLE_ID = "toolbar"; /** * This id is forwarded to {@link Extension}s which load additional * {@link DisplayerFactory}s */ public static final String DISPLAYER_ID = "toolbar"; /** Key for setting the size of the gap between the children of a {@link ToolbarDockStation}. */ public static final PropertyKey<Integer> GAP = new PropertyKey<Integer>( "dock.toolbar.gap", new ConstantPropertyFactory<Integer>( Integer.valueOf( 2 ) ), true ); /** Key for setting the size of the gap between the children of a station and the border of the station */ public static final PropertyKey<Integer> SIDE_GAP = new PropertyKey<Integer>( "dock.toolbar.sidegap", new ConstantPropertyFactory<Integer>( Integer.valueOf( 4 ) ), true ); /** A list of all children */ protected DockablePlaceholderList<StationChildHandle> dockables = new DockablePlaceholderList<StationChildHandle>(); /** * The graphical representation of this station: the pane which contains * component */ private OverpaintablePanelBase mainPanel; /** * Size of the lateral zone where no drop action can be done (Measured in * pixel). */ private int lateralNodropZoneSize = 2; /** current {@link PlaceholderStrategy} */ private final PropertyValue<PlaceholderStrategy> placeholderStrategy = new PropertyValue<PlaceholderStrategy>( PlaceholderStrategy.PLACEHOLDER_STRATEGY ){ @Override protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){ dockables.setStrategy( newValue ); } }; /** a manager to inform listeners about changes in the visibility state */ private DockableShowingManager visibility; /** added to the current parent of this dockable */ private VisibleListener visibleListener; /** the {@link LayoutManager} positioning the children of this station */ private SpanToolbarLayoutManager layoutManager; /** information about the {@link Dockable} that is currently dropped */ private DropInfo dropInfo; /** size of the gap between children */ private PropertyValue<Integer> gap = new PropertyValue<Integer>( GAP ){ @Override protected void valueChanged( Integer oldValue, Integer newValue ){ layoutManager.setGap( newValue.intValue() ); } }; /** size of the gap between children and border */ private PropertyValue<Integer> sideGap = new PropertyValue<Integer>( SIDE_GAP ){ @Override protected void valueChanged( Integer oldValue, Integer newValue ){ layoutManager.setSideGap( newValue.intValue() ); } }; // ######################################################## // ############ Initialization Managing ################### // ######################################################## /** * Creates a new {@link ToolbarDockStation}. */ public ToolbarDockStation(){ init(); } protected void init(){ super.init( ThemeManager.BACKGROUND_PAINT + ".station.toolbar" ); mainPanel = createMainPanel(); mainPanel.setupLayout(); paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".toolbar", this ); setOrientation( getOrientation() ); displayerFactory = createDisplayerFactory(); displayers = new DisplayerCollection( this, displayerFactory, getDisplayerId() ); displayers.addDockableDisplayerListener( new DockableDisplayerListener(){ @Override public void discard( DockableDisplayer displayer ){ ToolbarDockStation.this.discard( displayer ); } @Override public void moveableElementChanged( DockableDisplayer displayer ){ // ignore } } ); setTitleIcon( null ); visibility = new DockableShowingManager( listeners ); visibleListener = new VisibleListener(); getComponent().addHierarchyListener( new HierarchyListener(){ public void hierarchyChanged( HierarchyEvent e ){ if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 ) { if( getDockParent() == null ) { getDockableStateListeners().checkShowing(); } visibility.fire(); } } } ); } @Override protected DockComponentRootHandler createRootHandler() { return new DefaultDockStationComponentRootHandler( this, displayers ); } // ######################################################## // ############ General DockStation Managing ############## // ######################################################## @Override public Component getComponent(){ return mainPanel; } @Override public void configureDisplayerHints( DockableDisplayerHints hints ){ super.configureDisplayerHints( hints ); if( hints != null ){ if( hints.getStation() instanceof ScreenDockStation ){ hints.setShowBorderHint( Boolean.TRUE ); } } } @Override public int getDockableCount(){ return dockables.dockables().size(); } @Override public Dockable getDockable( int index ){ return dockables.dockables().get( index ).getDockable(); } @Override public String getFactoryID(){ return ToolbarDockStationFactory.ID; } /** * Sets the size of the two lateral zones where no drop action can be done * (Measured in pixel). * * @param lateralNodropZoneSize * the size of the rectangular lateral zones (in pixel) * @throws IllegalArgumentException * if the size is smaller than 0 */ public void setLateralNodropZoneSize( int lateralNodropZoneSize ){ if( lateralNodropZoneSize < 0 ) { throw new IllegalArgumentException( "borderSideSnapeSize must not be less than 0" ); } this.lateralNodropZoneSize = lateralNodropZoneSize; } /** * Gets the size of the two lateral zones where no drop action can be done * (Measured in pixel). * * @return the size of the rectangular lateral zones (in pixel) */ public int getLateralNodropZoneSize(){ return lateralNodropZoneSize; } @Override public void setDockParent( DockStation station ){ DockStation old = getDockParent(); if( old != null ) old.removeDockStationListener( visibleListener ); super.setDockParent( station ); if( station != null ) station.addDockStationListener( visibleListener ); visibility.fire(); } @Override public void setController( DockController controller ){ if( getController() != controller ) { if( getController() != null ) { dockables.unbind(); } for( final StationChildHandle handle : dockables.dockables() ) { handle.setTitleRequest( null ); } super.setController( controller ); // if not set controller of the DefaultStationPaintValue, call to // DefaultStationPaintValue do nothing if( controller == null ) { title = null; } else { title = registerTitle( controller ); } paint.setController( controller ); placeholderStrategy.setProperties( controller ); displayerFactory.setController( controller ); displayers.setController( controller ); mainPanel.setController( controller ); layoutManager.setController( controller ); gap.setProperties( controller ); sideGap.setProperties( controller ); if( controller != null ) { dockables.bind(); } for( final StationChildHandle handle : dockables.dockables() ) { handle.setTitleRequest( title, true ); } visibility.fire(); } } // ######################################################## // ############ Orientation Managing ###################### // ######################################################## @Override public void setOrientation( Orientation orientation ){ // it's very important to change position and orientation of inside // dockables first, else doLayout() is done on wrong inside information this.orientation = orientation; fireOrientingEvent(); mainPanel.revalidate(); } // ######################################################## // ############### Drop/Move Managing ##################### // ######################################################## @Override public DockStationDropLayer[] getLayers(){ return new DockStationDropLayer[]{ new ToolbarSlimDropLayer( this ) }; } @Override public boolean accept( Dockable child ){ return getToolbarStrategy().isToolbarPart( child ); } @Override public boolean accept( DockStation station ){ return getToolbarStrategy().isToolbarGroupPartParent( station, this, false ); } public boolean accept( DockStation base, Dockable neighbor ){ return false; } @Override public StationDropOperation prepareDrop( StationDropItem item ){ // System.out.println(this.toString() + "## prepareDrop(...) ##"); final DockController controller = getController(); if( getExpandedState() == ExpandedState.EXPANDED ) { return null; } Dockable dockable = item.getDockable(); // check if the dockable and the station accept each other if( this.accept( dockable ) && dockable.accept( this ) ) { // check if controller exist and if the controller accept that // the dockable become a child of this station if( controller != null ) { if( !controller.getAcceptance().accept( this, dockable ) ) { return null; } } Point mouse = new Point( item.getMouseX(), item.getMouseY() ); SwingUtilities.convertPointFromScreen( mouse, mainPanel.getDockablePane() ); int index = layoutManager.getInsertionIndex( mouse.x, mouse.y ); DropInfo info = new DropInfo( dockable, index ); if( info.hasNoEffect() ) { return null; } return info; } else { return null; } } private class DropInfo extends ToolbarDropInfo { public DropInfo( Dockable dockable, int index ){ super( dockable, ToolbarDockStation.this, index ); } @Override public void execute(){ dropInfo = null; layoutManager.setExpandedSpan( -1, false ); if( isMove() ) { move( getItem(), getIndex() ); } else { drop( getItem(), getIndex() ); } } @Override public void destroy( StationDropOperation next ){ if( dropInfo == this ) { dropInfo = null; } if( next == null || next.getTarget() != getTarget() ) { layoutManager.setExpandedSpan( -1, true ); } mainPanel.repaint(); } @Override public void draw(){ dropInfo = this; layoutManager.setSpanSize( getItem() ); layoutManager.setExpandedSpan( getIndex(), true ); mainPanel.repaint(); } } @Override public void drop( Dockable dockable ){ // System.out.println(this.toString() + "## drop(Dockable dockable)##"); this.drop( dockable, getDockableCount(), true ); } /** * Drops <code>dockable</code> at location <code>index</code>. * * @param dockable * the element to add * @param index * the location of <code>dockable</code> * @return whether the operation was successful or not */ public boolean drop( Dockable dockable, int index ){ return drop( dockable, index, false ); } protected boolean drop( Dockable dockable, int index, boolean force ){ // note: merging of two ToolbarGroupDockStations is done by the // ToolbarGroupDockStationMerger // System.out.println(this.toString() // + "## drop(Dockable dockable, int index)##"); if( force || this.accept( dockable ) ) { if( !force ) { Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable ); if( replacement == null ) { return false; } if( replacement != dockable ) { replacement.asDockStation().drop( dockable ); dockable = replacement; } } add( dockable, index ); return true; } return false; } /** * Adds dockable at the specified index. This method should be called in * case of move action (it means when the dockable to insert already belongs * to this station), because in this case this dockable was removed first. * * @param dockable * the dockable to insert * @param index * the index where insert the dockable */ protected void move( Dockable dockable, int index ){ final DockController controller = getController(); try { if( controller != null ) { controller.freezeLayout(); } int current = indexOf( dockable ); if( current == -1 ) { throw new IllegalArgumentException( "dockable is not known to this station" ); } if( current < index ) { index--; } if( current != index ) { this.add( dockable, index ); } } finally { if( controller != null ) { controller.meltLayout(); } } } protected void add( Dockable dockable, int index ){ add( dockable, index, null ); } protected void add( Dockable dockable, int index, Path placeholder ){ DockUtilities.ensureTreeValidity( this, dockable ); DockUtilities.checkLayoutLocked(); // Case where dockable is instance of ToolbarDockStation is handled by // the "ToolbarDockStationMerger" // Case where dockable is instance of ToolbarGroupDockStation is handled // by the "ToolbarStrategy.ensureToolbarLayer" method Dockable replacement = getToolbarStrategy().ensureToolbarLayer( this, dockable ); if( replacement != dockable ) { replacement.asDockStation().drop( dockable ); dockable = replacement; } if( getExpandedState() == ExpandedState.EXPANDED && getDockableCount() == 1){ DockStation stack = getDockable( 0 ).asDockStation(); stack.drop( dockable, new StackDockProperty( index, placeholder ) ); } else{ final DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable ); try { listeners.fireDockableAdding( dockable ); int inserted = -1; final StationChildHandle handle = new StationChildHandle( this, displayers, dockable, title ); handle.updateDisplayer(); if( (placeholder != null) && (dockables.getDockableAt( placeholder ) == null) ) { inserted = dockables.put( placeholder, handle ); } else if( placeholder != null ) { index = dockables.getDockableIndex( placeholder ); } if( inserted == -1 ) { getDockables().add( index, handle ); } else { index = inserted; } insertAt( handle, index ); listeners.fireDockableAdded( dockable ); fireDockablesRepositioned( index + 1 ); } finally { token.release(); } } } protected void insertAt( StationChildHandle handle, int index ){ final Dockable dockable = handle.getDockable(); dockable.setDockParent( this ); mainPanel.getDockablePane().add( handle.getDisplayer().getComponent(), index ); mainPanel.getDockablePane().invalidate(); mainPanel.revalidate(); mainPanel.getContentPane().repaint(); } @Override public void drag( Dockable dockable ){ if( dockable.getDockParent() != this ) { throw new IllegalArgumentException( "The dockable cannot be dragged, it is not child of this station." ); } this.remove( dockable ); } /** * Removes <code>dockable</code> from this station.<br> * Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure none else adds or * removes <code>Dockable</code>s. * * @param dockable * the child to remove */ @Override protected void remove( Dockable dockable ){ DockUtilities.checkLayoutLocked(); final int index = indexOf( dockable ); final StationChildHandle handle = dockables.dockables().get( index ); if( getFrontDockable() == dockable ) { setFrontDockable( null ); } final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable ); try { listeners.fireDockableRemoving( dockable ); dockable.setDockParent( null ); dockables.remove( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); mainPanel.doLayout(); mainPanel.revalidate(); mainPanel.repaint(); handle.destroy(); listeners.fireDockableRemoved( dockable ); fireDockablesRepositioned( index ); } finally { token.release(); } } /** * Removes the child with the given <code>index</code> from this station.<br> * Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure no-one else adds or * removes <code>Dockable</code>s. * * @param index * the index of the child that will be removed */ protected void remove( int index ){ DockUtilities.checkLayoutLocked(); final StationChildHandle handle = dockables.dockables().get( index ); final Dockable dockable = getDockable( index ); if( getFrontDockable() == dockable ) { setFrontDockable( null ); } final DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable ); try { listeners.fireDockableRemoving( dockable ); dockable.setDockParent( null ); dockables.remove( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); mainPanel.doLayout(); mainPanel.getContentPane().revalidate(); mainPanel.getContentPane().repaint(); handle.destroy(); listeners.fireDockableRemoved( dockable ); fireDockablesRepositioned( index ); } finally { token.release(); } } @Override public void replace( Dockable old, Dockable next ){ DockUtilities.checkLayoutLocked(); final DockController controller = getController(); if( controller != null ) { controller.freezeLayout(); } final int index = indexOf( old ); remove( old ); // the child is a ToolbarGroupDockStation because canReplace() // ensure it add( next, index ); controller.meltLayout(); } // ######################################################## // ###################### UI Managing ##################### // ######################################################## @Override protected void callDockUiUpdateTheme() throws IOException{ DockUI.updateTheme( this, new ToolbarDockStationFactory() ); } @Override protected DefaultDisplayerFactoryValue createDisplayerFactory(){ return new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".toolbar", this ); } /** * Gets a unique identifier used to get the {@link DisplayerFactory} for this station. * @return the unique identifier, not <code>null</code> */ protected String getDisplayerId(){ return DISPLAYER_ID; } @Override protected DockTitleVersion registerTitle( DockController controller ){ return controller.getDockTitleManager().getVersion( TITLE_ID, BasicDockTitleFactory.FACTORY ); } /** * Replaces <code>displayer</code> with a new {@link DockableDisplayer}. * * @param displayer * the displayer to replace * @throws IllegalArgumentException * if <code>displayer</code> is not a child of this station */ @Override protected void discard( DockableDisplayer displayer ){ final Dockable dockable = displayer.getDockable(); final int index = indexOf( dockable ); if( index < 0 ) { throw new IllegalArgumentException( "displayer is not a child of this station: " + displayer ); } final StationChildHandle handle = dockables.dockables().get( index ); mainPanel.getDockablePane().remove( handle.getDisplayer().getComponent() ); handle.updateDisplayer(); insertAt( handle, index ); } /** * A panel with a fixed size (minimum, maximum and preferred size have * same values). * * @author Herve Guillaume * */ @SuppressWarnings("serial") protected class SizeFixedPanel extends ConfiguredBackgroundPanel { public SizeFixedPanel(){ super( Transparency.SOLID ); setBackground( ToolbarDockStation.this.getBackgroundAlgorithm() ); } @Override public Dimension getPreferredSize(){ final Dimension pref = super.getPreferredSize(); // Insets insets = getInsets(); // pref.height += insets.top + insets.bottom; // pref.width += insets.left + insets.right; return pref; } @Override public Dimension getMaximumSize(){ return getPreferredSize(); } @Override public Dimension getMinimumSize(){ return getPreferredSize(); } } /** * Called by the constructor, this method creates the main component of this station. * @return the main component, must not be <code>null</code> */ protected OverpaintablePanelBase createMainPanel(){ return new OverpaintablePanelBase(); } /** * Creates the parent {@link JComponent} of the {@link Dockable}s that are shown in this * station. The default behavior is to create a new {@link SizeFixedPanel}, using * {@link #getBackgroundAlgorithm()} for managing painting. * @return the new content pane */ @Override protected JPanel createBackgroundPanel(){ return new SizeFixedPanel(); } /** * This panel is used as base of the station. All children of the station * have this panel as parent too. It allows to draw arbitrary figures over * the base panel * * @author Herve Guillaume */ protected class OverpaintablePanelBase extends SecureContainer { /** * Generated serial number */ private static final long serialVersionUID = -4399008463139189130L; /** * The content Pane of this {@link OverpaintablePanel} (with a * BoxLayout) */ private JComponent dockablePane; /** * Creates a new panel */ public OverpaintablePanelBase(){ setSolid( false ); } /** * Initializes this panel with the default layout, namely exactly one child which is the * {@link #setDockablePane(JComponent) dockable pane}. */ public void setupLayout(){ setDockablePane( createBackgroundPanel() ); setBasePane( dockablePane ); } public void setDockablePane(JComponent pane){ if(dockablePane != null){ throw new IllegalStateException( "dockablePane is already set" ); } dockablePane = pane; layoutManager = new SpanToolbarLayoutManager( ToolbarDockStation.this, dockablePane ){ @Override protected void revalidate(){ dockablePane.revalidate(); } }; dockablePane.setLayout( layoutManager ); } public JComponent getDockablePane(){ return dockablePane; } @Override public Dimension getPreferredSize(){ return getBasePane().getPreferredSize(); } @Override public Dimension getMinimumSize(){ return getPreferredSize(); } @Override public Dimension getMaximumSize(){ return getPreferredSize(); } private Insets subtractComponent( JComponent component, Insets insets ){ Point topLeft = new Point( 0, 0 ); topLeft = SwingUtilities.convertPoint( component, topLeft, this ); insets.left += topLeft.x; insets.top += topLeft.y; insets.right += (getWidth() - component.getWidth()) - topLeft.x; insets.bottom += (getHeight() - component.getHeight()) - topLeft.y; return insets; } @Override protected void paintOverlay( Graphics g ){ final Graphics2D g2D = (Graphics2D) g; paintRemoval( g ); if( dropInfo != null ) { Insets insets = dockablePane.getInsets(); if( insets == null ){ insets = new Insets( 0, 0, 0, 0 ); } insets = subtractComponent( dockablePane, insets ); int index = dropInfo.getIndex(); int x, y, width, height; if( getOrientation() == Orientation.HORIZONTAL ) { if( index == 0 ) { x = insets.left; } else { x = dockablePane.getComponent( index - 1 ).getX() + dockablePane.getComponent( index - 1 ).getWidth(); } if( index == dockablePane.getComponentCount() ) { width = getWidth() - x - insets.right; } else { width = dockablePane.getComponent( index ).getX() - x; } y = insets.top; height = getHeight() - insets.top - insets.bottom; } else { if( index == 0 ) { y = insets.top; } else { y = dockablePane.getComponent( index - 1 ).getY() + dockablePane.getComponent( index - 1 ).getHeight(); } if( index == dockablePane.getComponentCount() ) { height = getHeight() - y - insets.top; } else { height = dockablePane.getComponent( index ).getY() - y; } x = insets.left; width = getWidth() - insets.left - insets.right; } if( width > 0 && height > 0 ){ Rectangle stationBounds = new Rectangle( 0, 0, getWidth(), getHeight() ); paint.drawInsertion( g2D, stationBounds, new Rectangle( x, y, width, height ) ); } } } private void paintRemoval( Graphics g ){ Dockable removal = getRemoval(); if( removal != null ) { for( StationChildHandle handle : dockables.dockables() ) { if( handle.getDockable() == removal ) { Dimension size = handle.getDisplayer().getComponent().getSize(); Point location = new Point(0, 0); location = SwingUtilities.convertPoint( handle.getDisplayer().getComponent(), location, this ); Rectangle bounds = new Rectangle( location, size ); paint.drawRemoval( g, bounds, bounds ); break; } } } } @Override public String toString(){ return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() ); } } // ######################################################## // ############### PlaceHolder Managing ################### // ######################################################## // TODO don't use ToolbarProperty but a custom class, would be much safer // if the layout is screwed up /** * Creates a new {@link DockableProperty} describing the location of * <code>child</code> on this station. This method is called by * {@link #getDockableProperty(Dockable, Dockable)} once the location and * placeholder of <code>child</code> or <code>target</code> have been * calculated * * @param child * a child of this station * @param target * the item whose position is searched * @param index * the location of <code>child</code> * @param placeholder * the placeholder for <code>target</code> or <code>child</code> * @return a new {@link DockableProperty} that stores <code>index</code>, * <code>placeholder</code> and any other information a subclass * deems necessary to store */ protected DockableProperty getDockableProperty( Dockable child, Dockable target, int index, Path placeholder ){ return new ToolbarProperty( index, placeholder ); } /** * Tells whether the subclass knows how to handle <code>property</code>. * This means that the type of <code>property</code> is the same type as the * result of {@link #getDockableProperty(Dockable, Dockable, int, Path)} * * @param property * the property to check * @return <code>true</code> if this subclass knows how to handle the type * of <code>property</code> */ protected boolean isValidProperty( DockableProperty property ){ return property instanceof ToolbarProperty; } /** * Gets the location of a {@link Dockable} on this station. Called only if * <code>property</code> passed {@link #isValidProperty(DockableProperty)}. * * @param property * some property created by * {@link #getDockableProperty(Dockable, Dockable, int, Path)} * @return the index parameter */ protected int getIndex( DockableProperty property ){ return ((ToolbarProperty) property).getIndex(); } protected Path getPlaceholder( DockableProperty property ){ return ((ToolbarProperty) property).getPlaceholder(); } /** * Grants direct access to the list of {@link Dockable}s, subclasses should * not modify the list unless the fire the appropriate events. * * @return the list of dockables */ protected PlaceholderList.Filter<StationChildHandle> getDockables(){ return dockables.dockables(); } /** * Gets the placeholders of this station using a * {@link PlaceholderListItemConverter} to encode the children. The * converter puts the following parameters for each {@link Dockable} into * the map: * <ul> * <li>id: the integer from <code>children</code></li> * <li>index: the location of the element in the dockables-list</li> * <li>placeholder: the placeholder of the element, might be missing</li> * </ul> * * @param children * a unique identifier for each child of this station * @return the map */ public PlaceholderMap getPlaceholders( final Map<Dockable, Integer> children ){ final PlaceholderStrategy strategy = getPlaceholderStrategy(); return dockables.toMap( new PlaceholderListItemAdapter<Dockable, StationChildHandle>(){ @Override public ConvertedPlaceholderListItem convert( int index, StationChildHandle handle ){ final Dockable dockable = handle.getDockable(); final Integer id = children.get( dockable ); if( id == null ) { return null; } final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem(); item.putInt( "id", id ); item.putInt( "index", index ); if( strategy != null ) { final Path placeholder = strategy.getPlaceholderFor( dockable ); if( placeholder != null ) { item.putString( "placeholder", placeholder.toString() ); item.setPlaceholder( placeholder ); } } return item; } } ); } /** * Sets a new layout on this station, this method assumes that * <code>map</code> was created by the method {@link #getPlaceholders(Map)}. * * @param map * the map to read * @param children * the new children of this station * @throws IllegalStateException * if there are children left on this station */ public void setPlaceholders( PlaceholderMap map, final Map<Integer, Dockable> children ){ DockUtilities.checkLayoutLocked(); if( getDockableCount() > 0 ) { throw new IllegalStateException( "must not have any children" ); } final DockController controller = getController(); try { if( controller != null ) { controller.freezeLayout(); } final DockablePlaceholderList<StationChildHandle> next = new DockablePlaceholderList<StationChildHandle>(); if( getController() != null ) { dockables.setStrategy( null ); dockables.unbind(); dockables = next; } else { dockables = next; } next.read( map, new PlaceholderListItemAdapter<Dockable, StationChildHandle>(){ private DockHierarchyLock.Token token; private int index = 0; @Override public StationChildHandle convert( ConvertedPlaceholderListItem item ){ final int id = item.getInt( "id" ); final Dockable dockable = children.get( id ); if( dockable != null ) { DockUtilities.ensureTreeValidity( ToolbarDockStation.this, dockable ); token = DockHierarchyLock.acquireLinking( ToolbarDockStation.this, dockable ); listeners.fireDockableAdding( dockable ); return new StationChildHandle( ToolbarDockStation.this, displayers, dockable, title ); } return null; } @Override public void added( StationChildHandle handle ){ try { handle.updateDisplayer(); insertAt( handle, index++ ); listeners.fireDockableAdded( handle.getDockable() ); } finally { token.release(); } } } ); if( getController() != null ) { dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } } finally { if( controller != null ) { controller.meltLayout(); } } } @Override public PlaceholderMap getPlaceholders(){ return dockables.toMap(); } @Override public PlaceholderMapping getPlaceholderMapping() { return new PlaceholderListMapping( this, dockables ){ @Override public DockableProperty getLocationAt( Path placeholder ) { int index = dockables.getDockableIndex( placeholder ); return new ToolbarProperty( index, placeholder ); } }; } @Override public void setPlaceholders( PlaceholderMap placeholders ){ if( getDockableCount() > 0 ) { throw new IllegalStateException( "only allowed if there are not children present" ); } try { final DockablePlaceholderList<StationChildHandle> next = new DockablePlaceholderList<StationChildHandle>( placeholders ); if( getController() != null ) { dockables.setStrategy( null ); dockables.unbind(); dockables = next; dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } else { dockables = next; } } catch( final IllegalArgumentException ex ) { // silent } } /** * Gets the {@link PlaceholderStrategy} that is currently in use. * * @return the current strategy, may be <code>null</code> */ public PlaceholderStrategy getPlaceholderStrategy(){ return placeholderStrategy.getValue(); } /** * Sets the {@link PlaceholderStrategy} to use, <code>null</code> will set * the default strategy. * * @param strategy * the new strategy, can be <code>null</code> */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ placeholderStrategy.setValue( strategy ); } @Override public DockableProperty getDockableProperty( Dockable child, Dockable target ){ final int index = indexOf( child ); Path placeholder = null; final PlaceholderStrategy strategy = getPlaceholderStrategy(); if( strategy != null ) { placeholder = strategy.getPlaceholderFor( target == null ? child : target ); if( placeholder != null ) { dockables.dockables().addPlaceholder( index, placeholder ); } } return getDockableProperty( child, target, index, placeholder ); } public void aside( AsideRequest request ){ int index = -1; int resultIndex = -1; if( getExpandedState() == ExpandedState.EXPANDED && getDockableCount() == 1 ){ DockStation stack = getDockable( 0 ).asDockStation(); AsideAnswer answer = request.forward( stack ); if( answer.isCanceled() ){ return; } DockableProperty answerLocation = answer.getLocation(); if( answerLocation instanceof StackDockProperty ){ resultIndex = ((StackDockProperty) answerLocation).getIndex(); } } DockableProperty location = request.getLocation(); Path newPlaceholder = request.getPlaceholder(); if( location instanceof ToolbarProperty ){ ToolbarProperty toolbarLocation = (ToolbarProperty)location; index = dockables.getNextListIndex( toolbarLocation.getIndex(), toolbarLocation.getPlaceholder() ); if( newPlaceholder != null ){ dockables.list().insertPlaceholder( index, newPlaceholder ); } } else{ index = dockables.dockables().size(); if( newPlaceholder != null ){ dockables.dockables().insertPlaceholder( index, newPlaceholder ); } } if( resultIndex == -1 ){ resultIndex = index; } request.answer( new ToolbarProperty( index, newPlaceholder )); } @Override public boolean drop( Dockable dockable, DockableProperty property ){ if( isValidProperty( property ) ) { final boolean acceptable = acceptable( dockable ); boolean result = false; final int index = Math.min( getDockableCount(), getIndex( property ) ); final Path placeholder = getPlaceholder( property ); if( (placeholder != null) && (property.getSuccessor() != null) ) { final StationChildHandle preset = dockables.getDockableAt( placeholder ); if( preset != null ) { final DockStation station = preset.getDockable().asDockStation(); if( station != null ) { if( station.drop( dockable, property.getSuccessor() ) ) { dockables.removeAll( placeholder ); result = true; } } } } if( !result && (placeholder != null) ) { if( acceptable && dockables.hasPlaceholder( placeholder ) ) { add( dockable, index, placeholder ); result = true; } } if( !result && (dockables.dockables().size() == 0) ) { if( acceptable ) { drop( dockable ); result = true; } } if( !result ) { if( (index < dockables.dockables().size()) && (property.getSuccessor() != null) ) { final DockStation child = getDockable( index ).asDockStation(); if( child != null ) { result = child.drop( dockable, property.getSuccessor() ); } } } if( !result && acceptable ) { result = drop( dockable, index ); } return result; } return false; } @Override public void move( Dockable dockable, DockableProperty property ){ // TODO pending } /** * This listener is added to the parent of this station and will forward an * event to {@link ToolbarContainerDockStation#visibility} if the visibility * of the station changes. * * @author Benjamin Sigg */ private class VisibleListener extends DockStationAdapter { @Override public void dockableShowingChanged( DockStation station, Dockable dockable, boolean visible ){ if( dockable == ToolbarDockStation.this ) { visibility.fire(); } } } }