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

bibliothek.gui.dock.facile.mode.LocationModeManager 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.facile.mode;

import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.MultiDockActionSource;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.group.CGroupBehaviorCallback;
import bibliothek.gui.dock.common.group.CGroupMovement;
import bibliothek.gui.dock.common.group.StackGroupBehavior;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.control.DockRegister;
import bibliothek.gui.dock.control.focus.DefaultFocusRequest;
import bibliothek.gui.dock.control.relocator.DockRelocatorEvent;
import bibliothek.gui.dock.control.relocator.VetoableDockRelocatorAdapter;
import bibliothek.gui.dock.event.DockHierarchyEvent;
import bibliothek.gui.dock.event.DockHierarchyListener;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.event.DoubleClickListener;
import bibliothek.gui.dock.facile.mode.status.DefaultExtendedModeEnablement;
import bibliothek.gui.dock.facile.mode.status.ExtendedModeEnablement;
import bibliothek.gui.dock.facile.mode.status.ExtendedModeEnablementFactory;
import bibliothek.gui.dock.facile.mode.status.ExtendedModeEnablementListener;
import bibliothek.gui.dock.layout.location.AsideRequest;
import bibliothek.gui.dock.layout.location.AsideRequestFactory;
import bibliothek.gui.dock.support.mode.AffectedSet;
import bibliothek.gui.dock.support.mode.AffectingRunnable;
import bibliothek.gui.dock.support.mode.ModeManager;
import bibliothek.gui.dock.support.mode.ModeManagerListener;
import bibliothek.gui.dock.util.DockProperties;
import bibliothek.gui.dock.util.IconManager;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.util.Path;

/**
 * {@link ModeManager} for the location of a {@link Dockable}. This manager is able to
 * work together with {@link CControl} or together with {@link DockController}. Clients
 * using it together with a {@link DockController} need to set icons for the 
 * modes manually, they can use the {@link IconManager} and the keys provided in
 * each mode (e.g. {@link NormalMode#ICON_IDENTIFIER}).
 * @author Benjamin Sigg
 * @param  the kind of mode this manager handles
 */
public class LocationModeManager extends ModeManager{
    /**
	 * {@link PropertyKey} for the {@link ExtendedModeEnablement} that should be used
	 * by a {@link LocationModeManager} to activate and deactivate the modes.
	 */
	public static final PropertyKey MODE_ENABLEMENT = 
		new PropertyKey( "locationmodemanager.mode_enablement", 
				new ConstantPropertyFactory( DefaultExtendedModeEnablement.FACTORY ), true  );
	
	/**
	 * {@link PropertyKey} for the {@link DoubleClickLocationStrategy} that should be used
	 * to change the {@link ExtendedMode} of an element which has been double-clicked.
	 */
	public static final PropertyKey DOUBLE_CLICK_STRATEGY =
		new PropertyKey( "locationmodemanager.double_click_strategy",
				new ConstantPropertyFactory( DoubleClickLocationStrategy.DEFAULT ), true );
	
	/** a set of listeners that will be automatically added or removed from a {@link LocationMode} */
	private Map> listeners = new HashMap>();
	
	/** registers new dockables */
	private RegisterListener registerListener = new RegisterListener();
	
	/** registers when dockables change their position */
	private HierarchyListener hierarchyListener = new HierarchyListener();
	
	/** registers dragged and dropped dockables */
	private RelocatorListener relocatorListener = new RelocatorListener();
	
	/** how to group dockables */
	private CGroupBehavior behavior = new StackGroupBehavior();
	
	/** the action that is currently executing */
	private List currentAction = new ArrayList();
	
	/** the list of {@link Dockable}s for which {@link #refresh(Dockable, boolean)} has to be called */
	private LinkedHashSet pendingRefreshs = new LinkedHashSet();
	
	/** the current {@link ExtendedModeEnablementFactory} */
	private PropertyValue extendedModeFactory = new PropertyValue( MODE_ENABLEMENT ) {
		@Override
		protected void valueChanged( ExtendedModeEnablementFactory oldValue, ExtendedModeEnablementFactory newValue ){
			updateEnablement();
		}
	};
	
	/** the current {@link DoubleClickLocationStrategy} */
	private PropertyValue doubleClickStrategy = new PropertyValue( DOUBLE_CLICK_STRATEGY ) {
		@Override
		protected void valueChanged( DoubleClickLocationStrategy oldValue, DoubleClickLocationStrategy newValue ){
			// ignore
		}
	};
	
	/** a listener added to the current {@link #enablement} */
	private ExtendedModeEnablementListener enablementListener = new ExtendedModeEnablementListener() {
		public void availabilityChanged( Dockable dockable, ExtendedMode mode, boolean available ){
			refresh( dockable, true );
		}
	};
	
	/** detects double-click events and changes the mode of the clicked element */
	private DoubleClickListener doubleClickListener = new DoubleClickListener() {
		public DockElement getTreeLocation(){
			return null;
		}
		
		public boolean process( Dockable dockable, MouseEvent event ){
			if( event.isConsumed() )
				return false;
			
			dockable = getDoubleClickTarget( dockable );
			if( dockable != null ){
				M current = getCurrentMode( dockable );
				ExtendedMode next = getDoubleClickStrategy().handleDoubleClick( dockable, current == null ? null : current.getExtendedMode(), enablement );
				if( next != null && isModeAvailable( dockable, next )){
					setMode( dockable, next );
					ensureValidLocation( dockable );
					return true;
				}
			}
			return false;
		}
	};
	
	/** tells which modes are available for which element */
	private ExtendedModeEnablement enablement;

	/** 
	 * if > 0 then the layout-mode is active. In this mode this manager does not react on some
	 * events to not intervene layouting
	 */
	private int layoutMode = 0;
	
	/**
	 * Creates a new manager.
	 * @param controller the controller in whose realm this manager will work
	 */
	public LocationModeManager( DockController controller ){
		super( controller );
		registerListener.connect( controller );
		controller.getRelocator().addVetoableDockRelocatorListener( relocatorListener );
		
		updateEnablement();
		extendedModeFactory.setProperties( controller );
		
		addModeManagerListener( new LocationModeListenerAdapter() );
		
		controller.getDoubleClickController().addListener( doubleClickListener );
	}
	
	public void destroy(){
		registerListener.connect( null );
		DockController controller = getController();
		controller.getRelocator().removeVetoableDockRelocatorListener( relocatorListener );
		controller.getDoubleClickController().removeListener( doubleClickListener );
		
		for( LocationMode mode : this.modes() ){
			mode.setController( null );
		}
		
		super.destroy();
		extendedModeFactory.setProperties( (DockProperties)null );
	}
	
	/**
	 * Updates the current {@link ExtendedModeEnablement} using the factory
	 * provided by {@link #MODE_ENABLEMENT}.
	 */
	protected void updateEnablement(){
		if( enablement != null ){
			enablement.removeListener( enablementListener );
			enablement.destroy();
			enablement = null;
		}
		if( getController() != null ){
			enablement = extendedModeFactory.getValue().create( this );
			enablement.addListener( enablementListener );
		}
		rebuildAll();
	}
	
	/**
	 * Sets the group behavior. The group behavior is applied if {@link #setMode(Dockable, ExtendedMode)} is called and
	 * modifies the call such that another {@link Dockable} receives the event. Any call directly to any of
	 * the apply methods will not be modified by the group behavior.
	 * @param behavior the new behavior, not null
	 */
	public void setGroupBehavior( CGroupBehavior behavior ){
		if( behavior == null ){
			throw new IllegalArgumentException( "the group behavior must not be null" );
		}
		this.behavior = behavior;
	}
	
	/**
	 * Gets the current group behavior.
	 * @return the current behavior, not null
	 * @see #setGroupBehavior(CGroupBehavior)
	 */
	public CGroupBehavior getGroupBehavior(){
		return behavior;
	}
	
	/**
	 * Sets the current mode of dockable.
	 * @param dockable the dockable whose mode is to be set
	 * @param extendedMode the mode
	 * @throws IllegalArgumentException if extendedMode is unknown
	 */
	public void setMode( final Dockable dockable, final ExtendedMode extendedMode ){
		M mode = getMode( extendedMode.getModeIdentifier() );
		if( mode == null ){
			throw new IllegalArgumentException( "No mode '" + extendedMode.getModeIdentifier() + "' available" );
		}
	
		runTransaction( new Runnable(){
			public void run(){
				CGroupMovement action = behavior.prepare( LocationModeManager.this, dockable, extendedMode );
				if( action == null ){
					return;
				}
				
				apply( dockable, extendedMode, action );		
			}
		});
	}
	
	/**
	 * Gets the action that is currently carried out.
	 * @return the current action, can be null
	 */
	public CGroupMovement getCurrentAction(){
		if( currentAction.isEmpty() ){
			return null;
		}
		return currentAction.get( currentAction.size()-1 );
	}
	
	/**
	 * Executes action in a transaction assuming that the result of this action will lead to
	 * dockable having the new mode extendedMode.
	 * @param dockable the primary {@link Dockable}, this item may very well be the new focus owner
	 * @param extendedMode the expected mode dockable will have after action completed
	 * @param action the action to execute
	 */
	public void apply( final Dockable dockable, final ExtendedMode extendedMode, final CGroupMovement action ){
		runTransaction( new AffectingRunnable(){
			public void run( final AffectedSet set ){
				try{
					getController().getFocusController().freezeFocus();
					currentAction.add( action );
					action.apply( new CGroupBehaviorCallback(){
						public void setMode( Dockable element, ExtendedMode mode ){
							apply( element, mode.getModeIdentifier(), false );
						}
						
						public void setLocation( Dockable element, Location location ){
							apply( element, location.getMode(), location, set );
						}
						
						public LocationModeManager getManager(){
							return LocationModeManager.this;
						}
						
						public Location getLocation( Dockable dockable ){
							M mode = getCurrentMode( dockable );
							if( mode == null ){
								return null;
							}
							return mode.current( dockable );
						}
					});
				}
				finally{
					currentAction.remove( action );
					getController().getFocusController().meltFocus();
				}
				
				LocationMode mode = getMode( extendedMode.getModeIdentifier() );
				if( mode != null ){
					if( mode.shouldAutoFocus() ){
						getController().setFocusedDockable( new DefaultFocusRequest( dockable, null, true, true, false ));
					}
					else{
						getController().setFocusedDockable( new DefaultFocusRequest( null, null, true ));
					}
				}	
			}
		});
	}
	
	/**
	 * Gets the current mode of dockable.
	 * @param dockable the element whose mode is searched
	 * @return the mode or null if not found
	 */
	public ExtendedMode getMode( Dockable dockable ){
		LocationMode mode = getCurrentMode( dockable );
		if( mode == null )
			return null;
		return mode.getExtendedMode();
	}
	
	/**
	 * Checks all {@link LocationMode}s of this manager and returns all
	 * {@link DockStation}s that were registered with the given id. The same
	 * station or the same id might be used for different modes.
	 * @param id the id of some station
	 * @return each mode-area pair where the area is not null, can be empty
	 */
	public Map getRepresentations( String id ){
		if( id == null )
			throw new IllegalArgumentException( "id must not be null" );
		Map result = new HashMap();
		for( LocationMode mode : modes() ){
			DockStation station = mode.getRepresentation( id );
			if( station != null ){
				result.put( mode.getExtendedMode(), station );
			}
		}
		return result;
	}
	
	/**
	 * Ignores the call, the position of {@link Dockable}s is set elsewhere.
	 */
	@Override
	protected void applyDuringRead( String key, Path old, Path current, Dockable dockable ){
		// ignore
	}
	
	@Override
	public void apply( Dockable dockable, M mode, Location history, AffectedSet set ) {
		super.apply( dockable, mode, history, set );
		if( history != null ){
			history.resetApplicationDefined();
		}
	}
	
	/**
	 * Using the current {@link ExtendedModeEnablement} this method tells whether
	 * mode mode can be applied to dockable.
	 * @param dockable some element, not null
	 * @param mode some mode, not null
	 * @return the result of {@link ExtendedModeEnablement#isAvailable(Dockable, ExtendedMode)}
	 */
	public boolean isModeAvailable( Dockable dockable, ExtendedMode mode ){
		if( enablement == null )
			return false;
		
		return enablement.isAvailable( dockable, mode ).isAvailable();
	}
	
	/**
	 * Using the current {@link ExtendedModeEnablement} this method tells whether
	 * mode mode is hidden from the user when looking at dockable. A hidden
	 * mode 
	 * @param dockable some element, not null
	 * @param mode some mode, not null
	 * @return the result of {@link ExtendedModeEnablement#isAvailable(Dockable, ExtendedMode)}
	 */
	public boolean isModeHidden( Dockable dockable, ExtendedMode mode ){
		if( enablement == null )
			return false;
		
		return enablement.isHidden( dockable, mode ).isHidden();
	}
	
	/**
	 * Adds a listener to the mode with unique identifier identifier. If the
	 * mode is exchanged then this listener is automatically removed and may be re-added
	 * to the new mode.
	 * @param identifier the identifier of some mode (not necessarily registered yet).
	 * @param listener the new listener, not null
	 */
	public void addListener( Path identifier, LocationModeListener listener ){
		if( listener == null )
			throw new IllegalArgumentException( "listener must not be null" );
		
		List list = listeners.get( identifier );
		if( list == null ){
			list = new ArrayList();
			listeners.put( identifier, list );
		}
		list.add( listener );
		
		LocationMode mode = getMode( identifier );
		if( mode != null ){
			mode.addLocationModeListener( listener );
		}
	}
	
	/**
	 * Removes a listener from the mode identifier.
	 * @param identifier the name of a mode
	 * @param listener the listener to remove
	 */
	public void removeListener( Path identifier, LocationModeListener listener ){
		List list = listeners.get( identifier );
		if( list == null )
			return;
		
		list.remove( listener );
		if( list.isEmpty() )
			listeners.remove( identifier );
		
		LocationMode mode = getMode( identifier );
		if( mode != null ){
			mode.removeLocationModeListener( listener );
		}
	}
	
	@Override
	public M getCurrentMode( Dockable dockable ){
		while( dockable != null ){
			for( M mode : modes() ){
				if( mode.isCurrentMode( dockable ))
					return mode;
			}
			DockStation station = dockable.getDockParent();
			dockable = station == null ? null : station.asDockable();
		}
		
		return null;
	}
	
	/**
	 * Gets the current strategy for handing double-clicks.
	 * @return the strategy, never null
	 * @see #setDoubleClickStrategy(DoubleClickLocationStrategy)
	 */
	public DoubleClickLocationStrategy getDoubleClickStrategy(){
		return doubleClickStrategy.getValue();
	}
	
	/**
	 * Sets the current strategy for handling double-clicks on {@link Dockable}s. This
	 * strategy will be asked what mode to assign to an element that has been double-clicked.
	 * Results that are not allowed by the current {@link ExtendedModeEnablement} are ignored.
	 * @param strategy the new strategy, can be null to set the default strategy
	 */
	public void setDoubleClickStrategy( DoubleClickLocationStrategy strategy ){
		doubleClickStrategy.setValue( strategy );
	}
	
	/**
	 * Tells whether this mode is currently in layouting mode. Some
	 * methods of this manager do not react while in layouting mode.
	 * @return true if layouting mode is active
	 */
	public boolean isLayouting(){
		return layoutMode > 0; 
	}
	
	/**
	 * Activates the {@link #isLayouting() layout mode} while run
	 * is running.
	 * @param run some code to execute
	 */
	public void runLayoutTransaction( Runnable run ){
		try{
			layoutMode++;
			runTransaction( run, true );
		}
		finally{
			layoutMode--;
		}
	}
	
    /**
     * Ensures that dockable is not hidden behind another 
     * {@link Dockable}. That does not mean that dockable becomes
     * visible, just that it is easier reachable without the need to change
     * modes of any Dockables.
* This method returns immediately if in {@link #isLayouting() layouting mode} * @param dockable the element which should not be hidden */ public void ensureNotHidden( final Dockable dockable ){ if( isLayouting() ) return; runTransaction( new Runnable() { public void run(){ for( LocationMode mode : modes() ){ mode.ensureNotHidden( dockable ); } } }); } /** * Empty method evaluating the correct location of a {@link Dockable}. To be * overridden by subclasses to handle elements which have additional restrictions. * @param dockable the element to check */ public void ensureValidLocation( Dockable dockable ){ // nothing } /** * Iterates through all the {@link LocationMode}s for which aside has stored locations, * and sets dockable as neighbor. This method does not change the actual location of dockable, * rather a call to apply would be necessary to update the location.
* It is the responsibility of the caller to ensure that dockable can actually be placed at any * location where aside ever was. * @param dockable the item whose location should change * @param aside the item whose neighbor is set */ public void setLocationAside( Dockable dockable, Dockable aside ){ M current = getCurrentMode( aside ); List history = getModeHistory( aside ); for( M mode : history ){ Location location; if( mode == current ){ location = mode.current( aside ); } else{ location = getHistory( aside, mode.getUniqueIdentifier() ); } if( location != null ){ AsideRequestFactory factory = getController().getProperties().get( AsideRequest.REQUEST_FACTORY ); AsideRequest request = factory.createAsideRequest( location.getLocation(), dockable ); Location result = mode.aside( request, location ); if( result != null ){ addToModeHistory( dockable, mode, result ); } } } } @Override public DockActionSource getSharedActions( DockStation station ){ Dockable selected = station.getFrontDockable(); if( selected == null ){ return null; } LocationMode mode = getCurrentMode( selected ); if( mode == null ){ return null; } MultiDockActionSource result = new MultiDockActionSource(); for( LocationMode other : modes() ){ if( behavior.shouldForwardActions( this, station, selected, other.getExtendedMode() ) ){ DockActionSource source = other.getActionsFor( selected, mode ); if( source != null ){ result.add( source ); } } } return result; } /** * Adds and removes listeners from {@link LocationMode}s according to the map * {@link LocationModeManager#listeners}. * @author Benjamin Sigg */ private class LocationModeListenerAdapter implements ModeManagerListener{ public void modeAdded( ModeManager manager, LocationMode mode ){ mode.setManager( LocationModeManager.this ); mode.setController( getController() ); List list = listeners.get( mode.getUniqueIdentifier() ); if( list != null ){ for( LocationModeListener listener : list ){ mode.addLocationModeListener( listener ); } } } public void modeRemoved( ModeManager manager, LocationMode mode ){ mode.setManager( null ); mode.setController( null ); List list = listeners.get( mode.getUniqueIdentifier() ); if( list != null ){ for( LocationModeListener listener : list ){ mode.removeLocationModeListener( listener ); } } } public void dockableAdded( ModeManager manager, Dockable dockable ){ // ignore } public void dockableRemoved( ModeManager manager, Dockable dockable ){ // ignore } public void modeChanged( ModeManager manager, Dockable dockable, LocationMode oldMode, LocationMode newMode ){ // ignore } } /** * If the {@link DockRegister} is currently stalled, then a call to {@link LocationModeManager#refresh(Dockable, boolean)} * is scheduled, otherwise the call is performed directly. * @param dockable the dockable which should be forwarded to {@link LocationModeManager#refresh(Dockable, boolean)} */ public void delayedRefresh( Dockable dockable ){ if( getController().getRegister().isStalled() ){ pendingRefreshs.add( dockable ); } else{ refresh( dockable, true ); } } /** * This listener registers when {@link Dockable}s enter and leave and adds or * removes a {@link DockHierarchyListener}. * @author Benjamin Sigg */ private class RegisterListener extends DockRegisterAdapter{ private DockController controller; public void connect( DockController controller ){ if( this.controller != null ){ DockRegister register = this.controller.getRegister(); register.removeDockRegisterListener( this ); for( Dockable dockable : register.listDockables() ){ dockable.removeDockHierarchyListener( hierarchyListener ); rebuild( dockable ); } } this.controller = controller; if( controller != null ){ DockRegister register = controller.getRegister(); register.addDockRegisterListener( this ); for( Dockable dockable : register.listDockables() ){ dockable.addDockHierarchyListener( hierarchyListener ); } } } @Override public void dockableRegistered( DockController controller, Dockable dockable ){ dockable.addDockHierarchyListener( hierarchyListener ); rebuild( dockable ); } @Override public void dockableUnregistered( DockController controller, Dockable dockable ){ dockable.removeDockHierarchyListener( hierarchyListener ); } @Override public void registerUnstalled( DockController controller ){ while( pendingRefreshs.size() > 0 && !controller.getRegister().isStalled() ){ Iterator iter = pendingRefreshs.iterator(); Dockable next = iter.next(); iter.remove(); refresh( next, true ); } } } /** * Reacts on dockables that are changing their position by calling * {@link LocationModeManager#refresh(Dockable, boolean)}. * @author Benjamin Sigg */ private class HierarchyListener implements DockHierarchyListener{ public void controllerChanged( DockHierarchyEvent event ){ // ignore } public void hierarchyChanged( DockHierarchyEvent event ){ if( !isOnTransaction() ){ delayedRefresh( event.getDockable() ); } } } /** * Detects the drag-operation and calls {@link LocationModeManager#store(Dockable)}. * @author Benjamin Sigg */ private class RelocatorListener extends VetoableDockRelocatorAdapter{ @Override public void dragging( DockRelocatorEvent event ){ store( event.getDockable() ); for( Dockable dockable : event.getImplicitDockables() ){ store( dockable ); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy