/* * 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 * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.common.mode.station; import java.util.ArrayList; import java.util.HashSet; 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.SplitDockStation; import bibliothek.gui.dock.common.CLocation; import bibliothek.gui.dock.common.CStation; import bibliothek.gui.dock.common.group.CGroupMovement; import bibliothek.gui.dock.common.intern.CommonDockable; import bibliothek.gui.dock.common.intern.station.CSplitDockStation; import bibliothek.gui.dock.common.location.CMaximizedLocation; import bibliothek.gui.dock.common.mode.CLocationMode; import bibliothek.gui.dock.common.mode.CLocationModeManager; import bibliothek.gui.dock.common.mode.CMaximizedModeArea; import bibliothek.gui.dock.common.mode.CNormalModeArea; import bibliothek.gui.dock.common.mode.ExtendedMode; import bibliothek.gui.dock.common.util.CDockUtilities; import bibliothek.gui.dock.control.relocator.DockRelocatorEvent; import bibliothek.gui.dock.control.relocator.VetoableDockRelocatorAdapter; import bibliothek.gui.dock.control.relocator.VetoableDockRelocatorListener; import bibliothek.gui.dock.event.SplitDockListener; import bibliothek.gui.dock.facile.mode.Location; import bibliothek.gui.dock.facile.mode.LocationMode; import bibliothek.gui.dock.facile.mode.LocationModeEvent; import bibliothek.gui.dock.facile.mode.MaximizedMode; import bibliothek.gui.dock.facile.mode.MaximizedModeArea; import bibliothek.gui.dock.facile.mode.ModeArea; import bibliothek.gui.dock.facile.mode.ModeAreaListener; import bibliothek.gui.dock.facile.mode.NormalModeArea; import bibliothek.gui.dock.layout.DockableProperty; import bibliothek.gui.dock.station.split.DockableSplitDockTree; import bibliothek.gui.dock.station.split.SplitDockFullScreenProperty; import bibliothek.gui.dock.support.mode.AffectedSet; import bibliothek.gui.dock.support.mode.AffectingRunnable; import bibliothek.gui.dock.util.DockUtilities; import bibliothek.util.Path; /** * Combination of {@link CMaximizedModeArea}, {@link CNormalModeArea} and a * {@link SplitDockStation}. * @author Benjamin Sigg */ public class CSplitDockStationHandle{ /** the station which is handled by this handle */ private CStation<CSplitDockStation> station; /** normal-mode */ private Normal normal = new Normal(); /** maximized-mode */ private Maximal maximal = new Maximal(); /** the mode which is accessing this handler */ private LocationMode normalMode; /** the mode which is accessing this handler */ private MaximizedMode<?> maximizedMode; /** the manager in whose realm this handle is used */ private CLocationModeManager manager; /** the listeners added to this {@link ModeArea} */ private List<ModeAreaListenerWrapper> listeners = new ArrayList<ModeAreaListenerWrapper>(); private SplitDockListener fullScreenListener = new SplitDockListener() { public void fullScreenDockableChanged( SplitDockStation station, Dockable oldFullScreen, Dockable newFullScreen ){ Set<Dockable> affected = new HashSet<Dockable>(); if( oldFullScreen != null ) affected.add( oldFullScreen ); if( newFullScreen != null ) affected.add( newFullScreen ); ModeAreaListenerWrapper[] array = listeners.toArray( new ModeAreaListenerWrapper[ listeners.size() ] ); for( ModeAreaListenerWrapper listener : array ){ listener.fire( affected ); } } }; /** * This listener calls {@link MaximizedMode#unmaximize(Dockable, AffectedSet)} if some element is dropped * onto this station. */ private VetoableDockRelocatorListener relocatorListener = new VetoableDockRelocatorAdapter() { @Override public void dropped( DockRelocatorEvent event ){ if( !event.isMove() ){ MaximizedModeArea next = maximizedMode.getNextMaximizeArea( event.getTarget() ); if( next == maximal ){ manager.runTransaction( new AffectingRunnable() { public void run( AffectedSet set ){ maximizedMode.unmaximize( (DockStation)getStation(), set ); } }); } } } }; /** * Creates a new handle. * @param station the station to handle * @param manager the manager in whose realm this handle is used */ public CSplitDockStationHandle( CStation<CSplitDockStation> station, CLocationModeManager manager ){ this.station = station; this.manager = manager; } /** * Adds <code>listener</code> to this handle, the listener will be invoked if the current * fullscreen-Dockable of the {@link SplitDockStation} changed. * @param listener the new listener */ protected void add( ModeAreaListenerWrapper listener ){ if( listener == null ) throw new IllegalArgumentException( "listener must not be empty" ); if( listeners.isEmpty() ){ station.getStation().addSplitDockStationListener( fullScreenListener ); } listeners.add( listener ); } /** * Removes <code>listener</code> from this handle. * @param listener the listener to remove */ protected void remove( ModeAreaListenerWrapper listener ){ listeners.remove( listener ); if( listeners.isEmpty() ){ station.getStation().removeSplitDockStationListener( fullScreenListener ); } } /** * Gets the station which is managed by this handle. * @return the station */ public SplitDockStation getStation(){ return station.getStation(); } /** * Gets the {@link CStation} which is managed by this handle. * @return the station */ public CStation<CSplitDockStation> getCStation(){ return station; } /** * Returns this as {@link NormalModeArea} * @return a representation of <code>this</code> */ public CNormalModeArea asNormalModeArea(){ return normal; } /** * Returns this as {@link MaximizedModeArea} * @return a representation of <code>this</code> */ public CMaximizedModeArea asMaximziedModeArea(){ return maximal; } /** * Gets the mode which should be used to unmaximize children. * @return the mode to unmaximize children */ protected LocationMode getNormalMode(){ return normalMode; } private Path normalModeIdentifier(){ return normalExtendedMode().getModeIdentifier(); } private ExtendedMode normalExtendedMode(){ return getNormalMode().getExtendedMode(); } /** * Ensures that <code>dockable</code> is a child of this * station. * @param dockable the element to drop, must not yet be a child of this station * @throws IllegalStateException if <code>dockable</code> already * is a child of this station. */ public void dropAside( Dockable dockable ){ if( dockable.getDockParent() == station.getStation() ) throw new IllegalStateException( "dockable already a child" ); DockableSplitDockTree tree = getStation().createTree(); if( tree.getRoot() == null ) tree.root( dockable ); else{ tree.root( tree.horizontal( tree.put( dockable ), tree.unroot() ) ); } getStation().dropTree( tree, false ); } /** * A wrapper for a {@link ModeAreaListener}. * @author Benjamin Sigg */ protected static class ModeAreaListenerWrapper{ /** the listener */ private ModeAreaListener listener; /** the area */ private ModeArea area; public ModeAreaListenerWrapper( ModeArea area, ModeAreaListener listener ){ this.area = area; this.listener = listener; } /** * Calls {@link ModeAreaListener#internalLocationChange(ModeArea, Set)} with * <code>dockables</code> as set and {@link #area} as area. * @param dockables the set of changed elements */ public void fire( Set<Dockable> dockables ){ listener.internalLocationChange( area, dockables ); } @Override public boolean equals( Object obj ){ if( obj == this ) { return true; } if (obj == null) { return false; } if( this.getClass() == obj.getClass() ){ ModeAreaListenerWrapper other = (ModeAreaListenerWrapper)obj; return other.area.equals( area ) && other.listener.equals( listener ); } return false; } @Override public int hashCode(){ return area.hashCode() ^ listener.hashCode(); } } /** * Represents the {@link SplitDockStation} as {@link CNormalModeArea}. * @author Benjamin Sigg */ protected class Normal implements CNormalModeArea{ public void setMode( LocationMode mode ){ normalMode = mode; } public void addModeAreaListener( ModeAreaListener listener ){ add( new ModeAreaListenerWrapper( this, listener ) ); } public void removeModeAreaListener( ModeAreaListener listener ){ remove( new ModeAreaListenerWrapper( this, listener ) ); } public boolean autoDefaultArea() { return true; } public boolean isLocationRoot(){ return true; } public void setController( DockController controller ){ // ignore } public boolean isNormalModeChild( Dockable dockable ){ if( !isChild( dockable )){ return false; } if( getStation().getFullScreen() == dockable){ return false; } if( !isWorkingAreaValid( dockable )){ return false; } return true; } private boolean isWorkingAreaValid( Dockable dockable ){ if( dockable instanceof CommonDockable ){ CStation<?> workingArea = ((CommonDockable)dockable).getDockable().getWorkingArea(); if( workingArea == null ){ return CDockUtilities.getFirstWorkingArea( station ) == null; } else{ return CDockUtilities.getFirstWorkingArea( station ) == workingArea; } } return true; } public DockableProperty getLocation( Dockable child ){ return DockUtilities.getPropertyChain( getStation(), child ); } public String getUniqueId(){ return station.getUniqueId(); } public boolean isChild( Dockable dockable ){ return dockable.getDockParent() == getStation() && !maximal.isChild( dockable ); } public SplitDockStation getStation(){ return station.getStation(); } public boolean respectWorkingAreas(){ return true; } public boolean setLocation( Dockable dockable, DockableProperty location, AffectedSet set ){ set.add( dockable ); if( dockable.getDockParent() == station.getStation() ){ if( location != null ){ cleanFullscreen( set ); getStation().move( dockable, location ); return true; } } else{ boolean acceptable = DockUtilities.acceptable( getStation(), dockable ); if( acceptable ){ if( dockable.getDockParent() != null ){ dockable.getDockParent().drag( dockable ); } cleanFullscreen( set ); if( location != null ){ if( !getStation().drop( dockable, location )){ location = null; } } if( location == null ){ if( !DockUtilities.isAncestor( station.getStation(), dockable )){ getStation().drop( dockable ); } } return true; } } return false; } private void cleanFullscreen( AffectedSet set ){ Dockable fullscreen = getStation().getFullScreen(); if( fullscreen != null ){ maximal.setMaximized( fullscreen, false, null, set ); } } public CLocation getCLocation( Dockable dockable ){ DockableProperty property = DockUtilities.getPropertyChain( getStation(), dockable ); return station.getStationLocation().expandProperty( station.getStation().getController(), property ); } public CLocation getCLocation( Dockable dockable, Location location ){ DockableProperty property = location.getLocation(); if( property == null ) return station.getStationLocation(); return station.getStationLocation().expandProperty( station.getStation().getController(), property ); } public CLocation getBaseLocation(){ return station.getStationLocation(); } public boolean isWorkingArea(){ return station.isWorkingArea(); } } /** * Represents a {@link SplitDockStation} as a {@link CMaximizedModeArea}. * @author Benjamin Sigg */ protected class Maximal implements CMaximizedModeArea{ /** the controller in whose realm this area works */ private DockController controller; public void addModeAreaListener( ModeAreaListener listener ){ add( new ModeAreaListenerWrapper( this, listener ) ); } public void removeModeAreaListener( ModeAreaListener listener ){ remove( new ModeAreaListenerWrapper( this, listener ) ); } public void setMode( LocationMode mode ){ if( maximizedMode != null && mode != null ) throw new IllegalStateException( "handle already in use" ); maximizedMode = (MaximizedMode<?>)mode; } public void setController( DockController controller ){ if( this.controller != null ){ this.controller.getRelocator().removeVetoableDockRelocatorListener( relocatorListener ); } this.controller = controller; if( controller != null ){ controller.getRelocator().addVetoableDockRelocatorListener( relocatorListener ); } } public DockableProperty getLocation( Dockable child ){ DockableProperty property = DockUtilities.getPropertyChain( getStation(), child ); SplitDockFullScreenProperty result = new SplitDockFullScreenProperty(); result.setSuccessor( property.getSuccessor() ); return result; } public boolean autoDefaultArea() { return true; } public boolean isLocationRoot(){ return true; } public LocationMode getUnmaximizedMode(){ return getNormalMode(); } public void prepareApply( Dockable dockable, AffectedSet affected ){ CLocationMode normal = manager.getMode( normalModeIdentifier() ); if( normal != null ){ manager.apply( dockable, normal, affected, false ); } } public void prepareApply( Dockable dockable, Location history, AffectedSet set ){ boolean remaximize = history != null && history.getLocation() instanceof SplitDockFullScreenProperty; if( !remaximize ){ if( manager.getMode( dockable ) != normalExtendedMode() ){ CLocationMode normal = manager.getMode( normalModeIdentifier() ); if( normal != null ){ CGroupMovement movement = maximizedMode.getManager().getGroupBehavior().prepare( manager, dockable, normal.getExtendedMode() ); if( movement != null ){ manager.apply( dockable, normal.getExtendedMode(), movement ); } } } } } public Runnable onApply( LocationModeEvent event ){ if( event.isDone() ) return null; Location location = event.getLocation(); Dockable dockable = event.getDockable(); DockableProperty property = location == null ? null : location.getLocation(); if( event.getMode().getUniqueIdentifier().equals( normalModeIdentifier() )){ // try to set the mode prematurely if( property != null ){ if( property.getSuccessor() == null ){ CLocationMode last = manager.getCurrentMode( dockable ); CLocationMode secondLast = manager.getPreviousMode( dockable ); if( last != null && secondLast != null ){ if( normalModeIdentifier().equals( secondLast.getUniqueIdentifier() ) && MaximizedMode.IDENTIFIER.equals( last.getUniqueIdentifier() )){ MaximizedModeArea area = maximizedMode.get( location.getRoot() ); if( area == this ){ area.setMaximized( dockable, false, null, event.getAffected() ); event.done(true); return null; } } } } } } // if the element is about to become a child of this station, ensure // this station does not show a maximized element if( location != null && getMaximized() != null ){ Map<ExtendedMode, DockStation> roots = manager.getRepresentations( location.getRoot() ); for( DockStation station : roots.values() ){ if( DockUtilities.isAncestor( getStation(), station )){ maximizedMode.unmaximize( this, event.getAffected() ); break; } } } // if this station currently shows dockable as maximized element, ensure it is no longer maximized if( maximizedMode != null && event.getMode().getUniqueIdentifier().equals( normalModeIdentifier() )){ MaximizedModeArea area = maximizedMode.getMaximizeArea( dockable ); if( area == this ){ maximizedMode.unmaximize( dockable, event.getAffected() ); } } return null; } public Runnable onApply( final LocationModeEvent event, final Dockable replacement ){ if( event.isDone() ) return null; if( !event.getMode().getUniqueIdentifier().equals( normalModeIdentifier() )){ maximizedMode.unmaximize( getStation().getFullScreen(), event.getAffected() ); return new Runnable() { public void run(){ if( replacement != null && replacement.getDockParent() != null ){ maximizedMode.maximize( Maximal.this, replacement, event.getAffected() ); } } }; } return null; } public String getUniqueId(){ return station.getUniqueId(); } public boolean isChild( Dockable dockable ){ return getStation().getFullScreen() == dockable; } public SplitDockStation getStation(){ return station.getStation(); } public boolean respectWorkingAreas(){ return false; } public Dockable[] getMaximized(){ Dockable dockable = getStation().getFullScreen(); if( dockable == null ){ return null; } return new Dockable[]{ dockable }; } public void setMaximized( Dockable dockable, boolean maximized, Location location, AffectedSet set ){ SplitDockStation station = getStation(); if( !maximized ){ if( station.getFullScreen() != null && DockUtilities.isAncestor( station.getFullScreen(), dockable )){ station.setFullScreen( null ); } } else{ DockableProperty property = location == null ? null : location.getLocation(); if( property instanceof SplitDockFullScreenProperty ){ if( getMaximized() != null ){ if( getStation().drop( dockable, property ) ){ return; } } } if( dockable.getDockParent() == station ){ station.setFullScreen( dockable ); } else{ if( dockable.getDockParent() != null ) dockable.getDockParent().drag( dockable ); dropAside( dockable ); station.setFullScreen( dockable ); } } set.add( dockable ); } public boolean isRepresenting( DockStation station ){ return station == CSplitDockStationHandle.this.station.getStation(); } public CLocation getCLocation( Dockable dockable ){ DockableProperty property = DockUtilities.getPropertyChain( getStation(), dockable ); return getCLocation( property ); } public CLocation getCLocation( Dockable dockable, Location location ){ DockableProperty property = location.getLocation(); return getCLocation( property ); } private CLocation getCLocation( DockableProperty property ){ CLocation stationLocation = station.getStationLocation(); CMaximizedLocation result = new CMaximizedLocation( stationLocation.findRoot() ); if( property != null ){ property = property.getSuccessor(); } if( property != null ){ return result.expandProperty( station.getStation().getController(), property ); } else{ return result; } } } }