bibliothek.gui.dock.support.mode.ModeManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docking-frames-common Show documentation
Show all versions of docking-frames-common Show documentation
DockingFrames is an open source Java Swing docking framework, licenced under LGPL 2.1.
This is the same distribution as the original distribution (http://www.docking-frames.org/), only reinstalled in maven
/* * 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) 2009 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.support.mode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.Dockable; import bibliothek.gui.dock.action.ActionGuard; import bibliothek.gui.dock.action.DockAction; import bibliothek.gui.dock.action.DockActionSource; import bibliothek.gui.dock.action.LocationHint; import bibliothek.gui.dock.action.MultiDockActionSource; import bibliothek.gui.dock.control.DockRegister; import bibliothek.gui.dock.util.DockUtilities; import bibliothek.util.Path; /** * Associates {@link Dockable}s with one {@link Mode} out of a set * of modes. This manager remembers in which order the modes were applied * to a {@link Dockable}. * @param
. * @param dockable the element to remove */ public void remove( Dockable dockable ){ DockableHandle entry = dockables.remove( dockable ); if( entry != null ){ if( !entry.empty ){ entries.remove( entry.id ); } fireRemoved( dockable ); } } /** * Removesthe kind of properties that are to be stored in this manager * @param the kind of {@link Mode}s used by this manager * @author Benjamin Sigg */ public abstract class ModeManager > { /** the ordered list of available modes */ private List modes = new ArrayList (); /** factories for creating {@link ModeSetting}s */ private Map > factories = new HashMap >(); /** lists for all known {@link Dockable}s their {@link DockableHandle} */ private Map dockables = new HashMap (); /** list all {@link DockableHandle}s ever created and not dismissed by this manager */ private Map entries = new HashMap (); /** all the listeners that are registered at this manager */ private List > listeners = new ArrayList >(); /** whether a mode is currently applying itself */ private int onTransaction = 0; /** continuous mode without storing the position of elements */ private int onContinuous = 0; /** the controller in whose realm this manager works */ private DockController controller; /** the set of affected dockables of the current transaction */ private ChangeSet affected; /** how often the {@link #affected} set was opened */ private int affectedCount = 0; /** used to change the history of {@link Dockable}s before applying a new mode */ private HistoryRewriter historyRewriter; private ActionGuard guard = new ActionGuard() { public boolean react( Dockable dockable ){ return getHandle( dockable ) != null; } public DockActionSource getSource( Dockable dockable ){ DockableHandle handle = getHandle( dockable ); if( handle == null ) return null; return handle.source; } }; /** * This {@link ActionGuard} adds {@link DockAction}s to unregistered parents of * registered {@link Dockable}s. */ private ActionGuard stationGuard = new ActionGuard(){ public boolean react( Dockable dockable ){ DockStation station = dockable.asDockStation(); return station != null && getHandle( dockable ) == null; } public DockActionSource getSource( Dockable dockable ){ return new ModeForwardingActionSource ( dockable.asDockStation(), ModeManager.this ); } }; /** * Creates a new manager. * @param controller the controller in whose realm this manager will work */ public ModeManager( DockController controller ){ controller.addActionGuard( stationGuard ); controller.addActionGuard( guard ); this.controller = controller; } /** * Unregisters listeners which this manager added to the {@link DockController} and * other components. */ public void destroy(){ if( controller != null ){ controller.removeActionGuard( stationGuard ); controller.removeActionGuard( guard ); controller = null; } } /** * Gets the controller in whose realm this manager works. * @return the controller */ public DockController getController(){ return controller; } /** * Adds a listener to this manager, the listener will be informed about * changes in this manager. * @param listener the new listener, not null
*/ public void addModeManagerListener( ModeManagerListener super H, ? super M> listener ){ if( listener == null ) throw new IllegalArgumentException( "listener must not be null" ); listeners.add( listener ); } /** * Removeslistener
from this manager. * @param listener the listener to remove */ public void removeModeManagerListener( ModeManagerListener super H, ? super M> listener ){ listeners.remove( listener ); } /** * Puts a new mode in this manager. If there is already a mode with the * same id registered, then the old mode gets replaced by the new one. * @param mode the new mode */ public void putMode( M mode ){ if( mode == null ) throw new IllegalArgumentException( "mode must not be null" ); for( ModeHandle handle : modes ){ if( handle.mode.getUniqueIdentifier().equals( mode.getUniqueIdentifier() )){ fireRemoved( handle.mode ); handle.mode = mode; fireAdded( mode ); return; } } modes.add( new ModeHandle( mode ) ); fireAdded( mode ); } /** * Adds a factory to this {@link ModeManager}. The factory will be used by the * {@link ModeSettings} to read and write data of the mode with the same identifier * asfactory
persistently.
* Note: A {@link Mode} might also provide a {@link ModeSettingFactory}, if * there is a collision of unique identifiers the factory of the mode is used. * @param factory the new factory */ public void putFactory( ModeSettingFactoryfactory ){ factories.put( factory.getModeId(), factory ); } /** * Gets a set containing all the {@link ModeSettingFactory}s that were added * to this manager. * @return the factories */ public Collection > getFactories(){ return Collections.unmodifiableCollection( factories.values() ); } /** * Removes mode
from this manager. Note that history information * about the mode remains. * @param mode the mode to remove */ public void removeMode( M mode ){ if( mode == null ) throw new IllegalArgumentException( "mode must not be null" ); for( ModeHandle handle : modes ){ if( handle.mode.getUniqueIdentifier().equals( mode.getUniqueIdentifier() )){ handle.mode = null; fireRemoved( handle.mode ); modes.remove( handle ); return; } } } /** * Searches and returns the mode with given unique identifierpath
. * @param path some unique identifier * @return the mode with that identifier ornull
*/ public M getMode( Path path ){ ModeHandle handle = getAccess( path ); return handle == null ? null : handle.mode; } private ModeHandle getAccess( Path path ){ for( ModeHandle mode : modes ){ if( mode.mode.getUniqueIdentifier().equals( path )) return mode; } return null; } /** * Sets the current {@link HistoryRewriter}. The rewriter is invoked every time before * the {@link Mode#apply(Dockable, Object, AffectedSet) apply} method of a {@link Mode} is * called. The rewriter can then change the history of one {@link Dockable}, e.g. to apply * additional checks whether an old state is still valid.
* A history rewriter does not change the history permanently. It creates a new history * object before theapply
method is called, but that new history object * will not be stored by the {@link ModeManager}. * @param historyRewriter the new rewriter, can benull
*/ public void setHistoryRewriter( HistoryRewriterhistoryRewriter ){ this.historyRewriter = historyRewriter; } /** * Gets the current {@link HistoryRewriter}. * @return the rewriter, can be null
* @see #setHistoryRewriter(HistoryRewriter) */ public HistoryRewritergetHistoryRewriter(){ return historyRewriter; } /** * Gets all the listeners that are currently registered in this manager. * @return the list of registered listeners */ @SuppressWarnings("unchecked") protected ModeManagerListener super H, ? super M>[] listeners(){ return listeners.toArray( new ModeManagerListener[ listeners.size() ] ); } /** * Calls {@link ModeManagerListener#dockableAdded(ModeManager, Dockable)} * on all listeners that are currently registered * @param dockable the new element */ protected void fireAdded( Dockable dockable ){ for( ModeManagerListener super H, ? super M> listener : listeners() ){ listener.dockableAdded( this, dockable ); } } /** * Calls {@link ModeManagerListener#dockableRemoved(ModeManager, Dockable)} * on all listeners that are currently registered. * @param dockable the removed element */ protected void fireRemoved( Dockable dockable ){ for( ModeManagerListener super H, ? super M> listener : listeners() ){ listener.dockableRemoved( this, dockable ); } } /** * Calls {@link ModeManagerListener#modeChanged(ModeManager, Dockable, Mode, Mode)} * on all listeners that are currently registered. * @param dockable the element whose mode changed * @param oldMode its old mode * @param newMode its new mode */ protected void fireModeChanged( Dockable dockable, M oldMode, M newMode ){ for( ModeManagerListener super H, ? super M> listener : listeners() ){ listener.modeChanged( this, dockable, oldMode, newMode ); } } /** * Calls {@link ModeManagerListener#modeAdded(ModeManager, Mode)} on * all listeners that are currently registered. * @param mode the added mode */ protected void fireAdded( M mode ){ for( ModeManagerListener super H, ? super M> listener : listeners() ){ listener.modeAdded( this, mode ); } } /** * Calls {@link ModeManagerListener#modeRemoved(ModeManager, Mode)} on * all listeners that are currently registered. * @param mode the removed mode */ protected void fireRemoved( M mode ){ for( ModeManagerListener super H, ? super M> listener : listeners() ){ listener.modeRemoved( this, mode ); } } /** * Registers a new {@link Dockable} at this manager. If there is already * mode-information for key
present, thendockable
* inherits this information. * @param key the unique key ofdockable
* @param dockable the new element * @throws NullPointerException if eitherkey
ordockable
* isnull
* @throws IllegalArgumentException if there is already a dockable registered * withkey
*/ public void add( String key, Dockable dockable ){ if( key == null ) throw new NullPointerException( "key must not be null" ); if( dockable == null ) throw new NullPointerException( "dockable must not be null" ); DockableHandle entry = entries.get( key ); if( entry != null && entry.dockable != null ) throw new IllegalArgumentException( "There is already a dockable registered with the key: " + key ); if( dockables.containsKey( dockable )) throw new IllegalArgumentException( "The dockable is already known to this manager (but it does have a different name)" ); if( entry == null ){ entry = new DockableHandle( dockable, key ); entries.put( entry.id, entry ); } else{ entry.dockable = dockable; } dockables.put( dockable, entry ); entry.putMode( access( getCurrentMode( dockable ) ) ); fireAdded( dockable ); rebuild( dockable ); } /** * Registers a new {@link Dockable} at this manager. This method works * like {@link #add(String, Dockable)} but does not throw an exception * if another {@link Dockable} is already registered withkey
. * Instead the otherDockable
is unregistered anddockable
* inherits its mode-information. * @param key the unique identifier ofdockable
* @param dockable some new element * @throws NullPointerException if eitherkey
ordockable
* isnull
*/ public void put( String key, Dockable dockable ){ if( key == null ) throw new NullPointerException( "key must not be null" ); if( dockable == null ) throw new NullPointerException( "dockable must not be null" ); DockableHandle entry = entries.get( key ); if( entry != null ){ if( entry.dockable != null ){ dockables.remove( entry.dockable ); fireRemoved( entry.dockable ); } entry.dockable = dockable; dockables.put( dockable, entry ); } else{ // was not inserted entry = new DockableHandle( dockable, key ); dockables.put( dockable, entry ); entries.put( entry.id, entry ); entry.putMode( access( getCurrentMode( dockable ) ) ); } fireAdded( dockable ); rebuild( dockable ); } /** * Gets the unique identifier which is used fordockable
. * @param dockable some element * @return the unique identifier ornull
ifdockable
* is not registered */ public String getKey( Dockable dockable ){ DockableHandle handle = getHandle( dockable ); if( handle == null ) return null; return handle.id; } /** * Tells whether this {@link ModeManager} knowsdockable
* and can handle a call to any of theapply
methods. * @param dockable the element to check * @returntrue
if the element is known,false
otherwise */ public boolean isRegistered( Dockable dockable ){ return getKey( dockable ) != null; } /** * Returns a set containing all {@link Dockable}s that are currently * registered at this manager. * @return the set of dockables */ public SetlistDockables(){ return Collections.unmodifiableSet( dockables.keySet() ); } /** * Runs an algorithm which affects the mode of some {@link Dockable}s. * @param runnable the algorithm, null
will be ignored */ public void runTransaction( final AffectingRunnable runnable ){ runTransaction( runnable, false ); } /** * Runs an algorithm which affects the mode of some {@link Dockable}s. * @param run the algorithm,null
will be ignored * @param continuous if set totrue
the transaction should run without changing * the internal cache storing the position of all {@link Dockable}s. This can be important * if an operation runs anapply
method and additional work will change * the position of some elements again. Clients should call {@link #store(Dockable)} * afterwards. */ public void runTransaction( final AffectingRunnable run, boolean continuous ){ if( run == null ) return; try{ openAffected(); runTransaction( new Runnable() { public void run(){ run.run( affected ); } }, continuous ); } finally{ closeAffected(); } } /** * Runsrun
as transaction, the {@link DockRegister} is stalled * and {@link #isOnTransaction()} returnstrue
while *run
runs. * @param run the runnable to execute */ public void runTransaction( Runnable run ){ runTransaction( run, false ); } /** * Runsrun
as transaction, the {@link DockRegister} is stalled * and {@link #isOnTransaction()} returnstrue
while *run
runs. * @param run the runnable to execute * @param continuous if set totrue
the transaction should run without changing * the internal cache storing the position of all {@link Dockable}s. This can be important * if an operation runs anapply
method and additional work will change * the position of some elements again. Clients should call {@link #store(Dockable)} * afterwards. */ public void runTransaction( Runnable run, boolean continuous ){ try{ controller.getRegister().setStalled( true ); onTransaction++; if( continuous ){ onContinuous++; } run.run(); } finally{ controller.getRegister().setStalled( false ); onTransaction--; if( continuous ){ onContinuous--; } } } /** * Alters the mode ofdockable
tomode
. * This method just calls {@link #apply(Dockable, Mode, boolean)}. * @param dockable the element whose mode is going to be changed * @param mode the new mode * @param force iftrue
dockable
is relocated even if the * current mode already ismode
* @throws IllegalArgumentException ifdockable
isnull
, *mode
isnull
ordockable
is not * registered. * @returntrue
ifmode
was found,false
* otherwise */ public boolean apply( Dockable dockable, Path mode, boolean force ){ M resolved = getMode( mode ); if( resolved != null ){ apply( dockable, resolved, force ); return true; } return false; } /** * Alters the mode ofdockable
tomode
. * This method just calls {@link #apply(Dockable, Mode, AffectedSet, boolean)}. * @param dockable the element whose mode is going to be changed * @param mode the new mode * @param force iftrue
dockable
is relocated even if the * current mode already ismode
* @throws IllegalArgumentException ifdockable
isnull
, *mode
isnull
ordockable
is not * registered. */ public void apply( Dockable dockable, M mode, boolean force ){ try{ openAffected(); apply( dockable, mode, affected, force ); } finally{ closeAffected(); } } /** * Alters the mode ofdockable
tomode
. * This method just calls {@link #apply(Dockable, Mode, AffectedSet, boolean)}. * @param dockable the element whose mode is going to be changed * @param mode the new mode * @param set to store all dockables whose mode might have been changed * @param force iftrue
dockable
is relocated even if the * current mode already ismode
* @returntrue
ifmode
was found, *false
otherwise * @throws IllegalArgumentException ifdockable
isnull
, *mode
isnull
,set
isnull
, * ordockable
is not registered. */ public boolean apply( Dockable dockable, Path mode, AffectedSet set, boolean force ){ M resolved = getMode( mode ); if( resolved != null ){ apply( dockable, resolved, set, force ); return true; } return false; } /** * Alters the mode ofdockable
tomode
. This * method does nothing if the current mode ofdockable
* already ismode
.
* After initial checks and reading the history, this method calls * {@link #apply(Dockable, Mode, Object, AffectedSet)}. * @param dockable the element whose mode is going to be changed * @param mode the new mode * @param set to store all dockables whose mode might have been changed * @param force iftrue
dockable
is relocated even if the * current mode already ismode
* @throws IllegalArgumentException ifdockable
isnull
, *mode
isnull
,set
isnull
, * ordockable
is not registered. */ public void apply( Dockable dockable, M mode, AffectedSet set, boolean force ){ if( dockable == null ) throw new IllegalArgumentException( "dockable is null" ); if( mode == null ) throw new IllegalArgumentException( "mode is null" ); if( set == null ) throw new IllegalArgumentException( "set is null" ); DockableHandle entry = dockables.get( dockable ); if( entry == null ) throw new IllegalArgumentException( "dockable not registered" ); M dockableMode = getCurrentMode( dockable ); if( !force && dockableMode == mode ) return; H history = entry.properties.get( mode.getUniqueIdentifier() ); apply( dockable, mode, history, set ); } /** * Gets the history ofdockable
in modemodeId
. * @param dockable the element whose history is searched * @param modeId the identifier of the mode * @return the history information ornull
if not found */ public H getHistory( Dockable dockable, Path modeId ){ DockableHandle entry = dockables.get( dockable ); if( entry == null ) return null; return entry.properties.get( modeId ); } /** * Alters the mode ofdockable
to bemode
. * This method just calls {@link #apply(Dockable, Mode, Object, AffectedSet)}. * @param dockable the element whose mode is changed * @param mode the new mode ofdockable
* @param history history information for {@link Mode#apply(Dockable, Object, AffectedSet)}, * can benull
* @param set to store elements that have changed * @throws IllegalArgumentException if eitherdockable
,mode
* orset
isnull
* @returntrue
ifmode
was found,false
* otherwise */ public boolean apply( Dockable dockable, Path mode, H history, AffectedSet set ){ M resolved = getMode( mode ); if( resolved != null ){ apply( dockable, resolved, history, set ); return true; } return false; } /** * Alters the mode ofdockable
to bemode
. * This method does not alter the modes of other dockables, notice however * that the methods {@link Mode#apply(Dockable, Object, AffectedSet)} may * trigger additional mode-changes. * @param dockable the element whose mode is changed * @param mode the new mode ofdockable
* @param history history information for {@link Mode#apply(Dockable, Object, AffectedSet)}, * can benull
* @param set to store elements that have changed * @throws IllegalArgumentException if eitherdockable
,mode
* orset
isnull
*/ public void apply( final Dockable dockable, final M mode, final H history, final AffectedSet set ){ if( dockable == null ) throw new IllegalArgumentException( "dockable is null" ); if( mode == null ) throw new IllegalArgumentException( "mode is null" ); if( set == null ) throw new IllegalArgumentException( "set is null" ); M dockableMode = getCurrentMode( dockable ); if( dockableMode != null ){ store( dockable ); } set.add( dockable ); runTransaction( new Runnable(){ public void run(){ H rewritten = history; if( historyRewriter != null ){ rewritten = historyRewriter.rewrite( dockable, mode, history ); } mode.apply( dockable, rewritten, set ); } }); } /** * Stores a property fordockable
if in modemode
. This * method does not trigger any version of theapply
methods. * @param mode the mode which is affected * @param dockable the dockables whose property is changed * @param property the new property, can benull
*/ protected void setProperties( M mode, Dockable dockable, H property ){ DockableHandle entry = dockables.get( dockable ); if( entry != null ){ if( property == null ) entry.properties.remove( mode.getUniqueIdentifier() ); else entry.properties.put( mode.getUniqueIdentifier(), property ); } } /** * Gets the properties which correspond todockable
* andmode
. * @param mode the first part of the key * @param dockable the second part of the key * @return the properties ornull
*/ protected H getProperties( M mode, Dockable dockable ){ DockableHandle entry = dockables.get( dockable ); if( entry == null ) return null; return entry.properties.get( mode.getUniqueIdentifier() ); } /** * Tells whether this manager is currently changing the {@link Mode} of a {@link Dockable}. * @returntrue
if a mode is currently working */ public boolean isOnTransaction(){ return onTransaction > 0; } /** * Tells whether this manager currently runs a continuous transaction. As long as a continuous * transaction is running the internal states of this manager do not change. * @return whether a continuous transaction is running */ public boolean isOnContinuous(){ return onContinuous > 0; } /** * Updates the modes of all {@link Dockable}s that * are registered at this {@link ModeManager}. */ public void refresh(){ for( Dockable dockable : dockables.keySet() ){ refresh( dockable, false ); } } /** * Updates the mode ofdockable
and updates the actions * associated withdockable
. This method is intended to be * called by any code that changes the mode in a way that is not automatically * registered by this {@link ModeManager}. * @param dockable the element whose mode might have changed * @param recursive if set, then the children ofdockable
* are refreshed as well. */ public void refresh( Dockable dockable, boolean recursive ){ DockableHandle handle = getHandle( dockable ); if( handle != null ){ handle.putMode( access( getCurrentMode( dockable ) ) ); } if( recursive ){ DockStation station = dockable.asDockStation(); if( station != null ){ for( int i = 0, n = station.getDockableCount(); idockable dockable
itself, put the properties of *dockable
remain in the system. * @param dockable the element to reduce */ public void reduceToEmpty( Dockable dockable ){ DockableHandle entry = dockables.get( dockable ); if( entry != null ){ entry.dockable = null; fireRemoved( dockable ); } } /** * Called while reading modes in {@link #readSettings(ModeSettings)}. * Subclasses might change the mode according tonewMode
. * @param key the identifier ofdockable
* @param old the modedockable
is currently in * @param current the modedockable
is going to be * @param dockable the element that changes its mode, might benull
*/ protected abstract void applyDuringRead( String key, Path old, Path current, Dockable dockable ); /** * Tells whether an entry for a missing {@link Dockable} should be created. * This will result in a call to {@link #addEmpty(String)} during * {@link #readSettings(ModeSettings)}. * The default implementation returns alwaysfalse
. * @param key the key for which to create a new entry * @returntrue
if an entry should be created */ protected boolean createEntryDuringRead( String key ){ return false; } /** * Adds an empty entry to this manager. The empty entry can be used to store * information for a {@link Dockable} that has not yet been created. It is * helpful if the client intends to load first its properties and create * only those {@link Dockable}s which are visible.
* Also an empty entry gets never deleted unless {@link #removeEmpty(String)} is called. * @param key the name of the empty entry * @throws NullPointerException ifkey
isnull
*/ public void addEmpty( String key ){ if( key == null ) throw new NullPointerException( "name must not be null" ); DockableHandle entry = entries.get( key ); if( entry == null ){ entry = new DockableHandle( null, key ); entries.put( key, entry ); } entry.empty = true; } /** * Removes the entry forname
but only if the entry is not * associated with any {@link Dockable}. * @param name the name of the entry which might be empty * @throws NullPointerException ifkey
isnull
*/ public void removeEmpty( String name ){ if( name == null ) throw new NullPointerException( "name must not be null" ); DockableHandle entry = entries.get( name ); if( entry != null ){ entry.empty = false; if( entry.dockable == null ){ entries.remove( name ); } } } /** * Tells whether information about dockablekey
gets * stored indefinitely or not. * @param key the key to check * @returntrue
if the key is never removed automatically *false
otherwise */ public boolean isEmpty( String key ){ DockableHandle entry = entries.get( key ); return entry != null && entry.empty; } /** * Given some {@link Dockable} on which an event was registered, searches a * registered dockable that is a child oftarget
or *target
itself. * @param target the target whose registered child is searched * @returntarget
, a child oftarget
, ornull
*/ public Dockable getDoubleClickTarget( Dockable target ){ if( target == null ){ return null; } if( dockables.get( target ) != null ){ return target; } DockStation station = target.asDockStation(); if( station == null ){ return null; } return getDoubleClickTarget( station.getFrontDockable() ); } /** * Gets the default mode ofdockable
, the mode *dockable
is in if nothing else is specified. This method checks * {@link Mode#isDefaultMode(Dockable)} and returns the first * {@link Mode} where the answer wastrue
. * @param dockable some dockable, notnull
* @return its default mode, must be registered at this {@link ModeManager} * and not benull
*/ protected M getDefaultMode( Dockable dockable ){ if( modes.isEmpty() ) throw new IllegalStateException( "no modes available" ); for( ModeHandle mode : modes ){ if( mode.mode.isDefaultMode( dockable )){ return mode.mode; } } throw new IllegalStateException( "no mode is the default mode for '" + dockable.getTitleText() + "'" ); } /** * Tries to find the modedockable
is currently in. This method * calls {@link Mode#isCurrentMode(Dockable)} and returns the first * {@link Mode} where the answer wastrue
. * @param dockable some dockable, notnull
* @return the current mode ornull
if not found */ public M getCurrentMode( Dockable dockable ){ for( ModeHandle mode : modes ){ if( mode.mode.isCurrentMode( dockable )){ return mode.mode; } } return null; } /** * Reading the history this method tells which mode *dockable
was in before the current mode. * @param dockable some element * @return the previous mode ornull
if this * information is not available */ public M getPreviousMode( Dockable dockable ){ DockableHandle handle = getHandle( dockable ); if( handle == null ) return null; ModeHandle mode = handle.previousMode(); if( mode == null ) return null; return mode.mode; } /** * Gets the history which modesdockable
* used in the past. The older entries are at the beginning * of the list. The current mode may or may not be included * in the list. * @param dockable the element whose history is asked * @return the history or an empty list if no history is available */ public ListgetModeHistory( Dockable dockable ){ DockableHandle handle = getHandle( dockable ); if( handle == null ) return Collections.emptyList(); M lastMode = getCurrentMode( dockable ); List result = new ArrayList (); for( Path path : handle.history ){ M mode = getMode( path ); addMode( mode, result ); if( mode == lastMode ){ lastMode = null; } } addMode( lastMode, result ); return result; } private void addMode( M mode, List result ){ if( mode != null ){ result.add( mode ); } } /** * Adds the history data history
todockable
for modemode
, and * storesmode
as the newest used mode. * @param dockable the element whose history is modified must be known to this manager * @param mode the mode whose history is modified * @param history the new history * @throws IllegalStateException ifdockable
is not known to this manager */ public void addToModeHistory( Dockable dockable, M mode, H history ){ DockableHandle handle = getHandle( dockable ); if( handle == null ){ throw new IllegalArgumentException( "unknown dockable" ); } handle.addToHistory( mode.getUniqueIdentifier(), history ); } /** * Gets the history which propertiesdockable
* used in the past. Entries of valuenull
are ignored. * The older entries are at the beginning of the list. * @param dockable the element whose history is asked * @return the history or an empty list if no history is available */ public ListgetPropertyHistory( Dockable dockable ){ DockableHandle handle = getHandle( dockable ); if( handle == null ) return Collections.emptyList(); List result = new ArrayList (); for( Path path : handle.history ){ H history = handle.properties.get( path ); if( history != null ){ result.add( history ); } } return result; } /** * Stores the current location of dockable
and all its children in respect * to their current {@link Mode}. Dockables that are not registered at this manager * are ignored.
* This method does nothing if {@link #isOnContinuous()} returnstrue
* @param dockable a root of a tree */ public void store( Dockable dockable ){ if( isOnContinuous() ) return; DockUtilities.visit( dockable, new DockUtilities.DockVisitor(){ @Override public void handleDockable( Dockable check ) { M mode = getCurrentMode( check ); if( mode != null ) store( mode, check ); } }); } /** * Stores the location ofdockable
under the keymode
.
* This method does nothing if {@link #isOnContinuous()} returnstrue
* @param mode the modedockable
is currently in * @param dockable the element whose location will be stored */ protected void store( M mode, Dockable dockable ){ if( isOnContinuous() ) return; DockableHandle handle = getHandle( dockable ); if( handle != null ){ handle.properties.put( mode.getUniqueIdentifier(), mode.current( dockable ) ); } } /** * Gets theModeAccess
which representsmode
. * @param mode some mode ornull
* @return its access ornull
* @throws IllegalArgumentException ifmode
is unknown */ private ModeHandle access( M mode ){ if( mode == null ) return null; for( ModeHandle access : modes ){ if( access.mode == mode ){ return access; } } throw new IllegalArgumentException( "unknown mode: " + mode ); } /** * Returns an iteration of all modes that are stored in this manager. * @return the iteration */ public Iterablemodes(){ return new Iterable (){ public Iterator iterator(){ final Iterator handles = modes.iterator(); return new Iterator (){ public boolean hasNext(){ return handles.hasNext(); } public M next(){ return handles.next().mode; } public void remove(){ throw new UnsupportedOperationException( "cannot remove modes this way" ); } }; } }; } /** * Rebuilds the actions sources for all {@link Dockable}s. */ protected void rebuildAll(){ for( DockableHandle handle : dockables.values() ){ handle.updateActionSource(); } } /** * Rebuilds the action sources of dockable
. * @param dockable the element whose actions are to be updated */ protected void rebuild( Dockable dockable ){ DockableHandle entry = dockables.get( dockable ); if( entry != null ){ entry.updateActionSource(); } } /** * Gets a list of actions that should be shown onstation
depending on the * current children ofstation
. This method is called every time when either * a child is added, removed or selected onstation
. * @param station the station whose actions are asked * @return the actions, can benull
*/ public abstract DockActionSource getSharedActions( DockStation station ); private DockableHandle getHandle( Dockable dockable ){ return dockables.get( dockable ); } /** * Creates a new {@link ModeSetting} which is configured to transfer data from * this {@link ModeManager} to persistent storage or the other way. The new setting * contains all the {@link ModeSettingFactory}s which are currently known to this manager. * @param the intermediate format * @param converter conversion tool from this manager's meta-data format to the intermediate * format. * @return the new empty settings */ public ModeSettingscreateSettings( ModeSettingsConverter converter ){ ModeSettings settings = createModeSettings( converter ); for( ModeSettingFactory factory : factories.values() ){ settings.addFactory( factory ); } for( ModeHandle mode : modes ){ if( mode.mode != null ){ ModeSettingFactory factory = mode.mode.getSettingFactory(); if( factory != null ){ settings.addFactory( factory ); } } } return settings; } /** * Creates the empty set of settings for this {@link ModeManager}. Subclasses * may override this method to use another set of settings. This method does * not need to call {@link ModeSettings#addFactory(ModeSettingFactory)}. * @param the intermediate format * @param converter conversion tool from this manager's meta-data format to the * intermediate format. * @return the new empty settings */ public ModeSettings createModeSettings( ModeSettingsConverter converter ){ return new ModeSettings ( converter ); } /** * Writes all the information stored in this {@link ModeManager} to * setting
. * @param setting the settings to fill */ public void writeSettings( ModeSettingssetting ){ // dockables for( DockableHandle handle : entries.values() ){ setting.add( handle.id, handle.getCurrent(), handle.properties, handle.history ); } // modes for( ModeHandle handle : modes ){ if( handle.mode != null ){ setting.add( handle.mode ); } } } /** * Reads the contents of settings
and stores it. * @param settings the settings to read */ public void readSettings( ModeSettingssettings ){ readSettings( settings, null ); } /** * Reads the contents of settings
, creates new entries if either * {@link #createEntryDuringRead(String)} or ifpending
allows the setting * to be undone if not needed. * @param settings the settings to read * @param pending undoable settings, can benull
* @return an algorithm that will remove any entry that was created becausepending
* did advise so,null
ifpending
wasnull
*/ public Runnable readSettings( ModeSettingssettings, UndoableModeSettings pending ){ final List temporary = new ArrayList (); // dockables for( int i = 0, n = settings.size(); i < n; i++ ){ String key = settings.getId( i ); DockableHandle entry = entries.get( key ); if( entry == null ){ if( createEntryDuringRead( key )){ addEmpty( key ); entry = entries.get( key ); } else if( pending != null && pending.createTemporaryDuringRead( key )){ addEmpty( key ); entry = entries.get( key ); temporary.add( key ); } } if( entry != null ){ Path current = settings.getCurrent( i ); Path old = null; if( entry.dockable != null ){ M oldMode = getCurrentMode( entry.dockable ); if( oldMode != null ){ old = oldMode.getUniqueIdentifier(); } } if( current == null ) current = old; entry.history.clear(); for( Path next : settings.getHistory( i )) entry.history.add( next ); entry.properties = settings.getProperties( i ); if( (old == null && current != null) || (old != null && !old.equals( current ))){ applyDuringRead( key, old, current, entry.dockable ); } } } // modes for( ModeHandle handle : modes ){ if( handle.mode != null ){ ModeSetting setting = settings.getSettings( handle.mode.getUniqueIdentifier() ); if( setting != null ){ handle.mode.readSetting( setting ); } } } if( pending == null ){ return null; } else{ return new Runnable(){ public void run(){ for( String key : temporary ){ removeEmpty( key ); } } }; } } /** * Adds all elements of dockables
to the current * {@link AffectedSet}. * @param dockables the elements to add */ public void addAffected( Iterabledockables ){ openAffected(); for( Dockable element : dockables ){ affected.add( element ); } closeAffected(); } /** * Opens the {@link ChangeSet} that collects {@link Dockable}s whose mode * may have changed. */ private void openAffected(){ if( affectedCount == 0 ){ affected = new ChangeSet(); } affectedCount++; } /** * Closes the {@link ChangeSet} that collected {@link Dockable}s whose mode * may have changed. */ private void closeAffected(){ affectedCount--; if( affectedCount == 0 ){ ChangeSet old = affected; affected = null; old.finish(); } } @Override public String toString(){ StringBuilder builder = new StringBuilder(); builder.append( getClass().getName() ); builder.append( "[" ); for( DockableHandle handle : entries.values() ){ builder.append( "\n\t" ); builder.append( handle.id ); for( Map.Entry entry : handle.properties.entrySet() ){ builder.append( "\n\t\t" ); builder.append( entry.getKey() ); builder.append( " -> " ); builder.append( entry.getValue() ); } } builder.append( "\n]" ); return builder.toString(); } /** * A wrapper around a mode, giving access to its properties. The mode * inside this wrapper can be replaced any time. * @author Benjamin Sigg */ private class ModeHandle{ private M mode; public ModeHandle( M mode ){ this.mode = mode; } } /** * Describes all properties a {@link Dockable} has. * @author Benjamin Sigg */ private class DockableHandle{ /** the {@link Dockable} for which the properties are stored */ public Dockable dockable; /** a unique id associated with {@link #dockable} */ public String id; /** the set of actions available for {@link #dockable} */ public MultiDockActionSource source; /** a map that stores some properties mapped to the different modes */ public Map properties; /** The modes this entry already visited. No mode is more than once in this list. */ private List history; /** if true
, then this entry is not deleted automatically */ private boolean empty = false; /** * Creates a new entry * @param dockable the element whose properties are stores in this entry * @param id the unique if of this entry */ public DockableHandle( Dockable dockable, String id ){ this.dockable = dockable; this.id = id; source = new MultiDockActionSource( new LocationHint( LocationHint.ACTION_GUARD, LocationHint.RIGHT ) ); properties = new HashMap(); history = new LinkedList (); } /** * Updates the action source of this manager. */ public void updateActionSource(){ if( dockable != null ){ source.removeAll(); M mode = getCurrentMode( dockable ); if( mode == null ) mode = getDefaultMode( dockable ); for( ModeHandle access : modes ){ DockActionSource next = access.mode.getActionsFor( dockable, mode ); if( next != null ){ source.add( next ); } } } } /** * Stores mode
in a stack that describes the history * through which this entry moved. Ifmode
is already * in the stack, than it is moved to the top of the stack. * @param mode the mode to store,null
will be ignored */ public void putMode( ModeHandle mode ){ if( mode != null ){ ModeHandle oldMode = peekMode(); if( oldMode != mode ){ Path id = mode.mode.getUniqueIdentifier(); addToHistory( id, mode.mode.current( dockable ) ); rebuild( dockable ); fireModeChanged( dockable, oldMode == null ? null : oldMode.mode, mode.mode ); } else{ rebuild( dockable ); } } } /** * Adds the modeid
to the history. * @param id the unique identifier of a mode * @param data history data associated with modeid
*/ public void addToHistory( Path id, H data ){ history.remove( id ); history.add( id ); properties.put( id, data ); } /** * Gets the mode that was used previously to the current mode. * If the history gets empty, then {@link ModeManager#getDefaultMode(Dockable)} * is returned. * @return the mode in which this entry was before the current mode * was put onto the history */ public ModeHandle previousMode(){ if( history.size() < 2 ) return access( getDefaultMode( dockable ) ); else return getAccess( history.get( history.size()-2 ) ); } /** * Gets the current mode of this entry. * @return the mode ornull
*/ public ModeHandle peekMode(){ if( history.isEmpty() ) return null; else return getAccess( history.get( history.size()-1 ) ); } /** * Gets the id of the current mode (if any). * @return the id ornull
*/ public Path getCurrent(){ if( dockable == null ) return null; M mode = getCurrentMode( dockable ); if( mode == null ) return null; return mode.getUniqueIdentifier(); } } /** * Default implementation of {@link AffectedSet}. Linked to the enclosing * {@link ModeManager}. * @author Benjamin Sigg */ private class ChangeSet implements AffectedSet{ /** the changed elements */ private Setset = new HashSet (); /** * Creates a new set */ public ChangeSet(){ // nothing } public void add( Dockable dockable ){ if( dockable != null ){ set.add( dockable ); DockStation station = dockable.asDockStation(); if( station != null ){ for( int i = 0, n = station.getDockableCount(); i Dockables have changed their mode.
* for each element known to this set. */ public void finish(){ for( Dockable dockable : set ){ refresh( dockable, false ); } } } }