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

bibliothek.gui.dock.facile.mode.MaximizedMode 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.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.KeyStroke;

import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.action.predefined.CMaximizeAction;
import bibliothek.gui.dock.common.mode.CLocationModeManager;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.event.KeyboardListener;
import bibliothek.gui.dock.facile.mode.action.MaximizedModeAction;
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.Mode;
import bibliothek.gui.dock.support.mode.ModeManager;
import bibliothek.gui.dock.support.mode.ModeManagerListener;
import bibliothek.gui.dock.support.mode.ModeSetting;
import bibliothek.gui.dock.support.mode.ModeSettingFactory;
import bibliothek.gui.dock.util.DockProperties;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.gui.dock.util.IconManager;
import bibliothek.gui.dock.util.PropertyValue;
import bibliothek.util.Path;

/**
 * {@link Dockable}s are maximized if they take up the whole space a frame
 * or a screen offers.
 * @author Benjamin Sigg
 * @param  the kind of areas this mode handles
 */
public class MaximizedMode extends AbstractLocationMode{
	/** unique identifier for this mode */
	public static final Path IDENTIFIER = new Path( "dock.mode.maximized" );

	/** the key used for the {@link IconManager} to read the {@link javax.swing.Icon} for the "maximize"-action */
	public static final String ICON_IDENTIFIER = CLocationModeManager.ICON_MANAGER_KEY_MAXIMIZE;

	/** the mode in which some dockable with id=key was before maximizing */
	private Map lastMaximizedMode = new HashMap();

	/** the location some dockable had before maximizing */
	private Map lastMaximizedLocation = new HashMap();

	/** the listener responsible for detecting apply-events on other modes */
	private Listener listener = new Listener();

	/** all the {@link KeyHook}s that are currently used */
	private List hooks = new LinkedList();

	/**
	 * {@link KeyStroke} used to go into, or go out from the maximized state.
	 */
	private PropertyValue keyStrokeMaximizeChange = new PropertyValue( CControl.KEY_MAXIMIZE_CHANGE ){
		@Override
		protected void valueChanged( KeyStroke oldValue, KeyStroke newValue ) {
			// ignore
		}
	};

	/**
	 * Empty default constructor. Subclasses should call 
	 * {@link #setActionProvider(LocationModeActionProvider)} to complete
	 * initialization of this mode.
	 */
	protected MaximizedMode(){
		// nothing
	}

	/**
	 * Creates a new mode
	 * @param control the control in whose realm this mode will work
	 */
	public MaximizedMode( CControl control ){
		setActionProvider( new DefaultLocationModeActionProvider( new CMaximizeAction( control ) ) );
	}

	/**
	 * Creates a new mode.
	 * @param controller the owner of this mode
	 */
	public MaximizedMode( DockController controller ){
		setActionProvider( new DefaultLocationModeActionProvider( new MaximizedModeAction( controller, this ) ) );
	}

	@Override
	public void setManager( LocationModeManager manager ){
		for( KeyHook hook : hooks ){
			hook.destroy( false );
		}
		hooks.clear();

		LocationModeManager old = getManager();
		listener.replaceManager( old, manager );

		if( manager == null )
			keyStrokeMaximizeChange.setProperties( (DockProperties)null );
		else
			keyStrokeMaximizeChange.setProperties( manager.getController() );

		super.setManager( manager );
	}

	public Path getUniqueIdentifier(){
		return IDENTIFIER;
	}

	public ExtendedMode getExtendedMode(){
		return ExtendedMode.MAXIMIZED;
	}

	public boolean runApply( Dockable dockable, Location history, AffectedSet set ){
		MaximizedModeArea area = getMaximizeArea( dockable, history );
		
		if( area == null )
			area = getDefaultArea();
		
		area.prepareApply( dockable, history, set );
		return maximize( area, dockable, history, set );
	}

	public Location current( Dockable dockable ){
		MaximizedModeArea area = get( dockable );
		if( area == null )
			return null;
		
		DockableProperty location = area.getLocation( dockable );
		return new Location( getUniqueIdentifier(), area.getUniqueId(), location );
	}

	public boolean isCurrentMode( Dockable dockable ){
		for( MaximizedModeArea area : this ){
			if( area.isChild( dockable ) )
				return true;
		}
		return false;
	}

	public boolean isDefaultMode( Dockable dockable ){
		return false;
	}
	
	
	@Override
	public boolean isRepresenting( DockStation station ){
		if( super.isRepresenting( station )){
			return true;
		}
		
		Dockable dockable = station.asDockable();
		if( dockable == null ){
			return false;
		}
		
		for( MaximizedModeArea area : this ){
			if( area.isChild( dockable ) ){
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Assuming dockable is a maximized element, tells which
	 * mode would be the preferred mode for unmaximization.
	 * @param dockable some child
	 * @return the preferred unmaximized mode, can be null
	 */
	public LocationMode getUnmaximizedMode( Dockable dockable ){
		while( dockable != null ){
			for( MaximizedModeArea area : this ){
				if( area.isChild( dockable ) ){
					return area.getUnmaximizedMode();
				}
			}
			DockStation parent = dockable.getDockParent();
			dockable = parent == null ? null : parent.asDockable();
		}
		return null;
	}

	/**
	 * Ensures that dockable is maximized.
	 * @param area the future parent of dockable, can be null
	 * @param dockable the element that should be made maximized
	 * @param set a set of Dockables which will be filled by the
	 * elements that change their mode because of this method
	 */
	public void maximize( MaximizedModeArea area, Dockable dockable, AffectedSet set ){
		maximize( area, dockable, null, set );
	}

	/**
	 * Ensures that dockable is maximized.
	 * @param area the future parent of dockable, can be null
	 * @param dockable the element that should be made maximized
	 * @param history the expected location of dockable after this method has finished, can be null.
	 * No guarantees are given that the final location matches history.
	 * @param set a set of Dockables which will be filled by the
	 * elements that change their mode because of this method
	 * @return whether the operation was a success
	 */
	public boolean maximize( MaximizedModeArea area, Dockable dockable, Location history, AffectedSet set ){
		Dockable maximizing = getMaximizingElement( dockable );
		if( maximizing != dockable )
			getManager().store( maximizing );

		if( area == null )
			area = getMaximizeArea( maximizing );

		if( area == null )
			area = getDefaultArea();

		String id = getManager().getKey( maximizing );
		LocationMode current = getManager().getCurrentMode( maximizing );

		if( id == null && current == null ){
			throw new IllegalStateException( "an unidentified dockable without location has been found, all dockables except true root-station must have a location, true root-stations can never be used in this method." );
		}
		
		if( id == null && current != null ){
			lastMaximizedLocation.put( area.getUniqueId(), current.current( maximizing ) );
			lastMaximizedMode.put( area.getUniqueId(), current.getUniqueIdentifier() );
		}
		else{
			getManager().store( dockable );
		}

		List oldMaximized = getMaximized( area );
		area.setMaximized( maximizing, true, history, set );
		
		if( !(id == null && current == null )){
			for( Dockable newMaximized : area.getMaximized() ){
				if( newMaximized != maximizing ){
					if( DockUtilities.isAncestor( newMaximized, maximizing )){
						// the maximizing element was put on a DockStation
						if( !oldMaximized.contains( newMaximized )){
							// and the DockStation was created by this action
							for( Dockable replaced : oldMaximized ){
								if( DockUtilities.isAncestor( newMaximized, replaced )){
									storeLastMaximizedLocation( area, replaced );
									break;
								}
							}
						}
					}
				}
			}
		}
		
		set.add( maximizing );
		return true;
	}
	
	private List getMaximized( MaximizedModeArea area ){
		Dockable[] children = area.getMaximized();
		if( children == null ){
			return Collections.emptyList();
		}
		else{
			return Arrays.asList( children );
		}
	}
	
	private void storeLastMaximizedLocation( MaximizedModeArea area, Dockable dockable ){
		LocationMode previousMode = getManager().getPreviousMode( dockable );
		if( previousMode != null ){
			Location previousLocation = getManager().getHistory( dockable, previousMode.getUniqueIdentifier() );
			if( previousLocation != null ){
				lastMaximizedLocation.put( area.getUniqueId(), previousLocation );
				lastMaximizedMode.put( area.getUniqueId(), previousMode.getUniqueIdentifier() );
			}
		}
	}

	/**
	 * Ensures that dockable is not maximized. Does nothing if the parent
	 * {@link MaximizedModeArea} of dockable has not maximized dockable
	 * or if the {@link LocationModeManager} does not know dockable.
	 * @param dockable the element that might be maximized currently
	 * @param set a set of Dockables which will be filled by the
	 * elements that change their mode because of this method
	 */
	public void unmaximize( Dockable dockable, AffectedSet set ){
		final MaximizedModeArea area = getMaximizeArea( dockable );
		if( area != null && area.getMaximized() != null ){
			Dockable[] maximized = area.getMaximized();
			if( maximized != null ){
				for( Dockable check : maximized ){
					if( DockUtilities.isAncestor( check, dockable )){
						set.add( dockable );
						dockable = check;
						final Dockable element = dockable;
	
						final LocationModeManager manager = getManager();
						manager.runTransaction( new AffectingRunnable() {
							public void run( AffectedSet set ){
								area.setMaximized( element, false, null, set );
	
								String key = area.getUniqueId();
								boolean done = false;
	
								// try to apply the last mode
								if( lastMaximizedLocation.get( key ) != null ){
									done = getManager().apply( 
											element,
											lastMaximizedMode.remove( key ),
											lastMaximizedLocation.remove( key ),
											set );
								}
								
								if( !done ){
									applyOldLocation( element, set );
								}		
							}
						}, true );
	
						manager.store( dockable );
						return;
					}
				}
			}
		}
	}

	/**
	 * Recursively searches through the tree of {@link DockElement}s and applies old locations
	 * on those {@link Dockable}s which are known to have a location. Branches are not visited
	 * if the a parent element has been found with an old location.
	 * @param element the root of the tree to search through
	 * @param set a set to be filled with all the {@link Dockable}s whose location changed.
	 */
	private void applyOldLocation( Dockable element, AffectedSet set ){
		LocationModeManager manager = getManager();
		if( manager.isRegistered( element ) ){
			LocationMode mode = manager.getPreviousMode( element );
			if( mode == null || mode == MaximizedMode.this )
				mode = manager.getMode( NormalMode.IDENTIFIER );
			
			manager.apply( element, mode.getUniqueIdentifier(), set, true );
		}
		else if( element.asDockStation() != null ){
			DockStation station = element.asDockStation();
			Dockable[] children = new Dockable[ station.getDockableCount() ];
			for( int i = 0; i < children.length; i++ ){
				children[i] = station.getDockable( i );
			}
			for( Dockable child : children ){
				applyOldLocation( child, set );
			}
		}
	}
	
	/**
	 * Searches the {@link MaximizedModeArea} which either represents
	 * station or its nearest parent.
	 * @param station some station
	 * @return the nearest area or null
	 */
	public MaximizedModeArea getNextMaximizeArea( DockStation station ){
		while( station != null ){
			MaximizedModeArea area = getMaximizeArea( station );
			if( area != null ){
				return area;
				
			}

			Dockable dockable = station.asDockable();
			if( dockable == null )
				return null;

			station = dockable.getDockParent();
		}	
		return null;
	}
	
	/**
	 * Ensures that either the {@link MaximizedModeArea} station or its
	 * nearest parent does not show a maximized element.
	 * @param station an area or a child of an area
	 * @param affected elements whose mode changes will be added to this set
	 */
	public void unmaximize( DockStation station, AffectedSet affected ){
		MaximizedModeArea area = getNextMaximizeArea( station );
		if( area != null ){
			Dockable[] dockables = area.getMaximized();
			if( dockables != null ){
				for( Dockable dockable : dockables ){
					unmaximize( dockable, affected );
				}
			}
		}
	}

	/**
	 * Ensures that area has no maximized child.
	 * @param area some area
	 * @param affected the element whose mode might change
	 */
	public void unmaximize( MaximizedModeArea area, AffectedSet affected ){
		Dockable[] dockables = area.getMaximized();
		if( dockables != null ){
			for( Dockable dockable : dockables ){
				unmaximize( dockable, affected );
			}
		}
	}

	public void ensureNotHidden( final Dockable dockable ){
		getManager().runTransaction( new AffectingRunnable() {
			public void run( AffectedSet set ){
				Dockable mutableDockable = dockable;

				DockStation parent = mutableDockable.getDockParent();
				Dockable element = getMaximizingElement( mutableDockable );

				while( parent != null ){
					MaximizedModeArea area = getMaximizeArea( parent );
					if( area != null ){
						Dockable[] maximized = area.getMaximized();
						if( maximized != null ){
							for( Dockable check : maximized ){
								if( maximized != null && check != mutableDockable && check != element ){
									unmaximize( check, set );
								}	
							}
						}
					}

					mutableDockable = parent.asDockable();
					parent = mutableDockable == null ? null : mutableDockable.getDockParent();
				}	
			}
		});
	}
	
	/**
	 * Gets the area to which dockable should be maximized. This can be 
	 * {@link #getMaximizeArea(Dockable)}, or some other station.
	 * @param dockable the element that is maximized
	 * @param history the history of the last place where dockable was maximized, might be null
	 * @return the preferred area to maximize dockable
	 */
	public MaximizedModeArea getMaximizeArea( Dockable dockable, Location history ){
		return getMaximizeArea( dockable );
	}

	/**
	 * Searches the first {@link MaximizedModeArea} which is a parent
	 * of dockable. This method will never return
	 * dockable itself.
	 * @param dockable the element whose maximize area is searched
	 * @return the area or null
	 */
	public MaximizedModeArea getMaximizeArea( Dockable dockable ){
		DockStation parent = dockable.getDockParent();
		while( parent != null ){
			MaximizedModeArea area = getMaximizeArea( parent );
			if( area != null )
				return area;

			dockable = parent.asDockable();
			if( dockable == null ){
				parent = null;
			}
			else{
				parent = dockable.getDockParent();
			}
		}
		return null;
	}

	/**
	 * Searches the one {@link MaximizedModeArea} whose station is
	 * station.
	 * @param station the station whose area is searched
	 * @return the area or null if not found
	 */
	public MaximizedModeArea getMaximizeArea( DockStation station ){
		for( MaximizedModeArea area : this ){
			if( area.isRepresenting( station ) ){
				return area;
			}
		}
		return null;
	}

	/**
	 * Gets the element which must be maximized when the user requests that
	 * dockable is maximized.
	 * @param dockable some element, not null
	 * @return the element that must be maximized, might be dockable
	 * itself, not null
	 */
	public Dockable getMaximizingElement( Dockable dockable ){
		return getManager().getGroupBehavior().getGroupElement( getManager(), dockable, getExtendedMode() );
	}

	/**
	 * Gets the element which would be maximized if old is currently
	 * maximized, and dockable is or will not be maximized.
	 * @param old some element
	 * @param dockable some element, might be old
	 * @return the element which would be maximized if dockable is
	 * no longer maximized, can be null
	 */
	public Dockable getMaximizingElement( Dockable old, Dockable dockable ){
		return getManager().getGroupBehavior().getReplaceElement( getManager(), old, dockable, getExtendedMode() );
	}

	protected void applyStarting( LocationModeEvent event ){
		List runs = new ArrayList();

		for( MaximizedModeArea area : this ){
			Runnable run = area.onApply( event );
			if( run != null ){
				runs.add( run );
			}
		}

		Dockable dockable = event.getDockable();

		final MaximizedModeArea maxiarea = getMaximizeArea( dockable );
		if( maxiarea == null )
			return;

		Dockable[] maximizedNow = maxiarea.getMaximized();
		if( maximizedNow == null )
			return;

		Dockable maximized = null;
		
		for( int i = 0; i < maximizedNow.length; i++ ){
			if( DockUtilities.isAncestor( maximizedNow[i], dockable  )){
				maximized = getMaximizingElement( maximizedNow[i], dockable );
				break;
			}
		}
		
		Runnable run = maxiarea.onApply( event, maximized );
		if( run != null ){
			runs.add( run );
		}
		if( !runs.isEmpty() ){
			event.setClientObject( listener, runs );	
		}
	}

	@SuppressWarnings("unchecked")
	protected void applyDone( LocationModeEvent event ){
		List runs = (List)event.getClientObject( listener );
		if( runs != null ){
			for( Runnable run : runs ){
				run.run();
			}
		}
	}

	public ModeSettingFactory getSettingFactory(){
		return MaximizedModeSetting.FACTORY;
	}

	public void writeSetting( ModeSetting setting ){
		if( setting instanceof MaximizedModeSetting ){
			MaximizedModeSetting modeSetting = (MaximizedModeSetting)setting;
			modeSetting.setLastMaximizedLocation( lastMaximizedLocation );
			modeSetting.setLastMaximizedMode( lastMaximizedMode );
		}
	}

	public void readSetting( ModeSetting setting ){
		if( setting instanceof MaximizedModeSetting ){
			MaximizedModeSetting modeSetting = (MaximizedModeSetting)setting;
			lastMaximizedLocation = new HashMap( modeSetting.getLastMaximizedLocation() );
			lastMaximizedMode = new HashMap( modeSetting.getLastMaximizedMode() );
		}
	}


	/**
	 * Invoked whenever a key is pressed, released or typed.
	 * @param dockable the element to which the event belongs
	 * @param event the event
	 * @return true if the event has been processed, false
	 * if the event was not used up.
	 */
	protected boolean process( Dockable dockable, KeyEvent event ){
		KeyStroke stroke = KeyStroke.getKeyStrokeForEvent( event );
		if( stroke.equals( keyStrokeMaximizeChange.getValue() )){
			return switchMode( dockable );
		}

		return false;
	}

	/**
	 * Tries to switch the current mode of dockable to or from
	 * the maximized mode.
	 * @param dockable the element whose mode is to be changed
	 * @return whether the operation was successful
	 */
	public boolean switchMode( Dockable dockable ){
		LocationModeManager manager = getManager();
		LocationMode current = manager.getCurrentMode( dockable );
		if( current == this ){
			LocationMode mode = manager.getPreviousMode( dockable );
			if( mode != null ){
				if( manager.isModeAvailable( dockable, mode.getExtendedMode() )){
					manager.setMode( dockable, mode.getExtendedMode() );
					manager.ensureValidLocation( dockable );
					return true;
				}
			}
		}
		else{
			if( manager.isModeAvailable( dockable, getExtendedMode() )){
				manager.setMode( dockable, getExtendedMode() );
				manager.ensureValidLocation( dockable );
				return true;
			}
		}
		return false;
	}

	
	/**
	 * A hook recording key-events for a specific {@link Dockable}. The hook will remove
	 * itself from the {@link Dockable} automatically once the element is removed from
	 * the controller.
	 * @author Benjamin Sigg
	 */
	private class KeyHook extends DockRegisterAdapter implements KeyboardListener{
		/** the Dockable which is observed by this hook */
		private Dockable dockable;

		/** the controller on which this hook has registered its listeners */
		private DockController controller;

		/**
		 * Creates a new hook
		 * @param dockable the element which will be observed until it is removed
		 * from the {@link DockController}.
		 */
		public KeyHook( Dockable dockable ){
			this.dockable = dockable;
			controller = getController();
			controller.getKeyboardController().addListener( this );
			controller.getRegister().addDockRegisterListener( this );
			hooks.add( this );
		}

		@Override
		public void dockableUnregistered( DockController controller, Dockable dockable ) {
			if( this.dockable == dockable ){
				destroy( true );
			}
		}

		/**
		 * Removes this hook from the controller
		 * @param complete whether to remove this from {@link MaximizedMode#hooks}
		 */
		public void destroy( boolean complete ){
			controller.getKeyboardController().removeListener( this );
			controller.getRegister().removeDockRegisterListener( this );
			if( complete ){
				hooks.remove( this );
			}
		}

		public DockElement getTreeLocation() {
			return dockable;
		}

		public boolean keyPressed( DockElement element, KeyEvent event ) {
			return process( dockable, event );
		}

		public boolean keyReleased( DockElement element, KeyEvent event ) {
			return process( dockable, event );
		}

		public boolean keyTyped( DockElement element, KeyEvent event ) {
			return process( dockable, event );
		}
	}

	/**
	 * A listener that adds itself to all {@link LocationMode}s a {@link LocationModeManager} has.
	 * Calls to the {@link Mode#apply(Dockable, Object, AffectedSet) apply} method is forwarded
	 * to the enclosing {@link MaximizedMode}.
	 * @author Benjamin Sigg
	 */
	private class Listener implements ModeManagerListener, LocationModeListener {
		/**
		 * Removes this listener from oldManager and adds this to newManager.
		 * @param oldManager the old manager, can be null
		 * @param newManager the new manager, can be null
		 */
		public void replaceManager( LocationModeManager oldManager, LocationModeManager newManager ){
			if( oldManager != null ){
				oldManager.removeModeManagerListener( this );

				for( LocationMode mode : oldManager.modes() ){
					modeRemoved( oldManager, mode );
				}
			}

			if( newManager != null ){
				newManager.addModeManagerListener( this );

				for( LocationMode mode : newManager.modes() ){
					modeAdded( newManager, mode );
				}

				for( Dockable dockable : newManager.listDockables() ){
					new KeyHook( dockable );
				}
			}
		}

		public void dockableAdded( ModeManager manager, Dockable dockable ){
			new KeyHook( dockable );
		}

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

		public void modeAdded( ModeManager manager, LocationMode mode ){
			if( mode != MaximizedMode.this ){
				mode.addLocationModeListener( this );
			}
		}

		public void modeChanged( ModeManager manager, Dockable dockable, LocationMode oldMode, LocationMode newMode ){
			// ignore
		}

		public void modeRemoved( ModeManager manager, LocationMode mode ){
			mode.removeLocationModeListener( this );
		}

		public void applyDone( LocationModeEvent event ){
			MaximizedMode.this.applyDone( event );
		}

		public void applyStarting( LocationModeEvent event ){
			MaximizedMode.this.applyStarting( event );
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy