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

bibliothek.gui.dock.support.mode.ModeManager Maven / Gradle / Ivy

Go to download

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

There is a newer version: 1.1.2p20b.fix-1
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) 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  the 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 listener ){
		if( listener == null )
			throw new IllegalArgumentException( "listener must not be null" );
		listeners.add( listener );
	}
	
	/**
	 * Removes listener from this manager.
	 * @param listener the listener to remove
	 */
	public void removeModeManagerListener( ModeManagerListener 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
	 * as factory 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( ModeSettingFactory factory ){ 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 identifier path. * @param path some unique identifier * @return the mode with that identifier or null */ 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 the apply method is called, but that new history object * will not be stored by the {@link ModeManager}. * @param historyRewriter the new rewriter, can be null */ public void setHistoryRewriter( HistoryRewriter historyRewriter ){ this.historyRewriter = historyRewriter; } /** * Gets the current {@link HistoryRewriter}. * @return the rewriter, can be null * @see #setHistoryRewriter(HistoryRewriter) */ public HistoryRewriter getHistoryRewriter(){ return historyRewriter; } /** * Gets all the listeners that are currently registered in this manager. * @return the list of registered listeners */ @SuppressWarnings("unchecked") protected ModeManagerListener[] 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 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 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 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 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 listener : listeners() ){ listener.modeRemoved( this, mode ); } } /** * Registers a new {@link Dockable} at this manager. If there is already * mode-information for key present, then dockable * inherits this information. * @param key the unique key of dockable * @param dockable the new element * @throws NullPointerException if either key or dockable * is null * @throws IllegalArgumentException if there is already a dockable registered * with key */ 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 with key. * Instead the other Dockable is unregistered and dockable * inherits its mode-information. * @param key the unique identifier of dockable * @param dockable some new element * @throws NullPointerException if either key or dockable * is null */ 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 for dockable. * @param dockable some element * @return the unique identifier or null if dockable * 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} knows dockable * and can handle a call to any of the apply methods. * @param dockable the element to check * @return true 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 Set listDockables(){ 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 to true 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 an apply 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(); } } /** * Runs run as transaction, the {@link DockRegister} is stalled * and {@link #isOnTransaction()} returns true while * run runs. * @param run the runnable to execute */ public void runTransaction( Runnable run ){ runTransaction( run, false ); } /** * Runs run as transaction, the {@link DockRegister} is stalled * and {@link #isOnTransaction()} returns true while * run runs. * @param run the runnable to execute * @param continuous if set to true 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 an apply 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 of dockable to mode. * 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 if true dockable is relocated even if the * current mode already is mode * @throws IllegalArgumentException if dockable is null, * mode is null or dockable is not * registered. * @return true if mode 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 of dockable to mode. * 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 if true dockable is relocated even if the * current mode already is mode * @throws IllegalArgumentException if dockable is null, * mode is null or dockable is not * registered. */ public void apply( Dockable dockable, M mode, boolean force ){ try{ openAffected(); apply( dockable, mode, affected, force ); } finally{ closeAffected(); } } /** * Alters the mode of dockable to mode. * 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 if true dockable is relocated even if the * current mode already is mode * @return true if mode was found, * false otherwise * @throws IllegalArgumentException if dockable is null, * mode is null, set is null, * or dockable 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 of dockable to mode. This * method does nothing if the current mode of dockable * already is mode.
* 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 if true dockable is relocated even if the * current mode already is mode * @throws IllegalArgumentException if dockable is null, * mode is null, set is null, * or dockable 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 of dockable in mode modeId. * @param dockable the element whose history is searched * @param modeId the identifier of the mode * @return the history information or null 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 of dockable to be mode. * This method just calls {@link #apply(Dockable, Mode, Object, AffectedSet)}. * @param dockable the element whose mode is changed * @param mode the new mode of dockable * @param history history information for {@link Mode#apply(Dockable, Object, AffectedSet)}, * can be null * @param set to store elements that have changed * @throws IllegalArgumentException if either dockable, mode * or set is null * @return true if mode 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 of dockable to be mode. * 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 of dockable * @param history history information for {@link Mode#apply(Dockable, Object, AffectedSet)}, * can be null * @param set to store elements that have changed * @throws IllegalArgumentException if either dockable, mode * or set is null */ 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 for dockable if in mode mode. This * method does not trigger any version of the apply methods. * @param mode the mode which is affected * @param dockable the dockables whose property is changed * @param property the new property, can be null */ 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 to dockable * and mode. * @param mode the first part of the key * @param dockable the second part of the key * @return the properties or null */ 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}. * @return true 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 of dockable and updates the actions * associated with dockable. 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 of dockable * 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
. * @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 ); } } /** * Removes 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 to newMode. * @param key the identifier of dockable * @param old the mode dockable is currently in * @param current the mode dockable is going to be * @param dockable the element that changes its mode, might be null */ 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 always false. * @param key the key for which to create a new entry * @return true 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 if key is null */ 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 for name 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 if key is null */ 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 dockable key gets * stored indefinitely or not. * @param key the key to check * @return true 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 of target or * target itself. * @param target the target whose registered child is searched * @return target, a child of target, or null */ 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 of dockable, 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 was true. * @param dockable some dockable, not null * @return its default mode, must be registered at this {@link ModeManager} * and not be null */ 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 mode dockable is currently in. This method * calls {@link Mode#isCurrentMode(Dockable)} and returns the first * {@link Mode} where the answer was true. * @param dockable some dockable, not null * @return the current mode or null 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 or null 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 modes dockable * 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 List getModeHistory( 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 to dockable for mode mode, and * stores mode 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 if dockable 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 properties dockable * used in the past. Entries of value null 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 List getPropertyHistory( 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()} returns true * @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 of dockable under the key mode.
* This method does nothing if {@link #isOnContinuous()} returns true * @param mode the mode dockable 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 the ModeAccess which represents mode. * @param mode some mode or null * @return its access or null * @throws IllegalArgumentException if mode 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 Iterable modes(){ 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 on station depending on the * current children of station. This method is called every time when either * a child is added, removed or selected on station. * @param station the station whose actions are asked * @return the actions, can be null */ 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 ModeSettings createSettings( 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( ModeSettings setting ){ // 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( ModeSettings settings ){ readSettings( settings, null ); } /** * Reads the contents of settings, creates new entries if either * {@link #createEntryDuringRead(String)} or if pending allows the setting * to be undone if not needed. * @param settings the settings to read * @param pending undoable settings, can be null * @return an algorithm that will remove any entry that was created because pending * did advise so, null if pending was null */ public Runnable readSettings( ModeSettings settings, 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( Iterable dockables ){ 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. If mode 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 mode id to the history. * @param id the unique identifier of a mode * @param data history data associated with mode id */ 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 or null */ 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 or null */ 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 Set set = 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(); iDockables have changed their mode.
* for each element known to this set. */ public void finish(){ for( Dockable dockable : set ){ refresh( dockable, false ); } } } }