/** * Bibliothek - DockingFrames * Library built on Java/Swing, allows the user to "drag and drop" * panels containing any Swing-Component the developer likes to add. * * Copyright (C) 2007 Benjamin Sigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.action; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import bibliothek.gui.dock.action.actions.SeparatorAction; import bibliothek.gui.dock.event.DockActionSourceListener; /** * A {@link DockActionSource} that combines various sources in one source. * This source behaves like a list of {@link DockActionSource DockActionSources}. * @author Benjamin Sigg */ public class MultiDockActionSource extends AbstractDockActionSource { private List<DockActionSource> sources = new ArrayList<DockActionSource>(); private List<SeparatorSource> separators = new ArrayList<SeparatorSource>(); private Listener listener; private boolean separateSources = false; private LocationHint hint; /** * Constructs a new source. The <code>sources</code> are added as children * of this source. * @param sources The children of this source */ public MultiDockActionSource( DockActionSource...sources ){ this( LocationHint.UNKNOWN, sources ); } /** * Constructs a new source. The <code>sources</code> are added as children * of this source. * @param hint the preferred location of this source * @param sources The children of this source */ public MultiDockActionSource( LocationHint hint, DockActionSource...sources ){ listener = new Listener(); for( DockActionSource source : sources ){ this.sources.add( source ); } setHint( hint ); } public Iterator<DockAction> iterator(){ return new Iterator<DockAction>(){ private Iterator<DockActionSource> sourceIterator = sources.iterator(); private Iterator<DockAction> actionIterator; public boolean hasNext(){ if( actionIterator == null ){ if( sourceIterator.hasNext() ) actionIterator = sourceIterator.next().iterator(); else return false; } while( true ){ if( actionIterator.hasNext() ) return true; if( sourceIterator.hasNext() ) actionIterator = sourceIterator.next().iterator(); else return false; } } public DockAction next(){ hasNext(); return actionIterator.next(); } public void remove(){ hasNext(); actionIterator.remove(); } }; } @Override public void addDockActionSourceListener( DockActionSourceListener listener ){ boolean empty = listeners.isEmpty(); super.addDockActionSourceListener( listener ); if( empty && !listeners.isEmpty() ){ for( DockActionSource source : sources ) source.addDockActionSourceListener( this.listener ); updateSeparators(); } } @Override public void removeDockActionSourceListener( DockActionSourceListener listener ){ boolean empty = listeners.isEmpty(); super.removeDockActionSourceListener( listener ); if( !empty && listeners.isEmpty() ){ for( DockActionSource source : sources ) source.removeDockActionSourceListener( this.listener ); } } public LocationHint getLocationHint(){ return hint; } /** * Sets the location-hint of this source. * @param hint the hint that tells an {@link ActionOffer} where to * put this source. */ public void setHint( LocationHint hint ){ if( hint == null ) throw new IllegalArgumentException( "Hint must not be null" ); this.hint = hint; } /** * Adds a separator at the end of the current list of actions */ public void addSeparator(){ add( SeparatorAction.SEPARATOR ); } /** * Tells whether there is a separator between sources or not * @return <code>true</code> if there is a separator */ public boolean isSeparateSources() { return separateSources; } /** * Sets whether there are separators between the children of this * source or not. * @param separateSources <code>true</code> if children should be separated */ public void setSeparateSources( boolean separateSources ) { if( this.separateSources != separateSources ){ this.separateSources = separateSources; updateSeparators(); } } /** * Adds a source as child of this source. All {@link DockAction DockActions} * of <code>source</code> will be presented as actions of this source.<br> * Note: creating circles or adding a source more than once will lead to * unspecified behavior. * @param source the new child */ public void add( DockActionSource source ){ SeparatorSource separator = new SeparatorSource( source ); sources.add( source ); sources.add( separator ); separators.add( separator ); if( !listeners.isEmpty() ){ source.addDockActionSourceListener( listener ); separator.addDockActionSourceListener( listener ); } int index = getDockActionCountUntil( sources.size()-2, false ); int length = source.getDockActionCount(); if( length > 0 ) fireAdded( index, index+length-1 ); updateSeparators(); } /** * Removes <code>source</code> from this {@link MultiDockActionSource}. * @param source the child to remove */ public void remove( DockActionSource source ){ int index = sources.indexOf( source ); if( index < 0 ) return; SeparatorSource separator = (SeparatorSource)sources.get( index+1 ); int actionIndex = getDockActionCountUntil( index, false ); int length = source.getDockActionCount(); sources.remove( index+1 ); sources.remove( index ); separators.remove( separator ); if( !listeners.isEmpty() ){ source.removeDockActionSourceListener( listener ); separator.removeDockActionSourceListener( listener ); } if( length > 0 ){ fireRemoved( actionIndex, index+length-1 ); } updateSeparators(); } /** * Removes all children of this source. */ public void removeAll(){ int length = getDockActionCount(); if( !listeners.isEmpty() ){ for( SeparatorSource source : separators ){ source.removeDockActionSourceListener( listener ); } for( DockActionSource source : sources ){ source.removeDockActionSourceListener( listener ); } } separators.clear(); sources.clear(); if( length > 0 ){ fireRemoved( 0, length-1 ); } } /** * Adds several actions to this source. * @param actions the new actions */ public void add( DockAction... actions ){ add( new DefaultDockActionSource( actions )); } public int getDockActionCount(){ return getDockActionCountUntil( sources.size(), true ); } /** * Gets the index of the child-source which contains <code>action</code>. * @param action the action for which is searched * @return the index of the source which contains the action or -1 */ protected int getSource( DockAction action ){ for( int i = 0, n = sources.size(); i<n; i++ ){ DockActionSource source = sources.get( i ); for( int j = 0, m = source.getDockActionCount(); j<m; j++ ){ if( source.getDockAction( j ) == action ) return i; } } return -1; } /** * Counts how many {@link DockAction DockActions} are provided by the * source-children with index 0 (incl) to <code>index</code> (excl). * @param index the index of the first source that should not be counted * @param allowUpdate whether the {@link #updateSeparators()} can be called * by this method or not * @return the number of actions of the first <code>index</code> * child-sources. */ protected int getDockActionCountUntil( int index, boolean allowUpdate ){ if( allowUpdate && listeners.isEmpty() ) updateSeparators(); int sum = 0; for( int i = 0; i < index; i++ ) sum += sources.get( i ).getDockActionCount(); return sum; } public DockAction getDockAction( int index ) { if( listeners.isEmpty() ) updateSeparators(); int sum = 0; for( int i = 0, n = sources.size(); i<n; i++ ){ int length = sources.get( i ).getDockActionCount(); if( sum <= index && index < sum + length ) return sources.get( i ).getDockAction( index - sum ); sum += length; } throw new ArrayIndexOutOfBoundsException(); } /** * Ensures that all separators which must be visible are really visible. */ private void updateSeparators(){ int size = separators.size(); int index = 0; for( SeparatorSource source : separators ){ source.update( ++index == size ); } } /** * A listener to the sources of this group of sources. * @author Benjamin Sigg */ private class Listener implements DockActionSourceListener{ public void actionsAdded( DockActionSource source, int firstIndex, int lastIndex ) { int index = getDockActionCountUntil( sources.indexOf( source ), false ); fireAdded( firstIndex + index, lastIndex + index ); updateSeparators(); } public void actionsRemoved( DockActionSource source, int firstIndex, int lastIndex ) { int index = getDockActionCountUntil( sources.indexOf( source ), false ); fireRemoved( firstIndex + index, lastIndex + index ); updateSeparators(); } } /** * A source that shows one separator. * @author Benjamin Sigg */ private class SeparatorSource extends DefaultDockActionSource{ private DockActionSource predecessor; public SeparatorSource( DockActionSource predecessor ){ this.predecessor = predecessor; } public void update( boolean last ){ if( !separateSources || last ) remove( SeparatorAction.SEPARATOR ); else if( predecessor.getDockActionCount() > 0 && getDockActionCount() == 0 ) add( SeparatorAction.SEPARATOR ); } } }