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

bibliothek.gui.dock.common.mode.CLocationModeManager 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.common.mode;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CDockFrontend;
import bibliothek.gui.dock.common.intern.CDockFrontendListener;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CDockableAccess;
import bibliothek.gui.dock.common.intern.CSetting;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.facile.mode.CLocationModeSettings;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationMode;
import bibliothek.gui.dock.facile.mode.LocationModeManager;
import bibliothek.gui.dock.layout.DockableProperty;
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.support.mode.ModeSettings;
import bibliothek.gui.dock.support.mode.ModeSettingsConverter;
import bibliothek.gui.dock.support.mode.UndoableModeSettings;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.IconManager;
import bibliothek.util.Path;
import bibliothek.util.container.Single;

/**
 * {@link LocationModeManager} providing additional methods for working with
 * {@link CLocation}s, {@link CommonDockable}s and other items specific to the
 * common project. 
 * @author Benjamin Sigg
 *
 */
public class CLocationModeManager extends LocationModeManager{
	/** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "minimize"-action */
    public static final String ICON_MANAGER_KEY_MINIMIZE = "locationmanager.minimize";
    /** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "maximize"-action */
    public static final String ICON_MANAGER_KEY_MAXIMIZE = "locationmanager.maximize";
    /** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "normalize"-action */
    public static final String ICON_MANAGER_KEY_NORMALIZE = "locationmanager.normalize";
    /** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "externalize"-action */
    public static final String ICON_MANAGER_KEY_EXTERNALIZE = "locationmanager.externalize";
    /** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "unexternalize"-action */
    public static final String ICON_MANAGER_KEY_UNEXTERNALIZE = "locationmanager.unexternalize";
    /** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "unmaximize externalized"-action */
    public static final String ICON_MANAGER_KEY_UNMAXIMIZE_EXTERNALIZED = "locationmanager.unmaximize_externalized";
    
	private CControlAccess control;
	
	private CNormalMode normalMode;
	private CMaximizedMode maximizedMode;
	private CMinimizedMode minimizedMode;
	private CExternalizedMode externalizedMode;
	
	/**
	 * Creates a new manager.
	 * @param control the control in whose realm this manager works
	 */
	public CLocationModeManager( CControlAccess control ){
		super( control.getOwner().intern().getController() );
		this.control = control;

		setDoubleClickStrategy( new PreviousModeDoubleClickStrategy( this ) );
		
		minimizedMode = new CMinimizedMode( control.getOwner() );
		maximizedMode = new CMaximizedMode( control.getOwner() );
		normalMode = new CNormalMode( control.getOwner() );
		externalizedMode = new CExternalizedMode( control.getOwner() );
		
		putMode( minimizedMode );
		putMode( externalizedMode );
		putMode( normalMode );
		putMode( maximizedMode );
        
        addModeManagerListener(new ModeManagerListener(){
			public void dockableAdded( ModeManager manager, Dockable dockable ){
				// ignore
			}

			public void dockableRemoved( ModeManager manager, Dockable dockable ){
				// ignore
			}

			public void modeAdded( ModeManager manager, LocationMode mode ){
				// ignore
			}

			public void modeChanged( ModeManager manager, Dockable dockable, LocationMode oldMode, LocationMode newMode ){
				CDockableAccess access = CLocationModeManager.this.control.access( ((CommonDockable)dockable).getDockable() );
				if( access != null ){
					ExtendedMode mode = getMode(dockable);
					access.informMode( mode );
				}
			}

			public void modeRemoved( ModeManager manager, LocationMode mode ){
				// ignore
			}
		});
	}
	
	/**
	 * Direct access to the mode handling "normal" {@link Dockable}s.
	 * @return the mode
	 */
	public CNormalMode getNormalMode(){
		return normalMode;
	}

	/**
	 * Direct access to the mode handling "maximized" {@link Dockable}s.
	 * @return the mode
	 */
	public CMaximizedMode getMaximizedMode(){
		return maximizedMode;
	}
	
	/**
	 * Direct access to the mode handling "minimized" {@link Dockable}s.
	 * @return the mode
	 */
	public CMinimizedMode getMinimizedMode(){
		return minimizedMode;
	}
	
	/**
	 * Direct access to the mode handling "externalized" {@link Dockable}s.
	 * @return the mode
	 */
	public CExternalizedMode getExternalizedMode(){
		return externalizedMode;
	}
	
	
	@Override
	protected boolean createEntryDuringRead( String key ){
		return control.shouldStore( key );
	}
	
	@Override
	public void remove( Dockable dockable ){
		if( dockable instanceof CommonDockable ){
			CDockable cdockable = ((CommonDockable)dockable).getDockable();
			String key = control.shouldStore( cdockable );
			if( key != null ){
				addEmpty( key );
			}
		}
		super.remove( dockable );
	}

    @Override
    public  ModeSettings createModeSettings( ModeSettingsConverter converter ){
    	return new CLocationModeSettings( converter );
    }
    
    @Override
    public void readSettings( ModeSettings settings ){
        UndoableModeSettings undoable = new UndoableModeSettings(){
        	public boolean createTemporaryDuringRead( String key ){
        		return control.getRegister().isMultiId( key );
        	}
        };
    	
    	final Runnable temporary = readSettings( settings, undoable );
    	control.getOwner().intern().addListener( new CDockFrontendListener(){
			public void loading( CDockFrontend frontend, CSetting setting ){
				// ignore
			}
			
			public void loaded( CDockFrontend frontend, CSetting setting ){
				temporary.run();
				frontend.removeListener( this );
			}
		});
    }
    
    @Override
    public Runnable readSettings( ModeSettings settings, UndoableModeSettings pending ){
    	Runnable result = super.readSettings( settings, pending );
    	if( settings instanceof CLocationModeSettings ){
    		CLocationModeSettings locationSettings = (CLocationModeSettings)settings;
    		locationSettings.rescue( getMaximizedMode() );
    	}
    	return result;
    }
    
    /**
     * Tries to set the location of dockable. Does nothing if
     * location is invalid or requires information that is
     * not available. If dockable is a {@link CommonDockable} and
     * the {@link CLocationMode mode} respects {@link CLocationMode#respectWorkingAreas(DockStation) working-areas},
     * then the working-area is set or removed depending on the value of {@link CStation#isWorkingArea()}.
     * @param dockable the element to move
     * @param location the new location of dockable
     */
    public void setLocation( Dockable dockable, CLocation location ){
    	ExtendedMode mode = location.findMode();
    	if( mode == null )
    		return;
    	
    	CLocationMode newMode = getMode( mode.getModeIdentifier() );
    	if( newMode == null )
    		return;
    		
    	String root = location.findRoot();
    	
    	if( root != null ){
    		if( dockable instanceof CommonDockable ){
	    		CStation station = control.getOwner().getStation( root );
	    		if( station != null ){
		    		if( newMode.respectWorkingAreas( station.getStation() )){
		    			if( station.isWorkingArea() ){
		    				((CommonDockable)dockable).getDockable().setWorkingArea( station );
		    			}
		    			else{
		    				((CommonDockable)dockable).getDockable().setWorkingArea( null );
		    			}
		    		}
	    		}
	    	}
    		
        	// easy solution: set the location, then change the mode
    	    setProperties( newMode, dockable, new Location( mode.getModeIdentifier(), root, location.findProperty(), true ) );
    	    apply( dockable, newMode, true );
    	}
    	else{
    		apply( dockable, newMode, false );
    	}
    }
    
    /**
     * Sets the default location of dockable when going into
     * mode. The properties set here will be overridden
     * as soon as the user drags dockable to another location (within 
     * the same mode) or dockable is removed permanently.
* This method has no effect if dockable is already in * mode. There is also no effect if dockable * has not been registered at the {@link CLocationModeManager}.
* Note: it is the clients responsibility to ensure that location * and mode belong to each other. * @param dockable the element whose location will be set * @param mode the mode for which the location is to be set or null * @param location the new location * @throws IllegalArgumentException if either argument is null or * if {@link CLocation#findRoot()} or {@link CLocation#findProperty()} returns * null */ public void setLocation( Dockable dockable, ExtendedMode mode, CLocation location ){ if( dockable == null ) throw new IllegalArgumentException( "dockable must not be null" ); if( mode == null ) throw new IllegalArgumentException( "mode must not be null" ); if( location != null ){ String root = location.findRoot(); if( root == null ) throw new IllegalArgumentException( "the location is not sufficient to find the root station" ); DockableProperty property = location.findProperty(); if( property == null ) throw new IllegalArgumentException( "the location does not carry enough information to find the location of dockable" ); ExtendedMode locationMode = location.findMode(); if( locationMode == null ){ throw new IllegalArgumentException( "the location does not carry enough information to find the mode of dockable" ); } if( !mode.getModeIdentifier().equals( locationMode.getModeIdentifier() )) throw new IllegalArgumentException( "location and mode do not belong together, they do not have the same identifier" ); setProperties( getMode( mode.getModeIdentifier() ), dockable, new Location( mode.getModeIdentifier(), root, property, true ) ); } else{ setProperties( getMode( mode.getModeIdentifier() ), dockable, null ); } } /** * Gets an element describing the location of dockable as * good as possible. * @param dockable the element whose location should be searched * @return the location or null if no location was found */ public CLocation getLocation( Dockable dockable ){ CLocationMode mode = getCurrentMode( dockable ); if( mode == null ) return null; return mode.getCLocation( dockable ); } /** * Assuming that dockable is currently not in mode mode, * then this method searches for the previously stored location of dockable. * Note that this method can't tell where dockable would be * shown if it never was in that mode and the client never specified the * location. * @param dockable the dockable whose location is searched * @param mode the mode which might be taken by dockable * @return the location or null * @throws IllegalArgumentException if any argument is null */ public CLocation getLocation( Dockable dockable, ExtendedMode mode ){ if( dockable == null ) throw new IllegalArgumentException( "dockable must not be null" ); if( mode == null ) throw new IllegalArgumentException( "mode must not be null" ); CLocationMode cmode = getMode( mode.getModeIdentifier() ); Location location = getProperties( cmode, dockable ); if( location == null ) return null; return cmode.getCLocation( dockable, location ); } /** * Tries to find the "optimal spot" where to put a new child onto station. In this * case the optimal spot is {@link CLocation#aside()} the latest focused child of station. * @param station the station where a {@link CDockable} is about to be dropped onto * @return the preferred location of the new child */ public CLocation getDropLocation( CStation station ){ Dockable[] history = control.getOwner().getController().getFocusHistory().getHistory(); for( int i = history.length-1; i >= 0; i-- ){ Dockable next = history[i]; if( next instanceof CommonDockable && next.asDockStation() != station.getStation() ){ CDockable cnext = ((CommonDockable)next).getDockable(); if( DockUtilities.isAncestor( station.getStation(), next )){ boolean valid; if( station.isWorkingArea() ){ valid = cnext.getWorkingArea() == station; } else{ valid = cnext.getWorkingArea() == null; } if( valid ){ CLocation location = cnext.getBaseLocation(); if( location != null ){ return location.aside(); } } } if( cnext.getWorkingArea() == station ){ CLocation location = cnext.getBaseLocation(); if( location != null ){ return location.aside(); } } } } return station.getStationLocation(); } @Override public void ensureValidLocation( Dockable dockable ){ if( dockable instanceof CommonDockable ){ ensureValidLocation( ((CommonDockable)dockable).getDockable() ); } } /** * This method compares the current mode of dockable with its * availability set. If the current mode is not available, then dockable * is put into another mode (usually the {@link #getNormalMode() normal mode}).
* This method also checks the working area, provided that the current mode respects * the working-area settings.
* This method returns immediately if in {@link #isLayouting() layouting mode} * @param dockable the element whose mode is to be checked */ public void ensureValidLocation( CDockable dockable ){ if( isLayouting() ) return; ExtendedMode mode = getMode( dockable.intern() ); if( mode == ExtendedMode.NORMALIZED ){ CStation preferredArea = dockable.getWorkingArea(); CStation currentArea = findFirstParentWorkingArea( dockable.intern() ); if( preferredArea != currentArea ){ if( preferredArea == null ){ // the dockable is on a working-area, but should not be there CLocation defaultLocation = getNormalMode().getDefaultLocation(); dockable.setLocation( defaultLocation ); } else{ // reset the location dockable.setLocation( preferredArea.getStationLocation() ); } } mode = getMode( dockable.intern() ); } // normalize the element if its current mode is not valid if( !isModeAvailable( dockable.intern(), mode )){ dockable.setExtendedMode( ExtendedMode.NORMALIZED ); } } /** * Ensures that all dockables are in a basic mode.
* This method returns immediately if in {@link #isLayouting() layouting mode} * @return true if at least one element was affected by changes, * false if nothing happened. */ public boolean ensureBasicModes(){ if( isLayouting() ) return false; final Single result = new Single( false ); runTransaction( new AffectingRunnable() { public void run( AffectedSet set ){ for( Dockable dockable : listDockables() ){ CLocationMode current = getCurrentMode( dockable ); if( current != null && !current.isBasicMode() ){ List modes = getModeHistory( dockable ); CLocationMode next = null; for( int i = modes.size()-1; i >= 0 && next == null; i-- ){ CLocationMode mode = modes.get( i ); if( mode.isBasicMode() && isModeAvailable( dockable, mode.getExtendedMode() )){ next = mode; } } if( next == null ){ next = getNormalMode(); } result.setA( true ); setMode( dockable, next.getExtendedMode() ); } } } }); return result.getA(); } /** * Updates the location of all dockables that should be on a working-area * and that are currently in a mode that does not support working-areas. The history * of the elements is searched for the first mode which supports working-areas. If no * such mode is found, then the normal-mode is applied. */ public void resetWorkingAreaChildren(){ runTransaction( new AffectingRunnable() { public void run( AffectedSet set ){ for( Dockable dockable : listDockables() ){ if( dockable instanceof CommonDockable ){ CDockable cdockable = ((CommonDockable)dockable).getDockable(); resetWorkingArea( cdockable, set ); } } } }); } private void resetWorkingArea( CDockable dockable, AffectedSet set ){ if( dockable.getWorkingArea() == null ) return; DockStation parent = dockable.intern().getDockParent(); if( parent == null ) return; CLocationMode current = getCurrentMode( dockable.intern() ); if( current == null ) return; if( current.respectWorkingAreas( parent )) return; // need to reset List history = getPropertyHistory( dockable.intern() ); CLocationMode next = null; for( int i = history.size()-1; i >= 0 && next == null; i-- ){ Location check = history.get( i ); Path path = check.getMode(); String root = check.getRoot(); if( path != null && root != null ){ CLocationMode mode = getMode( path ); if( mode != null ){ CStation station = control.getOwner().getStation( root ); if( station != null ){ if( mode.respectWorkingAreas( station.getStation() ) && mode.isRepresenting( station.getStation() )){ if( isModeAvailable( dockable.intern(), mode.getExtendedMode() )){ next = mode; } } } } } } if( next == null ){ next = getNormalMode(); } apply( dockable.intern(), next, set, false ); } /** * Guesses the result of {@link #getCurrentMode(Dockable)} once a {@link Dockable} is * dropped onto {@link DockStation}. If more than one {@link LocationMode mode} is using * parent, then the guess might not always be correct. * @param parent some station * @return the mode its children are in, or null if no guess can be made */ public ExtendedMode childsExtendedMode( DockStation parent ){ while( parent != null ){ CLocationMode mode = getRepresentingMode( parent ); if( mode != null ){ return mode.getExtendedMode(); } Dockable dockable = parent.asDockable(); if( dockable == null ) return null; parent = dockable.getDockParent(); } return null; } /** * Finds the first {@link CStation} in the path up to the root from * dockable wich is a working area. * @param dockable the element which might have a {@link CStation} * as parent. * @return the first found {@link CStation}. */ private CStation findFirstParentWorkingArea( Dockable dockable ){ DockStation station = dockable.getDockParent(); dockable = station == null ? null : station.asDockable(); if( dockable != null ) return getAreaOf( dockable ); else return null; } /** * Searches dockable and its parent for the first {@link CStation} * that is a working area. * @param dockable the element whose working area is searched * @return the first working area or null */ protected CStation getAreaOf( Dockable dockable ){ Map> stations = new HashMap>(); for( CStation station : control.getOwner().getStations() ){ if( station.isWorkingArea() ){ stations.put( station.getStation(), station ); } } if( dockable.asDockStation() != null ){ CStation station = stations.get( dockable.asDockStation() ); if( station != null ) return station; } Dockable check = dockable; while( check != null ){ DockStation parent = check.getDockParent(); if( parent == null ) check = null; else check = parent.asDockable(); CStation station = stations.get( parent ); if( station != null ) return station; } return null; } /** * Searches the {@link CLocationMode mode} which represents the mode of * the children of station. This method calls * {@link LocationMode#isRepresenting(DockStation)} with station, * but does not check the parents of station. Basic modes are preferred * over non-basic modes by this method. * @param station some station * @return the mode or null if nothing found */ private CLocationMode getRepresentingMode( DockStation station ){ Iterable modes = modes(); CLocationMode first = null; for( CLocationMode mode : modes ){ if( mode.isRepresenting( station )){ if( mode.isBasicMode() ) return mode; if( first == null ) first = mode; } } return first; } }