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

bibliothek.gui.dock.facile.mode.AbstractLocationMode Maven / Gradle / Ivy

The 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.facile.mode;

import java.util.ArrayList;
import java.util.HashMap;
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.DockActionSource;
import bibliothek.gui.dock.layout.location.AsideAnswer;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.support.mode.Mode;
import bibliothek.gui.dock.support.mode.ModeManager;
import bibliothek.gui.dock.support.mode.ModeManagerListener;

/**
 * This abstract class offers various properties that may be useful for any implementation
 * of {@link LocationMode}. It also allows to store a set of {@link ModeArea}s. Basic methods
 * to verify and change the {@link Mode} of a {@link Dockable} are implemented too.
 * @author Benjamin Sigg
 * @param  the values managed by this mode
 */
public abstract class AbstractLocationMode implements Iterable, LocationMode{
	/** The areas managed by this mode */
	private Map areas = new HashMap();
	
	/** The order in which the areas were added */
	private List areaOrder = new LinkedList();
	
	/** default location to use when a key is not found in {@link #areas} */
	private A defaultArea;
	
	/** the manager responsible for this mode */
	private LocationModeManager manager;

	/** the list of known listeners */
	private List listeners = new ArrayList();
	
	/** the controller in whose realm this mode works */
	private DockController controller;
	
	/** added to all {@link ModeArea}s of this mode */
	private AreaListener modeAreaListener = new AreaListener();
	
	/** listener to register new dockables and remove old ones */
	private ModeManagerListener managerListener = new ManagerListener();
	
	/** temporary information associated with the currently registered Dockables */
	private Map handles = new HashMap();
	
	/** provides actions for the {@link Dockable}s known to this mode */
	private LocationModeActionProvider actionProvider = new DefaultLocationModeActionProvider();
	
	/** whether focus should be automatically transfered */
	private boolean autoFocus = true;
	
	/**
	 * Sets the {@link LocationModeActionProvider} for this mode.
	 * @param actionProvider the provider, not null
	 * @throws IllegalArgumentException if actionProvider is null
	 * @throws IllegalStateException if there are already {@link Dockable}s which show actions
	 * that are provided by the current {@link LocationModeActionProvider}
	 */
	public void setActionProvider( LocationModeActionProvider actionProvider ){
		if( actionProvider == null )
			throw new IllegalArgumentException( "actionProvider must not be null" );
		
		if( !handles.isEmpty() )
			throw new IllegalStateException( "can only set actionProvider if no Dockables are currently showing actions of the old provider" );
		
		this.actionProvider = actionProvider;
	}
	
	public void setManager( LocationModeManager manager ){
		if( this.manager != null ){
			for( A area : areas.values() ){
				area.removeModeAreaListener( modeAreaListener );
			}
		}
		
		this.manager = manager;
		
		if( this.manager != null ){
			for( A area : areas.values() ){
				area.addModeAreaListener( modeAreaListener );
			}
			this.manager.addModeManagerListener( managerListener );
		}
	}
	
	/**
	 * Gets the owner of this mode.
	 * @return the owner, not null
	 */
	public LocationModeManager getManager(){
		return manager;
	}
	
	public void setController( DockController controller ){
		this.controller = controller;
		for( A area : areas.values() ){	
			area.setController( controller );
		}
	}
	
	/**
	 * Gets the controller in whose realm this mode works.
	 * @return the controller or null
	 */
	public DockController getController(){
		return controller;
	}
	
	public void addLocationModeListener( LocationModeListener listener ){
		if( listener == null )
			throw new IllegalArgumentException( "listener must not be null" );
		listeners.add( listener );
	}
	
	public void removeLocationModeListener( LocationModeListener listener ){
		listeners.remove( listener );
	}
	
	/**
	 * Gets all the listeners that are currently registered at this mode.
	 * @return all the listeners
	 */
	protected LocationModeListener[] listeners(){
		return listeners.toArray( new LocationModeListener[ listeners.size() ] );
	}
	
	public boolean shouldAutoFocus(){
		return autoFocus;
	}
	
	/**
	 * Sets the result of {@link #shouldAutoFocus()}.
	 * @param autoFocus whether automatic focus transfer to {@link Dockable} in this mode
	 * should be allowed
	 */
	public void setShouldAutoFocus( boolean autoFocus ){
		this.autoFocus = autoFocus;
	}
	
	/**
	 * Adds an area to this mode.
	 * @param area the new area, not null
	 */
	public void add( A area ){
		if( area == null )
			throw new IllegalArgumentException( "area must not be null" );
		
		String key = area.getUniqueId();
		if( areas.containsKey( key ))
			throw new IllegalArgumentException( "key '" + key + "' already in use" );
		
		area.setController( getController() );
		area.setMode( this );
		areas.put( key, area );
		areaOrder.add( area );
		
		if( getManager() != null ){
			area.addModeAreaListener( modeAreaListener );
		}
	}
	
	/**
	 * Removes the area with identifier key from this
	 * mode.
	 * @param key the identifier of the area
	 * @return the removed area or null
	 */
	public A remove( String key ){
		A area = areas.remove( key );
		if( defaultArea == area ){
			defaultArea = null;
		}
		if( area != null ){
			area.setController( null );
			area.setMode( null );
			area.removeModeAreaListener( modeAreaListener );
			areaOrder.remove( area );
		}
		return area;
	}
	
	public Iterator iterator(){
		List copy = new ArrayList( areas.values() );
		return copy.iterator();
	}
	
	/**
	 * Sets the default area of this mode. The default area is used when
	 * {@link #get(Dockable)} returns null for some key.
	 * @param defaultArea the default area, can be null. Must be
	 * registered using {@link #add(ModeArea)} first.
	 */
	public void setDefaultArea( A defaultArea ){
		if( defaultArea != null ){
			if( !areas.containsKey( defaultArea.getUniqueId() ))
				throw new IllegalArgumentException( "default area must be registered, call 'add' first" );
		}
		this.defaultArea = defaultArea;
	}
	
	/**
	 * Gets the default area of this mode, can be null. The default area
	 * is the oldest area that was added to this mode and whose property
	 * {@link ModeArea#autoDefaultArea()} is true,  or the one area set
	 * through {@link #setDefaultArea(ModeArea)}.
	 * @return the default area
	 */
	public A getDefaultArea(){
		if( defaultArea == null ){
			for( A area : areaOrder ){
				if( area.autoDefaultArea() ){
					return area;
				}
			}
		}
		
		return defaultArea;
	}
	
	/**
	 * Gets the area with the specified id.
	 * @param key the name of the area
	 * @return the area or null
	 */
	public A get( String key ){
		return areas.get( key );
	}
	
	public DockStation getRepresentation( String uniqueId ){
		A area = get( uniqueId );
		if( area == null )
			return null;
		return area.getStation();
	}
	
	/**
	 * Recursively searches through all stations of dockable
	 * until a station is found that is registered at this mode.
	 * @param dockable the element whose root is searched
	 * @return the root or null, never dockable itself
	 */
	public A get( Dockable dockable ){
		return get( dockable, false );
	}

	/**
	 * Recursively searches through all stations of dockable
	 * until a station is found that is registered at this mode.
	 * @param dockable the element whose root is searched
	 * @param locationRoot if true, then only those {@link ModeArea}s are returned
	 * which are {@link ModeArea#isLocationRoot()} 
	 * @return the root or null, never dockable itself
	 */
	public A get( Dockable dockable, boolean locationRoot ){
		while( dockable != null ){
			for( A area : areas.values() ){
				if( !locationRoot || area.isLocationRoot() ){
					if( area.isChild( dockable ) ){
						return area;
					}
				}
			}
			DockStation station = dockable.getDockParent();
			if( station == null )
				return null;
			dockable = station.asDockable();
		}
		return null;
	}
	
	/**
	 * Recursively searches through all areas known to this mode until the
	 * mode is found that represents station. If station
	 * is a {@link Dockable} that its parent station is searched too.
	 * @param station the station whose area is to be found
	 * @return an area for which {@link ModeArea#getStation()} equals station,
	 * may be null
	 */
	public A get( DockStation station ){
		// search area
		while( station != null ){
			for( A area : this ){
				if( area.getStation() == station ){
					return area;
				}
			}
			Dockable dockable = station.asDockable();
			station = dockable == null ? null : dockable.getDockParent();
		}
		return null;
	}

	public Location aside( AsideRequest request, Location location ){
		A area = get( location.getRoot() );
		if( area == null ){
			return null;
		}
		
		AsideAnswer answer = request.execute( area.getStation() );
		if( answer.isCanceled() ){
			return null;
		}
		
		return new Location( getUniqueIdentifier(), location.getRoot(), answer.getLocation() );
	}
	
	public DockActionSource getActionsFor( Dockable dockable, Mode mode ){
		if( mode == this ){
			return null;
		}
		if( !isModeAvailable( dockable ) || isModeHidden( dockable )){
			return null;
		}
		DockableHandle handle = handles.get( dockable );
		if( handle == null )
			return null;
		else
			return handle.getActions( mode );
	}
	
	/**
	 * Tells whether this mode is available for dockable.
	 * @param dockable some element to check
	 * @return true if this mode is available
	 */
	protected boolean isModeAvailable( Dockable dockable ){
		LocationModeManager manager = getManager();
		if( manager == null )
			return false;
		return manager.isModeAvailable( dockable, getExtendedMode() );
	}
	
	/**
	 * Tells whether this mode is hidden for dockable. If the mode is hidden
	 * then the actions of the {@link #setActionProvider(LocationModeActionProvider) action provider} 
	 * do not show up.
	 * @param dockable some element to check
	 * @return true if this mode is available
	 */
	protected boolean isModeHidden( Dockable dockable ){
		LocationModeManager manager = getManager();
		if( manager == null )
			return false;
		return manager.isModeHidden( dockable, getExtendedMode() );
	}
	
	public boolean isRepresenting( DockStation station ){
		for( A area : areas.values() ){
			if( area.getStation() == station ){
				return true;
			}
		}
		
		return false;
	}
	
	public boolean apply( Dockable dockable, Location history, AffectedSet set ){
		LocationModeEvent event = new LocationModeEvent( this, history, dockable, set );
		for( LocationModeListener listener : listeners() ){
			listener.applyStarting( event );
		}
		if( !event.isDone() ){
			boolean success = runApply( dockable, history, set );
			event.done(success);
		}
		
		for( LocationModeListener listener : listeners() ){
			listener.applyDone( event );
		}
		
		return event.isSuccess();
	}
	
	/**
	 * Called by {@link #apply(Dockable, Location, AffectedSet)} after the {@link LocationModeListener}s
	 * are informed. Applies this mode to dockable.
	 * @param dockable the element whose mode becomes this
	 * @param history history information that was returned by this mode when calling
	 * {@link #current(Dockable)} the last time.
	 * @param set this method has to store all {@link Dockable}s which might have changed their
	 * mode in the set.
	 * @return true if dockable was moved, false if the method failed
	 * to set the location of dockable for any reason
	 */
	protected abstract boolean runApply( Dockable dockable, Location history, AffectedSet set );
	
	/**
	 * Creates a new handle for dockable.
	 * @param dockable the newly registered element
	 * @return the new handle
	 */
	protected DockableHandle createHandle( Dockable dockable ){
		return new DockableHandle( dockable );
	}
	
	/**
	 * A listener added to all {@link ModeArea}s.
	 * @author Benjamin Sigg
	 */
	private class AreaListener implements ModeAreaListener{
		public void internalLocationChange( ModeArea source, Set dockables ){
			LocationModeManager manager = getManager();
			if( manager != null ){
				if( manager.isOnTransaction() ){
					manager.addAffected( dockables );
				}
				else{
					for( Dockable dockable : dockables ){
						manager.refresh( dockable, true );
					}
				}
			}
		}
	}
	
	/**
	 * Listener added to register new and removed {@link Dockable}s.
	 * @author Benjamin Sigg
	 */
	private class ManagerListener implements ModeManagerListener{
		public void dockableAdded( ModeManager manager, Dockable dockable ){
			if( !handles.containsKey( dockable )){
				DockableHandle handle = createHandle( dockable );
				handles.put( dockable, handle );
			}
		}
		
		public void dockableRemoved( ModeManager manager, Dockable dockable ){
			DockableHandle handle = handles.remove( dockable );
			if( handle != null ){
				handle.destroy();
			}
		}
		
		public void modeAdded( ModeManager manager, LocationMode mode ){
			// ignore	
		}
		
		public void modeChanged( ModeManager manager, Dockable dockable, LocationMode oldMode, LocationMode newMode ){
			// ignore
		}
		
		public void modeRemoved( ModeManager manager, LocationMode mode ){
			// ignore	
		}
	}
	
	/**
	 * Meta information about a currently registered Dockable.
	 * @author Benjamin Sigg
	 */
	protected class DockableHandle{
		/** the dockable which is handled by this handle */
		private Dockable dockable;
		
		/** the current actions added by this mode to {@link #dockable} */
		private DockActionSource source;
		
		/**
		 * Creates a new handle.
		 * @param dockable the new element, not null
		 */
		public DockableHandle( Dockable dockable ){
			this.dockable = dockable;
		}
		
		/**
		 * Gets the element of this handle.
		 * @return the element, not null
		 */
		public Dockable getDockable(){
			return dockable;
		}
		
		/**
		 * Called when this handle is no longer of any use
		 */
		public void destroy(){
			actionProvider.destroy( dockable, source );
			dockable = null;
			source = null;
		}
		
		/**
		 * Called by {@link AbstractLocationMode#getActionsFor(Dockable, Mode)}
		 * to the actions related to this dockable. The default implementation
		 * is to return a source returned by the current {@link LocationModeActionProvider}.
		 * @param mode the current mode of this element
		 * @return the actions or null
		 */
		public DockActionSource getActions( Mode mode ){
			source = actionProvider.getActions( dockable, mode, source );
			return source;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy