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

bibliothek.gui.DockFrontend Maven / Gradle / Ivy

The newest version!
/*
 * Bibliothek - DockingFrames
 * Library built on Java/Swing, allows the user to "drag and drop"
 * panels containing any Swing-Component the developer likes to add.
 * 
 * Copyright (C) 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
 * [email protected]
 * 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:
 * 
    *
  • {@link #createSetting()}
  • *
  • {@link #getSetting(boolean)} and {@link #setSetting(Setting, boolean)}
  • *
  • {@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)}
  • *
  • {@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)}
  • *

* 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.
* Note: 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).
* Clients must also provide some {@link #registerFactory(DockFactory) factories} * to allow the storage of their elements. The default-factories are already * installed.
* Note: Clients may use the Common project instead of DockFrontend. * The Common project offers way more features than DockFrontend 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 HIDE_ACCELERATOR = new PropertyKey( "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 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 dockables = new HashMap(); /** the identifiers of the {@link DockInfo}s which should stay around even if their dockable is removed */ private Set empty = new HashSet(); /** 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 roots = new HashMap(); /** A set of factories needed to store Dockables */ private Set> dockFactories = new HashSet>(); /** A set of factories needed to read Dockables that are missing in the cache */ private Set> backupDockFactories = new HashSet>(); /** A set of factories needed to store additional information about Dockables */ private Set> adjacentDockFactories = new HashSet>(); /** A set of factories needed to store {@link DockableProperty properties} */ private Set propertyFactories = new HashSet(); /** The name of the setting which is currently loaded */ private String currentSetting; /** A map of all known settings */ private Map settings = new HashMap(); /** A list of observers */ private List listeners = new ArrayList(); /** 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 false. Can be null. */ private Setting lastAppliedFullSetting = null; /** * The last {@link Setting} that was {@link #setSetting(Setting, boolean) applied} * with the entry flag set to true. Can be null. */ 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 null */ 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 null */ 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 controller to owner. * @param controller the controller used to store the root stations * @param owner the owner of the dialog of a {@link ScreenDockStation}, * may be null */ 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 controller to owner. * @param controller the controller used to store the root stations * @param owner the owner of the dialog of a {@link ScreenDockStation}, * may be null */ 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( 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 null. * @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 null */ 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 getDockables(){ return listDockables(); } /** * Adds a listener to this frontend. The listener will recieve 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 listener to this frontend. The listener will be notified * when a {@link Dockable} will be or is closed.
* 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 listener 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 factoryId. 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 null */ 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 true, then factory is registered * as {@link #registerBackupFactory(DockFactory) backup factory} as well. */ public void registerFactory( DockFactory 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.
* 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 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 null */ 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 dockable. The frontend also assumes that dockable * can be reused when reading a setting. That means, that the factory which * matches the key of dockable does not create a new instance * when reading the preferences of dockable. 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 dockable or * id is null, or if id 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}.
* 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, null 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 null * @see #setMissingDockableStrategy(MissingDockableStrategy) */ public MissingDockableStrategy getMissingDockable() { return missingDockable; } /** * Sets the strategy this {@link DockFrontend} should use to read {@link Setting}s.
* WARNING: 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 null */ 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 null */ 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 getNamedDockables(){ Map result = new HashMap(); for( Map.Entry 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 name. * @param name the name of a {@link Dockable} * @return the element or null */ public Dockable getDockable( String name ){ DockInfo info = getInfo( name ); return info == null ? null : info.dockable; } /** * Searches the name of dockable as it was given to * {@link #addDockable(String, Dockable)}. * @param dockable some element whose name is searched * @return the name or null */ public String getNameOf( Dockable dockable ){ if( dockable == null ) throw new NullPointerException( "dockable is null" ); for( Map.Entry 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.
* 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 station or name * is null, or if name 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.
* 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 station or name * is null, or if name is not unique. * @deprecated replaced by {@link #addRoot(String, DockStation)}, since * name 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 null */ 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 representative 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 null */ 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 null */ public DockStation getDefaultStation() { if( defaultStation != null ) return defaultStation; Iterator 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.
* Note that this 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 * Dockablename to false. * @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 name denotes an entry that can be empty. * @param name some unique identifier * @return true if information about a Dockable name * is stored even if the element is null */ public boolean isEmpty( String name ){ return empty.contains( name ); } /** * Gets a list of all keys that are marked as empty. * @param all if true then just all keys are returned, if * false then only those keys are returned for which no * {@link Dockable} is registered. * @return the list of keys marked as empty, may be null * @see #addEmpty(String) * @see #removeEmpty(String) */ public String[] listEmpty( boolean all ){ if( all ){ return empty.toArray( new String[ empty.size() ] ); } else{ List result = new ArrayList(); 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 null. * @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 dockable. * @param dockable the element whose location might be known * @return true if the location of dockable 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 id. This method does not check whether there actuall is a visible dockable with the * given id. * @param id the id of some entry * @return true if there is an entry for id 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 null */ 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 null */ 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 null */ 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 null */ 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 false. This might be * null if no setting was yet applied. * @return the setting, can be null */ 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 true. This might be * null if no setting was yet applied or a non-entry setting * was applied. * @return the setting, can be null */ public Setting getLastAppliedEntrySetting() { return lastAppliedEntrySetting; } /** * Gets a set of the names of all known settings. * @return the set of names */ public Set getSettings(){ Set keys = settings.keySet(); return Collections.unmodifiableSet( keys ); } /** * Gets the {@link Setting} which stores locations and other information under the key name. * @param name a key that was used for calling {@link #save(String)} * @return the setting or null 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 null 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 null */ public void setCurrentSettingName( String setting ){ currentSetting = setting; } /** * Stores the setting setting with the given name. * @param name the name of the setting * @param setting the new setting, not null */ public void setSetting( String name, Setting setting ){ if( setting == null ){ throw new IllegalArgumentException( "setting is null" ); } settings.put( name, setting ); } /** * Tells whether dockable is hidden or not. A {@link Dockable} is hidden if either * {@link #isHiddenRootStation(DockElement)} is true or if {@link #isShown(Dockable)} is false.
* @param dockable the element whose state is asked * @return true if dockable is not visible * @see #isHiddenRootStation(DockElement) */ public boolean isHidden( Dockable dockable ){ return !isShown( dockable ); } /** * Tells whether dockable 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 true if dockable is visible * @see #isHiddenRootStation(DockElement) */ public boolean isShown( Dockable dockable ){ return controller.getRegister().willBeRegistered( dockable ); } /** * Tells whether element is a root-station and at the same time a {@link Dockable} * without parent. * @param element the element to check * @return true if element 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.
* As a side effect a value of false will make new, unmodified {@link Dockable}s * visible when loading a layout (by calling a method like {@link #readXML(XElement)}).
* The default value is false, 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 dockable or not. * @param dockable the element whose state is asked, must be known to this * frontend. * @return true if dockable 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 dockable. Changing this * property has an immediate effect on the action.
* 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 dockable 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 true if the action is shown on * {@link #isHideable(Dockable) hideable} dockables or false * 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 entry {@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 dockable should be stored * for entry {@link Setting}s. * @param dockable the element whose state is to be set * @param layout the new state * @throws IllegalArgumentException if dockable 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 id should be stored * for entry {@link Setting}s. * @param id the id of the element whose state is to be changed * @param layout the new state * @throws IllegalArgumentException if id 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 dockable should be stored * for entry {@link Setting}s. * @param dockable the element whose state is asked * @return the state * @throws IllegalArgumentException if dockable 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 id should be stored * for entry {@link Setting}s. * @param id the identifier of an element whose state is requested * @return the state * @throws IllegalArgumentException if id 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 dockable such that it is aside aside. 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: * DockFronted frontend = ... * Dockable dockable = ... * * frontend.addDockable( "x", dockable ); * frontend.setLocationAside( dockable, someOtherDockable ); * frontend.show( dockable );
* Clients may also combine this feature with the {@link DockController#getFocusHistory() focus history} to access * the last focused {@link Dockable} for the argument aside. * @param dockable the item whose location is to be set * @param aside the new neighbor of dockable * @return whether the operation was a success, an operation requires at least that both dockable * and aside were added to this {@link DockFrontend}, and that aside currently * has a location. There is no need for aside 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 dockable 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 dockable 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 dockable invisible. The location of dockable * 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 dockable invisible. The location of dockable * 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 null */ 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" inforation should be saved, or all the information. The default * value should be true * @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 name 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 true if only information for normal entries should be extracted, false 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 name 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 true if only the information for an ordinary * entry should be stored, false 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 setting.
* 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 true if only information for an ordinary * entry should be extracted, false 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}.
* Note: 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 null * @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 elements.
* 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 null * @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. *
    *
  • Tries to read empty {@link DockInfo}s which have a layout and a position.
  • *
  • Tries to replace raw data in {@link DockLayoutComposition}s
  • *
*/ 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 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 factory. * @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 * factory 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 normalizedFactory = (DockFactory)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 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 listShownDockables(){ Set set = new HashSet(); 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 listDockables(){ List result = new ArrayList( 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 listFrontendEntries(){ return new ArrayList( dockables.values() ); } /** * Gets all the information known about the {@link Dockable} with * name key. * @param key some key of a dockable * @return all information known or null if nothing is available */ public FrontendEntry getFrontendEntry( String key ){ return dockables.get( key ); } /** * Gets all the information known about the registered dockable. * @param dockable some dockable that may be registered at this {@link DockFrontend} * @return any available information about dockable, null if * dockable is unknown to this frontend */ public FrontendEntry getFrontendEntry( Dockable dockable ){ return getInfo( dockable ); } /** * Removes all child-parent relations expect the ones filtered out * by ignore. * @param ignore a filter, never null */ protected void clean( DockSituationIgnore ignore ){ for( RootInfo root : roots.values() ){ if( !ignore.ignoreElement( root.getStation() )){ clean( root.getStation(), ignore ); } } } /** * Removes recursively all children from station, but only * if the children are not filtered by ignore. * @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 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 name. * @param name the name of the setting to delete * @return true if the setting was deleted, false * 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 out. * @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 blop into out. * @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 setting is an ordinary entry, or * the finall 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 in. 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 in. 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 in 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 in cannot be read properbly */ 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 usuall. * @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 element */ public void writeXML( XElement element ){ writeBlopXML( writeBlop(), element ); } /** * Writes all the {@link Setting}s of blop into element, 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 element */ 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 setting is an ordinary entry, or * the finall 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 element using all the facotries 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 element */ 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 usuall. * @param element the xml element containg 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 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 blop. * @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 false 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 dockable. * @param dockable the element whose states are asked * @return the states or null */ 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 null */ private DockInfo getInfo( String name ){ return dockables.get( name ); } /** * Gets information about the root station. * @param station a root * @return the information or null */ private RootInfo getRoot( DockStation station ){ for( RootInfo info : roots.values() ) if( info.getStation() == station ) return info; return null; } /** * Gets information about the root of dockable. * @param dockable some dockable * @return the information or null */ 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 dockable 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 null */ protected void fireAllHidden( Dockable dockable, final Set 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 dockable 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 null */ protected void fireAllShown( Dockable dockable, final Set 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; /** true if the element has a "close"-action, false 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 null */ 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 null */ 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 stroke = new PropertyValue( 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 ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy