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

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

There is a newer version: 1.1.2p6a
Show 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) 2008 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.BorderLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridLayout;
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.ArrayList;
import java.util.List;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
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.displayer.DockableDisplayerHints;
import bibliothek.gui.dock.event.DockStationAdapter;
import bibliothek.gui.dock.event.DockStationListener;
import bibliothek.gui.dock.event.DockableListener;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.security.SecureContainer;
import bibliothek.gui.dock.station.AbstractDockableStation;
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.StationBackgroundComponent;
import bibliothek.gui.dock.station.StationChildHandle;
import bibliothek.gui.dock.station.StationPaint;
import bibliothek.gui.dock.station.stack.DefaultStackDockComponent;
import bibliothek.gui.dock.station.stack.StackDockComponent;
import bibliothek.gui.dock.station.stack.StackDockComponentFactory;
import bibliothek.gui.dock.station.stack.StackDockComponentParent;
import bibliothek.gui.dock.station.stack.StackDockComponentRepresentative;
import bibliothek.gui.dock.station.stack.StackDockProperty;
import bibliothek.gui.dock.station.stack.StackDockStationFactory;
import bibliothek.gui.dock.station.stack.TabContent;
import bibliothek.gui.dock.station.stack.TabContentFilterListener;
import bibliothek.gui.dock.station.stack.tab.TabContentFilter;
import bibliothek.gui.dock.station.stack.tab.layouting.TabPlacement;
import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem;
import bibliothek.gui.dock.station.support.DockablePlaceholderList;
import bibliothek.gui.dock.station.support.DockableVisibilityManager;
import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter;
import bibliothek.gui.dock.station.support.PlaceholderListItemConverter;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.support.PlaceholderList.Filter;
import bibliothek.gui.dock.station.support.PlaceholderList.Level;
import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue;
import bibliothek.gui.dock.themes.DefaultStationPaintValue;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.title.ControllerTitleFactory;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleFactory;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.BackgroundAlgorithm;
import bibliothek.gui.dock.util.BackgroundPanel;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.util.Path;

/**
 * On this station, only one of many children is visible. The other children
 * are hidden behind the visible child. There are some buttons where the
 * user can choose which child is visible. This station behaves like
 * a {@link JTabbedPane}.
* This station tries to register a {@link DockTitleFactory} to its * {@link DockController} with the key {@link #TITLE_ID}. * @author Benjamin Sigg */ public class StackDockStation extends AbstractDockableStation implements StackDockComponentParent{ /** The id of the titlefactory which is used by this station */ public static final String TITLE_ID = "stack"; /** Key used to read the current {@link StackDockComponentFactory} */ public static final PropertyKey COMPONENT_FACTORY = new PropertyKey( "stack dock component factory" ); /** Key for setting the side at which the tabs appear in relation to the selected dockable */ public static final PropertyKey TAB_PLACEMENT = new PropertyKey( "stack dock station tab side", new ConstantPropertyFactory( TabPlacement.TOP_OF_DOCKABLE ), true ); /** Key for setting the {@link TabContentFilter} */ public static final PropertyKey TAB_CONTENT_FILTER = new PropertyKey( "stack dock tab content filter" ); /** A list of all children */ private DockablePlaceholderList dockables = new DockablePlaceholderList(); /** * A list of {@link MouseInputListener MouseInputListeners} which are * registered at this station. */ private List mouseInputListeners = new ArrayList(); /** A manager for firing events if a child changes its visibility-state */ private DockableVisibilityManager visibility; /** A paint to draw lines */ private DefaultStationPaintValue paint; /** A factory to create {@link DockableDisplayer} */ private DefaultDisplayerFactoryValue displayerFactory; /** The set of displayers shown on this station */ private DisplayerCollection displayers; /** The {@link Dockable} which is currently moved or dropped */ private Dockable dropping; /** Tells whether some lines have to be painted, or not */ private boolean draw = false; /** The preferred location where {@link #dropping} should be added */ private Insert insert; /** The graphical representation of this station */ private Background background; /** The panel where components are added */ private JComponent panel; /** The background of the {@link #panel} */ private PanelBackground panelBackground = new PanelBackground(); /** A Component which shows two or more children of this station */ private StackDockComponent stackComponent; /** Responsible for updating a {@link DockElementRepresentative} that covers the empty areas of {@link #stackComponent} */ private StackDockComponentRepresentative stackComponentRepresentative; /** The current component factory */ private PropertyValue stackComponentFactory; /** Where to put tabs */ private PropertyValue tabPlacement; /** The version of titles which should be used for this station */ private DockTitleVersion title; /** A listener observing the children for changes of their icon or titletext */ private Listener listener = new Listener(); /** strategy for selecting placeholders */ private PropertyValue placeholderStrategy = new PropertyValue( PlaceholderStrategy.PLACEHOLDER_STRATEGY ) { @Override protected void valueChanged( PlaceholderStrategy oldValue, PlaceholderStrategy newValue ){ dockables.setStrategy( newValue ); } }; /** filter for setting appearance of tabs */ private PropertyValue tabContentFilter = new PropertyValue( TAB_CONTENT_FILTER ) { @Override protected void valueChanged( TabContentFilter oldValue, TabContentFilter newValue ){ if( oldValue != newValue ){ if( oldValue != null ){ oldValue.removeListener( tabContentFilterListener ); oldValue.uninstall( StackDockStation.this ); } if( newValue != null ){ newValue.install( StackDockStation.this ); newValue.addListener( tabContentFilterListener ); } tabContentFilterListener.contentChanged(); } } }; /** a listener to {@link #tabContentFilter} */ private TabContentFilterListener tabContentFilterListener = new TabContentFilterListener() { public void contentChanged(){ int count = getDockableCount(); for( int i = 0; i < count; i++ ){ updateContent( i ); } } public void contentChanged( StackDockComponent component ){ // ignore } public void contentChanged( StackDockStation station ){ if( StackDockStation.this == station ){ contentChanged(); } } public void contentChanged( Dockable dockable ){ if( dockable.getDockParent() == StackDockStation.this ){ int index = indexOf( dockable ); if( index >= 0 ){ updateContent( index ); } } } }; /** * A listener added to the parent of this station. The listener ensures * that the visibility-state is always correct. */ private VisibleListener visibleListener; /** * The dockable which was or is currently selected. */ private Dockable lastSelectedDockable = null; /** * Constructs a new StackDockStation */ public StackDockStation(){ this( null ); } /** * Constructs a new station and sets the theme. * @param theme the theme of the station, may be null */ public StackDockStation( DockTheme theme ){ super( theme ); init(); } /** * Creates a new station. * @param theme the theme of this station, can be null * @param init true if the fields of this object should * be initialized, false otherwise. If false, * then the subclass has to call {@link #init()} exactly once. */ protected StackDockStation( DockTheme theme, boolean init ){ super( theme ); if( init ) init(); } /** * Initializes the fields of this object, has to be called exactly once. */ protected void init(){ paint = new DefaultStationPaintValue( ThemeManager.STATION_PAINT + ".stack", this ); displayerFactory = new DefaultDisplayerFactoryValue( ThemeManager.DISPLAYER_FACTORY + ".stack", this ); visibleListener = new VisibleListener(); visibility = new DockableVisibilityManager( listeners ); displayers = new DisplayerCollection( this, displayerFactory ); displayers.addDockableDisplayerListener( new DockableDisplayerListener(){ public void discard( DockableDisplayer displayer ){ StackDockStation.this.discard( displayer ); } }); background = createBackground(); background.setController( getController() ); panel = background.getContentPane(); stackComponentFactory = new PropertyValue( COMPONENT_FACTORY ){ @Override protected void valueChanged( StackDockComponentFactory oldValue, StackDockComponentFactory newValue ) { if( newValue == null ) setStackComponent( createStackDockComponent() ); else setStackComponent( newValue.create( StackDockStation.this ) ); } }; tabPlacement = new PropertyValue( TAB_PLACEMENT ){ @Override protected void valueChanged( TabPlacement oldValue, TabPlacement newValue ){ if( stackComponent != null ){ stackComponent.setTabPlacement( newValue ); } } }; stackComponent = createStackDockComponent(); stackComponent.addChangeListener( visibleListener ); stackComponentRepresentative = new StackDockComponentRepresentative(); stackComponentRepresentative.setComponent( stackComponent ); stackComponentRepresentative.setTarget( this ); addDockStationListener( new DockStationAdapter() { @Override public void dockableSelected( DockStation station, Dockable oldSelection, Dockable newSelection ){ lastSelectedDockable = newSelection; } }); panel.addHierarchyListener( new HierarchyListener(){ public void hierarchyChanged( HierarchyEvent e ){ if( (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 ){ if( getDockParent() == null ){ getDockableStateListeners().checkVisibility(); } visibility.fire(); } } }); } /** * Creates the panel onto which this station will lay its children. * @return the new background */ protected Background createBackground(){ return new Background(); } /** * Creates the {@link StackDockComponent} which will be shown on * this station if the station has more then one child.
* This method is called directly by the constructor. * @return the new component */ protected StackDockComponent createStackDockComponent(){ return new DefaultStackDockComponent(); } public DockStation getStackDockParent(){ return this; } /** * Tells this station where to put the tabs. * @param placement the side or null to use the default value */ public void setTabPlacement( TabPlacement placement ){ tabPlacement.setValue( placement ); } /** * Gets the location where tabs are currently placed. * @return the side at which tabs are */ public TabPlacement getTabPlacement(){ return tabPlacement.getValue(); } /** * Sets the filter that tells this station how to set the content of the tabs. * @param filter the filter, can be null */ public void setTabContentFilter( TabContentFilter filter ){ tabContentFilter.setValue( filter ); } /** * Gets the filter that tells this station how to set the content of the tabs. * @return the filter, may be null */ public TabContentFilter getTabContentFilter(){ return tabContentFilter.getValue(); } /** * Tells whether this station should show its {@link StackDockComponent} even if it * has only one child. This property may only be changed if the {@link StackDockComponent} * is exchanged as well. * @return true if the {@link StackDockComponent} is to be always shown */ protected boolean singleTabStackDockComponent(){ StackDockComponent component = getStackComponent(); if( component == null ){ return false; } return component.isSingleTabComponent(); } /** * Sets the {@link StackDockComponent} which should be used by this * station. The component is shown when this station has more then * one child. Note that the stackComponent depends also * on the property {@link #COMPONENT_FACTORY}, and will be automatically * exchanged if that property changes. Clients should use * {@link #setStackComponentFactory(StackDockComponentFactory)} if they * want to exchange the component permanently. * @param stackComponent the new component * @throws IllegalArgumentException if stackComponent is null */ public void setStackComponent( StackDockComponent stackComponent ) { if( stackComponent == null ) throw new IllegalArgumentException( "Component must not be null" ); if( stackComponent != this.stackComponent ){ int selected = -1; if( this.stackComponent != null ){ this.stackComponent.setController( null ); Component component = this.stackComponent.getComponent(); for( MouseInputListener listener : mouseInputListeners ){ component.removeMouseListener( listener ); component.removeMouseMotionListener( listener ); } selected = this.stackComponent.getSelectedIndex(); this.stackComponent.removeChangeListener( visibleListener ); this.stackComponent.removeAll(); } this.stackComponent = stackComponent; stackComponent.setTabPlacement( tabPlacement.getValue() ); stackComponentRepresentative.setComponent( stackComponent ); if( getDockableCount() < 2 && !singleTabStackDockComponent() ){ stackComponent.addChangeListener( visibleListener ); } else{ panel.removeAll(); for( StationChildHandle handle : dockables.dockables() ){ DockableDisplayer displayer = handle.getDisplayer(); int index = stackComponent.getTabCount(); insertTab( displayer, index ); } panel.add( stackComponent.getComponent() ); if( selected >= 0 && selected < stackComponent.getTabCount() ) stackComponent.setSelectedIndex( selected ); stackComponent.addChangeListener( visibleListener ); } Component component = stackComponent.getComponent(); stackComponent.setController( getController() ); for( MouseInputListener listener : mouseInputListeners ){ component.addMouseListener( listener ); component.addMouseMotionListener( listener ); } updateConfigurableDisplayerHints(); } } /** * Gets the currently used {@link StackDockComponent} * @return the component * @see #setStackComponent(StackDockComponent) */ public StackDockComponent getStackComponent() { return stackComponent; } /** * Sets the factory which will be used to create a {@link StackDockComponent} * for this station. * @param factory the new factory, can be null if the default-factory * should be used */ public void setStackComponentFactory( StackDockComponentFactory factory ){ stackComponentFactory.setValue( factory ); } /** * Gets the factory which is used to create a {@link StackDockComponent}. * This method returns null if no factory was set through * {@link #setStackComponentFactory(StackDockComponentFactory)}. * @return the factory or null */ public StackDockComponentFactory getStackComponentFactory(){ return stackComponentFactory.getOwnValue(); } @Override protected void callDockUiUpdateTheme() throws IOException { DockUI.updateTheme( this, new StackDockStationFactory()); } @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( this.getController() != controller ){ for( StationChildHandle handle : dockables.dockables() ){ handle.setTitleRequest( null ); } boolean wasNull = getController() == null; background.setController( controller ); stackComponentFactory.setProperties( controller ); super.setController(controller); stackComponent.setController( controller ); tabPlacement.setProperties( controller ); placeholderStrategy.setProperties( controller ); tabContentFilter.setProperties( controller ); stackComponentRepresentative.setController( controller ); paint.setController( controller ); displayerFactory.setController( controller ); panelBackground.setController( controller ); if( controller != null ){ title = controller.getDockTitleManager().getVersion( TITLE_ID, ControllerTitleFactory.INSTANCE ); } else{ title = null; } displayers.setController( controller ); boolean isNull = controller == null; if( wasNull != isNull ){ if( wasNull ){ dockables.bind(); } else{ dockables.unbind(); } } for( StationChildHandle handle : dockables.dockables() ){ handle.setTitleRequest( title, true ); } visibility.fire(); } } /** * Gets a {@link StationPaint} which is used to paint some lines onto * this station. Use a {@link DefaultStationPaintValue#setDelegate(StationPaint) delegate} * to exchange the paint. * @return the paint */ public DefaultStationPaintValue getPaint() { return paint; } /** * Gets a {@link DisplayerFactory} which is used to create new * {@link DockableDisplayer} for this station. Use a * {@link DefaultDisplayerFactoryValue#setDelegate(DisplayerFactory) delegate} * to exchange the factory. * @return the factory */ public DefaultDisplayerFactoryValue getDisplayerFactory() { return displayerFactory; } /** * Gets the set of {@link DockableDisplayer displayers} used on this station. * @return the set of displayers */ public DisplayerCollection getDisplayers() { return displayers; } @Override public boolean isStationVisible() { DockStation parent = getDockParent(); if( parent != null ) return parent.isVisible( this ); else return panel.isDisplayable(); } @Override public boolean isVisible( Dockable dockable ) { return isStationVisible() && (dockables.dockables().size() == 1 || indexOf( dockable ) == stackComponent.getSelectedIndex() ); } public int getDockableCount() { return dockables.dockables().size(); } public Dockable getDockable( int index ) { return dockables.dockables().get( index ).getDockable(); } public DockableProperty getDockableProperty( Dockable dockable, Dockable target ) { int index = indexOf( dockable ); PlaceholderStrategy strategy = getPlaceholderStrategy(); Path placeholder = null; if( strategy != null ){ placeholder = strategy.getPlaceholderFor( target == null ? dockable : target ); if( placeholder != null ){ dockables.dockables().addPlaceholder( index, placeholder ); } } return new StackDockProperty( index, placeholder ); } public Dockable getFrontDockable() { if( dockables.dockables().size() == 0 ) return null; if( dockables.dockables().size() == 1 ) return dockables.dockables().get( 0 ).getDockable(); int index = stackComponent.getSelectedIndex(); if( index >= 0 ) return dockables.dockables().get( index ).getDockable(); return null; } public void setFrontDockable( Dockable dockable ) { if( dockables.dockables().size() > 1 && dockable != null ) stackComponent.setSelectedIndex( indexOf( dockable )); fireDockableSelected(); } /** * Informs all {@link DockStationListener}s that the selected element of this station changed. * This method only fires if there really is a change, hence it can be safely called multiple times. */ protected void fireDockableSelected(){ Dockable selection = getFrontDockable(); if( lastSelectedDockable != selection ){ listeners.fireDockableSelected( lastSelectedDockable, selection ); } } /** * Gets the index of a child. * @param dockable the child which is searched * @return the index of dockable or -1 if it was not found */ public int indexOf( Dockable dockable ){ int index = 0; for( StationChildHandle check : dockables.dockables() ){ if( check.getDockable() == dockable ){ return index; } index++; } return -1; } public PlaceholderMap getPlaceholders(){ return dockables.toMap(); } public void setPlaceholders( PlaceholderMap placeholders ){ if( getDockableCount() > 0 ){ throw new IllegalStateException( "there are children on this station" ); } try{ DockablePlaceholderList next = new DockablePlaceholderList( placeholders ); if( getController() != null ){ dockables.setStrategy( null ); dockables.unbind(); dockables = next; dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } else{ dockables = next; } } catch( IllegalArgumentException ex ){ // ignore } } /** * Gets the placeholders of this station using a {@link PlaceholderListItemConverter} to * encode the children of this station. To be exact, the converter puts the following * parameters for each {@link Dockable} into the map: *
    *
  • id: the integer from children
  • *
  • index: the location of the element in the dockables-list
  • *
  • placeholder: the placeholder of the element, might not be written
  • *
* @param children a unique identifier for each child of this station * @return the map */ public PlaceholderMap getPlaceholders( final Map children ){ dockables.insertAllPlaceholders(); final PlaceholderStrategy strategy = getPlaceholderStrategy(); return dockables.toMap( new PlaceholderListItemAdapter() { @Override public ConvertedPlaceholderListItem convert( int index, StationChildHandle dockable ){ ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem(); Integer id = children.get( dockable.getDockable() ); if( id == null ){ return null; } item.putInt( "id", id ); item.putInt( "index", index ); if( strategy != null ){ Path placeholder = strategy.getPlaceholderFor( dockable.getDockable() ); if( placeholder != null ){ item.putString( "placeholder", placeholder.toString() ); item.setPlaceholder( placeholder ); } } return item; } public StationChildHandle convert( ConvertedPlaceholderListItem item ){ // ignore return null; } }); } /** * Sets all placeholders and children of this station. * @param placeholders the new children and placeholders * @param children map to convert items to {@link Dockable}s * @throws IllegalStateException if there are still children on this station */ public void setPlaceholders( PlaceholderMap placeholders, final Map children ){ DockUtilities.checkLayoutLocked(); if( getDockableCount() > 0 ){ throw new IllegalStateException( "there are children on this station" ); } DockController controller = getController(); try{ if( controller != null ){ controller.freezeLayout(); } dockables.setStrategy( null ); dockables.unbind(); DockablePlaceholderList next = new DockablePlaceholderList(); dockables = next; next.read( placeholders, new PlaceholderListItemAdapter() { private DockHierarchyLock.Token token; private int size = 0; @Override public StationChildHandle convert( ConvertedPlaceholderListItem item ){ int id = item.getInt( "id" ); Dockable dockable = children.get( id ); if( dockable == null ){ return null; } DockUtilities.ensureTreeValidity( StackDockStation.this, dockable ); token = DockHierarchyLock.acquireLinking( StackDockStation.this, dockable ); listeners.fireDockableAdding( dockable ); dockable.addDockableListener( listener ); StationChildHandle handle = new StationChildHandle( StackDockStation.this, getDisplayers(), dockable, title ); return handle; } @Override public void added( StationChildHandle handle ){ try{ Dockable dockable = handle.getDockable(); dockable.setDockParent( StackDockStation.this ); handle.updateDisplayer(); addToPanel( handle, size, size ); size++; listeners.fireDockableAdded( dockable ); } finally{ token.release(); } } }); if( controller != null ){ dockables.bind(); dockables.setStrategy( getPlaceholderStrategy() ); } } finally{ if( controller != null ){ controller.meltLayout(); } } } /** * Gets the {@link PlaceholderStrategy} that is currently in use. * @return the current strategy, may be null */ public PlaceholderStrategy getPlaceholderStrategy(){ return placeholderStrategy.getValue(); } /** * Sets the {@link PlaceholderStrategy} to use, null will set * the default strategy. * @param strategy the new strategy, can be null */ public void setPlaceholderStrategy( PlaceholderStrategy strategy ){ placeholderStrategy.setValue( strategy ); } public boolean prepareDrop( int x, int y, int titleX, int titleY, boolean checkOverrideZone, Dockable dockable ){ if( SwingUtilities.isDescendingFrom( getComponent(), dockable.getComponent() )){ setInsert( null, null ); return false; } DockStation parent = getDockParent(); Point point = new Point( x, y ); SwingUtilities.convertPointFromScreen( point, panel ); if( parent != null ){ if( checkOverrideZone && parent.isInOverrideZone( x, y, this, dockable )){ if( dockables.dockables().size() > 1 ){ if( setInsert( exactTabIndexAt( point.x, point.y ), dockable )) return true; } else if( dockables.dockables().size() == 1 ){ DockTitle title = dockables.dockables().get( 0 ).getDisplayer().getTitle(); if( title != null ){ Component component = title.getComponent(); Point p = new Point( x, y ); SwingUtilities.convertPointFromScreen( p, component ); if( component.getBounds().contains( p )){ return setInsert( new Insert( 0, true ), dockable ); } } } return false; } } return setInsert( tabIndexAt( point.x, point.y ), dockable ); } public void drop(){ add( dropping, insert.tab + (insert.right ? 1 : 0), null ); } public void drop( Dockable dockable ) { drop( dockable, true ); } /** * Adds dockable to this station. * @param dockable the element to drop * @param autoPlaceholder whether the {@link PlaceholderStrategy} can be invoked to search for a matching placeholder */ public void drop( Dockable dockable, boolean autoPlaceholder ){ Path placeholder = null; if( autoPlaceholder ){ PlaceholderStrategy strategy = getPlaceholderStrategy(); placeholder = strategy == null ? null : strategy.getPlaceholderFor( dockable ); } boolean done = false; if( placeholder != null ){ done = drop( dockable, new StackDockProperty( dockables.dockables().size(), placeholder ) ); } if( !done ){ add( dockable, dockables.dockables().size(), null ); } } public boolean drop( Dockable dockable, DockableProperty property ) { if( property instanceof StackDockProperty ){ return drop( dockable, (StackDockProperty)property ); } else return false; } /** * Adds a new child to this station, and tries to match the property * as good as possible. * @param dockable the new child * @param property the preferred location of the child * @return true if the child could be added, false * if the child couldn't be added */ public boolean drop( Dockable dockable, StackDockProperty property ){ DockUtilities.checkLayoutLocked(); int index = property.getIndex(); Path placeholder = property.getPlaceholder(); boolean acceptable = acceptable( dockable ); DockableProperty successor = property.getSuccessor(); boolean result = false; if( placeholder != null && successor != null ){ StationChildHandle preset = dockables.getDockableAt( placeholder ); if( preset != null ){ DockStation station = preset.getDockable().asDockStation(); if( station != null ){ if( station.drop( dockable, successor )){ result = true; dockables.removeAll( placeholder ); } } } } else if( placeholder != null ){ if( acceptable && dockables.hasPlaceholder( placeholder )){ add( dockable, 0, placeholder ); result = true; } } if( !result && dockables.dockables().size() == 0 ){ if( acceptable ){ drop( dockable, false ); result = true; } } if( !result ){ index = Math.min( index, dockables.dockables().size() ); if( index < dockables.dockables().size() && successor != null ){ DockStation station = dockables.dockables().get( index ).getDockable().asDockStation(); if( station != null ){ if( station.drop( dockable, successor )){ result = true; } } } if( !result && acceptable ){ add( dockable, index ); result = true; } } return result; } public boolean prepareMove( int x, int y, int titleX, int titleY, boolean checkOverrideZone, Dockable dockable ) { DockStation parent = getDockParent(); Point point = new Point( x, y ); SwingUtilities.convertPointFromScreen( point, panel ); if( parent != null ){ if( checkOverrideZone && parent.isInOverrideZone( x, y, this, dockable )){ if( dockables.dockables().size() > 1 ){ if( setInsert( exactTabIndexAt( point.x, point.y ), dockable ) ) return true; } return false; } } return setInsert( tabIndexAt( point.x, point.y ), dockable ); } /** * Checks whether child can be inserted at insert. * If so, then the field {@link #insert} and {@link #dropping} are set. * @param insert the new location * @param child the element to insert * @return true if the combination is valid */ private boolean setInsert( Insert insert, Dockable child ){ if( insert != null && accept( child ) && child.accept( this ) && getController().getAcceptance().accept( this, child )){ this.insert = insert; this.dropping = child; } else{ this.insert = null; this.dropping = null; } return this.insert != null; } /** * Gets the location where {@link #drop()} or {@link #move()} will insert the next * {@link Dockable}. * @return the insertion location, can be null */ public Insert getInsert(){ return insert; } public void move() { int index = indexOf( dropping ); if( index >= 0 ){ int drop = insert.tab + (insert.right ? 1 : 0 ); if( drop > index ){ drop--; } move( index, drop ); } } public void move( Dockable dockable, DockableProperty property ) { if( property instanceof StackDockProperty ){ DockUtilities.checkLayoutLocked(); int index = indexOf( dockable ); if( index < 0 ) throw new IllegalArgumentException( "dockable not child of this station" ); int destination = ((StackDockProperty)property).getIndex(); destination = Math.min( destination, getDockableCount()-1 ); destination = Math.max( 0, destination ); move( index, destination ); } } private void move( int source, int destination ){ if( source != destination ){ DockUtilities.checkLayoutLocked(); dockables.dockables().move( source, destination ); stackComponent.moveTab( source, destination ); fireDockablesRepositioned( Math.min( source, destination ), Math.max( source, destination ) ); } } /** * Tells which gap (between tabs) is chosen if the mouse has the coordinates x/y. * If there is no tab at this location, a default-tab is chosen. * @param x x-coordinate in the system of this station * @param y y-coordinate in the system of this station * @return the location of a tab */ protected Insert tabIndexAt( int x, int y ){ if( dockables.dockables().size() == 0 ) return new Insert( 0, false ); if( dockables.dockables().size() == 1 ) return new Insert( 1, false ); Insert insert = exactTabIndexAt( x, y ); if( insert == null ) insert = new Insert( dockables.dockables().size()-1, true ); return insert; } /** * Gets the gap which is selected when the mouse is at x/y. * @param x x-coordinate in the system of this station * @param y y-coordinate in the system of this station * @return the location of a tab or null if no tab is * under x/y */ protected Insert exactTabIndexAt( int x, int y ){ Point point = SwingUtilities.convertPoint( panel, x, y, stackComponent.getComponent() ); for( int i = 0, n = dockables.dockables().size(); i boolean isInOverrideZone( int x, int y, D invoker, Dockable drop ){ DockStation parent = getDockParent(); if( parent != null ) return parent.isInOverrideZone( x, y, invoker, drop ); return false; } public boolean canDrag( Dockable dockable ) { return true; } public void drag( Dockable dockable ) { if( dockable.getDockParent() != this ) throw new IllegalArgumentException( "The dockable can't be dragged, it is not child of this station" ); int index = indexOf( dockable ); if( index < 0 ) throw new IllegalArgumentException( "The dockable is not part of this station." ); remove( index ); } 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 old, Dockable next ) { replace( old, next, false ); } public void replace( Dockable old, Dockable next, boolean station ) { DockUtilities.checkLayoutLocked(); DockController controller = getController(); try{ if( controller != null ) controller.freezeLayout(); int index = indexOf( old ); int listIndex = dockables.levelToBase( index, Level.DOCKABLE ); DockablePlaceholderList.Item oldItem = dockables.list().get( listIndex ); remove( index ); add( next, index ); DockablePlaceholderList.Item newItem = dockables.list().get( listIndex ); if( station ){ newItem.setPlaceholderMap( old.asDockStation().getPlaceholders() ); } else{ newItem.setPlaceholderMap( oldItem.getPlaceholderMap() ); } newItem.setPlaceholderSet( oldItem.getPlaceholderSet() ); } finally{ if( controller != null ) controller.meltLayout(); } } /** * Adds a child to this station at the location index. * @param dockable the new child * @param index the preferred location of the new child */ public void add( Dockable dockable, int index ){ add( dockable, index, null ); } /** * Adds a child to this station at the location index. * @param dockable the new child * @param index the preferred location of the new child * @param placeholder the preferred location of the new child, can be null */ protected void add( Dockable dockable, int index, Path placeholder ){ DockUtilities.ensureTreeValidity( this, dockable ); DockUtilities.checkLayoutLocked(); DockHierarchyLock.Token token = DockHierarchyLock.acquireLinking( this, dockable ); try{ listeners.fireDockableAdding( dockable ); StationChildHandle handle = new StationChildHandle( this, getDisplayers(), dockable, title ); handle.updateDisplayer(); int inserted = -1; if( placeholder != null && dockables.getDockableAt( placeholder ) == null ){ inserted = dockables.put( placeholder, handle ); } else if( placeholder != null ){ index = dockables.getDockableIndex( placeholder ); } if( inserted == -1 ){ dockables.dockables().add( index, handle ); } else{ index = inserted; } addToPanel( handle, index, dockables.dockables().size()-1 ); dockable.setDockParent( this ); dockable.addDockableListener( listener ); listeners.fireDockableAdded( dockable ); fireDockablesRepositioned( index+1 ); fireDockableSelected(); } finally{ token.release(); } } /** * Adds the contents of handle to the {@link #stackComponent} of this station. The new * handle may or may not already be stored in {@link #dockables}. * @param handle the handle to add * @param index the index where to add the handle * @param size the current amount of children of the panel, must be either the size of {@link #dockables} if * handle is not yet stored or the size of {@link #dockables}-1 is handle already is * stored. */ protected void addToPanel( StationChildHandle handle, int index, int size ){ if( size == 0 && !singleTabStackDockComponent() ){ DockableDisplayer displayer = handle.getDisplayer(); panel.add( displayer.getComponent() ); } else{ int selectionIndex = index; if( size == 1 && !singleTabStackDockComponent() ){ panel.removeAll(); Filter list = dockables.dockables(); if( list.get( 0 ) == handle ){ if( list.size() != 2 ){ throw new IllegalStateException( "handle is stored, size is 1, but number of known dockables is not 2" ); } handle = list.get( 1 ); index = 1; } DockableDisplayer child = list.get( 0 ).getDisplayer(); insertTab( child, 0 ); panel.add( stackComponent.getComponent() ); } DockableDisplayer displayer = handle.getDisplayer(); insertTab( displayer, index ); stackComponent.setSelectedIndex( selectionIndex ); } panel.revalidate(); panel.repaint(); } private void insertTab( DockableDisplayer displayer, int index ){ Dockable dockable = displayer.getDockable(); String title = dockable.getTitleText(); String tooltip = dockable.getTitleToolTip(); Icon icon = dockable.getTitleIcon(); TabContentFilter filter = getTabContentFilter(); if( filter != null ){ TabContent content = new TabContent( icon, title, tooltip ); content = filter.filter( content, this, dockable ); if( content == null ){ title = null; tooltip = null; icon = null; } else{ title = content.getTitle(); tooltip = content.getTooltip(); icon = content.getIcon(); } } stackComponent.insertTab( title, icon, displayer.getComponent(), dockable, index ); stackComponent.setTooltipAt( index, tooltip ); } /** * Replaces displayer with a new instance. * @param displayer the displayer to replace */ protected void discard( DockableDisplayer displayer ){ Dockable dockable = displayer.getDockable(); int index = indexOf( dockable ); if( index < 0 ) throw new IllegalArgumentException( "displayer is not a child of this station: " + displayer ); StationChildHandle handle = dockables.dockables().get( index ); handle.updateDisplayer(); displayer = handle.getDisplayer(); if( dockables.dockables().size() == 1 && singleTabStackDockComponent() ){ panel.removeAll(); panel.add( displayer.getComponent() ); } else{ stackComponent.setComponentAt( index, displayer.getComponent() ); } } /** * Removes the child of location index.
* Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure noone else adds or * removes Dockables. * @param index the location of the child which will be removed */ public void remove( int index ){ if( index < 0 || index >= dockables.dockables().size() ) throw new IllegalArgumentException( "Index out of bounds" ); DockUtilities.checkLayoutLocked(); StationChildHandle handle = dockables.dockables().get( index ); Dockable dockable = handle.getDockable(); DockHierarchyLock.Token token = DockHierarchyLock.acquireUnlinking( this, dockable ); try{ listeners.fireDockableRemoving( dockable ); if( dockables.dockables().size() == 1 ){ if( singleTabStackDockComponent() ){ stackComponent.remove( 0 ); } else{ panel.remove( dockables.dockables().get( 0 ).getDisplayer().getComponent() ); } dockables.remove( 0 ); panel.repaint(); } else if( dockables.dockables().size() == 2 && !singleTabStackDockComponent() ){ panel.remove( stackComponent.getComponent() ); dockables.remove( index ); stackComponent.removeAll(); panel.add( dockables.dockables().get( 0 ).getDisplayer().getComponent() ); } else{ dockables.remove( index ); stackComponent.remove( index ); } handle.destroy(); dockable.removeDockableListener( listener ); panel.revalidate(); dockable.setDockParent( null ); listeners.fireDockableRemoved( dockable ); } finally{ token.release(); } fireDockablesRepositioned( index ); fireDockableSelected(); } public Component getComponent() { return background; } @Override public void configureDisplayerHints( DockableDisplayerHints hints ) { super.configureDisplayerHints( hints ); updateConfigurableDisplayerHints(); } /** * Updates the {@link #getConfigurableDisplayerHints() displayer hints} * of this station. */ protected void updateConfigurableDisplayerHints(){ DockableDisplayerHints hints = getConfigurableDisplayerHints(); if( hints != null ){ hints.setShowBorderHint( !getStackComponent().hasBorder() ); } } @Override public void addMouseInputListener( MouseInputListener listener ) { panel.addMouseListener( listener ); panel.addMouseMotionListener( listener ); mouseInputListeners.add( listener ); if( stackComponent != null ){ stackComponent.getComponent().addMouseListener( listener ); stackComponent.getComponent().addMouseMotionListener( listener ); } } @Override public void removeMouseInputListener( MouseInputListener listener ) { panel.removeMouseListener( listener ); panel.removeMouseMotionListener( listener ); mouseInputListeners.remove( listener ); if( stackComponent != null ){ stackComponent.getComponent().removeMouseListener( listener ); stackComponent.getComponent().removeMouseMotionListener( listener ); } } public String getFactoryID() { return StackDockStationFactory.ID; } private void updateContent( int index ){ if( index >= 0 && (getDockableCount() > 1 || singleTabStackDockComponent()) ){ Dockable dockable = getDockable( index ); TabContentFilter filter = getTabContentFilter(); TabContent content = new TabContent( dockable.getTitleIcon(), dockable.getTitleText(), dockable.getTitleToolTip() ); if( filter != null ){ content = filter.filter( content, this, dockable ); } if( content == null ){ stackComponent.setTitleAt( index, null ); stackComponent.setIconAt( index, null ); stackComponent.setTooltipAt( index, null ); } else{ stackComponent.setTitleAt( index, content.getTitle() ); stackComponent.setIconAt( index, content.getIcon() ); stackComponent.setTooltipAt( index, content.getTooltip() ); } } } /** * A listener for the parent of this station. This listener will fire * events if the visibility-state of this station changes.
* This listener is also added to the {@link StackDockStation#getStackComponent() stack-component} * of the station, and ensures that the visible child has the focus. * @author Benjamin Sigg */ private class VisibleListener extends DockStationAdapter implements ChangeListener{ @Override public void dockableVisibiltySet( DockStation station, Dockable dockable, boolean visible ) { visibility.fire(); } public void stateChanged( ChangeEvent e ) { DockController controller = getController(); if( controller != null ){ Dockable selection = getFrontDockable(); if( selection != null ) controller.setFocusedDockable( selection, null, false ); fireDockableSelected(); } visibility.fire(); } } /** * This listener is added to the children of the station. Whenever the * icon or the title-text of a child changes, the listener will inform * the {@link StackDockStation#getStackComponent() stack-component} about * the change. * @author Benjamin Sigg */ private class Listener implements DockableListener{ public void titleBound( Dockable dockable, DockTitle title ) { // do nothing } public void titleUnbound( Dockable dockable, DockTitle title ) { // do nothing } public void titleTextChanged( Dockable dockable, String oldTitle, String newTitle ) { if( dockables.dockables().size() > 1 ){ int index = indexOf( dockable ); updateContent( index ); } } public void titleToolTipChanged( Dockable dockable, String oldTooltip, String newTooltip ) { if( dockables.dockables().size() > 1 ){ int index = indexOf( dockable ); updateContent( index ); } } public void titleIconChanged( Dockable dockable, Icon oldIcon, Icon newIcon ) { if( dockables.dockables().size() > 1 ){ int index = indexOf( dockable ); updateContent( index ); } } public void titleExchanged( Dockable dockable, DockTitle title ) { // ignore } } /** * This panel is used as base of the station. All children of the station * have this panel as parent too. * @author Benjamin Sigg */ protected class Background extends SecureContainer{ private BackgroundPanel content; /** * Creates a new panel */ public Background(){ content = new BackgroundPanel( true, false ); content.setBackground( panelBackground ); setContentPane( content ); content.setLayout( new GridLayout( 1, 1 )); getBasePane().removeAll(); getBasePane().setLayout( new BorderLayout() ); getBasePane().add( content, BorderLayout.CENTER ); } @Override protected void paintOverlay( Graphics g ) { DefaultStationPaintValue paint = getPaint(); if( draw && dockables.dockables().size() > 1 && insert != null ){ Rectangle bounds = null; if( insert.tab >= 0 && insert.tab < stackComponent.getTabCount() ) bounds = stackComponent.getBoundsAt( insert.tab ); if( bounds != null ){ Point a = new Point(); Point b = new Point(); if( insert.right ){ insertionLine( bounds, insert.tab+1 < stackComponent.getTabCount() ? stackComponent.getBoundsAt( insert.tab+1 ) : null, a, b, true ); } else{ insertionLine( insert.tab > 0 ? stackComponent.getBoundsAt( insert.tab-1 ) : null, bounds, a, b, false ); } paint.drawInsertionLine( g, a.x, a.y, b.x, b.y ); } } if( draw ){ Rectangle bounds = new Rectangle( 0, 0, getWidth(), getHeight() ); Rectangle insert = null; if( getDockableCount() < 2 ) insert = bounds; else{ int index = stackComponent.getSelectedIndex(); if( index >= 0 ){ Component front = dockables.dockables().get( index ).getDisplayer().getComponent(); Point location = new Point( 0, 0 ); location = SwingUtilities.convertPoint( front, location, this ); insert = new Rectangle( location.x, location.y, front.getWidth(), front.getHeight() ); } } if( insert != null ){ paint.drawInsertion( g, bounds, insert ); } } } } /** * When dropping or moving a {@link Dockable}, a line has to be painted * between two tabs. This method determines the exact location of that line. * @param left the bounds of the tab left to the line, might be null if * leftImportant is false. * @param right the bounds of the tab right to the line, might be null if * leftImportant is true. * @param a the first point of the line, should be used as output of this method * @param b the second point of the line, should be used as output of this method * @param leftImportant true if the mouse is over the left tab, false * if the mouse is over the right tab. */ protected void insertionLine( Rectangle left, Rectangle right, Point a, Point b, boolean leftImportant ){ if( tabPlacement.getValue().isHorizontal() ){ if( left != null && right != null ){ int top = Math.max( left.y, right.y ); int bottom = Math.min( left.y + left.height, right.y + right.height ); if( bottom > top ){ int dif = bottom - top; if( dif >= 0.8*left.height && dif >= 0.8*right.height ){ a.x = (left.x+left.width+right.x) / 2; a.y = top; b.x = a.x; b.y = bottom; return; } } } if( leftImportant ){ a.x = left.x + left.width; a.y = left.y; b.x = a.x; b.y = a.y + left.height; } else{ a.x = right.x; a.y = right.y; b.x = a.x; b.y = a.y + right.height; } } else{ if( left != null && right != null ){ int top = Math.max( left.x, right.x ); int bottom = Math.min( left.x + left.width, right.x + right.width ); if( bottom > top ){ int dif = bottom - top; if( dif >= 0.8*left.width && dif >= 0.8*right.width ){ a.y = (left.y+left.height+right.y) / 2; a.x = top; b.y = a.y; b.x = bottom; return; } } } if( leftImportant ){ a.y = left.y + left.height; a.x = left.x; b.y = a.y; b.x = a.x + left.width; } else{ a.y = right.y; a.x = right.x; b.y = a.y; b.x = a.x + right.width; } } } /** * Describes the gap between two tabs. * @author Benjamin Sigg */ public static class Insert{ /** The location of a base-tab */ private final int tab; /** Whether the gap is left or right of {@link #tab}*/ private final boolean right; /** * Constructs a new Gap-location * @param tab The location of a base-tab * @param right Whether the gap is left or right of tab */ public Insert( int tab, boolean right ){ this.tab = tab; this.right = right; } /** * A reference for this gap, the gap is either at the left * or at the right side of this tab. * @return the reference */ public int getTab(){ return tab; } /** * Whether the tab is at the left or the right of {@link #getTab()}. * @return true if the gab is at the right side */ public boolean isRight(){ return right; } } /** * The algorithm that paints the background of this station. * @author Benjamin Sigg */ private class PanelBackground extends BackgroundAlgorithm implements StationBackgroundComponent{ /** * Creates a new algorithm. */ public PanelBackground(){ super( StationBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".station.stack" ); } public Component getComponent(){ return StackDockStation.this.getComponent(); } public DockStation getStation(){ return StackDockStation.this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy