/* * 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) 2007 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; import java.awt.Window; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.security.KeyStore; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.Icon; import javax.swing.KeyStroke; import bibliothek.extension.gui.dock.theme.eclipse.EclipseTabDockAction; import bibliothek.gui.dock.DockElement; import bibliothek.gui.dock.DockElementRepresentative; import bibliothek.gui.dock.DockFactory; import bibliothek.gui.dock.ScreenDockStation; import bibliothek.gui.dock.action.ActionGuard; import bibliothek.gui.dock.action.DefaultDockActionSource; import bibliothek.gui.dock.action.DockActionIcon; import bibliothek.gui.dock.action.DockActionSource; import bibliothek.gui.dock.action.DockActionText; import bibliothek.gui.dock.action.LocationHint; import bibliothek.gui.dock.action.actions.SimpleButtonAction; import bibliothek.gui.dock.control.DockRegister; import bibliothek.gui.dock.dockable.DefaultDockableFactory; import bibliothek.gui.dock.event.DockFrontendListener; import bibliothek.gui.dock.event.DockRegisterAdapter; import bibliothek.gui.dock.event.VetoableDockFrontendListener; import bibliothek.gui.dock.frontend.DefaultFrontendPerspectiveCache; import bibliothek.gui.dock.frontend.DefaultLayoutChangeStrategy; import bibliothek.gui.dock.frontend.DockFrontendExtension; import bibliothek.gui.dock.frontend.DockFrontendInternals; import bibliothek.gui.dock.frontend.DockFrontendPerspective; import bibliothek.gui.dock.frontend.FrontendEntry; import bibliothek.gui.dock.frontend.FrontendPerspectiveCache; import bibliothek.gui.dock.frontend.LayoutChangeStrategy; import bibliothek.gui.dock.frontend.MissingDockableStrategy; import bibliothek.gui.dock.frontend.Setting; import bibliothek.gui.dock.frontend.SettingsBlop; import bibliothek.gui.dock.frontend.VetoManager; import bibliothek.gui.dock.layout.AdjacentDockFactory; import bibliothek.gui.dock.layout.DockLayoutComposition; import bibliothek.gui.dock.layout.DockLayoutInfo; import bibliothek.gui.dock.layout.DockSituation; import bibliothek.gui.dock.layout.DockSituationIgnore; import bibliothek.gui.dock.layout.DockableProperty; import bibliothek.gui.dock.layout.DockablePropertyFactory; import bibliothek.gui.dock.layout.PredefinedDockSituation; import bibliothek.gui.dock.layout.PropertyTransformer; import bibliothek.gui.dock.layout.location.AsideAnswer; import bibliothek.gui.dock.layout.location.AsideRequest; import bibliothek.gui.dock.layout.location.AsideRequestFactory; import bibliothek.gui.dock.perspective.Perspective; import bibliothek.gui.dock.perspective.PerspectiveElement; import bibliothek.gui.dock.station.flap.FlapDockPropertyFactory; import bibliothek.gui.dock.station.flap.FlapDockStationFactory; import bibliothek.gui.dock.station.screen.ScreenDockPropertyFactory; import bibliothek.gui.dock.station.screen.ScreenDockStationFactory; import bibliothek.gui.dock.station.split.SplitDockPropertyFactory; import bibliothek.gui.dock.station.split.SplitDockStationFactory; import bibliothek.gui.dock.station.stack.StackDockPropertyFactory; import bibliothek.gui.dock.station.stack.StackDockStationFactory; import bibliothek.gui.dock.util.DirectWindowProvider; import bibliothek.gui.dock.util.DockProperties; import bibliothek.gui.dock.util.DockUtilities; import bibliothek.gui.dock.util.NullWindowProvider; import bibliothek.gui.dock.util.PropertyKey; import bibliothek.gui.dock.util.PropertyValue; import bibliothek.gui.dock.util.WindowProvider; import bibliothek.gui.dock.util.extension.ExtensionName; import bibliothek.util.Path; import bibliothek.util.Todo; import bibliothek.util.Todo.Compatibility; import bibliothek.util.Todo.Priority; import bibliothek.util.Version; import bibliothek.util.xml.XAttribute; import bibliothek.util.xml.XElement; import bibliothek.util.xml.XException; /** * A DockFrontend provides some methods to handle the storage of various layouts. * The frontend can save the current layout (the location of all Dockables) and * later restore it. Each set of properties is stored in a {@link Setting}. Subclasses * might override the following methods to store additional information: * <ul> * <li>{@link #createSetting()}</li> * <li>{@link #getSetting(boolean)} and {@link #setSetting(Setting, boolean)}</li> * <li>{@link #write(Setting, boolean, DataOutputStream)} and {@link #read(boolean, DataInputStream)} or * {@link Setting#write(DockSituation, PropertyTransformer, boolean, DataOutputStream)} and * {@link Setting#read(DockSituation, PropertyTransformer, boolean, DataInputStream)}</li> * <li>{@link #writeXML(Setting, boolean, XElement)} and {@link #readXML(boolean, XElement)} or * {@link Setting#writeXML(DockSituation, PropertyTransformer, boolean, XElement)} and * {@link Setting#readXML(DockSituation, PropertyTransformer, boolean, XElement)} </li> * </ul><br> * The frontend has a list of Dockables. It assumes that these Dockables never * change. The frontend can add a "close"-button to these Dockables. The location * of these Dockables is stored as well. Dockables which are not {@link #addDockable(String, Dockable) added} * to this frontend, are just ignored.<br> * <b>Note:</b> Clients must provide a set of root stations * ({@link #addRoot(DockStation, String) addRoot}). The frontend will only * store the locations of children of these roots. The frontend adds these * roots also to its {@link DockController controller}, but the frontend does * not observe the controller, and so all changes must be applied directly * on the frontend (on the other hand, clients may use more than one frontend).<br> * Clients must also provide some {@link #registerFactory(DockFactory) factories} * to allow the storage of their elements. The default-factories are already * installed.<br> * <b>Note:</b> Clients may use the <code>Common project</code> instead of <code>DockFrontend</code>. * The <code>Common project</code> offers way more features than <code>DockFrontend</code> and is even * easier to handle. * @author Benjamin Sigg */ public class DockFrontend { /** This {@link KeyStore} calls {@link #hide(Dockable)} for the currently selected {@link Dockable}. */ public static final PropertyKey<KeyStroke> HIDE_ACCELERATOR = new PropertyKey<KeyStroke>( "frontend hide accelerator" ); /** prefix used for {@link Dockable}s when creating a new {@link PredefinedDockSituation} */ public static final String DOCKABLE_KEY_PREFIX = "dockable"; /** prefix used for {@link DockStation root}s when creating a new {@link PredefinedDockSituation} */ public static final String ROOT_KEY_PREFIX = "root"; /** Name of an {@link DockFrontendExtension} for the {@link DockFrontend} */ public static final Path FRONTEND_EXTENSION = new Path( "dock", "DockFrontendExtension" ); /** All the extensions of this frontend */ private List<DockFrontendExtension> extensions; /** The controller where roots are added */ private DockController controller; /** An action and actionguard which hides Dockables */ private Hider hider; /** The locations of the known Dockables */ private Map<String, DockInfo> dockables = new HashMap<String, DockInfo>(); /** the identifiers of the {@link DockInfo}s which should stay around even if their dockable is removed */ private Set<String> empty = new HashSet<String>(); /** The station which is used to add Dockables if no other station is explicitly requested */ private DockStation defaultStation; /** The roots of this frontend */ private Map<String, RootInfo> roots = new HashMap<String, RootInfo>(); /** A set of factories needed to store Dockables */ private Set<DockFactory<?,?,?>> dockFactories = new HashSet<DockFactory<?,?,?>>(); /** A set of factories needed to read Dockables that are missing in the cache */ private Set<DockFactory<? extends Dockable,?,?>> backupDockFactories = new HashSet<DockFactory<? extends Dockable,?,?>>(); /** A set of factories needed to store additional information about Dockables */ private Set<AdjacentDockFactory<?>> adjacentDockFactories = new HashSet<AdjacentDockFactory<?>>(); /** A set of factories needed to store {@link DockableProperty properties} */ private Set<DockablePropertyFactory> propertyFactories = new HashSet<DockablePropertyFactory>(); /** The name of the setting which is currently loaded */ private String currentSetting; /** A map of all known settings */ private Map<String, Setting> settings = new HashMap<String, Setting>(); /** A list of observers */ private List<DockFrontendListener> listeners = new ArrayList<DockFrontendListener>(); /** handles all the events regarding {@link VetoableDockFrontendListener} */ private VetoManager veto; /** A filter for elements which should not be changed when writing or reading a normal setting */ private DockSituationIgnore ignoreForEntry; /** * A filter for elements which should not be changed when writing or reading the * final setting during the startup or shutdown of the application. */ private DockSituationIgnore ignoreForFinal; /** tells what to do with the location information of missing {@link Dockable}s */ private MissingDockableStrategy missingDockable = MissingDockableStrategy.DISCARD_ALL; /** algorithm for changing the layout of this frontend */ private LayoutChangeStrategy layoutChangeStrategy = new DefaultLayoutChangeStrategy(); /** * Tells whether to show the hide-action on hideable dockables or not */ private boolean showHideAction = true; /** the default value for {@link DockInfo#entryLayout} */ private boolean defaultEntryLayout = true; /** the default value for {@link DockInfo#hideActionVisible} */ private boolean defaultHideable = false; /** * Whether the {@link DockFrontendListener} and the {@link VetoableDockFrontendListener} * should be called automatically when triggered by a {@link DockRegister}-event or not. * If this property is 0 then the listeners are armed. */ private int onAutoFire = 0; /** * The last {@link Setting} that was {@link #setSetting(Setting, boolean) applied} * with the entry flag set to <code>false</code>. Can be <code>null</code>. */ private Setting lastAppliedFullSetting = null; /** * The last {@link Setting} that was {@link #setSetting(Setting, boolean) applied} * with the entry flag set to <code>true</code>. Can be <code>null</code>. */ private Setting lastAppliedEntrySetting = null; /** * Tells whether the layout of a {@link Dockable} that is being {@link #addDockable(String, Dockable) added} is * currently read and applied to said {@link Dockable}. If so, no other layout will be applied automatically when * loading a child of the new element. */ @Todo(compatibility=Compatibility.COMPATIBLE, priority=Priority.MINOR, target=Todo.Version.VERSION_1_1_3, description="This really is a workaround preventing loading a layout while loading another layout. The " + "better solution would be to not apply layouts to children that already have a layout stored " + "in the DockInfo-object") private boolean readingOldLayoutInformation = false; /** * Constructs a new frontend, creates a new controller. */ public DockFrontend(){ this( new DockController(), new NullWindowProvider() ); } /** * Constructs a new frontend, creates a new controller. Registers a * {@link ScreenDockStationFactory}, which can only be created if the owner * of the dialogs is known. * @param owner the owner of the dialogs of a {@link ScreenDockStationFactory}, * may be <code>null</code> */ public DockFrontend( Window owner ){ this( new DockController(), owner == null ? new NullWindowProvider() : new DirectWindowProvider( owner ) ); } /** * Constructs a new frontend, creates a new controller. Registers a * {@link ScreenDockStationFactory}, which can only be created if the owner * of the dialogs is known. * @param owner the owner of the dialogs of a {@link ScreenDockStationFactory}, * may be <code>null</code> */ public DockFrontend( WindowProvider owner ){ this( new DockController(), owner ); } /** * Constructs a new frontend. * @param controller the controller used to store root stations */ public DockFrontend( DockController controller ){ this( controller, new NullWindowProvider() ); } /** * Constructs a new frontend, tries to set up a {@link ScreenDockStationFactory} * and sets the root window of <code>controller</code> to <code>owner</code>. * @param controller the controller used to store the root stations * @param owner the owner of the dialog of a {@link ScreenDockStation}, * may be <code>null</code> */ public DockFrontend( DockController controller, Window owner ){ this( controller, owner == null ? new NullWindowProvider() : new DirectWindowProvider( owner )); } /** * Constructs a new frontend, tries to set up a {@link ScreenDockStationFactory} * and sets the root window of <code>controller</code> to <code>owner</code>. * @param controller the controller used to store the root stations * @param owner the owner of the dialog of a {@link ScreenDockStation}, * may be <code>null</code> */ public DockFrontend( DockController controller, WindowProvider owner ){ if( controller == null ) throw new IllegalArgumentException( "controller must not be null" ); this.controller = controller; controller.setRootWindowProvider( owner ); veto = new VetoManager( this ); hider = createHider(); controller.addActionGuard( hider ); registerFactory( new DefaultDockableFactory() ); registerFactory( new SplitDockStationFactory() ); registerFactory( new StackDockStationFactory() ); registerFactory( new FlapDockStationFactory() ); registerFactory( new ScreenDockStationFactory( controller.getRootWindowProvider() )); registerFactory( new SplitDockPropertyFactory() ); registerFactory( new StackDockPropertyFactory() ); registerFactory( new FlapDockPropertyFactory() ); registerFactory( new ScreenDockPropertyFactory() ); controller.getRegister().addDockRegisterListener( new DockRegisterAdapter(){ @Override public void dockableRegistered( DockController controller, Dockable dockable ) { if( onAutoFire == 0 ){ fireShown( dockable ); } } @Override public void dockableUnregistered( DockController controller, Dockable dockable ) { if( onAutoFire == 0 ){ fireHidden( dockable ); } } }); extensions = controller.getExtensions().load( new ExtensionName<DockFrontendExtension>( FRONTEND_EXTENSION, DockFrontendExtension.class ) ); for( DockFrontendExtension extension : extensions ){ extension.install( this ); } } /** * Gets the controller which is used by this frontend. * @return the controller */ public DockController getController() { return controller; } /** * Sets the window which is used as root for any dialog, can be <code>null</code>. * @param owner the owning window * @see DockController#setRootWindowProvider(WindowProvider) */ public void setOwner( WindowProvider owner ){ controller.setRootWindowProvider( owner ); } /** * Gets the current provider for the root window. Note that this might not * be the same as given to {@link #setOwner(WindowProvider)}, however it * will return the same value. * @return the provider, never <code>null</code> */ public WindowProvider getOwner(){ return controller.getRootWindowProvider(); } /** * Destroys this {@link DockFrontend}, it will no longer be useful but * can be removed by the garbage collector. */ public void kill(){ controller.kill(); for( DockFrontendExtension extension : extensions ){ extension.uninstall( this ); } } /** * Gets the list of {@link Dockable Dockables} which are added to this frontend. * @return the Dockables * @deprecated please use {@link #listDockables()} */ @Deprecated public Collection<Dockable> getDockables(){ return listDockables(); } /** * Adds a listener to this frontend. The listener will receive notifications * if anything changes on this frontend. * @param listener the observer */ public void addFrontendListener( DockFrontendListener listener ){ listeners.add( listener ); } /** * Removes an earlier added listener from this frontend. * @param listener the observer which will be removed */ public void removeFrontendListener( DockFrontendListener listener ){ listeners.remove( listener ); } /** * Adds <code>listener</code> to this frontend. The listener will be notified * when a {@link Dockable} will be or is closed.<br> * Note: the listener is only guaranteed to receive events for {@link Dockable}s that are * {@link #addDockable(String, Dockable) known} to this {@link DockFrontend}. * It may or may not receive events for other {@link Dockable}s. * @param listener the new listener */ public void addVetoableListener( VetoableDockFrontendListener listener ){ veto.addVetoableListener( listener ); } /** * Removes <code>listener</code> from this frontend. * @param listener the listener to remove */ public void removeVetoableListener( VetoableDockFrontendListener listener ){ veto.removeVetoableListener( listener ); } /** * Registers a factory to write and read {@link Dockable Dockables} and * {@link DockStation DockStations}. * @param factory the new factory */ public void registerFactory( DockFactory<?,?,?> factory ){ if( factory == null ) throw new IllegalArgumentException( "factory must not be null" ); dockFactories.add( factory ); fillMissing( factory ); } /** * Searches for a {@link DockFactory} which id <code>factoryId</code>. This method checks * all the factories that were added by {@link #registerFactory(DockFactory)}. * @param factoryId the unique identifier of the factory * @return the factory or <code>null</code> */ public DockFactory<?, ?, ?> getDockFactory( String factoryId ){ for( DockFactory<?, ?, ?> factory : dockFactories ){ if( factory.getID().equals( factoryId )){ return factory; } } return null; } /** * Registers a factory to write and read {@link Dockable}s and {@link DockStation}s. * @param factory the new factory * @param backup if <code>true</code>, then <code>factory</code> is registered * as {@link #registerBackupFactory(DockFactory) backup factory} as well. */ public void registerFactory( DockFactory<? extends Dockable,?,?> factory, boolean backup ){ if( factory == null ) throw new IllegalArgumentException( "factory must not be null" ); dockFactories.add( factory ); if( backup ) backupDockFactories.add( factory ); fillMissing( factory ); } /** * Register a backup factory. A backup factory is used to create a {@link Dockable} * that is expected to be in the cache, but is missing. The new {@link Dockable} * is automatically added to this frontend.<br> * The difference between a normal and a backup factory is: a normal factory will just create * the {@link Dockable}, a backup factory will also install the {@link Dockable} on the * {@link DockFrontend} using the identifier the element had when it was saved (this happens automatically). * @param factory a new factory */ public void registerBackupFactory( DockFactory<? extends Dockable,?,?> factory ){ if( factory == null ) throw new IllegalArgumentException( "factory must not be null" ); backupDockFactories.add( factory ); fillMissing( factory ); } /** * Registers a factory that stores additional information for a set of * {@link Dockable}s. * @param factory the additional factory, not <code>null</code> */ public void registerAdjacentFactory( AdjacentDockFactory<?> factory ){ if( factory == null ) throw new IllegalArgumentException( "factory must not be null" ); adjacentDockFactories.add( factory ); } /** * Removes a factory from this frontend. This method does not remove * backup factories. * @param factory the factory to remove * @see #unregisterBackupFactory(DockFactory) */ public void unregisterFactory( DockFactory<?,?,?> factory ){ dockFactories.remove( factory ); } /** * Removes a backup factory from this frontend. * @param factory the factory to remove */ public void unregisterBackupFactory( DockFactory<?,?,?> factory ){ backupDockFactories.remove( factory ); } /** * Removes an additional factory from this frontend. * @param factory the factory to remove */ public void unregisterAdjacentFactory( AdjacentDockFactory<?> factory ){ adjacentDockFactories.remove( factory ); } /** * Registers a factory to write and read properties. Clients only need this * method if they provide a new type of {@link DockStation}. * @param factory the new factory */ public void registerFactory( DockablePropertyFactory factory ){ if( factory == null ) throw new IllegalArgumentException( "factory must not be null" ); propertyFactories.add( factory ); } /** * Adds a Dockable to this frontend. The frontend provides a "close"-button * for <code>dockable</code>. The frontend also assumes that <code>dockable</code> * can be reused when reading a setting. That means, that the factory which * matches the key of <code>dockable</code> does not create a new instance * when reading the preferences of <code>dockable</code>. You should note that * the frontend does not support {@link Dockable Dockables} whose lifespan * ends when they are made invisible. * @param id the unique name of the Dockable * @param dockable the new Dockable * @throws IllegalArgumentException if either of <code>dockable</code> or * <code>id</code> is <code>null</code>, or if <code>id</code> is not * unique. */ public void addDockable( String id, Dockable dockable ){ if( dockable == null ) throw new IllegalArgumentException( "Dockable must not be null" ); if( id == null ) throw new IllegalArgumentException( "name must not be null" ); DockInfo info = dockables.get( id ); if( info != null ){ if( info.getDockable() == null ){ info.setDockable( dockable ); info.updateHideAction(); } else throw new IllegalArgumentException( "There is already a dockable registered with name " + id ); } else{ info = new DockInfo( dockable, id ); dockables.put( id, info ); } DockLayoutComposition layout = info.getLayout(); if( layout != null && !readingOldLayoutInformation && layoutChangeStrategy.shouldUpdateLayoutOnAdd(dockable) ){ try{ readingOldLayoutInformation = true; DockSituation situation = layoutChangeStrategy.createSituation( new Internals(), false ); layout = situation.fillMissing( layout ); situation.convert( layout ); info.setLayout( null ); } catch( IOException ex ){ throw new IllegalArgumentException( "Cannot read old layout information", ex ); } finally{ readingOldLayoutInformation = false; } } fireAdded( dockable ); } /** * Sets the strategy how to deal with location information of {@link Dockable}s * which are missing and which are not marked as {@link #addEmpty(String) empty}.<br> * If information passes the strategy, then a new {@link #addEmpty(String) empty info} * will be added to store it. Note that setting the strategy does only * affect future actions, information already stored or discarded will not * be rescued or thrown away. * @param missingDockable the new strategy, <code>null</code> is valid and * will force this frontend to discard any information. */ public void setMissingDockableStrategy( MissingDockableStrategy missingDockable ) { if( missingDockable == null ) this.missingDockable = MissingDockableStrategy.DISCARD_ALL; else this.missingDockable = missingDockable; } /** * Gets the strategy that is applied for location information of * missing {@link Dockable}s. * @return the strategy, never <code>null</code> * @see #setMissingDockableStrategy(MissingDockableStrategy) */ public MissingDockableStrategy getMissingDockable() { return missingDockable; } /** * Sets the strategy this {@link DockFrontend} should use to read {@link Setting}s.<br> * <b>WARNING: </b> strategies may leave a trail of data, some even stored persistently. This * method should only be called once: directly after this {@link DockFrontend} has been created. Clients * should always set the same kind of strategy. * @param strategy the new strategy, not <code>null</code> */ public void setLayoutChangeStrategy( LayoutChangeStrategy strategy ){ if( strategy == null ) throw new IllegalArgumentException( "strategy must not be null" ); this.layoutChangeStrategy = strategy; } /** * Gets the current strategy that is used to read {@link Setting}s by this {@link DockFrontend}. * @return the strategy, not <code>null</code> */ public LayoutChangeStrategy getLayoutChangeStrategy(){ return layoutChangeStrategy; } /** * Creates a new {@link PropertyTransformer} that can be used to read and write * {@link DockableProperty}s that are associated with this {@link DockFrontend}. * @return the new transformer, created by the current {@link #setLayoutChangeStrategy(LayoutChangeStrategy) LayoutChangeStrategy} */ public PropertyTransformer createPropertyTransformer(){ return layoutChangeStrategy.createTransformer( new Internals() ); } /** * Gets an independent map containing all Dockables registered to this * frontend. * @return the map of Dockables */ public Map<String, Dockable> getNamedDockables(){ Map<String, Dockable> result = new HashMap<String, Dockable>(); for( Map.Entry<String, DockInfo> entry : dockables.entrySet() ){ if( entry.getValue().getDockable() != null ) result.put( entry.getKey(), entry.getValue().getDockable() ); } return result; } /** * Gets the {@link Dockable} which was {@link #addDockable(String, Dockable) added} * to this frontend with the name <code>name</code>. * @param name the name of a {@link Dockable} * @return the element or <code>null</code> */ public Dockable getDockable( String name ){ DockInfo info = getInfo( name ); return info == null ? null : info.dockable; } /** * Searches the name of <code>dockable</code> as it was given to * {@link #addDockable(String, Dockable)}. * @param dockable some element whose name is searched * @return the name or <code>null</code> */ public String getNameOf( Dockable dockable ){ if( dockable == null ) throw new NullPointerException( "dockable is null" ); for( Map.Entry<String, DockInfo> entry : dockables.entrySet() ){ if( entry.getValue().dockable == dockable ) return entry.getKey(); } return null; } /** * Adds a root to this frontend. Only {@link Dockable Dockables} which are * children of a root can be stored. The frontend forwards the roots to * its {@link #getController() controller} * (through the {@link DockController#add(DockStation) add}-method). Note * that the frontend does not observe its controller and therefore does not * know whether there are other roots registered at the controller.<br> * Clients should also provide a {@link #setDefaultStation(DockStation) default station}. * @param id the unique name of the station * @param station the new station * @throws IllegalArgumentException if <code>station</code> or <code>name</code> * is <code>null</code>, or if <code>name</code> is not unique. */ public void addRoot( String id, DockStation station ){ addRoot( station, id ); } /** * Adds a root to this frontend. Only {@link Dockable Dockables} which are * children of a root can be stored. The frontend forwards the roots to * its {@link #getController() controller} * (through the {@link DockController#add(DockStation) add}-method). Note * that the frontend does not observe its controller and therefore does not * know whether there are other roots registered at the controller.<br> * Clients should also provide a {@link #setDefaultStation(DockStation) default station}. * @param station the new station * @param name the unique name of the station * @throws IllegalArgumentException if <code>station</code> or <code>name</code> * is <code>null</code>, or if <code>name</code> is not unique. * @deprecated replaced by {@link #addRoot(String, DockStation)}, since * <code>name</code> is used as key in a map it should come first */ @Deprecated public void addRoot( DockStation station, String name ){ if( station == null ) throw new IllegalArgumentException( "Stations must not be null" ); if( name == null ) throw new IllegalArgumentException( "name must not be null" ); if( roots.containsKey( name )) throw new IllegalArgumentException( "There is already a station registered with name " + name ); controller.add( station ); roots.put( name, new RootInfo( station, name )); } /** * Gets the root with the designated name. * @param name the name of the root * @return the station or <code>null</code> */ public DockStation getRoot( String name ){ RootInfo info = roots.get( name ); if( info == null ) return null; return info.getStation(); } /** * Gets the keys for all the root {@link DockStation}s known to this frontend. * @return the keys of all root stations */ public String[] getRootNames(){ return roots.keySet().toArray( new String[ roots.size() ] ); } /** * Gets a modifiable array containing all {@link DockStation}s which are * registered as root. * @return the list of roots */ public DockStation[] getRoots(){ DockStation[] stations = new DockStation[ roots.size() ]; int i = 0; for( RootInfo info : roots.values() ){ stations[i++] = info.station; } return stations; } /** * Adds a representative for some {@link DockElement}. Note that no two * representatives can have the same * {@link DockElementRepresentative#getComponent() component}. If two have * the same, then the second one overrides the first one. * @param representative the new representative * @see DockController#addRepresentative(DockElementRepresentative) */ public void addRepresentative( DockElementRepresentative representative ){ controller.addRepresentative( representative ); } /** * Removes <code>representative</code> from this frontend. * @param representative the element to remove * @see DockController#removeRepresentative(DockElementRepresentative) */ public void removeRepresentative( DockElementRepresentative representative ){ controller.removeRepresentative( representative ); } /** * Sets the default station of this frontend. The default station is needed * to add {@link Dockable Dockables} whose location could not be stored * earlier or whose location has become invalid. * @param defaultStation the default station, can be <code>null</code> */ public void setDefaultStation( DockStation defaultStation ) { if( defaultStation != null && getRoot( defaultStation ) == null ) throw new IllegalArgumentException( "The default station must be registered as root" ); this.defaultStation = defaultStation; } /** * Gets the default station of this frontend. This is either the value of * {@link #setDefaultStation(DockStation)} or a root picked at random. * @return the station, might be <code>null</code> */ public DockStation getDefaultStation() { if( defaultStation != null ) return defaultStation; Iterator<RootInfo> infos = roots.values().iterator(); if( infos.hasNext() ) return infos.next().getStation(); return null; } /** * Removes a {@link Dockable} which was earlier added to this frontend. * @param dockable the element to remove */ public void remove( Dockable dockable ){ DockInfo info = getInfo( dockable ); if( info != null ){ boolean hideable = info.isHideable(); info.setHideable( false ); if( empty.contains( info.getKey() )){ info.updateLocation(); fireRemoved( dockable ); info.setDockable( null ); info.setHideable( hideable ); } else{ dockables.remove( info.getKey() ); fireRemoved( dockable ); } } } /** * Adds the name of a {@link Dockable} whose properties should be stored * in this frontend even if the {@link Dockable} itself is not * registered.<br> * Note that <code>this</code> can add "empty infos" automatically * when calling {@link #setSetting(Setting, boolean)} and information * is found that is not associated with any {@link Dockable}, but * whose key passes the methods of {@link MissingDockableStrategy}. * @param name the name of the dockable */ public void addEmpty( String name ){ if( name == null ) throw new IllegalArgumentException( "name must not be null" ); empty.add( name ); if( !dockables.containsKey( name )){ dockables.put( name, new DockInfo( null, name )); } } /** * Removes the properties of a non existing {@link Dockable} and/or * changes the flag to store information about the non existing * <code>Dockable</code><code>name</code> to <code>false</code>. * @param name the empty element to remove */ public void removeEmpty( String name ){ empty.remove( name ); DockInfo info = getInfo( name ); if( info != null ){ if( info.getDockable() == null ){ dockables.remove( name ); } } } /** * Tells whether <code>name</code> denotes an entry that can be empty. * @param name some unique identifier * @return <code>true</code> if information about a Dockable <code>name</code> * is stored even if the element is <code>null</code> */ public boolean isEmpty( String name ){ return empty.contains( name ); } /** * Gets a list of all keys that are marked as <code>empty</code>. * @param all if <code>true</code> then just all keys are returned, if * <code>false</code> then only those keys are returned for which no * {@link Dockable} is registered. * @return the list of keys marked as empty, may be <code>null</code> * @see #addEmpty(String) * @see #removeEmpty(String) */ public String[] listEmpty( boolean all ){ if( all ){ return empty.toArray( new String[ empty.size() ] ); } else{ List<String> result = new ArrayList<String>(); for( String key : empty ){ DockInfo info = getInfo( key ); if( info.getDockable() == null ){ result.add( key ); } } return result.toArray( new String[ result.size() ] ); } } /** * Removes a root from this frontend. If the root is the * {@link #setDefaultStation(DockStation) default station}, then the * default station is set to <code>null</code>. * @param station the root to remove */ public void removeRoot( DockStation station ){ RootInfo info = getRoot( station ); if( info != null ){ if( defaultStation == info.getStation() ) defaultStation = null; roots.remove( info.getName() ); controller.remove( station ); } } /** * Tells whether this {@link DockFrontend} currently knows where to * put <code>dockable</code>. * @param dockable the element whose location might be known * @return <code>true</code> if the location of <code>dockable</code> is known */ public boolean hasLocation( Dockable dockable ){ DockInfo info = getInfo( dockable ); if( info == null ) return false; if( isShown( dockable )) return true; return info.root != null && info.location != null; } /** * Tells whether this {@link DockFrontend} stores location information for a {@link Dockable} with * id <code>id</code>. This method does not check whether there actually is a visible dockable with the * given id. * @param id the id of some entry * @return <code>true</code> if there is an entry for <code>id</code> and this entry has a location attached */ public boolean hasLocation( String id ){ DockInfo info = getInfo( id ); if( info == null ) return false; return info.root != null && info.location != null; } /** * Sets a filter which is applied when saving or loading a normal entry. * @param ignoreForEntry the filter, can be <code>null</code> */ public void setIgnoreForEntry(DockSituationIgnore ignoreForEntry) { this.ignoreForEntry = ignoreForEntry; } /** * Gets the filter which is used when saving or loading a normal entry. * @return the filter, might be <code>null</code> */ public DockSituationIgnore getIgnoreForEntry() { return ignoreForEntry; } /** * Sets the filter which is applied when saving or loading the final layout * at the startup or shutdown of the application. * @param ignoreForFinal the filter, can be <code>null</code> */ public void setIgnoreForFinal(DockSituationIgnore ignoreForFinal) { this.ignoreForFinal = ignoreForFinal; } /** * Gets the filter which is applied when saving or loading the final layout * at the startup or shutdown of the application. * @return the filter, can be <code>null</code> */ public DockSituationIgnore getIgnoreForFinal() { return ignoreForFinal; } /** * Gets the set of properties which have a controller-global influence. * @return the set of properties */ public DockProperties getDockProperties(){ return controller.getProperties(); } /** * Gets the last {@link Setting} that was given to {@link #setSetting(Setting, boolean)} * when the entry-parameter was set to <code>false</code>. This might be * <code>null</code> if no setting was yet applied. * @return the setting, can be <code>null</code> */ public Setting getLastAppliedFullSetting() { return lastAppliedFullSetting; } /** * Gets the last {@link Setting} that was given to {@link #setSetting(Setting, boolean)} * when the entry-parameter was set to <code>true</code>. This might be * <code>null</code> if no setting was yet applied or a non-entry setting * was applied. * @return the setting, can be <code>null</code> */ public Setting getLastAppliedEntrySetting() { return lastAppliedEntrySetting; } /** * Gets a set of the names of all known settings. * @return the set of names */ public Set<String> getSettings(){ Set<String> keys = settings.keySet(); return Collections.unmodifiableSet( keys ); } /** * Gets the {@link Setting} which stores locations and other information under the key <code>name</code>. * @param name a key that was used for calling {@link #save(String)} * @return the setting or <code>null</code> if not found */ public Setting getSetting( String name ){ return settings.get( name ); } /** * Gets the name of the setting which was loaded or saved the last time. * @return the name, might be <code>null</code> if no setting was saved yet */ public String getCurrentSetting(){ return currentSetting; } /** * Sets the name of the current setting. If there is already a setting * with this name, then this setting is loaded. Otherwise the * current setting is saved with the new name. * @param setting the name of the new setting */ public void setCurrentSetting( String setting ){ if( setting == null ) throw new IllegalArgumentException( "the name of a setting must not be null" ); if( settings.containsKey( setting )) load( setting ); else save( setting ); } /** * Changes the name of the current setting. This is not a renaming operation, no layout is loaded, removed or * renamed, this method only changes the result of {@link #getCurrentSetting()}. This method does not fire * any events as nothing happens. * @param setting the name to use, can be <code>null</code> */ public void setCurrentSettingName( String setting ){ currentSetting = setting; } /** * Stores the setting <code>setting</code> with the given name. * @param name the name of the setting * @param setting the new setting, not <code>null</code> */ public void setSetting( String name, Setting setting ){ if( setting == null ){ throw new IllegalArgumentException( "setting is null" ); } settings.put( name, setting ); } /** * Tells whether <code>dockable</code> is hidden or not. A {@link Dockable} is hidden if either * {@link #isHiddenRootStation(DockElement)} is <code>true</code> or if {@link #isShown(Dockable)} is <code>false</code>.<br> * @param dockable the element whose state is asked * @return <code>true</code> if <code>dockable</code> is not visible * @see #isHiddenRootStation(DockElement) */ public boolean isHidden( Dockable dockable ){ return !isShown( dockable ); } /** * Tells whether <code>dockable</code> is visible or not. A {@link Dockable} is visible if it is or will * be registered. A root-station is always visible. * @param dockable the element whose state is asked * @return <code>true</code> if <code>dockable</code> is visible * @see #isHiddenRootStation(DockElement) */ public boolean isShown( Dockable dockable ){ return controller.getRegister().willBeRegistered( dockable ); } /** * Tells whether <code>element</code> is a root-station and at the same time a {@link Dockable} * without parent. * @param element the element to check * @return <code>true</code> if <code>element</code> is a root-station and a dockable without parent */ public boolean isHiddenRootStation( DockElement element ){ Dockable dockable = element.asDockable(); DockStation station = element.asDockStation(); if( station == null || dockable == null ){ return false; } DockRegister register = controller.getRegister(); if( register.isProtected( station )){ return dockable.getDockParent() == null; } return false; } /** * Sets the default setting for {@link #setHideable(Dockable, boolean)}. This * default value is stored as soon as the identifier of a {@link Dockable} * becomes known and further changes of the default value will not affect it.<br> * As a side effect a value of <code>false</code> will make new, unmodified {@link Dockable}s * visible when loading a layout (by calling a method like {@link #readXML(XElement)}).<br> * The default value is <code>false</code>, because the most simple applications will * not offer any way of making an invisible {@link Dockable} visible again. * @param defaultHideable the default value * @see #setHideable(Dockable, boolean) */ public void setDefaultHideable( boolean defaultHideable ) { this.defaultHideable = defaultHideable; } /** * Gets the default value of {@link #setHideable(Dockable, boolean)}. * @return the default value * @see #setDefaultHideable(boolean) */ public boolean isDefaultHideable() { return defaultHideable; } /** * Tells whether there is a "close"-action for <code>dockable</code> or not. * @param dockable the element whose state is asked, must be known to this * frontend. * @return <code>true</code> if <code>dockable</code> has a close-action */ public boolean isHideable( Dockable dockable ){ DockInfo info = getInfo( dockable ); if( info == null ) throw new IllegalArgumentException( "Dockable not registered" ); return info.isHideable(); } /** * Sets whether to show a close-action for <code>dockable</code>. Changing this * property has an immediate effect on the action.<br> * As a side effect any non-hideable {@link Dockable} will become visible if a new layout * is loaded by calling a method like {@link #setCurrentSetting(String)} or * {@link #readXML(XElement)}. * @param dockable the element whose state will be changed * @param hideable the new state * @throws IllegalArgumentException if <code>dockable</code> is not known * to this frontend */ public void setHideable( Dockable dockable, boolean hideable ){ DockInfo info = getInfo( dockable ); if( info == null ) throw new IllegalArgumentException( "Dockable not registered" ); if( info.isHideable() != hideable ){ info.setHideable( hideable ); fireHideable( dockable, hideable ); } } /** * Sets whether to show the hide-action or not. That property only affects * the elements visible to the user, not the logic how to handle Dockables. * This property is useful for clients which supply their own action * (which might invoke {@link #hide(Dockable) hide}). * @param show whether to show the action * @see #setHideable(Dockable, boolean) */ public void setShowHideAction( boolean show ){ if( showHideAction != show ){ showHideAction = show; for( DockInfo info : dockables.values() ) info.updateHideAction(); } } /** * Tells whether the hide-action is shown or not. * @return <code>true</code> if the action is shown on * {@link #isHideable(Dockable) hideable} dockables or <code>false</code> * otherwise */ public boolean isShowHideAction(){ return showHideAction; } /** * Sets the default value for {@link #setEntryLayout(Dockable, boolean)}. * This default value is stored as soon as the identifier of a {@link Dockable} * becomes known and will not be affected by further changes of the default * value. * @param defaultEntryLayout whether the contents of {@link Dockable}s should * be stored in <code>entry</code> {@link Setting}s or not * @see #getSetting(boolean) */ public void setDefaultEntryLayout( boolean defaultEntryLayout ) { this.defaultEntryLayout = defaultEntryLayout; } /** * Gets the default value of {@link #isEntryLayout(Dockable)}. * @return the default value */ public boolean isDefaultEntryLayout() { return defaultEntryLayout; } /** * Sets whether the layout of <code>dockable</code> should be stored * for <code>entry</code> {@link Setting}s. * @param dockable the element whose state is to be set * @param layout the new state * @throws IllegalArgumentException if <code>dockable</code> is not * known * @see #getSetting(boolean) */ public void setEntryLayout( Dockable dockable, boolean layout ){ DockInfo info = getInfo( dockable ); if( info == null ) throw new IllegalArgumentException( "dockable not registered" ); info.setEntryLayout( layout ); } /** * Sets whether the layout of <code>id</code> should be stored * for <code>entry</code> {@link Setting}s. * @param id the id of the element whose state is to be changed * @param layout the new state * @throws IllegalArgumentException if <code>id</code> is not * known * @see #getSetting(boolean) */ public void setEntryLayout( String id, boolean layout ){ DockInfo info = getInfo( id ); if( info == null ) throw new IllegalArgumentException( "no entry present for: " + id ); info.setEntryLayout( layout ); } /** * Tells whether the layout of <code>dockable</code> should be stored * for <code>entry</code> {@link Setting}s. * @param dockable the element whose state is asked * @return the state * @throws IllegalArgumentException if <code>dockable</code> is not known * @see #getSetting(boolean) */ public boolean isEntryLayout( Dockable dockable ){ DockInfo info = getInfo( dockable ); if( info == null ) throw new IllegalArgumentException( "dockable not registered" ); return info.isEntryLayout(); } /** * Tells whether the layout of <code>id</code> should be stored * for <code>entry</code> {@link Setting}s. * @param id the identifier of an element whose state is requested * @return the state * @throws IllegalArgumentException if <code>id</code> is not known * @see #getSetting(boolean) */ public boolean isEntryLayout( String id ){ DockInfo info = getInfo( id ); if( info == null ) throw new IllegalArgumentException( "no entry present for: " + id ); return info.isEntryLayout(); } /** * Updates the stored location of <code>dockable</code> such that it is aside <code>aside</code>. This method * should be used to set the location of an invisible {@link Dockable}, as it does not affect the location of * a {@link Dockable} that is already visible. Usually the method is used like this: * <code>DockFronted frontend = ... * Dockable dockable = ... * * frontend.addDockable( "x", dockable ); * frontend.setLocationAside( dockable, someOtherDockable ); * frontend.show( dockable );</code><br> * Clients may also combine this feature with the {@link DockController#getFocusHistory() focus history} to access * the last focused {@link Dockable} for the argument <code>aside</code>. * @param dockable the item whose location is to be set * @param aside the new neighbor of <code>dockable</code> * @return whether the operation was a success, an operation requires at least that both <code>dockable</code> * and <code>aside</code> were added to this {@link DockFrontend}, and that <code>aside</code> currently * has a location. There is no need for <code>aside</code> to be visible. */ public boolean setLocationAside( Dockable dockable, Dockable aside ){ if( dockable == null ){ throw new IllegalArgumentException( "dockable must not be null" ); } if( aside == null ){ throw new IllegalArgumentException( "aside must not be null" ); } if( dockable == aside ){ throw new IllegalArgumentException( "dockable and aside must not be the same object" ); } DockInfo info = getInfo( aside ); if( info == null ){ return false; } DockInfo newInfo = getInfo( dockable ); if( newInfo == null ){ return false; } info.updateLocation(); String root = info.getRoot(); DockableProperty location = info.getLocation(); if( root == null || location == null ){ return false; } DockStation rootStation = getRoot( root ); if( rootStation == null ){ return false; } AsideRequestFactory factory = controller.getProperties().get( AsideRequest.REQUEST_FACTORY ); AsideRequest request = factory.createAsideRequest( location, dockable ); AsideAnswer answer = request.execute( rootStation ); if( answer.isCanceled() || answer.getLocation() == null ){ return false; } newInfo.setLocation( root, answer.getLocation() ); return true; } /** * Ensures that <code>dockable</code> is child of a root known to this * frontend. * @param dockable the element which should be made visible * @throws IllegalStateException if the {@link #getDefaultStation() default station} is * needed but can't be found */ public void show( Dockable dockable ){ show( dockable, true ); } /** * Ensures that <code>dockable</code> is child of a root known to this * frontend. * @param dockable the element which should be made visible * @param cancelable whether a {@link VetoableDockFrontendListener} can * cancel the operation or not * @throws IllegalStateException if the {@link #getDefaultStation() default station} is * needed but can't be found */ public void show( Dockable dockable, boolean cancelable ){ try{ onAutoFire++; if( isHidden( dockable ) || isHiddenRootStation( dockable )){ if( veto.expectToShow( dockable, cancelable )){ DockInfo info = getInfo( dockable ); if( info == null ){ DockStation station = getDefaultStation(); if( station == null ) throw new IllegalStateException( "Can't find the default station" ); station.drop( dockable ); } else{ String root = info.getRoot(); DockableProperty location = info.getLocation(); DockStation station; if( root == null ) station = getDefaultStation(); else station = getRoot( root ); if( station == null ){ station = getDefaultStation(); if( station == null ) throw new IllegalStateException( "Can't find the default station" ); } if( location == null ) getDefaultStation().drop( dockable ); else{ if( !station.drop( dockable, location )) getDefaultStation().drop( dockable ); } } fireAllShown( dockable, null ); } } } finally{ onAutoFire--; } } /** * Makes <code>dockable</code> invisible. The location of <code>dockable</code> * is saved, and if made visible again, it will reappear at its old location. * @param dockable the element which should be hidden */ public void hide( Dockable dockable ){ hide( dockable, true ); } /** * Makes <code>dockable</code> invisible. The location of <code>dockable</code> * is saved, and if made visible again, it will reappear at its old location. * @param dockable the element which should be hidden * @param cancelable whether a {@link VetoableDockFrontendListener} can cancel * the operation or not */ public void hide( Dockable dockable, boolean cancelable ){ try{ onAutoFire++; if( isShown( dockable )){ if( dockable.getDockParent() == null || veto.expectToHide( dockable, cancelable ) ){ DockInfo info = getInfo( dockable ); if( info != null ){ info.updateLocation(); } if( dockable.getDockParent() != null ){ dockable.getDockParent().drag( dockable ); fireAllHidden( dockable, null ); } } } } finally{ onAutoFire--; } } /** * Saves the current layout under the name of the {@link #getCurrentSetting() current setting}. * @throws IllegalStateException if the name of the current setting is <code>null</code> */ public void save(){ if( currentSetting == null ) throw new IllegalStateException( "No setting loaded yet" ); save( currentSetting ); } /** * Saves the current layout with the specified name. * @param name the name for the setting */ public void save( String name ){ save( name, true ); } /** * Saves the current layout with the specified name. * @param name the name for the setting * @param entry whether only the normal "entry" information should be saved, or all the information. The default * value should be <code>true</code> * @see #getPerspective(boolean) */ public void save( String name, boolean entry ){ if( name == null ) throw new IllegalArgumentException( "name must not be null" ); Setting setting = getSetting( entry ); setSetting( name, setting ); currentSetting = name; fireSaved( name ); } /** * Loads a setting of this frontend. * @param name the name of the setting * @throws IllegalArgumentException if no setting <code>name</code> could be found */ public void load( String name ){ load( name, true ); } /** * Loads a setting of this frontend. * @param name the name of the setting * @param entry <code>true</code> if only information for normal entries should be extracted, <code>false</code> if * as much information as possible should be read. Should be the same value as was used for {@link #save(String, boolean)} * @throws IllegalArgumentException if no setting <code>name</code> could be found */ public void load( String name, boolean entry ){ if( name == null ) throw new IllegalArgumentException( "name must not be null" ); Setting setting = settings.get( name ); if( setting == null ) throw new IllegalArgumentException( "Unknown setting \""+ name +"\""); currentSetting = name; setSetting( setting, entry ); fireLoaded( name ); } /** * Creates a new {@link Setting} which describes the current set of * properties of this frontend. The setting contains information about * the location of each {@link Dockable}. * @param entry <code>true</code> if only the information for an ordinary * entry should be stored, <code>false</code> if the setting should contain * as much information as possible. * @return the setting * @see #createSetting() */ public Setting getSetting( boolean entry ){ Setting setting = createSetting(); DockSituation situation = layoutChangeStrategy.createSituation( new Internals(), entry ); for( RootInfo info : roots.values() ){ DockStation station = info.getStation(); if( station.asDockable() == null || station.asDockable().getDockParent() == null ){ DockLayoutComposition layout = situation.convert( station ); setting.putRoot( info.getName(), layout ); } } for( DockInfo info : dockables.values() ){ Dockable dockable = info.getDockable(); if( dockable == null || dockable.getController() == null ){ DockLayoutComposition layout = null; if( !entry || info.isEntryLayout() ){ if( dockable != null ){ layout = situation.convert( dockable ); } else{ layout = info.getLayout(); } } setting.addInvisible( info.getKey(), info.getRoot(), layout, info.getLocation() ); } } return setting; } /** * Changes the content of all root-stations according to <code>setting</code>.<br> * This method may add new {@link #addEmpty(String) empty infos} if it finds * information for a non existing, non empty {@link Dockable} but whose * key passes the methods of {@link MissingDockableStrategy}. * @param setting a new set of properties * @param entry <code>true</code> if only information for an ordinary * entry should be extracted, <code>false</code> if as much information * as possible should be extracted. The value of this argument should * be the same as was used when {@link #getSetting(boolean)} was called. */ public void setSetting( Setting setting, boolean entry ){ try{ onAutoFire++; controller.getRegister().setStalled( true ); if( layoutChangeStrategy.setLayout( new Internals(), setting, entry ) ){ if( entry ){ lastAppliedEntrySetting = setting; } else{ lastAppliedEntrySetting = null; lastAppliedFullSetting = setting; } } } catch( IOException e ){ throw new IllegalArgumentException( "Cannot set Setting", e ); } catch( XException e ){ throw new IllegalArgumentException( "Cannot set Setting", e ); } finally{ onAutoFire--; controller.getRegister().setStalled( false ); } for( DockInfo info : dockables.values() ){ if( info.getDockable() != null && !info.isHideable() && isHidden( info.getDockable() )){ show( info.getDockable() ); } } } /** * Creates and returns a new {@link Perspective} which can be used to read, write and convert * {@link PerspectiveElement}s. This method creates a new {@link DefaultFrontendPerspectiveCache}.<br> * <b>Note:</b> Please read the documentation of {@link DefaultFrontendPerspectiveCache} to learn * about the drawbacks of using that class. * @param entry Whether the perspective should act as if loading or storing an "normal setting". A normal setting * is a setting created by {@link #save(String)} or by {@link #save()}. * @return the new perspective, not <code>null</code> * @see #getPerspective(boolean, FrontendPerspectiveCache) */ public DockFrontendPerspective getPerspective( boolean entry ){ return getPerspective( entry, new DefaultFrontendPerspectiveCache( this ) ); } /** * Creates and returns a new {@link Perspective} which can be used to read, write and convert * {@link PerspectiveElement}s. The new perspective will be set up with the factories known to this * frontend and with the entries from <code>elements</code>.<br> * Please note that the new perspective does not contain any information about the current layout of * this {@link DockFrontend}. Clients can use a {@link Setting} to access layout information. * @param entry Whether the perspective should act as if loading or storing an "normal setting". A normal setting * is a setting created by {@link #save(String)} or by {@link #save()}. * @param factory a factory that will be used to translate {@link DockElement}s to {@link PerspectiveElement}s * @return the new perspective, not <code>null</code> * @see #getSetting(boolean) * @see #getSetting(String) * @see #setSetting(Setting, boolean) * @see #setSetting(String, Setting) */ public DockFrontendPerspective getPerspective( boolean entry, FrontendPerspectiveCache factory ){ return layoutChangeStrategy.createPerspective( new Internals(), entry, factory ); } /** * Tries to fill gaps in the layout information. * <ul> * <li>Tries to read empty {@link DockInfo}s which have a layout and a position.</li> * <li>Tries to replace raw data in {@link DockLayoutComposition}s</li> * </ul> */ private void fillMissing(){ DockSituation situation = layoutChangeStrategy.createSituation( new Internals(), false ); // fill gaps for( Setting setting : settings.values() ){ setting.fillMissing( situation ); } if( lastAppliedFullSetting != null && !settings.containsValue( lastAppliedFullSetting )){ lastAppliedFullSetting.fillMissing( situation ); } if( lastAppliedEntrySetting != null && lastAppliedEntrySetting != lastAppliedFullSetting && !settings.containsValue( lastAppliedEntrySetting )){ lastAppliedEntrySetting.fillMissing( situation ); } // try fill in missing dockables which have a name List<FrontendEntry> entries = listFrontendEntries(); for( FrontendEntry entry : entries ){ if( entry.getDockable() == null && entry.getLayout() != null ){ DockElement element = situation.convert( entry.getLayout() ); Dockable dockable = element == null ? null : element.asDockable(); if( dockable != null ){ entry.setLayout( null ); addDockable( entry.getKey(), dockable ); } } } } /** * Tries to locate and create those {@link Dockable}s for which location * information can be found in the last applied {@link Setting} and which * use the newly added factory <code>factory</code>. * @param factory the new factory */ private void fillMissing( DockFactory<?,?,?> factory ){ fillMissing(); Setting last = getLastAppliedEntrySetting(); boolean entry = true; if( last == null ){ last = getLastAppliedFullSetting(); entry = false; } if( last == null ){ return; } Internals internals = new Internals(); DockSituation situation = layoutChangeStrategy.createSituation( internals, entry ); String factoryId = situation.convertFactoryId( factory ); for( String root : roots.keySet() ){ DockLayoutComposition composition = last.getRoot( root ); if( composition != null ){ layoutChangeStrategy.estimateLocations( internals, situation, composition ); fillMissing( root, composition, factory, factoryId ); } } } /** * Searches for elements which can be created by the factory * <code>factory</code> and creates them. * @param root the root station * @param composition the composition to search in * @param factory the factory to look out for * @param factoryId the identifier of the factory, translated for the {@link DockLayoutComposition} */ @SuppressWarnings("unchecked") private void fillMissing( String root, DockLayoutComposition composition, DockFactory<?,?,?> factory, String factoryId ){ DockLayoutInfo info = composition.getLayout(); if( info.getKind() == DockLayoutInfo.Data.DOCK_LAYOUT ){ if( info.getDataLayout().getFactoryID().equals( factoryId )){ DockableProperty location = info.getLocation(); if( location != null ){ DockFactory<DockElement,?,Object> normalizedFactory = (DockFactory<DockElement,?,Object>)factory; if( missingDockable.shouldCreate( normalizedFactory, info.getDataLayout().getData() ) ){ DockElement element = normalizedFactory.layout( info.getDataLayout().getData(), layoutChangeStrategy.getPlaceholderStrategy( new Internals() ) ); if( element != null ){ Dockable dockable = element.asDockable(); if( dockable != null ){ RootInfo rootInfo = roots.get( root ); if( !rootInfo.getStation().drop( dockable, location ) ){ rootInfo.getStation().drop( dockable ); } } } } } } } List<DockLayoutComposition> children = composition.getChildren(); if( children != null ){ for( DockLayoutComposition child : children ){ fillMissing( root, child, factory, factoryId ); } } } /** * Gets a set of all {@link Dockable} which are known to this frontend * and which are visible. * @return the set of the visible elements */ public Set<Dockable> listShownDockables(){ Set<Dockable> set = new HashSet<Dockable>(); for( DockInfo info : dockables.values() ){ if( info.getDockable() != null && isShown( info.getDockable() )){ set.add( info.getDockable() ); } } return set; } /** * Gets a list of all {@link Dockable}s which are registered at this * frontend. * @return the list of elements */ public List<Dockable> listDockables(){ List<Dockable> result = new ArrayList<Dockable>( dockables.size() ); for( DockInfo info : dockables.values() ){ if( info.getDockable() != null ){ result.add( info.getDockable() ); } } return result; } /** * Gets a list of all informations known of any {@link Dockable} * that is or might be registered at this frontend. * @return all known information. Changes to this list itself will not * affect this frontend, changes to the entries however might have * effects. */ public List<FrontendEntry> listFrontendEntries(){ return new ArrayList<FrontendEntry>( dockables.values() ); } /** * Gets all the information known about the {@link Dockable} with * name <code>key</code>. * @param key some key of a dockable * @return all information known or <code>null</code> if nothing is available */ public FrontendEntry getFrontendEntry( String key ){ return dockables.get( key ); } /** * Gets all the information known about the registered <code>dockable</code>. * @param dockable some dockable that may be registered at this {@link DockFrontend} * @return any available information about <code>dockable</code>, <code>null</code> if * <code>dockable</code> is unknown to this frontend */ public FrontendEntry getFrontendEntry( Dockable dockable ){ return getInfo( dockable ); } /** * Removes all child-parent relations expect the ones filtered out * by <code>ignore</code>. * @param ignore a filter, never <code>null</code> */ protected void clean( DockSituationIgnore ignore ){ for( RootInfo root : roots.values() ){ if( !ignore.ignoreElement( root.getStation() )){ clean( root.getStation(), ignore ); } } } /** * Removes recursively all children from <code>station</code>, but only * if the children are not filtered by <code>ignore</code>. * @param station a station to clean * @param ignore a filter */ protected void clean( DockStation station, DockSituationIgnore ignore ){ try{ controller.getRegister().setStalled( true ); if( !ignore.ignoreChildren( station ) ){ for( int i = station.getDockableCount()-1; i >= 0; i-- ){ Dockable dockable = station.getDockable( i ); if( !ignore.ignoreElement( dockable )){ DockStation check = dockable.asDockStation(); if( check != null ) clean( check, ignore ); station.drag( dockable ); } } } } finally{ controller.getRegister().setStalled( false ); } } /** * Deletes all settings known to this frontend, this method is equivalent of calling * {@link #delete(String)} with all known names for settings. * @return the number of settings that were deleted * @see #delete(String) */ public int deleteAll(){ int count = 0; Set<String> settings = getSettings(); String[] array = settings.toArray( new String[ settings.size() ] ); for( String name : array ){ if( delete( name ) ){ count++; } } return count; } /** * Deletes the setting with the given <code>name</code>. * @param name the name of the setting to delete * @return <code>true</code> if the setting was deleted, <code>false</code> * if the setting was unknown anyway. * @see #deleteAll() */ public boolean delete( String name ){ if( name == null ) throw new IllegalArgumentException( "name must not be null" ); boolean deleted = settings.remove( name ) != null; if( deleted ){ if( name.equals( currentSetting )) currentSetting = null; fireDeleted( name ); } return deleted; } /** * Writes all settings of this frontend, including the current layout, * into <code>out</code>. * @param out the stream to write into * @throws IOException if there are any problems */ public void write( DataOutputStream out ) throws IOException{ writeBlop( writeBlop(), out ); } /** * Writes the contents of <code>blop</code> into <code>out</code>. * @param blop the {@link Setting}s to write * @param out the stream to write into * @throws IOException if there are any problems */ public void writeBlop( SettingsBlop blop, DataOutputStream out ) throws IOException{ Version.write( out, Version.VERSION_1_1_1a ); String currentSetting = blop.getCurrentName(); if( currentSetting == null ) out.writeBoolean( false ); else{ out.writeBoolean( true ); out.writeUTF( currentSetting ); } String[] names = blop.getNames(); out.writeInt( names.length ); for( String name : names ){ out.writeUTF( name ); write( blop.getSetting( name ), true, out ); } write( blop.getCurrentSetting(), false, out ); } /** * Calls {@link Setting#write(DockSituation, PropertyTransformer, boolean, DataOutputStream)} * @param setting the setting which will be written * @param entry whether <code>setting</code> is an ordinary entry, or * the final setting that contains more data. * @param out the stream to write into * @throws IOException if an I/O-error occurs */ protected void write( Setting setting, boolean entry, DataOutputStream out ) throws IOException{ Internals internals = new Internals(); DockSituation situation = layoutChangeStrategy.createSituation( internals, entry ); PropertyTransformer properties = layoutChangeStrategy.createTransformer( internals ); setting.write( situation, properties, entry, out ); } /** * Reads the settings of this frontend from <code>in</code>. The layout * will be changed according to the contents that are read. All existing * settings are deleted by this method. * @param in the stream to read from * @throws IOException if there are any problems */ public void read( DataInputStream in ) throws IOException{ read( in, false ); } /** * Reads the settings of this frontend from <code>in</code>. The layout * will be changed according to the contents that are read. * @param in the stream to read from * @param keepExistingSettings whether to keep or to delete (see {@link #deleteAll()}) the * existing settings. * @throws IOException if there are any problems */ public void read( DataInputStream in, boolean keepExistingSettings ) throws IOException{ readBlop( readBlop( in ), keepExistingSettings ); } /** * Reads the contents of <code>in</code> using all the factories that are currently installed * on this {@link DockFrontend}, this method does not change any properties of the frontend. * @param in the stream to read from * @return the {@link Setting}s that were read * @throws IOException if <code>in</code> cannot be read properly */ public SettingsBlop readBlop( DataInputStream in ) throws IOException{ SettingsBlop blop = new SettingsBlop(); Version version = Version.read( in ); version.checkCurrent(); String currentSetting = null; if( in.readBoolean() ) currentSetting = in.readUTF(); int count = in.readInt(); for( int i = 0; i < count; i++ ){ String key = in.readUTF(); Setting setting = read( true, in ); blop.put( key, setting ); } blop.setCurrent( currentSetting, read( false, in ) ); return blop; } /** * Calls first {@link #createSetting()} and then * {@link Setting#read(DockSituation, PropertyTransformer, boolean, DataInputStream)}. * @param entry whether the set of properties is used as ordinary entry, * or contains more data than usual. * @param in the stream to read from * @return the new setting * @throws IOException if an I/O-error occurs * @see #createSetting() */ protected Setting read( boolean entry, DataInputStream in ) throws IOException{ Setting setting = createSetting(); Internals internals = new Internals(); DockSituation situation = layoutChangeStrategy.createSituation( internals, entry ); PropertyTransformer properties = layoutChangeStrategy.createTransformer( internals ); setting.read( situation, properties, entry, in ); return setting; } /** * Writes all properties of this frontend into an xml element. * @param element the element to write into, this method will not * change the attributes of <code>element</code> */ public void writeXML( XElement element ){ writeBlopXML( writeBlop(), element ); } /** * Writes all the {@link Setting}s of <code>blop</code> into <code>element</code>, this * method does use the factories installed on this {@link DockFrontend}, but does not * change any properties of the frontend. * @param blop the settings to write * @param element the element to write into, this method will not * change the attributes of <code>element</code> */ public void writeBlopXML( SettingsBlop blop, XElement element ){ String[] names = blop.getNames(); if( names.length > 0 ){ XElement xsettings = element.addElement( "settings" ); for( String name : names ){ XElement xsetting = xsettings.addElement( "setting" ); xsetting.addString( "name", name ); writeXML( blop.getSetting( name ), true, xsetting ); } } XElement xcurrent = element.addElement( "current" ); String current = blop.getCurrentName(); if( current != null ) xcurrent.addString( "name", current ); writeXML( blop.getCurrentSetting(), false, xcurrent ); } /** * Calls {@link Setting#writeXML(DockSituation, PropertyTransformer, boolean, XElement)}. * @param setting the setting to write * @param entry whether <code>setting</code> is an ordinary entry, or * the final setting that contains more data. * @param element the xml element to write into, this method does not * change the attributes of the entry */ protected void writeXML( Setting setting, boolean entry, XElement element ){ Internals internals = new Internals(); DockSituation situation = layoutChangeStrategy.createSituation( internals, entry ); PropertyTransformer properties = layoutChangeStrategy.createTransformer( internals ); setting.writeXML( situation, properties, entry, element ); } /** * Reads the contents of this frontend from an xml element. All existing settings * are deleted (see {@link #deleteAll()}) by this method. * @param element the element to read */ public void readXML( XElement element ){ readXML( element, false ); } /** * Reads the contents of this frontend from an xml element. * @param element the element to read * @param keepExistingSettings whether to keep or to delete (see {@link #deleteAll()}) the * existing settings. */ public void readXML( XElement element, boolean keepExistingSettings ){ readBlop( readBlopXML( element ), keepExistingSettings ); } /** * Reads the contents of <code>element</code> using all the factories installed on this * {@link DockFrontend}, without actually changing any property of this frontend. * @param element the element to read * @return all the layouts stored in <code>element</code> */ public SettingsBlop readBlopXML( XElement element ){ SettingsBlop blop = new SettingsBlop(); XElement xsettings = element.getElement( "settings" ); if( xsettings != null ){ for( XElement xsetting : xsettings.getElements( "setting" )){ String key = xsetting.getString( "name" ); Setting setting = readXML( true, xsetting ); blop.put( key, setting ); } } XElement xcurrent = element.getElement( "current" ); if( xcurrent != null ){ XAttribute xname = xcurrent.getAttribute( "name" ); String name = null; if( xname != null ){ name = xname.getString(); } blop.setCurrent( name, readXML( false, xcurrent) ); } return blop; } /** * Calls {@link #createSetting()} and then * {@link Setting#readXML(DockSituation, PropertyTransformer, boolean, XElement)}. * @param entry whether the set of properties is used as ordinary entry, * or contains more data than usual. * @param element the xml element containing the data for the new setting * @return the new setting * @see #createSetting() */ protected Setting readXML( boolean entry, XElement element ){ Setting setting = createSetting(); Internals internals = new Internals(); DockSituation situation = layoutChangeStrategy.createSituation( internals, entry ); PropertyTransformer properties = layoutChangeStrategy.createTransformer( internals ); setting.readXML( situation, properties, entry, element ); return setting; } /** * Stores all the current {@link Setting}s of this {@link DockFrontend} in a new {@link SettingsBlop}. * @return the blop that contains all the settings of this frontend */ public SettingsBlop writeBlop(){ SettingsBlop blop = new SettingsBlop(); for( Map.Entry<String, Setting> entry : settings.entrySet() ){ blop.put( entry.getKey(), entry.getValue() ); } blop.setCurrent( currentSetting, getSetting( false ) ); return blop; } /** * Reads and applies the {@link Setting}s stored in <code>blop</code>. * @param blop the settings to read * @param keepExistingSettings whether {@link #deleteAll()} should be called, which would * result in deleting all existing {@link Setting}s, a value of <code>false</code> will * call {@link #deleteAll()} */ public void readBlop( SettingsBlop blop, boolean keepExistingSettings ){ if( !keepExistingSettings ){ deleteAll(); } for( String name : blop.getNames() ){ settings.put( name, blop.getSetting( name ) ); fireRead( name ); } currentSetting = blop.getCurrentName(); setSetting( blop.getCurrentSetting(), false ); } /** * Creates the action that is added to all known dockables, and which * is called the "close"-action. * @return the action */ protected Hider createHider() { return new Hider(); } /** * Creates a bag that contains all information needed to describe the * current set of properties. * @return the new bag */ protected Setting createSetting(){ return new Setting(); } /** * Gets the action which is added to all known Dockables, and which is * called the "close"-action. Clients may use this method set another * text, icon, ... to the action. * @return the action */ public Hider getHider() { return hider; } /** * Gets the information about <code>dockable</code>. * @param dockable the element whose states are asked * @return the states or <code>null</code> */ private DockInfo getInfo( Dockable dockable ){ if( dockable == null ) throw new NullPointerException( "dockable is null" ); for( DockInfo info : dockables.values() ) if( info.getDockable() == dockable ) return info; return null; } /** * Gets the information for the element with the designated name. * @param name the name of the element whose states are asked * @return the states or <code>null</code> */ private DockInfo getInfo( String name ){ return dockables.get( name ); } /** * Gets information about the root <code>station</code>. * @param station a root * @return the information or <code>null</code> */ private RootInfo getRoot( DockStation station ){ for( RootInfo info : roots.values() ) if( info.getStation() == station ) return info; return null; } /** * Gets information about the root of <code>dockable</code>. * @param dockable some dockable * @return the information or <code>null</code> */ private RootInfo getRoot( Dockable dockable ){ DockStation station = dockable.asDockStation(); if( station != null ){ RootInfo info = getRoot( station ); if( info != null ) return info; } station = dockable.getDockParent(); while( station != null ){ RootInfo info = getRoot( station ); if( info != null ) return info; dockable = station.asDockable(); if( dockable == null ) return null; station = dockable.getDockParent(); } return null; } /** * Gets an independent array containing all currently registered listeners. * @return the array of listeners */ protected DockFrontendListener[] listeners(){ return listeners.toArray( new DockFrontendListener[ listeners.size() ]); } /** * Invokes the method {@link DockFrontendListener#hidden(DockFrontend, Dockable)} * on all listeners for <code>dockable</code> and all its children. * @param dockable the hidden element * @param processed Set of {@link Dockable}s for which the event is already fired, * will be modified by this method, can be <code>null</code> */ protected void fireAllHidden( Dockable dockable, final Set<Dockable> processed ){ DockUtilities.visit( dockable, new DockUtilities.DockVisitor(){ @Override public void handleDockable( Dockable dockable ) { if( processed == null || processed.add( dockable )){ fireHidden( dockable ); DockInfo info = getInfo( dockable ); if( info != null ){ info.setShown( false ); } } } }); } /** * Invokes the method {@link DockFrontendListener#hidden(DockFrontend, Dockable)} * on all listeners. * @param dockable the hidden element */ protected void fireHidden( Dockable dockable ){ for( DockFrontendListener listener : listeners() ) listener.hidden( this, dockable ); } /** * Invokes the method {@link DockFrontendListener#added(DockFrontend, Dockable)} * on all listeners. * @param dockable the added element */ protected void fireAdded( Dockable dockable ){ for( DockFrontendListener listener : listeners() ) listener.added( this, dockable ); } /** * Invokes the method {@link DockFrontendListener#hideable(DockFrontend, Dockable, boolean)} * on all listeners. * @param dockable the element whose state changed * @param value the new state */ protected void fireHideable( Dockable dockable, boolean value ){ for( DockFrontendListener listener : listeners() ) listener.hideable( this, dockable, value ); } /** * Invokes the method {@link DockFrontendListener#removed(DockFrontend, Dockable)} * on all listeners. * @param dockable the removed element */ protected void fireRemoved( Dockable dockable ){ for( DockFrontendListener listener : listeners() ) listener.removed( this, dockable ); } /** * Invokes the method {@link DockFrontendListener#shown(DockFrontend, Dockable)} * on all listeners for <code>dockable</code> and all its children. * @param dockable the shown element * @param processed Set of {@link Dockable}s whose event is already fired, * will be modified by this method, can be <code>null</code> */ protected void fireAllShown( Dockable dockable, final Set<Dockable> processed ){ DockUtilities.visit( dockable, new DockUtilities.DockVisitor(){ @Override public void handleDockable( Dockable dockable ) { if( processed == null || processed.add( dockable )){ fireShown( dockable ); DockInfo info = getInfo( dockable ); if( info != null ){ info.setShown( true ); } } } }); } /** * Invokes the method {@link DockFrontendListener#shown(DockFrontend, Dockable)} * on all listeners. * @param dockable the shown element */ protected void fireShown( Dockable dockable ){ for( DockFrontendListener listener : listeners() ) listener.shown( this, dockable ); } /** * Invokes the method {@link DockFrontendListener#saved(DockFrontend, String)} * on all listeners. * @param name the name of the saved setting */ protected void fireSaved( String name ){ for( DockFrontendListener listener : listeners() ) listener.saved( this, name ); } /** * Invokes the method {@link DockFrontendListener#loaded(DockFrontend, String)} * on all listeners. * @param name the name of the loaded setting */ protected void fireLoaded( String name ){ for( DockFrontendListener listener : listeners() ) listener.loaded( this, name ); } /** * Invokes the method {@link DockFrontendListener#read(DockFrontend, String)} * on all listeners. * @param name the name of the read setting */ protected void fireRead( String name ){ for( DockFrontendListener listener : listeners() ) listener.read( this, name ); } /** * Invokes the method {@link DockFrontendListener#deleted(DockFrontend, String)} * on all listeners. * @param name the name of the deleted setting */ protected void fireDeleted( String name ){ for( DockFrontendListener listener : listeners() ) listener.deleted( this, name ); } /** * Internal information about this frontend. * @author Benjamin Sigg */ private class Internals implements DockFrontendInternals{ public void clean( DockSituationIgnore ignore ){ DockFrontend.this.clean( ignore ); } public AdjacentDockFactory<?>[] getAdjacentDockFactories(){ return adjacentDockFactories.toArray( new AdjacentDockFactory[ adjacentDockFactories.size() ] ); } public DockFactory<?,?,?>[] getBackupDockFactories(){ return backupDockFactories.toArray( new DockFactory[ backupDockFactories.size() ] ); } public DockFactory<?,?,?>[] getDockFactories(){ return dockFactories.toArray( new DockFactory[ dockFactories.size() ] ); } public DockInfo[] getDockables(){ return dockables.values().toArray( new DockInfo[ dockables.size() ] ); } public DockFrontend getFrontend(){ return DockFrontend.this; } public DockInfo getInfo( String key ){ return DockFrontend.this.getInfo( key ); } public DockInfo getInfo( Dockable dockable ){ return DockFrontend.this.getInfo( dockable ); } public MissingDockableStrategy getMissingDockableStrategy(){ return DockFrontend.this.getMissingDockable(); } public DockablePropertyFactory[] getPropertyFactories(){ return propertyFactories.toArray( new DockablePropertyFactory[ propertyFactories.size() ] ); } public RootInfo[] getRoots(){ return roots.values().toArray( new RootInfo[ roots.size() ] ); } public VetoManager getVetos(){ return veto; } } /** * Information about a {@link Dockable}. * @author Benjamin Sigg */ public class DockInfo implements FrontendEntry{ /** The element for which information is stored */ private Dockable dockable; /** The name of the element */ private String key; /** <code>true</code> if the element has a "close"-action, <code>false</code> otherwise */ private boolean hideable; /** The {@link DockActionSource} which is used for {@link #dockable} */ private DefaultDockActionSource source; /** The name of the root on which {@link #dockable} was, when it was made invisible */ private String root; /** The location of {@link #dockable} on the station named {@link #root} */ private DockableProperty location; /** if set, then every entry-Setting can store the layout of this element */ private boolean entryLayout; /** Information about the layout of this {@link #dockable}, can be <code>null</code> */ private DockLayoutComposition layout; /** Whether the hide-action is currently visible or not */ private boolean hideActionVisible; /** whether {@link #dockable} is or should be shown */ private boolean shown = false; /** * Creates a new DockInfo. * @param dockable the element whose informations are stored * @param key the name of the element */ public DockInfo( Dockable dockable, String key ){ this.dockable = dockable; this.key = key; entryLayout = defaultEntryLayout; source = new DefaultDockActionSource( new LocationHint( LocationHint.ACTION_GUARD, LocationHint.RIGHT_OF_ALL )); hideActionVisible = false; setHideable( defaultHideable ); } public void setShown( boolean shown ) { this.shown = shown; } public boolean isShown() { return shown; } public boolean isEntryLayout() { return entryLayout; } public void setEntryLayout( boolean entryLayout ) { this.entryLayout = entryLayout; } public boolean isHideable() { return hideable; } public void setHideable( boolean hideable ) { this.hideable = hideable; updateHideAction(); } /** * Updates the visibility-state of the hide action */ public void updateHideAction(){ boolean shouldShow = hideable && showHideAction; if( shouldShow != hideActionVisible ){ hideActionVisible = shouldShow; if( shouldShow ){ source.add( hider ); } else{ source.remove( hider ); } } } /** * Gets the {@link DockActionSource} which will be added to the offers * of {@link #getDockable() the element}. * @return the additional source */ public DefaultDockActionSource getSource() { return source; } public Dockable getDockable() { return dockable; } /** * Exchanges the dockable which is stored in this {@link DockInfo} * @param dockable the new dockable, can be <code>null</code> */ public void setDockable( Dockable dockable ) { this.dockable = dockable; } public String getKey() { return key; } public void updateLocation(){ RootInfo info = DockFrontend.this.getRoot( dockable ); if( info == null ) return; if( info.getStation() == dockable ){ if( dockable.getDockParent() != null ){ info = DockFrontend.this.getRoot( dockable.getDockParent() ); if( info == null ) return; } else{ return; } } root = info.getName(); location = DockUtilities.getPropertyChain( info.getStation(), dockable ); } public void setLocation( String root, DockableProperty location ){ this.root = root; this.location = location; } public String getRoot() { return root; } public DockableProperty getLocation() { return location; } public void setLayout( DockLayoutComposition layout ) { this.layout = layout; } public DockLayoutComposition getLayout() { return layout; } } /** * Stores information about a root-station. * @author Benjamin Sigg */ public static class RootInfo{ /** the root */ private DockStation station; /** the name of the root */ private String name; /** * Creates a new object. * @param station the root * @param name the name of the root */ public RootInfo( DockStation station, String name ){ this.name = name; this.station = station; } /** * Gets the name of the station stored in this object. * @return the name */ public String getName() { return name; } /** * Gets the root-station. * @return the root */ public DockStation getStation() { return station; } } /** * An object which is action and {@link ActionGuard} at the same time. The * action is always to invoke {@link DockFrontend#hide(Dockable) hide} of * the enclosing a {@link DockFrontend}. The guard reacts on all * {@link Dockable Dockables} which are known to the enclosing frontend. * @author Benjamin Sigg */ @EclipseTabDockAction public class Hider extends SimpleButtonAction implements ActionGuard{ private DockActionIcon icon; private DockActionText text; private DockActionText tooltip; /** * Creates a new action/guard. */ public Hider(){ text = new DockActionText( "close", this ){ protected void changed( String oldValue, String newValue ){ setText( newValue ); } }; tooltip = new DockActionText( "close.tooltip", this ){ protected void changed( String oldValue, String newValue ){ setTooltip( newValue ); } }; icon = new DockActionIcon("close", this){ protected void changed( Icon oldValue, Icon newValue ){ setIcon( newValue ); } }; text.setController( controller ); tooltip.setController( controller ); icon.setManager( controller.getIcons() ); PropertyValue<KeyStroke> stroke = new PropertyValue<KeyStroke>( HIDE_ACCELERATOR ){ @Override protected void valueChanged( KeyStroke oldValue, KeyStroke newValue ) { setAccelerator( newValue ); } }; stroke.setProperties( controller ); setAccelerator( stroke.getValue() ); } public void iconChanged( String key, Icon icon ) { setIcon( icon ); } public DockActionSource getSource( Dockable dockable ) { DockInfo info = getInfo( dockable ); if( info == null ){ return new DefaultDockActionSource( new LocationHint( LocationHint.ACTION_GUARD, LocationHint.RIGHT_OF_ALL ), this ); } else{ return info.getSource(); } } public boolean react( Dockable dockable ) { DockInfo info = getInfo( dockable ); return info != null; } @Override public void action( Dockable dockable ) { hide( dockable ); } } }