/*
* 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.dock.common;
import java.awt.Component;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.swing.FocusManager;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import bibliothek.extension.gui.dock.preference.PreferenceModel;
import bibliothek.extension.gui.dock.preference.PreferenceStorage;
import bibliothek.extension.gui.dock.theme.BubbleTheme;
import bibliothek.extension.gui.dock.theme.EclipseTheme;
import bibliothek.extension.gui.dock.theme.FlatTheme;
import bibliothek.gui.DockController;
import bibliothek.gui.DockFrontend;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockTheme;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.DockElement;
import bibliothek.gui.dock.DockFactory;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.ScreenDockStation;
import bibliothek.gui.dock.SplitDockStation;
import bibliothek.gui.dock.StackDockStation;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.common.action.CloseActionFactory;
import bibliothek.gui.dock.common.action.util.CDefaultDockActionDistributor;
import bibliothek.gui.dock.common.event.CControlListener;
import bibliothek.gui.dock.common.event.CDockableAdapter;
import bibliothek.gui.dock.common.event.CDockablePropertyListener;
import bibliothek.gui.dock.common.event.CDockableStateListener;
import bibliothek.gui.dock.common.event.CDoubleClickListener;
import bibliothek.gui.dock.common.event.CFocusListener;
import bibliothek.gui.dock.common.event.CKeyboardListener;
import bibliothek.gui.dock.common.event.CVetoClosingListener;
import bibliothek.gui.dock.common.event.CVetoFocusListener;
import bibliothek.gui.dock.common.event.ResizeRequestListener;
import bibliothek.gui.dock.common.group.CGroupBehavior;
import bibliothek.gui.dock.common.grouping.CGroupingBehavior;
import bibliothek.gui.dock.common.grouping.DefaultCGroupingBehavior;
import bibliothek.gui.dock.common.grouping.DockableGrouping;
import bibliothek.gui.dock.common.grouping.GroupingDockLocationListener;
import bibliothek.gui.dock.common.grouping.GroupingHistoryRewriter;
import bibliothek.gui.dock.common.intern.CControlAccess;
import bibliothek.gui.dock.common.intern.CControlFactory;
import bibliothek.gui.dock.common.intern.CDockFrontend;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CDockableAccess;
import bibliothek.gui.dock.common.intern.CListenerCollection;
import bibliothek.gui.dock.common.intern.CPlaceholderStrategy;
import bibliothek.gui.dock.common.intern.CancelDragAndDropOperation;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.CommonMultipleDockableFactory;
import bibliothek.gui.dock.common.intern.CommonMultipleDockableLayout;
import bibliothek.gui.dock.common.intern.CommonSingleDockableFactory;
import bibliothek.gui.dock.common.intern.ControlVetoClosingListener;
import bibliothek.gui.dock.common.intern.ControlVetoFocusListener;
import bibliothek.gui.dock.common.intern.EfficientControlFactory;
import bibliothek.gui.dock.common.intern.MutableCControlRegister;
import bibliothek.gui.dock.common.intern.action.CActionImportanceOrder;
import bibliothek.gui.dock.common.intern.action.CActionOffer;
import bibliothek.gui.dock.common.intern.action.CButtonContentFilter;
import bibliothek.gui.dock.common.intern.station.CFlapLayoutManager;
import bibliothek.gui.dock.common.intern.station.CLockedResizeLayoutManager;
import bibliothek.gui.dock.common.intern.station.CScreenDockStationWindowClosingStrategy;
import bibliothek.gui.dock.common.intern.station.CommonDockStation;
import bibliothek.gui.dock.common.intern.station.CommonDockStationFactory;
import bibliothek.gui.dock.common.intern.ui.CDisablingStrategy;
import bibliothek.gui.dock.common.intern.ui.CSingleParentRemover;
import bibliothek.gui.dock.common.intern.ui.CommonSingleTabDecider;
import bibliothek.gui.dock.common.intern.ui.ExtendedModeAcceptance;
import bibliothek.gui.dock.common.intern.ui.StackableAcceptance;
import bibliothek.gui.dock.common.intern.ui.WorkingAreaAcceptance;
import bibliothek.gui.dock.common.layout.FullLockConflictResolver;
import bibliothek.gui.dock.common.layout.RequestDimension;
import bibliothek.gui.dock.common.mode.CLocationMode;
import bibliothek.gui.dock.common.mode.CLocationModeManager;
import bibliothek.gui.dock.common.mode.CMaximizedMode;
import bibliothek.gui.dock.common.mode.CMaximizedModeArea;
import bibliothek.gui.dock.common.mode.CStationContainerHistoryRewriter;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CControlPerspective;
import bibliothek.gui.dock.common.perspective.CDockablePerspective;
import bibliothek.gui.dock.common.perspective.CStackPerspective;
import bibliothek.gui.dock.common.perspective.CStationPerspective;
import bibliothek.gui.dock.common.perspective.CommonElementPerspective;
import bibliothek.gui.dock.common.perspective.DefaultMissingPerspectiveFactory;
import bibliothek.gui.dock.common.perspective.MissingPerspectiveStrategy;
import bibliothek.gui.dock.common.theme.ThemeMap;
import bibliothek.gui.dock.common.theme.eclipse.CommonEclipseThemeConnector;
import bibliothek.gui.dock.control.DockRegister;
import bibliothek.gui.dock.control.DockRelocatorMode;
import bibliothek.gui.dock.control.DockableSelector;
import bibliothek.gui.dock.control.focus.DefaultFocusStrategy;
import bibliothek.gui.dock.control.focus.FocusStrategyRequest;
import bibliothek.gui.dock.control.relocator.DefaultDockRelocator;
import bibliothek.gui.dock.disable.DisablingStrategy;
import bibliothek.gui.dock.displayer.SingleTabDecider;
import bibliothek.gui.dock.dockable.DockableMovingImageFactory;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.event.DockableFocusEvent;
import bibliothek.gui.dock.event.DockableFocusListener;
import bibliothek.gui.dock.event.DoubleClickListener;
import bibliothek.gui.dock.event.KeyboardListener;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.facile.mode.LocationModeManager;
import bibliothek.gui.dock.facile.station.split.ConflictResolver;
import bibliothek.gui.dock.facile.station.split.DefaultConflictResolver;
import bibliothek.gui.dock.focus.DockableSelection;
import bibliothek.gui.dock.frontend.FrontendEntry;
import bibliothek.gui.dock.frontend.MissingDockableStrategy;
import bibliothek.gui.dock.layout.DockSituationIgnore;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.perspective.PerspectiveElement;
import bibliothek.gui.dock.perspective.PerspectiveStation;
import bibliothek.gui.dock.station.Combiner;
import bibliothek.gui.dock.station.DisplayerFactory;
import bibliothek.gui.dock.station.StationPaint;
import bibliothek.gui.dock.station.flap.FlapWindow;
import bibliothek.gui.dock.station.layer.DockStationDropLayerFactory;
import bibliothek.gui.dock.station.screen.ScreenDockWindow;
import bibliothek.gui.dock.station.stack.StackDockPerspective;
import bibliothek.gui.dock.station.stack.StackDockStationFactory;
import bibliothek.gui.dock.station.stack.StackDockStationLayout;
import bibliothek.gui.dock.station.stack.action.DockActionDistributor;
import bibliothek.gui.dock.station.stack.menu.CombinedMenuContent;
import bibliothek.gui.dock.station.stack.tab.TabPane;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.support.mode.HistoryRewriter;
import bibliothek.gui.dock.support.util.ApplicationResource;
import bibliothek.gui.dock.support.util.ApplicationResourceManager;
import bibliothek.gui.dock.themes.BasicTheme;
import bibliothek.gui.dock.themes.ColorScheme;
import bibliothek.gui.dock.themes.ThemeFactory;
import bibliothek.gui.dock.themes.basic.action.DockActionImportanceOrder;
import bibliothek.gui.dock.themes.border.BorderModifier;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.util.AWTComponentCaptureStrategy;
import bibliothek.gui.dock.util.BackgroundPaint;
import bibliothek.gui.dock.util.DirectWindowProvider;
import bibliothek.gui.dock.util.IconManager;
import bibliothek.gui.dock.util.NullWindowProvider;
import bibliothek.gui.dock.util.Priority;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.WindowProvider;
import bibliothek.gui.dock.util.extension.Extension;
import bibliothek.gui.dock.util.extension.ExtensionManager;
import bibliothek.gui.dock.util.extension.ExtensionName;
import bibliothek.gui.dock.util.icon.DefaultIconScheme;
import bibliothek.gui.dock.util.property.ConstantPropertyFactory;
import bibliothek.gui.dock.util.text.DefaultTextScheme;
import bibliothek.util.Filter;
import bibliothek.util.Path;
import bibliothek.util.Version;
import bibliothek.util.xml.XElement;
import bibliothek.util.xml.XException;
import bibliothek.util.xml.XIO;
/**
* Manages the interaction between {@link SingleCDockable}, {@link MultipleCDockable}
* and {@link CStation}s.<br>
* Clients which do no longer need a {@link CControl} can call {@link #destroy()}
* to free resources.<br>
* A <code>CControl</code> is an interface between the application and the framework. The task of
* <code>CControl</code> is to provide access for actions that affect the entire realm. Such
* actions may include:
* <ul>
* <li>add/remove a global listener.</li>
* <li>add/remove/access {@link CDockable}s, {@link CStation}s and {@link CStationContainer}s.</li>
* <li>add/remove/access factories for {@link CDockable}s.</li>
* <li>read/write/apply the layout, this includes reading/writing from files.</li>
* <li>store properties, like for example the {@link #setTheme(String) theme}.</li>
* </ul>
* @author Benjamin Sigg
*
*/
public class CControl {
/**
* A key for this {@link CControl}. Will be set with the highest priority. To be used
* wherever a {@link DockController} but not a {@link CControl} is accessible.
*/
public static final PropertyKey<CControl> CCONTROL = new PropertyKey<CControl>( "ccontrol" );
/**
* Name of an {@link ExtensionName} that adds extensions to this control. The extensions
* are of type {@link Object} and are not actually used. Rather this extension informs
* {@link Extension}s that a {@link CControl} has been created.
*/
public static final Path CCONTROL_EXTENSION = new Path( "dock.ccontrol" );
/** name of a parameter of an {@link ExtensionName} that points to <code>this</code> */
public static final String EXTENSION_PARAM = "control";
/**
* {@link KeyStroke} used to change a {@link CDockable} into maximized-state,
* or to go out of maximized-state when needed.
*/
public static final PropertyKey<KeyStroke> KEY_MAXIMIZE_CHANGE =
new PropertyKey<KeyStroke>( "ccontrol.maximize_change" );
/**
* {@link KeyStroke} used to change a {@link CDockable} into
* maximized-state.
*/
public static final PropertyKey<KeyStroke> KEY_GOTO_MAXIMIZED =
new PropertyKey<KeyStroke>( "ccontrol.goto_maximized" );
/**
* {@link KeyStroke} used to change a {@link CDockable} into
* normalized-state.
*/
public static final PropertyKey<KeyStroke> KEY_GOTO_NORMALIZED =
new PropertyKey<KeyStroke>( "ccontrol.goto_normalized" );
/**
* {@link KeyStroke} used to change a {@link CDockable} into
* minimized-state.
*/
public static final PropertyKey<KeyStroke> KEY_GOTO_MINIMIZED =
new PropertyKey<KeyStroke>( "ccontrol.goto_minimized" );
/**
* {@link KeyStroke} used to change a {@link CDockable} into
* externalized-state.
*/
public static final PropertyKey<KeyStroke> KEY_GOTO_EXTERNALIZED =
new PropertyKey<KeyStroke>( "ccontrol.goto_externalized" );
/**
* {@link KeyStroke} used to close a {@link CDockable}.
*/
public static final PropertyKey<KeyStroke> KEY_CLOSE =
new PropertyKey<KeyStroke>( "ccontrol.close" );
/**
* {@link KeyStroke} that can be hit during a drag and drop operation, that will cancel the operation (default is ESCAPE).
*/
public static final PropertyKey<KeyStroke> KEY_CANCEL_OPERATION =
new PropertyKey<KeyStroke>( "ccontrol.cancel_dnd" );
/**
* {@link ConflictResolver} used to determine what happens when there is
* a conflict between two resize requests on a {@link SplitDockStation} like
* {@link CGridArea}, {@link CWorkingArea} or {@link CContentArea}.
* @see DefaultConflictResolver
* @see FullLockConflictResolver
*/
public static final PropertyKey<ConflictResolver<RequestDimension>> RESIZE_LOCK_CONFLICT_RESOLVER =
new PropertyKey<ConflictResolver<RequestDimension>>(
"ccontrol.resize_lock_conflict_resolver",
new ConstantPropertyFactory<ConflictResolver<RequestDimension>>( new DefaultConflictResolver<RequestDimension>()), true );
/**
* This factory creates the actions that close dockables.
*/
public static final PropertyKey<CloseActionFactory> CLOSE_ACTION_FACTORY =
new PropertyKey<CloseActionFactory>( "ccontrol.closeActionFactory",
new ConstantPropertyFactory<CloseActionFactory>( CloseActionFactory.DEFAULT ), true );
/**
* The grouping behavior defines how {@link Dockable}s tend to automatically group together.
*/
public static final PropertyKey<CGroupingBehavior> GROUPING_BEHAVIOR =
new PropertyKey<CGroupingBehavior>( "ccontrol.groupingBehavior",
new ConstantPropertyFactory<CGroupingBehavior>( new DefaultCGroupingBehavior() ), true );
/** the unique id of the station that handles the externalized dockables */
public static final String EXTERNALIZED_STATION_ID = "external";
/** the unique id of the default-{@link CContentArea} created by this control */
public static final String CONTENT_AREA_STATIONS_ID = "ccontrol";
/** connection to the real DockingFrames */
private CDockFrontend frontend;
/** strategy what to do when reading layout information of a missing dockable */
private MissingCDockableStrategy missingStrategy = MissingCDockableStrategy.PURGE;
/** access to internal methods of some {@link CDockable}s */
private Map<CDockable, CDockableAccess> accesses = new HashMap<CDockable, CDockableAccess>();
/** a manager allowing the user to change the extended-state of some {@link CDockable}s */
private CLocationModeManager locationManager;
/** the default location of newly opened {@link CDockable}s */
private CLocation defaultLocation;
/** a list of available {@link DockTheme}s */
private ThemeMap themes;
/** Access to the internal methods of this control */
private CControlAccess access = new Access();
/** A strategy that can create missing {@link CStationPerspective} */
private MissingPerspectiveStrategy missingPerspectiveStrategy = new DefaultMissingPerspectiveFactory();
/** manager used to store and read configurations */
private ApplicationResourceManager resources = new ApplicationResourceManager();
/** a list of listeners which are to be informed when this control is no longer in use */
private List<DestroyHook> hooks = new ArrayList<DestroyHook>();
/** factory used to create new elements for this control */
private CControlFactory factory;
/** the {@link CDockable}s and other elements used by this control */
private MutableCControlRegister register;
/** the list of listeners to this {@link CControl} */
private List<CControlListener> listeners = new ArrayList<CControlListener>();
/** the list of resize-listeners */
private List<ResizeRequestListener> resizeListeners = new ArrayList<ResizeRequestListener>();
/** the collection of global listeners */
private CListenerCollection listenerCollection = new CListenerCollection();
/** the preferences used by this instance of {@link CControl} */
private PreferenceStorage preferences = new PreferenceStorage();
/** the model which is used to translate between {@link #preferences} and <code>this</code> */
private PreferenceModel preferenceModel;
/** if <code>true</code>, then minimizing a Dockable will automatically transfer focus to a not minimized Dockable */
private boolean transferFocusOnMinimize = true;
/**
* Creates a new control. Note that a control should know the main
* window of the application, thus {@link CControl#CControl(WindowProvider)}
* would be the better choice than this constructor.
*/
public CControl(){
this( new NullWindowProvider() );
}
/**
* Creates a new control
* @param frame the main frame of the application, needed to create
* dialogs for externalized {@link CDockable}s
*/
public CControl( JFrame frame ){
this( frame == null ? new NullWindowProvider() : new DirectWindowProvider( frame ) );
}
/**
* Creates a new control
* @param restrictedEnvironment whether this application runs in a
* restricted environment and is not allowed to listen for global events.
* @deprecated it is not necessary to set the <code>restrictedEnvironment</code> parameter anymore, the framework
* will choose a fitting value itself
*/
@Deprecated
public CControl( boolean restrictedEnvironment ){
this( new NullWindowProvider() );
getController().setRestrictedEnvironment( restrictedEnvironment );
}
/**
* Creates a new control
* @param window a provider for the main window of this application. Needed
* to create dialogs for externalized {@link CDockable}s. Must not be <code>null</code>, but
* its search method may return <code>null</code>
*/
public CControl( WindowProvider window ){
this( window, new EfficientControlFactory() );
}
/**
* Creates a new control
* @param frame the main frame of the application, needed to create
* dialogs for externalized {@link CDockable}s
* @param restrictedEnvironment whether this application runs in a
* restricted environment and is not allowed to listen for global events.
* @deprecated it is not necessary to set the <code>restrictedEnvironment</code> parameter anymore, the framework
* will choose a fitting value itself
*/
@Deprecated
public CControl( JFrame frame, boolean restrictedEnvironment ){
this( frame == null ? new NullWindowProvider() : new DirectWindowProvider( frame ) );
getController().setRestrictedEnvironment( restrictedEnvironment );
}
/**
* Creates a new control
* @param window a provider for the main window of this application. Needed
* to create dialogs for externalized {@link CDockable}s. Must not be <code>null</code>, but
* its search method may return <code>null</code>
* @param restrictedEnvironment whether this application runs in a
* restricted environment and is not allowed to listen for global events.
* @deprecated it is not necessary to set the <code>restrictedEnvironment</code> parameter anymore, the framework
* will choose a fitting value itself
*/
@Deprecated
public CControl( WindowProvider window, boolean restrictedEnvironment ){
this( window );
getController().setRestrictedEnvironment( restrictedEnvironment );
}
/**
* Creates a new control
* @param frame the main frame of the application, needed to create
* dialogs for externalized {@link CDockable}s
* @param factory a factory which is used to create new elements for this
* control.
*/
public CControl( JFrame frame, CControlFactory factory ){
this( frame == null ? new NullWindowProvider() : new DirectWindowProvider( frame ), factory );
}
/**
* Creates a new control
* @param window a provider for the main window of this application. Needed
* to create dialogs for externalized {@link CDockable}s. Must not be <code>null</code>, but
* its search method may return <code>null</code>
* @param factory a factory which is used to create new elements for this
* control.
*/
public CControl( WindowProvider window, CControlFactory factory ){
this( window, factory, true );
}
/**
* Creates a new control
* @param window a provider for the main window of this application. Needed
* to create dialogs for externalized {@link CDockable}s. Must not be <code>null</code>, but
* its search method may return <code>null</code>
* @param factory a factory which is used to create new elements for this
* control.
* @param init if <code>true</code> then this constructor calls {@link #init(WindowProvider, CControlFactory)},
* otherwise this constructor does nothing and returns immediately. Subclasses should call
* {@link #init(WindowProvider, CControlFactory)} in that case.
*/
protected CControl( WindowProvider window, CControlFactory factory, boolean init ){
if( init ){
init( window, factory );
}
}
/**
* Initializes the fields of this {@link CControl}. This method is called during construction
* of this {@link CControl}. Subclasses may use {@link #CControl(WindowProvider, CControlFactory, boolean)}
* to create an uninitialized {@link CControl} and then call this method by themselves.
* @param window a provider for the main window of this application. Needed
* to create dialogs for externalized {@link CDockable}s. Must not be <code>null</code>, but
* its search method may return <code>null</code>
* @param factory a factory which is used to create new elements for this
* control.
*/
protected void init( WindowProvider window, CControlFactory factory ){
if( window == null ){
throw new IllegalArgumentException( "window must not be null, however its search method may return null" );
}
this.factory = factory;
register = factory.createRegister( this );
DockController controller = factory.createController( this );
controller.getProperties().set( CCONTROL, this, Priority.CLIENT );
controller.getProperties().finalize( CCONTROL );
controller.setSingleParentRemover( new CSingleParentRemover( this ) );
initExtensions( controller );
initFocusListeners( controller );
initInputListener( controller );
initTransferFocusOnMinimize( controller );
frontend = factory.createFrontend( access, controller );
frontend.setOwner( window );
frontend.setMissingDockableStrategy( new MissingDockableStrategy(){
public boolean shouldStoreHidden( String key ) {
return shouldStore( key );
}
public boolean shouldStoreShown( String key ) {
return shouldStore( key );
}
public <L> boolean shouldCreate( DockFactory<?,?,L> factory, L data ) {
if( factory instanceof CommonMultipleDockableFactory && data instanceof CommonMultipleDockableLayout ){
return CControl.this.shouldCreate(
((CommonMultipleDockableFactory)factory).getFactory(),
(CommonMultipleDockableLayout)data );
}
return false;
}
});
setIgnoreWorkingForEntry( true );
frontend.setShowHideAction( false );
frontend.getController().addActionOffer( new CActionOffer( this ) );
frontend.getController().getRegister().addDockRegisterListener( new DockRegisterAdapter(){
@Override
public void dockableRegistered( DockController controller, Dockable dockable ) {
if( dockable instanceof CommonDockable ){
CDockable cdock = ((CommonDockable)dockable).getDockable();
CDockableAccess access = accesses.get( cdock );
if( access != null ){
access.informVisibility( true );
}
for( CControlListener listener : listeners() )
listener.opened( CControl.this, cdock );
}
}
@Override
public void dockableUnregistered( DockController controller, Dockable dockable ) {
if( dockable instanceof CommonDockable ){
CDockable cdock = ((CommonDockable)dockable).getDockable();
CDockableAccess access = accesses.get( cdock );
if( access != null ){
access.informVisibility( false );
}
for( CControlListener listener : listeners() )
listener.closed( CControl.this, cdock );
if( cdock instanceof MultipleCDockable ){
MultipleCDockable multiple = (MultipleCDockable)cdock;
if( multiple.isRemoveOnClose() ){
removeDockable( multiple );
}
}
}
}
});
frontend.getController().getFocusController().addVetoListener( new ControlVetoFocusListener( this, listenerCollection.getVetoFocusListener() ) );
frontend.getController().getFocusController().setStrategy( new DefaultFocusStrategy( frontend.getController() ){
public Component getFocusComponent( FocusStrategyRequest request ){
Component mouseClicked = request.getMouseClicked();
Dockable dockable = request.getDockable();
if( mouseClicked != null ){
if( (mouseClicked.isFocusable() && !excluded( mouseClicked, request )) || focusable( mouseClicked, request )){
return mouseClicked;
}
}
if( dockable instanceof CommonDockable ){
Component result = ((CommonDockable)dockable).getDockable().getFocusComponent();
if( result != null ){
return result;
}
}
return super.getFocusComponent( request );
}
});
frontend.addVetoableListener( new ControlVetoClosingListener( this, listenerCollection.getVetoClosingListener() ) );
frontend.getController().addAcceptance( new StackableAcceptance() );
frontend.getController().addAcceptance( new WorkingAreaAcceptance( access ) );
frontend.getController().addAcceptance( new ExtendedModeAcceptance( access ) );
initFactories();
themes = new ThemeMap( this );
initPersistentStorage();
initExtendedModes();
initProperties();
initIcons();
initTexts();
setTheme( ThemeMap.KEY_SMOOTH_THEME );
controller.getExtensions().load( new ExtensionName<Object>( CCONTROL_EXTENSION, Object.class, EXTENSION_PARAM, this ) );
}
/**
* Initializes additional {@link Extension}s and registers them at the
* {@link ExtensionManager} of <code>controller</code>.
* @param controller the controller for which additional extensions should be
* loaded
*/
protected void initExtensions( DockController controller ){
ExtensionManager manager = controller.getExtensions();
String[] list = { "glass.eclipse.CGlassExtension",
"bibliothek.gui.dock.toolbar.CToolbarExtension" };
for( String className : list ){
try {
Class<?> clazz = Class.forName( className );
Object extension = clazz.newInstance();
if( extension instanceof Extension ){
manager.add( (Extension)extension );
}
} catch( ClassNotFoundException e ) {
// ignore
} catch( InstantiationException e ) {
e.printStackTrace();
} catch( IllegalAccessException e ) {
// ignore
}
}
}
/**
* Creates and adds the listeners needed to track the focus.
* @param controller the controller which will be observed
*/
private void initFocusListeners( DockController controller ){
controller.addDockableFocusListener( new DockableFocusListener(){
public void dockableFocused( DockableFocusEvent event ) {
Dockable oldFocused = event.getOldFocusOwner();
Dockable newFocused = event.getNewFocusOwner();
if( oldFocused instanceof CommonDockable ){
CDockable oldC = ((CommonDockable)oldFocused).getDockable();
CDockableAccess access = accesses.get( oldC );
if( access != null ){
access.getFocusListener().focusLost( oldC );
}
listenerCollection.getFocusListener().focusLost( oldC );
}
if( newFocused instanceof CommonDockable ){
CDockable newC = ((CommonDockable)newFocused).getDockable();
CDockableAccess access = accesses.get( newC );
if( access != null ){
access.getFocusListener().focusGained( newC );
}
listenerCollection.getFocusListener().focusGained( newC );
}
}
});
}
/**
* Adds a {@link CDockableStateListener} to this {@link CControl}, if a {@link CDockable} is
* {@link ExtendedMode#MINIMIZED minimized}, another {@link Dockable} receives the focus. Subclasses
* may override this method to disable or modify the feature.
* @param controller the controller used by this {@link CControl}
* @see #setTransferFocusOnMinimize(boolean)
*/
protected void initTransferFocusOnMinimize( DockController controller ){
addStateListener( new CDockableAdapter(){
@Override
public void extendedModeChanged( CDockable dockable, ExtendedMode mode ){
if( transferFocusOnMinimize ){
if( mode == ExtendedMode.MINIMIZED ){
Dockable[] history = getController().getFocusHistory().getHistory();
for( int i = history.length-1; i >= 0; i-- ){
Dockable next = history[i];
if( next instanceof CommonDockable ){
CDockable cdockable = ((CommonDockable)next).getDockable();
if( cdockable.getExtendedMode() != ExtendedMode.MINIMIZED ){
getController().setFocusedDockable( cdockable.intern(), true );
break;
}
}
}
}
}
}
});
}
private void initInputListener( DockController controller ){
controller.getKeyboardController().addListener( new KeyboardListener(){
public boolean keyPressed( DockElement element, KeyEvent event ) {
if( element instanceof CommonDockable ){
CDockable source = ((CommonDockable)element).getDockable();
CDockableAccess access = accesses.get( source );
if( access != null ){
if( access.getKeyboardListener().keyPressed( source, event ))
return true;
}
return listenerCollection.getKeyboardListener().keyPressed( source, event );
}
return false;
}
public boolean keyReleased( DockElement element, KeyEvent event ) {
if( element instanceof CommonDockable ){
CDockable source = ((CommonDockable)element).getDockable();
CDockableAccess access = accesses.get( source );
if( access != null ){
if( access.getKeyboardListener().keyReleased( source, event ))
return true;
}
return listenerCollection.getKeyboardListener().keyReleased( source, event );
}
return false;
}
public boolean keyTyped( DockElement element, KeyEvent event ) {
if( element instanceof CommonDockable ){
CDockable source = ((CommonDockable)element).getDockable();
CDockableAccess access = accesses.get( source );
if( access != null ){
if( access.getKeyboardListener().keyTyped( source, event ))
return true;
}
return listenerCollection.getKeyboardListener().keyTyped( source, event );
}
return false;
}
public DockElement getTreeLocation() {
return null;
}
});
controller.getDoubleClickController().addListener( new DoubleClickListener(){
public boolean process( Dockable dockable, MouseEvent event ) {
if( dockable instanceof CommonDockable ){
CDockable source = ((CommonDockable)dockable).getDockable();
CDockableAccess access = accesses.get( source );
if( access != null ){
if( access.getDoubleClickListener().clicked( source, event ))
return true;
}
return listenerCollection.getDoubleClickListener().clicked( source, event );
}
return false;
}
public DockElement getTreeLocation() {
return null;
}
});
addKeyboardListener( new CancelDragAndDropOperation( this ) );
}
/**
* Sets up the {@link #locationManager}.
*/
private void initExtendedModes(){
locationManager = new CLocationModeManager( access );
HistoryRewriter<Location, CLocationMode> validation = new CStationContainerHistoryRewriter( this );
locationManager.setHistoryRewriter( new GroupingHistoryRewriter( this, validation ));
GroupingDockLocationListener groupingListener = new GroupingDockLocationListener( this );
getController().getRegister().addDockRegisterListener( groupingListener );
getController().addDockableFocusListener( groupingListener );
initExternalizeArea();
}
/**
* Called during construction of this {@link CControl}, this method creates a new
* {@link CExternalizeArea} and registers it as root-station using the unique identifier
* {@value #EXTERNALIZED_STATION_ID}.<br>
* Subclasses may override this method and not create a {@link CExternalizeArea} or create
* a customized {@link CExternalizeArea}.
*/
protected void initExternalizeArea(){
addStation( new CExternalizeArea( this, EXTERNALIZED_STATION_ID ), true );
}
/**
* Called during construction of this {@link CControl}, this method adds {@link DockFactory}s
* to the {@link #intern() intern representation} of this {@link CControl}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected void initFactories(){
CommonSingleDockableFactory backupFactory = register.getBackupFactory();
frontend.registerFactory( backupFactory );
frontend.registerBackupFactory( backupFactory );
frontend.registerFactory( new StackDockStationFactory(){
@Override
public StackDockPerspective layoutPerspective( StackDockStationLayout layout, Map<Integer, PerspectiveDockable> children ){
CStackPerspective stack = new CStackPerspective();
layoutPerspective( stack, layout, children );
return stack;
}
});
CommonDockStationFactory stationFactory = new CommonDockStationFactory( this, null, backupFactory );
frontend.registerFactory( stationFactory );
// when creating new DockStations, the factory only creates DockStations that implement Dockable. Altough
// the factory can layout DockStations of any kind.
frontend.registerBackupFactory( (DockFactory)stationFactory );
}
/**
* Sets up the default properties. While subclasses can override this method, they should call
* this method first. Some parts of this {@link CControl} will not work correctly if the wrong
* properties are set or if no properties are set at all.
*/
protected void initProperties(){
putProperty( KEY_MAXIMIZE_CHANGE, KeyStroke.getKeyStroke( KeyEvent.VK_M, InputEvent.CTRL_MASK ) );
putProperty( KEY_GOTO_EXTERNALIZED, KeyStroke.getKeyStroke( KeyEvent.VK_E, InputEvent.CTRL_MASK ) );
putProperty( KEY_GOTO_NORMALIZED, KeyStroke.getKeyStroke( KeyEvent.VK_N, InputEvent.CTRL_MASK ) );
putProperty( KEY_CLOSE, KeyStroke.getKeyStroke( KeyEvent.VK_F4, InputEvent.CTRL_MASK ) );
putProperty( KEY_CANCEL_OPERATION, KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ) );
putProperty( SplitDockStation.LAYOUT_MANAGER, new CLockedResizeLayoutManager( this ) );
putProperty( FlapDockStation.LAYOUT_MANAGER, new CFlapLayoutManager() );
putProperty( EclipseTheme.THEME_CONNECTOR, new CommonEclipseThemeConnector( this ) );
putProperty( SingleTabDecider.SINGLE_TAB_DECIDER, new CommonSingleTabDecider( this ) );
putProperty( PlaceholderStrategy.PLACEHOLDER_STRATEGY, new CPlaceholderStrategy( this ) );
putProperty( BubbleTheme.ACTION_DISTRIBUTOR, new CDefaultDockActionDistributor() );
putProperty( FlatTheme.ACTION_DISTRIBUTOR, new CDefaultDockActionDistributor() );
putProperty( DockActionImportanceOrder.ORDER, new CActionImportanceOrder() );
putProperty( DockAction.BUTTON_CONTENT_FILTER, new CButtonContentFilter() );
putProperty( DisablingStrategy.STRATEGY, new CDisablingStrategy( this ) );
putProperty( ScreenDockStation.WINDOW_CLOSING_STRATEGY, new CScreenDockStationWindowClosingStrategy() );
}
/**
* Sets up all the default icons used in the realm of this {@link CControl}.
*/
protected void initIcons(){
DefaultIconScheme scheme = new DefaultIconScheme( getController(),
new DefaultIconScheme.IconResource( "data/bibliothek/gui/dock/core/icons.ini", null, DockController.class.getClassLoader() ),
new DefaultIconScheme.IconResource( "data/bibliothek/gui/dock/common/icons/icons.ini", null, CControl.class.getClassLoader() ));
scheme.link( PropertyKey.DOCKABLE_ICON, "dockable.default" );
scheme.link( PropertyKey.DOCK_STATION_ICON, "dockStation.default" );
getController().getIcons().setScheme( Priority.DEFAULT, scheme );
}
/**
* Sets up all the default text that is used in the realm of this {@link CControl}
*/
protected void initTexts(){
initTexts( Locale.getDefault() );
}
/**
* Re-initializes the default text that is used in the realm of this {@link CControl}.
* @param locale the new language, must not be <code>null</code>
*/
public void setLanguage( Locale locale ){
initTexts( locale );
}
/**
* Sets up all the default text that is used in the realm of this {@link CControl}
* @param locale what language to use
*/
protected void initTexts( Locale locale ){
ResourceBundle bundleCore = ResourceBundle.getBundle( "data.bibliothek.gui.dock.core.locale.text", locale, DockController.class.getClassLoader() );
ResourceBundle bundleCommon = ResourceBundle.getBundle( "data.bibliothek.gui.dock.common.locale.common", locale, CControl.class.getClassLoader() );
List<ResourceBundle> list = getController().getTexts().loadExtensionBundles( locale );
ResourceBundle[] bundles = list.toArray( new ResourceBundle[ list.size() + 2 ] );
bundles[ bundles.length-2 ] = bundleCore;
bundles[ bundles.length-1 ] = bundleCommon;
getController().getTexts().setScheme( Priority.DEFAULT, new DefaultTextScheme( bundles ) );
}
/**
* Creates new {@link ApplicationResource}s and registers them at the
* {@link #getResources() ApplicationResourceManager} of this {@link CControl}. While subclasses
* can override this method, they should be aware that missing {@link ApplicationResource}s will
* break persistent storage for the location and size of {@link Dockable}s.
*/
protected void initPersistentStorage(){
try{
addMultipleDockableFactory( "", NullMultipleCDockableFactory.NULL, false );
resources.put( "ccontrol.frontend", new ApplicationResource(){
public void write( DataOutputStream out ) throws IOException {
Version.write( out, Version.VERSION_1_1_1 );
frontend.write( out );
}
public void read( DataInputStream in ) throws IOException {
Version version = Version.read( in );
version.checkCurrent();
if( Version.VERSION_1_1_1.compareTo( version ) > 0 && Version.VERSION_1_0_4.compareTo( version ) <= 0 ){
readWorkingAreas( in );
}
frontend.read( in );
}
public void writeXML( XElement element ) {
frontend.writeXML( element.addElement( "frontend" ) );
}
public void readXML( XElement element ) {
frontend.readXML( element.getElement( "frontend" ) );
}
});
resources.put( "ccontrol.preferences", new ApplicationResource(){
public void read( DataInputStream in ) throws IOException {
Version version = Version.read( in );
version.checkCurrent();
preferences.read( in );
if( preferenceModel != null ){
preferences.load( preferenceModel, false );
preferenceModel.write();
}
}
public void readXML( XElement element ) {
preferences.readXML( element );
if( preferenceModel != null ){
preferences.load( preferenceModel, false );
preferenceModel.write();
}
}
public void write( DataOutputStream out ) throws IOException {
if( preferenceModel != null ){
preferenceModel.read();
preferences.store( preferenceModel );
}
Version.write( out, Version.VERSION_1_0_6 );
preferences.write( out );
}
public void writeXML( XElement element ) {
if( preferenceModel != null ){
preferenceModel.read();
preferences.store( preferenceModel );
}
preferences.writeXML( element );
}
});
}
catch( IOException ex ){
System.err.println( "Non lethal IO-error:" );
ex.printStackTrace();
}
}
/**
* Adds a listener to this control.
* @param listener the new listener
*/
public void addControlListener( CControlListener listener ){
if( listener == null )
throw new IllegalArgumentException( "Listener must not be null" );
listeners.add( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeControlListener( CControlListener listener ){
listeners.remove( listener );
}
/**
* Adds a new focus listener to this control. The listener gets informed
* about changes in the focus.
* @param listener the new listener
*/
public void addFocusListener( CFocusListener listener ){
listenerCollection.addFocusListener( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeFocusListener( CFocusListener listener ){
listenerCollection.removeFocusListener( listener );
}
/**
* Gets the currently focused {@link CDockable}. This might be <code>null</code> if some
* {@link Dockable} that is not a {@link CommonDockable} has the focus.
* @return the currently focused {@link CDockable}, can be <code>null</code>
* @see #addFocusListener(CFocusListener)
* @see DockController#getFocusedDockable()
*/
public CDockable getFocusedCDockable(){
Dockable focused = getController().getFocusedDockable();
if( focused instanceof CommonDockable ){
return ((CommonDockable)focused).getDockable();
}
return null;
}
/**
* Gets an object describing which {@link CDockable}s did have the focus in
* which order.
* @return the focus history
*/
public CFocusHistory getFocusHistory(){
return new DefaultCFocusHistory( this );
}
/**
* Adds a new veto focus listener to this control. The listener gets
* informed about pending changes in the focus.
* @param listener the new listener
*/
public void addVetoFocusListener( CVetoFocusListener listener ){
listenerCollection.addVetoFocusListener( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeVetoFocusListener( CVetoFocusListener listener ){
listenerCollection.removeVetoFocusListener( listener );
}
/**
* Adds a global state listener. This has the same effect as adding
* a state listener to each {@link CDockable} that is known to this
* control.
* @param listener the new listener
*/
public void addStateListener( CDockableStateListener listener ){
listenerCollection.addCDockableStateListener( listener );
}
/**
* Removes a global state listener.
* @param listener the listener to remove
*/
public void removeStateListener( CDockableStateListener listener ){
listenerCollection.removeCDockableStateListener( listener );
}
/**
* Adds a global property listener. This has the same effect as adding
* a property listener to each {@link CDockable} that is known to this
* control.
* @param listener the new listener
*/
public void addPropertyListener( CDockablePropertyListener listener ){
listenerCollection.addCDockablePropertyListener( listener );
}
/**
* Removes a global listener from this control.
* @param listener the listener to remove
*/
public void removePropertyListener( CDockablePropertyListener listener ){
listenerCollection.removeCDockablePropertyListener( listener );
}
/**
* Adds a global keyboard listener to this control. The listener gets
* informed whenever a key is touched on a {@link Component} which is a child
* of a {@link CDockable}.<br>
* Note: listeners directly added to a {@link CDockable} will always
* be informed first.<br>
* Note: if a listener processes the event, then the other listeners will
* not be informed.
* @param listener the new listener
*/
public void addKeyboardListener( CKeyboardListener listener ){
listenerCollection.addKeyboardListener( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeKeyboardListener( CKeyboardListener listener ){
listenerCollection.removeKeyboardListener( listener );
}
/**
* Adds a key listener to this control that will be informed about any
* {@link KeyEvent} that gets processed or analyzed by this control. Especially
* any event that gets forwarded to a {@link CKeyboardListener} gets also
* forwarded to <code>listener</code>.
* @param listener the new listener
*/
public void addGlobalKeyListener( KeyListener listener ){
intern().getController().getKeyboardController().addGlobalListener( listener );
}
/**
* Removes a global {@link KeyListener} from this control.
* @param listener the listener to remove
*/
public void removeGlobalKeyListener( KeyListener listener ){
intern().getController().getKeyboardController().removeGlobalListener( listener );
}
/**
* Adds a global mouse double click listener to this control. The listener gets
* informed whenever the mouse is clicked twice on a {@link Component} which
* is a child of a {@link CDockable}.<br>
* Note: listeners directly added to a {@link CDockable} will always
* be informed first.<br>
* Note: if a listener processes the event, then the other listeners will
* not be informed.
* @param listener the new listener
*/
public void addDoubleClickListener( CDoubleClickListener listener ){
listenerCollection.addDoubleClickListener( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeDoubleClickListener( CDoubleClickListener listener ){
listenerCollection.removeDoubleClickListener( listener );
}
/**
* Adds <code>listener</code> to this control, the listener will be informed whenever a set of
* {@link CDockable}s is about to be closed.<br>
* {@link CVetoClosingListener}s added to the {@link CControl} are invoked before listeners that
* are added to a {@link CDockable}.
* @param listener the new listener, not <code>null</code>
*/
public void addVetoClosingListener( CVetoClosingListener listener ){
listenerCollection.addVetoClosingListener( listener );
}
/**
* Removes a listener from this control.
* @param listener the listener to remove
*/
public void removeVetoClosingListener( CVetoClosingListener listener ){
listenerCollection.removeVetoClosingListener( listener );
}
/**
* Gets a list of currently registered listeners.
* @return the listeners
*/
private CControlListener[] listeners(){
return listeners.toArray( new CControlListener[ listeners.size() ] );
}
/**
* Informs this {@link CControl} whether location of {@link CDockable}s that are associated with a
* {@link CStation#isWorkingArea() working area} should be stored when storing a layout.<br>
* This method installs a {@link DockSituationIgnore} on the intern {@link DockFrontend}, the filter is only
* used for "normal entries", "final entries" (stored when the application shuts down) are not affected.<br>
* The default value for this property is <code>true</code>.
* @param ignore if <code>true</code> then some {@link CDockable}s are filtered out, otherwise their location
* is stored.
*/
public void setIgnoreWorkingForEntry( boolean ignore ){
if( ignore ){
frontend.setIgnoreForEntry( new DockSituationIgnore(){
public boolean ignoreChildren( DockStation station ) {
CStation<?> cstation = getStation( station );
if( cstation != null )
return cstation.isWorkingArea();
return false;
}
public boolean ignoreChildren( PerspectiveStation station ){
if( station instanceof CommonElementPerspective ){
CStationPerspective perspective = ((CommonElementPerspective)station).getElement().asStation();
if( perspective != null ){
return perspective.isWorkingArea();
}
}
return false;
}
public boolean ignoreElement( DockElement element ) {
if( element instanceof CommonDockable ){
CDockable cdockable = ((CommonDockable)element).getDockable();
if( cdockable.getWorkingArea() != null )
return true;
}
return false;
}
public boolean ignoreElement( PerspectiveElement element ){
if( element instanceof CommonElementPerspective ){
CDockablePerspective perspective = ((CommonElementPerspective)element).getElement().asDockable();
if( perspective != null ){
return perspective.getWorkingArea() != null;
}
}
return false;
}
});
}
else{
frontend.setIgnoreForEntry( null );
}
}
/**
* Reads a map telling for each {@link SingleCDockable} to which {@link CWorkingArea}
* it belongs.<br>
* This method only remains for backwards compatibility, it does not do anything but
* reading some obsolete data from <code>in</code>
* @param in the stream to read from
* @throws IOException if an I/O error occurs
*/
private void readWorkingAreas( DataInputStream in ) throws IOException{
for( int i = 0, n = in.readInt(); i<n; i++ ){
in.readUTF(); // key
in.readUTF(); // value
}
}
/**
* Frees as much resources as possible. This {@link CControl} will no longer
* work correctly after this method was called.
*/
public void destroy(){
frontend.kill();
for( DestroyHook hook : hooks )
hook.destroy();
}
/**
* Creates and adds a new {@link CWorkingArea} to this control. The area
* is not made visible by this method.
* @param uniqueId the unique id of the area
* @return the new area
*/
public CWorkingArea createWorkingArea( String uniqueId ){
CWorkingArea area = new CWorkingArea( this, uniqueId );
addDockable( area );
addStation( area, true );
return area;
}
/**
* Creates a new area where minimized {@link CDockable}s can be stored. This
* method adds the new area directly as a root station to this control.
* @param uniqueId a unique identifier
* @return the new area
*/
public CMinimizeArea createMinimizeArea( String uniqueId ){
CMinimizeArea area = new CMinimizeArea( this, uniqueId );
addStation( area, true );
return area;
}
/**
* Creates a new area where normalized {@link CDockable}s can be stored.
* This method adds the new area directly as a root station to this control
* @param uniqueId a unique identifier
* @return the new area
*/
public CGridArea createGridArea( String uniqueId ){
CGridArea area = new CGridArea( this, uniqueId );
addStation( area, true );
if( frontend.getDefaultStation() == null )
frontend.setDefaultStation( area.getStation() );
return area;
}
/**
* Creates and adds a new {@link CContentArea}.
* @param uniqueId the unique id of the new contentarea, the id must be unique
* in respect to all other contentareas which are registered at this control.
* @return the new contentarea
* @throws IllegalArgumentException if the id is not unique
* @throws NullPointerException if the id is <code>null</code>
*/
public CContentArea createContentArea( String uniqueId ){
return createContentArea( uniqueId, false );
}
private CContentArea createContentArea( String uniqueId, boolean isDefaultContentArea ){
if( uniqueId == null )
throw new NullPointerException( "uniqueId must not be null" );
if( !isDefaultContentArea && uniqueId.equals( CONTENT_AREA_STATIONS_ID )){
throw new IllegalArgumentException( "the unique identifier '" + uniqueId + "' is reserved for the default CContentArea and may not be used by the client" );
}
CContentArea center = new CContentArea( this, uniqueId );
if( isDefaultContentArea ){
register.setDefaultContentArea( center );
}
addStationContainer( center );
return center;
}
/**
* Adds <code>container</code> to this control. All children {@link CStation}s of <code>container</code> will
* be added as root station to this control.
* @param container the additional set of stations
* @throws IllegalArgumentException if <code>container</code> is already registered or if the unique identifier
* of <code>container</code> is already known
* @throws NullPointerException if <code>container</code> is <code>null</code>
*/
public void addStationContainer( CStationContainer container ){
if( container == null ){
throw new NullPointerException( "container is null" );
}
checkValidUniqueId( container.getUniqueId() );
// check control?
DockStation defaultStation = frontend.getDefaultStation();
boolean noDefaultStation = defaultStation == null || defaultStation instanceof ScreenDockStation;
register.addStationContainer( container );
if( noDefaultStation ){
CStation<?> newDefaultStation = container.getDefaultStation();
if( newDefaultStation != null ){
frontend.setDefaultStation( newDefaultStation.getStation() );
}
}
}
/**
* Removes <code>content</code> from the list of known contentareas. This also removes
* the stations of <code>content</code> from this control. Elements aboard the
* stations are made invisible, but not removed from this control.
* @param content the contentarea to remove
* @throws IllegalArgumentException if the default-contentarea equals <code>content</code>
* @deprecated use {@link #removeStationContainer(CStationContainer)} instead
*/
@Deprecated
public void removeContentArea( CContentArea content ){
removeStationContainer( content );
}
/**
* Removes <code>container</code> from the list of known {@link CStationContainer}s. This also
* ensures that all child {@link CStation}s of <code>container</code> are removed. Elements aboard the
* stations are made invisible, but not removed from this {@link CControl}.
* @param container the set of stations to remove
* @throws IllegalArgumentException if container is the default {@link CContentArea}
*/
public void removeStationContainer( CStationContainer container ){
if( container == null )
throw new NullPointerException( "container must not be null" );
if( register.getDefaultContentArea() == container )
throw new IllegalArgumentException( "The default-contentarea can't be removed" );
register.removeStationContainer( container );
}
/**
* Gets the set of dockables, stations and other elements that are used
* by this control.
* @return the set of elements, never <code>null</code>
*/
public CControlRegister getRegister(){
return register;
}
/**
* Gets an unmodifiable list of all {@link CStationContainer}s that are registered at this {@link CControl}.
* @return the list of containers
*/
public List<CStationContainer> getStationContainers(){
return register.getStationContainers();
}
/**
* Gets the factory which is mainly used to create new elements for this
* control.
* @return the factory
*/
public CControlFactory getFactory() {
return factory;
}
/**
* Gets the manager that is responsible to handle all changes of the
* modes (maximized, normalized, ... ) of {@link Dockable}s.<br>
* Note: clients should be careful when working with the location manager.
* Changing the properties of the location manager might introduce failures that
* are not visible directly.
* @return the manager
*/
public CLocationModeManager getLocationManager() {
return locationManager;
}
/**
* Adds a destroy-hook. The hook is called when this {@link CControl} is
* destroyed through {@link #destroy()}.
* @param hook the new hook
*/
public void addDestroyHook( DestroyHook hook ){
if( hook == null )
throw new NullPointerException( "hook must not be null" );
hooks.add( hook );
}
/**
* Removes a destroy-hook from this {@link CControl}.
* @param hook the hook to remove
*/
public void removeDestroyHook( DestroyHook hook ){
hooks.remove( hook );
}
/**
* Grants access to the manager that reads and stores configurations
* of the common-project.<br>
* Clients can add their own {@link ApplicationResource}s to this manager,
* however clients are strongly discouraged from removing {@link ApplicationResource}
* which they did not add themselves.
* @return the persistent storage
*/
public ApplicationResourceManager getResources() {
return resources;
}
/**
* Changes the value of a property. The incomplete list of properties, in alphabetical order, includes:
* (properties marked with '*' should not be changed by clients if using the Common project).
* <table>
* <tr><td>{@link BubbleTheme#ACTION_DISTRIBUTOR}</td><td>Default instance of a {@link DockActionDistributor}.</td></tr>
* <tr><td>{@link FlatTheme#ACTION_DISTRIBUTOR}</td><td>Default instance of a {@link DockActionDistributor}.</td></tr>
* <tr><td>{@link DefaultDockRelocator#AUTO_DROP_ON_ANY_MOUSE_RELEASED_EVENT}</td><td>Stop drag-and-drop operations on any mouse-released event (a workaround necessary for some Linux and Mac systems).</td></tr>
* <tr><td>{@link DockTheme#BACKGROUND_PAINT} </td><td>The default value of the {@link BackgroundPaint}.</td></tr>
* <tr><td>{@link BasicTheme#BASIC_COLOR_SCHEME}</td><td>The {@link ColorScheme} to use if the {@link BasicTheme} is installed.</td></tr>
* <tr><td>{@link DockTheme#BORDER_MODIFIER} </td><td>The default value of the {@link BorderModifier}.</td></tr>
* <tr><td>{@link ScreenDockStation#BOUNDARY_RESTRICTION}</td><td>How far the user can push a window with a {@link Dockable} out of the screen(s).</td></tr>
* <tr><td>{@link BubbleTheme#BUBBLE_COLOR_SCHEME} </td><td>The {@link ColorScheme} to use if the {@link BubbleTheme} is installed.</td></tr>
* <tr><td>{@link FlapDockStation#BUTTON_CONTENT} </td><td>Tells what content should be on the buttons that represent minimized {@link Dockable}s.</td></tr>
* <tr><td>{@link FlapDockStation#BUTTON_CONTENT_FILTER} </td><td>Tells which {@link DockAction}s should be shown on a button representing a minimized {@link Dockable}.</td></tr>
* <tr><td>* {@link CControl#CCONTROL} </td><td>The {@link CControl} in whose realm the property is read, is a read-only property.</td></tr>
* <tr><td>{@link DockTheme#COMBINER} </td><td>Default value of the {@link Combiner}.</td></tr>
* <tr><td>{@link StackDockStation#COMPONENT_FACTORY} </td><td>The factory creating the "tabbed panes" of the {@link StackDockStation}.</td></tr>
* <tr><td>{@link DockTheme#DISPLAYER_FACTORY} </td><td>Default value of the {@link DisplayerFactory}.</td></tr>
* <tr><td>{@link DockStationDropLayerFactory#DROP_LAYER_FACTORY}</td><td>Factory that defines which parts of the screen are targets for a drag and drop operation/</td></tr>
* <tr><td>{@link PropertyKey#DOCK_STATION_ICON} </td><td>The default icon of {@link DockStation}s.</td></tr>
* <tr><td>{@link PropertyKey#DOCK_STATION_TITLE} </td><td>The default title of {@link DockStation}s.</td></tr>
* <tr><td>{@link PropertyKey#DOCK_STATION_TOOLTIP} </td><td>The default tooltip of {@link DockStation}s.</td></tr>
* <tr><td>{@link PropertyKey#DOCKABLE_ICON} </td><td>The default icon of {@link Dockable}s.</td></tr>
* <tr><td>{@link DockTheme#DOCKABLE_MOVING_IMAGE_FACTORY} </td><td>Default value of the {@link DockableMovingImageFactory}.</td></tr>
* <tr><td>{@link DockTheme#DOCKABLE_SELECTION} </td><td>Default value of the {@link DockableSelection}.</td></tr>
* <tr><td>{@link PropertyKey#DOCKABLE_TITLE} </td><td>The default title of {@link Dockable}s.</td></tr>
* <tr><td>{@link PropertyKey#DOCKABLE_TOOLTIP} </td><td>The default tooltip of {@link Dockable}s.</td></tr>
* <tr><td>{@link LocationModeManager#DOUBLE_CLICK_STRATEGY} </td><td>Tells what happens if the user double clicks on a {@link DockTitle} or a {@link Dockable}.</td></tr>
* <tr><td>{@link EclipseTheme#ECLIPSE_COLOR_SCHEME} </td><td>The {@link ColorScheme} to use if the {@link EclipseTheme} is installed.</td></tr>
* <tr><td>* {@link ScreenDockStation#EXPAND_ON_DOUBLE_CLICK} </td><td>Whether a double click on a child of a {@link ScreenDockStation} should maximize the child.</td></tr>
* <tr><td>{@link FlatTheme#FLAT_COLOR_SCHEME} </td><td>The {@link ColorScheme} to use if the {@link FlatTheme} is installed.</td></tr>
* <tr><td>{@link ScreenDockStation#FULL_SCREEN_STRATEGY} </td><td>Defines when a floating {@link Dockable} is considered to be in fullscreen mode.</td></tr>
* <tr><td>* {@link DockFrontend#HIDE_ACCELERATOR} </td><td>The {@link KeyStroke} that will call {@link DockFrontend#hide(Dockable)}</td></tr>
* <tr><td>{@link DockableSelector#INIT_SELECTION} </td><td>The {@link KeyStroke} that opens a window where the user can select a new {@link Dockable}.</td></tr>
* <tr><td>{@link StackDockStation#IMMUTABLE_SELECTION_INDEX} </td><td>Prevents the {@link StackDockStation} from switching the selected index on a drop operation (but does not prevent the {@link FocusManager} from switching the focus!).</td></tr>
* <tr><td>{@link CControl#KEY_CLOSE} </td><td>The {@link KeyStroke} that closes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_GOTO_EXTERNALIZED} </td><td>The {@link KeyStroke} that externalizes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_GOTO_MAXIMIZED} </td><td>The {@link KeyStroke} that maximizes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_GOTO_MINIMIZED} </td><td>The {@link KeyStroke} that minimizes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_GOTO_NORMALIZED} </td><td>The {@link KeyStroke} that normalizes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_MAXIMIZE_CHANGE} </td><td>The {@link KeyStroke} that either maximizes or normalizes a {@link CDockable}.</td></tr>
* <tr><td>{@link CControl#KEY_CANCEL_OPERATION} </td><td> The {@link KeyStroke} that will cancel the current drag and drop operation. </td></tr>
* <tr><td>{@link FlapDockStation#LAYOUT_MANAGER} </td><td>Tells the {@link FlapDockStation} the size and the hold property of its children.</td></tr>
* <tr><td>{@link SplitDockStation#LAYOUT_MANAGER} </td><td>Logic of all {@link SplitDockStation}s, used when dropping a {@link Dockable} or resizing the station.</td></tr>
* <tr><td>{@link TabPane#LAYOUT_MANAGER} </td><td>Defines the size and location of tabs of a stack.</td></tr>
* <tr><td>{@link TabPane#USE_SMALL_MINIMUM_SIZE} </td><td>Use really small minimum sizes for calculating the minimum size of a tab-pane, instead of trying to make the content look good</td></tr>
* <tr><td>* {@link SplitDockStation#MAXIMIZE_ACCELERATOR} </td><td>The {@link KeyStroke} that maximizes a child of a {@link SplitDockStation}.</td></tr>
* <tr><td>{@link CombinedMenuContent#MENU_CONTENT} </td><td>The menu that shows overflowing {@link Dockable}s on a stack.</td></tr>
* <tr><td>{@link IconManager#MINIMUM_ICON_SIZE}</td><td>The expected minimal size of all icons. </td></tr>
* <tr><td>{@link FlapDockStation#MINIMUM_SIZE} </td><td>The minimum size of the {@link Component} that represents the {@link FlapDockStation}.</td></tr>
* <tr><td>* {@link LocationModeManager#MODE_ENABLEMENT} </td><td>Tells which {@link CDockable} is allowed to have which {@link ExtendedMode}.</td></tr>
* <tr><td>{@link DockRelocatorMode#NO_COMBINATION_MASK} </td><td>What keys the user has to press during a drag and drop operation to prevent the framework from combining {@link Dockable}s.</td></tr>
* <tr><td>{@link DockTitle#ORIENTATION_STRATEGY} </td><td>Tells how to rotate text on a {@link DockTitle}.</td></tr>
* <tr><td>{@link EclipseTheme#PAINT_ICONS_WHEN_DESELECTED} </td><td>Whether to paint icons on unselected tabs if using the {@link EclipseTheme}.</td></tr>
* <tr><td>{@link PlaceholderStrategy#PLACEHOLDER_STRATEGY} </td><td>A strategy that creates placeholders for {@link Dockable}s, see {@link CPlaceholderStrategy}.</td></tr>
* <tr><td>{@link CControl#RESIZE_LOCK_CONFLICT_RESOLVER} </td><td>Tells what happens if two {@link CDockable}s have a locked size and the user is resizing the parent of these two elements.</td></tr>
* <tr><td>{@link DockController#RESTRICTED_ENVIRONMENT} </td><td>Tells whether the application runs as applet/with webstart or as free or authenticated application.</td></tr>
* <tr><td>{@link DockRelocatorMode#SCREEN_MASK} </td><td>The keys the user has to press during a drag and drop operation to ensure that the {@link Dockable} is added to a {@link ScreenDockStation}.</td></tr>
* <tr><td>{@link SingleTabDecider#SINGLE_TAB_DECIDER} </td><td>Tells which {@link Dockable}s should be presented with a single tab - even if there is no reason to show a tab.</td></tr>
* <tr><td>{@link DockTheme#STATION_PAINT} </td><td>The default value of {@link StationPaint}.</td></tr>
* <tr><td>{@link AWTComponentCaptureStrategy#STRATEGY} </td><td>How to make an image of an AWT component.</td></tr>
* <tr><td>{@link DisablingStrategy#STRATEGY}</td><td>Which element to disable.</td></tr>
* <tr><td>{@link StackDockStation#TAB_CONTENT_FILTER} </td><td>A filter deciding what content to show on a tab of a {@link StackDockStation}.</td></tr>
* <tr><td>{@link EclipseTheme#TAB_PAINTER} </td><td>The look of tabs if using the {@link EclipseTheme}.</td></tr>
* <tr><td>{@link StackDockStation#TAB_PLACEMENT} </td><td>The location of the tabs on a {@link StackDockStation}.</td></tr>
* <tr><td>{@link EclipseTheme#THEME_CONNECTOR} </td><td>Detailed instructions how to present a {@link Dockable} if using the {@link EclipseTheme}.</td></tr>
* <tr><td>{@link FlapDockStation#WINDOW_FACTORY} </td><td>A factory creating {@link FlapWindow}s for the {@link FlapDockStation}.</td></tr>
* <tr><td>{@link ScreenDockStation#WINDOW_FACTORY} </td><td>A factory creating {@link ScreenDockWindow}s for the {@link ScreenDockStation}.</td></tr>
* </table>
*
*
* @param <A> the type of the value
* @param key the name of the property
* @param value the new value, can be <code>null</code>
*/
public <A> void putProperty( PropertyKey<A> key, A value ){
putProperty( key, value, Priority.CLIENT );
}
/**
* Changes the value of a property.
* @param <A> the type of the value
* @param key the name of the property
* @param priority the priority of the new value
* @param value the new value, can be <code>null</code>
* @see #putProperty(PropertyKey, Object)
*/
protected <A> void putProperty( PropertyKey<A> key, A value, Priority priority ){
frontend.getController().getProperties().set( key, value, priority );
}
/**
* Gets the value of a property.
* @param <A> the type of the property
* @param key the name of the property
* @return the value or <code>null</code>
*/
public <A> A getProperty( PropertyKey<A> key ){
return frontend.getController().getProperties().get( key );
}
/**
* Gets the element that should be in the center of the mainframe. The {@link CContentArea}
* is created the first time this method is called.
* @return the center of the mainframe of the application
*/
public CContentArea getContentArea() {
CContentArea content = register.getDefaultContentArea();
if( content == null ){
content = createContentArea( CONTENT_AREA_STATIONS_ID, true );
}
return content;
}
/**
* Adds an additional station to this control.
* @param station the new station
*/
public void addStation( CStation<?> station ){
addStation( station, true );
}
/**
* Adds an additional station to this control. Most {@link CStation}s should
* be root-stations, even if they are nested.
* @param station the new station
* @param root <code>true</code> if the station should be a root station. A root station may
* or may not have any parent station. The location of a {@link CDockable} is always relative
* to the first root station that can be found when travelling the tree upwards. For most stations
* this attribute should be <code>true</code>
*/
public void addStation( CStation<?> station, boolean root ){
String id = station.getUniqueId();
checkValidUniqueId( id );
register.addStation( station );
if( root ){
frontend.addRoot( id, station.getStation() );
}
station.setControlAccess( access );
}
/**
* Tells whether <code>station</code> was {@link #addStation(CStation, boolean) added} to this {@link CControl}
* with the <code>root</code> flag set to <code>true</code>.
* @param station the station whose root flag is asked
* @return the value of the root flag or <code>false</code> if <code>station</code> is not registered at all
*/
public boolean isRootStation( CStation<?> station ){
DockStation root = frontend.getRoot( station.getUniqueId() );
return root == station.getStation();
}
/**
* Removes a {@link CStation} from this control. It is unspecified what
* happens with the children on <code>station</code>
* @param station the station to remove
*/
public void removeStation( CStation<?> station ){
if( register.removeStation( station ) ){
frontend.removeRoot( station.getStation() );
station.setControlAccess( null );
}
}
/**
* Gets an unmodifiable list of all stations that are currently
* registered at this control.
* @return the list of stations
*/
public List<CStation<?>> getStations(){
return register.getStations();
}
/**
* Searches the {@link CStation} whose {@link CStation#getStation() internal representation}
* is <code>intern</code>.
* @param intern the internal representation
* @return the station or <code>null</code>
*/
public CStation<?> getStation( DockStation intern ){
if( intern instanceof CommonDockStation<?,?>){
return ((CommonDockStation<?, ?>)intern).getStation();
}
return null;
}
/**
* Searches along the path to the root {@link DockStation} the first {@link CStation} that matches
* the {@link DockStation}. If <code>intern</code> is a {@link CStation}, then this method behaves
* as if {@link #getStation(DockStation)} was called. If the parent of <code>intern</code> is a {@link CStation},
* then this method behaves as if <code>getStation( intern.getDockParent() )</code> was called.
* @param intern the starting point for the search of a {@link CStation}
* @return the next {@link CStation} on the path from <code>intern</code> (incl.) to the root station (incl.)
*/
public CStation<?> findStation( DockStation intern ){
CStation<?> result = null;
while( result == null && intern != null ){
result = getStation( intern );
Dockable dockable = intern.asDockable();
if( dockable == null ){
intern = null;
}else{
intern = dockable.getDockParent();
}
}
return result;
}
/**
* Searches the {@link CStation} with unique identifier <code>id</code>.
* @param id the identifier
* @return the station or <code>null</code>
*/
public CStation<?> getStation( String id ){
for( CStation<?> station : register.getStations() ){
if( station.getUniqueId().equals( id )){
return station;
}
}
return null;
}
/**
* Adds a dockable to this control. The dockable can be made visible afterwards. This method will do nothing
* if <code>dockable</code> was already registered at this {@link CControl}.
* @param <S> the type of the new element
* @param dockable the new element to show
* @return <code>dockable</code>
* @throws IllegalArgumentException if <code>dockable</code> already is registered at another {@link CControl}
* or if the unique id of <code>dockable</code> already is used for another object
*/
public <S extends SingleCDockable> S addDockable( S dockable ){
if( dockable == null )
throw new NullPointerException( "dockable must not be null" );
checkValidUniqueId( dockable.getUniqueId() );
boolean alreadyKnown = dockable.getControl() == this;
if( dockable.getControl() != null && !alreadyKnown ){
throw new IllegalArgumentException( "dockable is already part of a control" );
}
SingleCDockable preset = register.getSingleDockable( dockable.getUniqueId() );
if( preset != null ){
if( preset == dockable ){
return dockable;
}
else{
throw new IllegalArgumentException( "unique id \'" + dockable.getUniqueId() + "\' already in use for another SingleCDockable" );
}
}
if( !alreadyKnown ){
dockable.setControlAccess( access );
}
String id = register.toSingleId( dockable.getUniqueId() );
accesses.get( dockable ).setUniqueId( id );
frontend.addDockable( id, dockable.intern() );
frontend.setHideable( dockable.intern(), true );
register.addSingleDockable( dockable );
for( CControlListener listener : listeners() )
listener.added( CControl.this, dockable );
return dockable;
}
/**
* Checks whether the unique identifier <code>id</code> is a valid identifier. This means that <code>id</code>
* is not <code>null</code> and contains at least one sign that is not a whitespace.
* @param id the unique identifier to check
* @throws IllegalArgumentException if <code>id</code> is not valid
*/
private void checkValidUniqueId( String id ){
if( id == null ){
throw new IllegalArgumentException( "unique id is 'null'");
}
if( id.length() == 0 ){
throw new IllegalArgumentException( "unique id has length of 0" );
}
if( id.trim().length() == 0 ){
throw new IllegalArgumentException( "unique id consists of whitespaces only" );
}
}
/**
* Searches for the {@link SingleCDockable} which has the unique identifier
* <code>id</code>.
* @param id the identifier to look out for
* @return the element with that identifier or <code>null</code>
*/
public SingleCDockable getSingleDockable( String id ){
for( SingleCDockable dockable : register.getSingleDockables() ){
if( dockable.getUniqueId().equals( id )){
return dockable;
}
}
return null;
}
/**
* Removes the {@link SingleCDockable} with the identifier <code>id</code>.
* This has the same effect as calling {@link #remove(SingleCDockable)}.
* @param id the id of the element to remove
* @return <code>true</code> if the element was removed, <code>false</code>
* otherwise
*/
public boolean removeSingleDockable( String id ){
for( SingleCDockable dockable : register.getSingleDockables() ){
if( dockable.getUniqueId().equals( id )){
return removeDockable( dockable );
}
}
return false;
}
/**
* Removes <code>dockable</code> from this control. The location information
* for <code>dockable</code> remains stored if either there is a
* {@link #addSingleDockableFactory(String, SingleCDockableFactory) SingleCDockableFactory}
* registered or the {@link #setMissingStrategy(MissingCDockableStrategy) MissingCDockableStrategy}
* tells to store the values.
* @param dockable the element to remove
* @return true if the element was removed, <code>false</code> otherwise
*/
public boolean removeDockable( SingleCDockable dockable ){
if( dockable == null )
throw new NullPointerException( "dockable must not be null" );
if( dockable.getControl() == this ){
dockable.setVisible( false );
frontend.remove( dockable.intern() );
register.removeSingleDockable( dockable );
dockable.setControlAccess( null );
for( CControlListener listener : listeners() )
listener.removed( CControl.this, dockable );
return true;
}
return false;
}
/**
* Adds a factory to this control. The factory will be used
* to create and add a {@link SingleCDockable} when one is requested that
* is not yet in the cache.<br>
* If there is already information for <code>id</code> available and
* <code>id</code> should be visible, then the factory will be used
* instantaneously.<br>
* Factories added with a specific identifier always have higher priority than factories
* added with a filter, see {@link #addSingleDockableFactory(Filter, SingleCDockableFactory)}.
* @param id the id of the dockable that might be requested
* @param backupFactory the new factory
*/
public void addSingleDockableFactory( String id, SingleCDockableFactory backupFactory ){
register.getBackupFactory().add( id, backupFactory );
String singleId = register.toSingleId( id );
// This would happen automatically when loading a layout. However code reading
// the entries of DockFrontend is now informed about the possible existence of
// such an identifier
locationManager.addEmpty( singleId );
frontend.addEmpty( singleId );
// if there is already layout information for id, then load this information now
FrontendEntry entry = frontend.getFrontendEntry( singleId );
if( entry != null && entry.getDockable() == null && entry.isShown() ){
SingleCDockable dockable = backupFactory.createBackup( id );
if( dockable != null ){
addDockable( dockable );
if( entry.isShown() || !dockable.isCloseable() ){
dockable.setVisible( true );
}
}
}
}
/**
* Adds a factory to this control. The factory will be used
* to create and add a {@link SingleCDockable} when one is requested that
* is not yet in the cache.<br>
* If there is already information for identifiers that are included by <code>ids</code> available and
* if they should be visible, then the <code>factory</code> will be used instantaneously to create these elements.
* During this action <code>factory</code> has a higher priority than any other factory.<br>
* Factories added with a general filter always have lower priority than factories that were added
* with a specific identifier. The factories are stored in a list and a search starts at the front of that
* list, so a factory added early has higher priority than a factory that was added lately.
* @param ids a filter telling which dockables can be handled by <code>factory</code>
* @param factory the new factory
*/
public void addSingleDockableFactory( Filter<String> ids, SingleCDockableFactory factory ){
register.getBackupFactory().add( ids, factory );
for( FrontendEntry entry : frontend.listFrontendEntries() ){
if( entry.getDockable() == null && entry.isShown() ){
if( register.isSingleId( entry.getKey() )){
String id = register.singleToNormalId( entry.getKey() );
if( ids.includes( id )){
SingleCDockable dockable = factory.createBackup( id );
if( dockable != null ){
addDockable( dockable );
if( entry.isShown() || !dockable.isCloseable() ){
dockable.setVisible( true );
}
}
}
}
}
}
}
/**
* Searches the {@link SingleCDockableFactory} which is responsible for creating the
* {@link SingleCDockable} with identifier <code>id</code>. This method first searches
* for a factory which was added with a specific identifier ({@link #addSingleDockableFactory(String, SingleCDockableFactory)}),
* if nothing is found then the factories with a filter are searched ({@link #addSingleDockableFactory(Filter, SingleCDockableFactory)}).
* @param id the identifier of some factory
* @return the factory or <code>null</code>
*/
public SingleCDockableFactory getSingleDockableFactory( String id ){
return register.getBackupFactory().getFactory( id );
}
/**
* Removes all occurrences of <code>factory</code>. Any location information that was held
* because of the existence of <code>factory</code> will be removed as well.
* @param factory the factory to remove
*/
public void removeSingleDockableFactory( SingleCDockableFactory factory ){
register.getBackupFactory().remove( factory );
for( FrontendEntry entry : frontend.listFrontendEntries() ){
if( entry.getDockable() == null && entry.isShown() ){
if( register.isSingleId( entry.getKey() )){
String id = register.singleToNormalId( entry.getKey() );
if( !missingStrategy.shouldStoreSingle( id )){
locationManager.removeEmpty( entry.getKey());
frontend.removeEmpty( entry.getKey() );
}
}
}
}
}
/**
* Removes a factory from this control. Location information for
* <code>id</code> will be deleted if neither a {@link #addDockable(SingleCDockable) SingleCDockable}
* is added nor the {@link #setMissingStrategy(MissingCDockableStrategy) MissingCDockableStrategy}
* tells to store the information.
* @param id the name of the factory
* @see #addSingleDockableFactory(String, SingleCDockableFactory)
*/
public void removeSingleDockableFactory( String id ){
register.getBackupFactory().remove( id );
if( !missingStrategy.shouldStoreSingle( id )){
id = register.toSingleId( id );
locationManager.removeEmpty( id );
frontend.removeEmpty( id );
}
}
/**
* Adds a dockable to this control. The dockable can be made visible afterwards. A random identifier
* is assigned to <code>dockable</code>, clients can also use {@link #addDockable(String, MultipleCDockable)} if
* they want to specify the identifier themselves.
* @param <M> the type of the new element
* @param dockable the new element to show
* @return <code>dockable</code>
* @throws IllegalArgumentException if either the {@link MultipleCDockable#getFactory() factory} of <code>dockable</code> is <code>null</code>,
* or is not registered (see {@link #addMultipleDockableFactory(String, MultipleCDockableFactory)}).
*/
public <M extends MultipleCDockable> M addDockable( M dockable) {
Set<String> ids = new HashSet<String>();
String factoryId;
MultipleCDockableFactory<?, ?> factory = dockable.getFactory();
if( factory == null ){
throw new IllegalArgumentException( "factory of dockable must not be null" );
}
factoryId = access.getFactoryId( dockable.getFactory() );
if( factoryId == null ){
throw new IllegalStateException( "the factory for a MultipleCDockable is not registered: " + dockable.getFactory() );
}
for( MultipleCDockable multi : register.getMultipleDockables() ){
if( factoryId.equals( access.getFactoryId( multi.getFactory() ))){
ids.add( accesses.get( multi ).getUniqueId() );
}
}
int count = 0;
String id = count + " " + factoryId;
while( ids.contains( register.toMultiId( id ) ) ){
count++;
id = count + " " + factoryId;
}
return addDockable( id, dockable );
}
/**
* Adds a dockable to this control. The dockable can be made visible afterwards.
* This method will throw an exception when the unique identifier is already
* in use. Clients can also use {@link #addDockable(MultipleCDockable)} if they want to assign a
* random identifier to <code>dockable</code>.
* @param <M> the type of the new element
* @param uniqueId id the unique id of the new element
* @param dockable the new element to show
* @return <code>dockable</code>
* @throws IllegalArgumentException if the unique identifier is already in
* use, if <code>dockable</code> is already used elsewhere, if there is
* no factory for <code>dockable</code>
* @throws NullPointerException if any argument is <code>null</code>
*/
public <M extends MultipleCDockable> M addDockable( String uniqueId, M dockable ){
if( dockable == null )
throw new NullPointerException( "dockable must not be null" );
checkValidUniqueId( uniqueId );
String factory = access.getFactoryId( dockable.getFactory() );
if( factory == null ){
throw new IllegalStateException( "the factory for a MultipleCDockable is not registered: " + dockable.getFactory() );
}
if( dockable.getControl() != null )
throw new IllegalStateException( "dockable is already part of a control" );
uniqueId = register.toMultiId( uniqueId );
for( MultipleCDockable multi : register.getMultipleDockables() ){
String id = accesses.get( multi ).getUniqueId();
if( uniqueId.equals( id )){
throw new IllegalArgumentException( "The unique identifier is already in use: " + uniqueId );
}
}
dockable.setControlAccess( access );
accesses.get( dockable ).setUniqueId( uniqueId );
frontend.addDockable( uniqueId, dockable.intern() );
frontend.setHideable( dockable.intern(), true );
register.addMultipleDockable( dockable );
for( CControlListener listener : listeners() )
listener.added( CControl.this, dockable );
return dockable;
}
/**
* Replaces <code>oldDockable</code> with <code>newDockable</code>. The new dockable
* inherits settings and location of the old one.
* @param oldDockable the old dockable, not <code>null</code>
* @param newDockable the new dockable, not <code>null</code>
*/
public void replace( MultipleCDockable oldDockable, MultipleCDockable newDockable ){
if( oldDockable == null )
throw new IllegalArgumentException( "old dockable must not be null" );
if( newDockable == null )
throw new IllegalArgumentException( "new dockable must not be null" );
if( oldDockable.getControl() != this )
throw new IllegalArgumentException( "old dockable not registered at this CControl" );
if( newDockable.getControl() != null )
throw new IllegalArgumentException( "new dockable alread registered at some CControl" );
String id = accesses.get( oldDockable ).getUniqueId();
boolean frontendEmpty = frontend.isEmpty( id );
if( !frontendEmpty ){
frontend.addEmpty( id );
}
boolean locationEmpty = locationManager.isEmpty( id );
if( !locationEmpty ){
locationManager.addEmpty( id );
}
id = register.multiToNormalId( id );
removeDockable( oldDockable );
addDockable( id, newDockable );
if( !frontendEmpty ){
frontend.removeEmpty( id );
}
if( !locationEmpty ){
locationManager.removeEmpty( id );
}
}
/**
* Searches and returns the one {@link MultipleCDockable} which uses
* the unique identifier <code>id</code>.
* @param id the identifier to look out for
* @return the element using <code>id</code> or <code>null</code> if nothing
* was found
*/
public MultipleCDockable getMultipleDockable( String id ){
id = register.toMultiId( id );
for( MultipleCDockable dockable : register.getMultipleDockables() ){
if( accesses.get( dockable ).getUniqueId().equals( id )){
return dockable;
}
}
return null;
}
/**
* Gets the unique identifier which is used internally for <code>dockable</code>
* @param dockable the item to search
* @return the internal unique identifier of <code>dockable</code>, may be <code>null</code>
*/
public String getUniqueId( MultipleCDockable dockable ){
CDockableAccess access = accesses.get( dockable );
if( access == null ){
return null;
}
return register.multiToNormalId( access.getUniqueId() );
}
private boolean shouldStore( String id ){
if( register.isSingleId( id )){
if( register.getBackupFactory().getFactory( register.singleToNormalId( id ) ) != null ){
return true;
}
return missingStrategy.shouldStoreSingle( register.singleToNormalId( id ) );
}
else if( register.isMultiId( id )){
return missingStrategy.shouldStoreMulti( register.multiToNormalId( id ) );
}
else{
return false;
}
}
private String shouldStore( CDockable dockable ){
String key = null;
if( dockable instanceof SingleCDockable ){
key = ((SingleCDockable)dockable).getUniqueId();
key = register.toSingleId( key );
}
else if( dockable instanceof MultipleCDockable ){
CDockableAccess access = accesses.get( dockable );
if( access == null ){
return null;
}
key = access.getUniqueId();
}
if( shouldStore( key )){
return key;
}
else{
return null;
}
}
@SuppressWarnings("unchecked")
private boolean shouldCreate( MultipleCDockableFactory<?, ?> factory, CommonMultipleDockableLayout layout ){
String uniqueId = layout.getId();
String multiId = register.toMultiId( uniqueId );
for( MultipleCDockable multi : register.getMultipleDockables() ){
if( accesses.get( multi ).getUniqueId().equals( multiId )){
return false;
}
}
String factoryId = access.getFactoryId( factory );
MultipleCDockableFactory<?, MultipleCDockableLayout> normalizedFactory = (MultipleCDockableFactory<?, MultipleCDockableLayout>)factory;
return missingStrategy.shouldCreate( factoryId, normalizedFactory, uniqueId, layout.getLayout() );
}
/**
* Removes a dockable from this control. The dockable is made invisible.
* @param dockable the element to remove
*/
public void removeDockable( MultipleCDockable dockable ){
if( dockable == null )
throw new NullPointerException( "dockable must not be null" );
if( dockable.getControl() == this ){
dockable.setVisible( false );
frontend.remove( dockable.intern() );
register.removeMultipleDockable( dockable );
dockable.setControlAccess( null );
for( CControlListener listener : listeners() )
listener.removed( CControl.this, dockable );
}
}
/**
* Gets the number of {@link CDockable}s that are registered in this
* {@link CControl}.
* @return the number of dockables
*/
public int getCDockableCount(){
return register.getDockableCount();
}
/**
* Gets the index'th dockable that is registered in this control
* @param index the index of the element
* @return the selected dockable
*/
public CDockable getCDockable( int index ){
return register.getDockable( index );
}
/**
* Adds a factory to this control. The factory will create {@link MultipleCDockable}s
* when a layout is loaded. The {@link NullMultipleCDockableFactory} will always be preinstalled using
* the empty identifier.
* @param id the unique id of the factory, must consist of at least one character
* @param factory the new factory
*/
public void addMultipleDockableFactory( final String id, final MultipleCDockableFactory<?,?> factory ){
addMultipleDockableFactory( id, factory, true );
}
private void addMultipleDockableFactory( final String id, final MultipleCDockableFactory<?,?> factory, boolean check ){
if( check ){
checkValidUniqueId( id );
}
if( factory == null ){
throw new NullPointerException( "factory must not be null" );
}
if( register.getCommonMultipleDockableFactory( id ) != null ){
throw new IllegalArgumentException( "there is already a factory named " + id );
}
if( access.getFactoryId( factory ) != null ){
throw new IllegalArgumentException( "this factory-object is already in use and cannot be added a second time" );
}
CommonMultipleDockableFactory cfactory = new CommonMultipleDockableFactory( id, factory, access );
register.putCommonMultipleDockableFactory( id, cfactory );
frontend.registerFactory( cfactory );
}
/**
* Searches for the {@link MultipleCDockableFactory} with the identifier
* <code>id</code>.
* @param id the identifier to search for
* @return the factory or <code>null</code>
*/
public MultipleCDockableFactory<?, ?> getMultipleDockableFactory( String id ){
return register.getFactory( id );
}
/**
* Gets the unique identifier of <code>factory</code>.
* @param factory the factory to search
* @return the unique identifier or <code>null</code>
*/
public String getFactoryId( MultipleCDockableFactory<?, ?> factory ){
return access.getFactoryId( factory );
}
/**
* Removes the {@link MultipleCDockableFactory} with identifier <code>id</code>
* from this control. As a side effect all {@link MultipleCDockable}s which
* use that factory are removed as well. Nothing happens if there is no
* factory registered with <code>id</code>.
* @param id the identifier of the factory to remove
*/
public void removeMultipleDockableFactory( String id ){
CommonMultipleDockableFactory factory = register.removeCommonMultipleDockableFactory( id );
if( factory != null ){
frontend.unregisterFactory( factory );
List<MultipleCDockable> toRemove = new ArrayList<MultipleCDockable>();
for( MultipleCDockable dockable : register.getMultipleDockables() ){
if( dockable.getFactory() == factory.getFactory() ){
toRemove.add( dockable );
}
}
for( MultipleCDockable dockable : toRemove ){
removeDockable( dockable );
}
}
}
/**
* Sets the location where {@link CDockable}s are opened when there is
* nothing else specified for these <code>CDockable</code>s.
* @param defaultLocation the location, can be <code>null</code>
*/
public void setDefaultLocation( CLocation defaultLocation ){
this.defaultLocation = defaultLocation;
}
/**
* Gets the location where {@link CDockable}s are opened when nothing else
* is specified.
* @return the location, might be <code>null</code>
* @see #setDefaultLocation(CLocation)
*/
public CLocation getDefaultLocation(){
return defaultLocation;
}
/**
* Makes sure that all {@link CDockable}s are maximized onto the area
* which is registered under the given unique id.
* @param id the unique id of the area
* @see CGridArea#getUniqueId()
* @see CContentArea#getCenterIdentifier()
*/
public void setMaximizeArea( String id ){
CMaximizedMode mode = locationManager.getMaximizedMode();
CMaximizedModeArea area = mode.get( id );
if( area == null )
throw new IllegalArgumentException( "No area registered with key '" + id + "'" );
mode.setDefaultArea( area );
}
/**
* Sets the {@link CGroupBehavior}. The behavior decides what happens when the user wants to change
* the {@link ExtendedMode} of a {@link CDockable}.<br>
* To be exact: the group behavior is applied for a call to {@link CDockable#setExtendedMode(ExtendedMode)}
* respective a call to {@link LocationModeManager#setMode(Dockable, ExtendedMode)}. The buttons that are
* visible to the user all link to these methods.
* @param behavior the new behavior, not <code>null</code>
*/
public void setGroupBehavior( CGroupBehavior behavior ){
locationManager.setGroupBehavior( behavior );
}
/**
* Gets the currently used {@link CGroupBehavior}.
* @return the current behavior, not <code>null</code>
* @see #setGroupBehavior(CGroupBehavior)
*/
public CGroupBehavior getGroupBehavior(){
return locationManager.getGroupBehavior();
}
/**
* Sets the theme of the elements in the realm of this control.
* @param theme the new theme
* @deprecated replaced by {@link #setTheme(String)}. While this method still
* works, the theme will not get stored persistent and any module using
* the {@link ThemeMap} ({@link #getThemes()}) will not be informed about
* the change.
*/
@Deprecated
public void setTheme( DockTheme theme ){
frontend.getController().setTheme( theme );
}
/**
* Sets the theme of the elements in the realm of this control. The String
* <code>theme</code> is used as key for {@link ThemeMap#select(String)}.
* @param theme the name of the theme, this might be one of
* {@link ThemeMap#KEY_BASIC_THEME}, {@link ThemeMap#KEY_BUBBLE_THEME},
* {@link ThemeMap#KEY_ECLIPSE_THEME}, {@link ThemeMap#KEY_FLAT_THEME}
* or {@link ThemeMap#KEY_SMOOTH_THEME}. This can also be a any other
* string which was used for {@link ThemeMap#put(String, ThemeFactory)},
* {@link ThemeMap#add(String, ThemeFactory)} or {@link ThemeMap#insert(int, String, ThemeFactory)}.
*/
public void setTheme( String theme ){
themes.select( theme );
}
/**
* Gets the list of installed themes.
* @return the list of themes
*/
public ThemeMap getThemes(){
return themes;
}
/**
* Sets a strategy that creates missing {@link CStationPerspective}s.
* @param missingPerspectiveStrategy the strategy, not <code>null</code>
*/
public void setMissingPerspectiveStrategy( MissingPerspectiveStrategy missingPerspectiveStrategy ){
if( missingPerspectiveStrategy == null ){
throw new IllegalArgumentException( "strategy must not be null" );
}
this.missingPerspectiveStrategy = missingPerspectiveStrategy;
}
/**
* Gets the strategy that is used to create missing {@link CStationPerspective}.
* @return the strategy, not <code>null</code>
*/
public MissingPerspectiveStrategy getMissingPerspectiveStrategy(){
return missingPerspectiveStrategy;
}
/**
* Grants access to the perspective API which allows clients to build complex layouts without
* the need to create any {@link CDockable dockables} or {@link CStation stations}.
* @return access a wrapper around this {@link CControl} allowing to inspect and modify the layouts
* that are available
* @see #load(String)
* @see #save(String)
* @see #setMissingPerspectiveStrategy(MissingPerspectiveStrategy)
*/
public CControlPerspective getPerspectives(){
return new CControlPerspective( access );
}
/**
* Sets the root window of the application. The root window is used
* as owner of any dialog that is created. Already existing dialogs
* may be closed and reopened in order to change the owner. Short living
* dialogs will not change their owner.
* @param window the new owner, can be <code>null</code>
*/
public void setRootWindow( WindowProvider window ){
frontend.setOwner( window );
}
/**
* Gets the root window of the application. Note that this method might
* not return the same object as given to {@link #setRootWindow(WindowProvider)},
* however the provide returned by this method will return the same window
* as specified by {@link #setRootWindow(WindowProvider)}.
* @return the provider, never <code>null</code>
*/
public WindowProvider getRootWindow(){
return frontend.getOwner();
}
/**
* Gets the storage container for {@link PreferenceModel}s for this control.
* The contents of this container are stored in the
* {@link #getResources() resource manager}.
* @return the storage for preferences
* @see #getResources()
*/
public PreferenceStorage getPreferences(){
return preferences;
}
/**
* Sets the {@link PreferenceModel} which will be used to translate between
* <code>this</code> and the {@link #getPreferences() preferences}. This
* model can be set to <code>null</code>.<br>
* The default value of this property is <code>null</code>.
* @param preferenceModel the new model, it will used to translate
* the contents of {@link #getPreferences()} immediately, can be <code>null</code>
*/
public void setPreferenceModel( PreferenceModel preferenceModel ) {
if( this.preferenceModel != null ){
this.preferenceModel.read();
preferences.store( this.preferenceModel );
}
this.preferenceModel = preferenceModel;
if( preferenceModel != null ){
preferences.load( preferenceModel, false );
preferenceModel.write();
}
}
/**
* Gets the preference model which is used to translate between the
* {@link #getPreferences() preferences} and <code>this</code>.
* @return the model, can be <code>null</code>
* @see #setPreferenceModel(PreferenceModel)
*/
public PreferenceModel getPreferenceModel() {
return preferenceModel;
}
/**
* Sets the strategy that tells what to do if layout information of a missing
* {@link CDockable} is found.
* @param missingStrategy the strategy, <code>null</code> will set
* the default strategy
*/
public void setMissingStrategy( MissingCDockableStrategy missingStrategy ) {
if( missingStrategy == null ){
this.missingStrategy = MissingCDockableStrategy.PURGE;
}
else{
this.missingStrategy = missingStrategy;
}
}
/**
* Gets the strategy that tells what to do if layout information of a missing
* {@link CDockable} is found.
* @return the strategy, never <code>null</code>
*/
public MissingCDockableStrategy getMissingStrategy() {
return missingStrategy;
}
/**
* Adds a {@link ResizeRequestListener} to this {@link CControl}. The listener
* will be informed when the resize requests of a {@link CDockable} should
* be processed.
* @param listener the new listener, not <code>null</code>
*/
public void addResizeRequestListener( ResizeRequestListener listener ){
if( listener == null )
throw new NullPointerException( "listener must not be null" );
resizeListeners.add( listener );
}
/**
* Removes a {@link ResizeRequestListener} from this {@link CControl}.
* @param listener the listener to remove
*/
public void removeResizeRequestListener( ResizeRequestListener listener ){
resizeListeners.remove( listener );
}
/**
* Informs all {@link ResizeRequestListener}s, that the
* {@link CDockable#getAndClearResizeRequest() resize request} of all
* <code>CDockable</code>s should be processed. There are no
* guarantees that a resize requests can be granted or even gets processed.<br>
* All requests, independent from whether they were processed, will be deleted
* by this method.<br>
* Note that a request might conflict with a "resize lock"
* {@link CDockable#isResizeLockedHorizontally()} and
* {@link CDockable#isResizeLockedVertically()}. The behavior of that case is not
* specified, but clients can assume that the locked components introduce
* additional resize requests.
*/
public void handleResizeRequests(){
ResizeRequestListener[] listeners = resizeListeners.toArray( new ResizeRequestListener[ resizeListeners.size() ] );
for( ResizeRequestListener listener : listeners )
listener.handleResizeRequest( this );
for( CDockable dockable : register.getDockables() )
dockable.getAndClearResizeRequest();
}
/**
* Gets the representation of the layer beneath the common-layer.
* @return the entry point to DockingFrames
*/
public CDockFrontend intern(){
return frontend;
}
/**
* Gets the {@link DockController} which is used by this {@link CControl}.
* @return the core system of the framework
*/
public DockController getController(){
return intern().getController();
}
/**
* Grants access to all the {@link Icon}s that are used within the realm of this
* {@link CControl}. Clients are free to modify the set of icons.
* @return the set of icons that are used
*/
public IconManager getIcons(){
return getController().getIcons();
}
/**
* Tells this control whether basic modes like "normalized", "minimized" or "externalized" are forced upon
* {@link Dockable}s after loading a persistent layout. Basically if this property is set, then all {@link Dockable}s
* are un-maximized after a layout change. The default value of this property is <code>true</code>.<br>
* The reasons behind forcing basic modes are:
* <ul>
* <li>If the user changes the layout, he/she most likely would like to see the effects. A maximized {@link Dockable} would
* hide the effects.</li>
* <li>For the user re-maximizing an element requires no more than one click with the mouse. It's a cheap operation.</li>
* <li>It is an additional layer of security preventing {@link Dockable}s from being in the wrong position if the client
* was stared with new settings.</li>
* </ul>
* @param revert whether non-basic modes should be forbidden when loading a persistent layout
*/
public void setRevertToBasicModes( boolean revert ){
intern().setRevertToBasicModes( revert );
}
/**
* Tells whether basic modes are forcibly applied when loading a persistent layout.
* @return whether the non-basic modes are forbidden
* @see #setRevertToBasicModes(boolean)
*/
public boolean isRevertToBasicModes(){
return intern().isRevertToBasicModes();
}
/**
* If a {@link CDockable} is minimized, the focus can be automatically transferred to another {@link CDockable}. This
* feature is implemented by the method {@link #initTransferFocusOnMinimize(DockController)}, which may be
* overridden by subclasses.
* @param transferFocusOnMinimize whether to enable the feature or not (default is <code>true</code>)
*/
public void setTransferFocusOnMinimize( boolean transferFocusOnMinimize ){
this.transferFocusOnMinimize = transferFocusOnMinimize;
}
/**
* If a {@link CDockable} is minimized, the focus can be automatically transferred to another {@link Dockable}.
* @return whether the focus will be transferred
* @see #setTransferFocusOnMinimize(boolean)
*/
public boolean isTransferFocusOnMinimize(){
return transferFocusOnMinimize;
}
/**
* Writes the current and all known layouts into <code>file</code>.<br>
* This is the same as calling <code>getResources().writeFile( file )</code>.
* @param file the file to override
* @throws IOException if the file can't be written
*/
public void write( File file ) throws IOException{
getResources().writeFile( file );
}
/**
* Writes the current and all known layouts into <code>out</code>.<br>
* This is the same as calling <code>getResources().writeStream( out )</code>.
* @param out the stream to write into
* @throws IOException if the stream is not writable
*/
public void write( DataOutputStream out ) throws IOException{
getResources().writeStream( out );
}
/**
* Writes the current and all known layouts into <code>element</code>.<br>
* This is the same as calling <code>getResources().writeXML( element )</code>.
* @param element the element to write into
*/
public void writeXML( XElement element ){
getResources().writeXML( element );
}
/**
* Writes the current and all known layouts into <code>file</code> in xml format.
* @param file the file to write into
* @throws IOException if the file is not writable
*/
public void writeXML( File file ) throws IOException{
XElement root = new XElement( "root" );
getResources().writeXML( root );
BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( file ));
XIO.writeUTF( root, out );
out.close();
}
/**
* Reads the current and other known layouts from <code>file</code>.<br>
* This is the same as calling <code>getResources().readFile( file )</code>.
* @param file the file to read from
* @throws IOException if the file can't be read
*/
public void read( File file ) throws IOException{
getResources().readFile( file );
}
/**
* Reads the current and other known layouts from <code>in</code>.<br>
* This is the same as calling <code>getResources().readStream( in )</code>.
* @param in the stream to read from
* @throws IOException if the stream can't be read
*/
public void read( DataInputStream in ) throws IOException{
getResources().readStream( in );
}
/**
* Reads the current and other known layouts from <code>element</code>.<br>
* This is the same as calling <code>getResources().readXML( element )</code>.
* @param element the element to read
* @throws XException if the xml file has the wrong structure
*/
public void readXML( XElement element ){
getResources().readXML( element );
}
/**
* Reads the current and other known layouts from <code>file</code>.
* @param file the file to open and to read
* @throws IOException if the file cannot be read
* @throws XException if the xml file has the wrong structure
*/
public void readXML( File file ) throws IOException{
BufferedInputStream in = new BufferedInputStream( new FileInputStream( file ));
XElement element = XIO.readUTF( in );
in.close();
readXML( element );
}
/**
* Saves the current layout with the current name. Does nothing if there is no name for the current layout.
* @return the name that was used to save the layout
* @see #save(String)
*/
public String save(){
return save( false );
}
/**
* Saves the current layout with the current name. Does nothing if there is no name for the current layout.
* @param includeWorkingAreas whether the content of the {@link CStation}s that are marked as
* {@link CStation#isWorkingArea() working area} should be stored as well.
* @return the name that was used to save the layout
* @see #save(String)
*/
public String save( boolean includeWorkingAreas ){
String current = frontend.getCurrentSetting();
if( current == null ){
return null;
}
else{
save( current, includeWorkingAreas );
return current;
}
}
/**
* Stores the current layout with the given name. This creates "entry" (partial) layout information.
* @param name the name of the current layout.
*/
public void save( String name ){
frontend.save( name );
}
/**
* Stores the current layout with the given name. This creates "entry" (partial) layout information.
* @param name the name of the current layout.
* @param includeWorkingAreas whether the content of the {@link CStation}s that are marked as
* {@link CStation#isWorkingArea() working area} should be stored as well.
*/
public void save( String name, boolean includeWorkingAreas ){
frontend.save( name, !includeWorkingAreas );
}
/**
* Loads an earlier stored layout.
* @param name the name of the layout.
*/
public void load( String name ){
frontend.load( name );
}
/**
* Loads an earlier stored layout.
* @param name the name of the layout.
* @param includeWorkingAreas whether the content of the {@link CStation}s that are marked as
* {@link CStation#isWorkingArea() working area} should be updated as well. This value should be the same
* as was used to call {@link #save(String, boolean)}.
*/
public void load( String name, boolean includeWorkingAreas ){
frontend.load( name, !includeWorkingAreas );
}
/**
* Deletes a layout that has been stored earlier.
* @param name the name of the layout to delete
*/
public void delete( String name ){
frontend.delete( name );
}
/**
* Gets a list of all layouts that are currently known.
* @return the list of layouts
*/
public String[] layouts(){
Set<String> settings = frontend.getSettings();
return settings.toArray( new String[ settings.size() ] );
}
/**
* Gets the name of the current layout (the one with which {@link #save(String)} was called). The current
* layout may not have a name if it was never saved. The result of this method will be a {@link String}
* that is part of {@link #layouts()}.
* @return the name of the current layout, or <code>null</code>
*/
public String getLayout(){
return frontend.getCurrentSetting();
}
/**
* A class giving access to the internal methods of the enclosing
* {@link CControl}.
* @author Benjamin Sigg
*/
private class Access implements CControlAccess{
/** action used to close {@link CDockable}s */
private DockAction closeAction;
public CControl getOwner(){
return CControl.this;
}
public void link( CDockable dockable, CDockableAccess access ) {
if( access == null ){
CDockableAccess oldAccess = accesses.remove( dockable );
if( oldAccess != null ){
oldAccess.setUniqueId( null );
}
dockable.removeCDockablePropertyListener( listenerCollection.getCDockablePropertyListener() );
dockable.removeCDockableStateListener( listenerCollection.getCDockableStateListener() );
}
else{
if( accesses.put( dockable, access ) == null ){
dockable.addCDockablePropertyListener( listenerCollection.getCDockablePropertyListener() );
dockable.addCDockableStateListener( listenerCollection.getCDockableStateListener() );
}
}
}
public CDockableAccess access( CDockable dockable ) {
return accesses.get( dockable );
}
public void hide( CDockable dockable ){
if( !dockable.isVisible() )
return;
DockRegister register = frontend.getController().getRegister();
try{
register.setStalled( true );
Map<Dockable, ExtendedMode> nonBasic = new HashMap<Dockable, ExtendedMode>();
for( Dockable check : locationManager.listDockables() ){
if( check != dockable.intern() ){
CLocationMode mode = locationManager.getCurrentMode( check );
if( mode != null && !mode.isBasicMode() ){
nonBasic.put( check, mode.getExtendedMode() );
}
}
}
Dockable[] focusHistory = getController().getFocusHistory().getHistory();
boolean changes = locationManager.ensureBasicModes();
frontend.hide( dockable.intern() );
if( changes ){
for( Dockable focused : focusHistory ){
ExtendedMode mode = nonBasic.get( focused );
if( mode != null ){
if( frontend.isShown( focused ) && locationManager.isModeAvailable( focused, mode )){
locationManager.setMode( focused, mode );
}
}
}
}
}
finally{
register.setStalled( false );
}
}
public void show( CDockable dockable ){
if( dockable.hasParent() )
return;
DockRegister register = frontend.getController().getRegister();
register.setStalled( true );
try{
CLocation location = dockable.getAutoBaseLocation( true );
CDockableAccess access = access( dockable );
if( access != null ){
access.internalLocation( true );
}
CStation<?> area = dockable.getWorkingArea();
if( area != null && area.asDockable() != null ){
if( !area.asDockable().isVisible() ){
throw new IllegalStateException( "A dockable that wants to be on a working-area can't be made visible unless the working-area is visible." );
}
}
if( location == null ){
dockable.setExtendedMode( findInitialMode( dockable ) );
}
else{
locationManager.setLocation( dockable.intern(), location );
}
if( !frontend.isShown( dockable.intern() )){
frontend.show( dockable.intern(), false );
}
locationManager.ensureValidLocation( dockable );
}
finally{
register.setStalled( false );
}
}
private ExtendedMode findInitialMode( CDockable dockable ){
CGroupingBehavior groupingBehavior = getProperty( GROUPING_BEHAVIOR );
DockableGrouping grouping = groupingBehavior.getGrouping( dockable.intern() );
ExtendedMode mode = null;
if( grouping != null ){
mode = grouping.getInitialMode( dockable.intern() );
}
if( mode == null ){
mode = ExtendedMode.NORMALIZED;
}
return mode;
}
public CLocation getAutoBaseLocation( CDockable dockable, boolean noBackwardsTransformation ){
CDockableAccess access = access( dockable );
CLocation location = null;
if( access != null ){
location = access.internalLocation( false );
}
if( location == null ){
if( frontend.hasLocation( dockable.intern() )){
FrontendEntry entry = frontend.getFrontendEntry( dockable.intern() );
String root = entry.getRoot();
DockableProperty property = entry.getLocation();
CStation<?> station = getStation( root );
if( station != null ){
if( noBackwardsTransformation ){
return null;
}
location = station.getStationLocation().expandProperty( getController(), property );
}
}
if( location == null ){
CStation<?> area = dockable.getWorkingArea();
if( area != null ){
location = area.getStationLocation();
}
if( location == null ){
location = defaultLocation;
}
if( location == null && !noBackwardsTransformation ){
location = locationManager.getLocation( dockable.intern(), ExtendedMode.NORMALIZED );
}
}
}
return location;
}
public boolean isVisible( CDockable dockable ){
return frontend.isShown( dockable.intern() );
}
public boolean hasParent( CDockable dockable ){
if( frontend.isHiddenRootStation( dockable.intern() )){
return false;
}
return isVisible( dockable );
}
public String getFactoryId( MultipleCDockableFactory<?,?> factory ){
for( Map.Entry<String, MultipleCDockableFactory<?, ?>> entry : register.getFactories().entrySet() ){
if( entry.getValue() == factory ){
return entry.getKey();
}
}
return null;
}
public CLocationModeManager getLocationManager() {
return locationManager;
}
public DockAction createCloseAction( final CDockable dockable ) {
if( closeAction == null ){
CloseActionFactory factory = getController().getProperties().get( CLOSE_ACTION_FACTORY );
closeAction = factory.create( CControl.this, dockable ).intern();
}
return closeAction;
}
public MutableCControlRegister getRegister() {
return register;
}
public boolean shouldStore( String key ) {
return CControl.this.shouldStore( key );
}
public String shouldStore( CDockable dockable ) {
return CControl.this.shouldStore( dockable );
}
}
}