/*
* Copyright (c) 2004 Christopher M Butler
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.flexdock.docking.defaults;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.DockingStrategy;
import org.flexdock.docking.RegionChecker;
import org.flexdock.docking.activation.ActiveDockableTracker;
import org.flexdock.docking.event.DockingEvent;
import org.flexdock.docking.event.DockingListener;
import org.flexdock.docking.event.DockingMonitor;
import org.flexdock.docking.event.TabbedDragListener;
import org.flexdock.docking.event.hierarchy.DockingPortTracker;
import org.flexdock.docking.props.DockingPortPropertySet;
import org.flexdock.docking.props.PropertyChangeListenerFactory;
import org.flexdock.docking.props.PropertyManager;
import org.flexdock.docking.state.LayoutNode;
import org.flexdock.docking.state.tree.DockableNode;
import org.flexdock.docking.state.tree.DockingPortNode;
import org.flexdock.docking.state.tree.SplitNode;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.LookAndFeelSettings;
import org.flexdock.util.SwingUtility;
import org.flexdock.util.UUID;
import org.flexdock.util.Utilities;
/**
* This is a {@code Container} that implements the {@code DockingPort}
* interface. It provides a default implementation of {@code DockingPort} to
* allow ease of development within docking-enabled applications.
* <p>
* The {@code DefaultDockingPort} handles docking in one of three ways. If the
* port is empty, then all incoming {@code Dockables} are docked to the CENTER
* region. If the port is not empty, then all incoming {@code Dockables} docked
* to the CENTER region are embedded within a {@code JTabbedPane}. All incoming
* {@code Dockables} docked to an outer region (NORTH, SOUTH, EAST, and WEST) of
* a non-empty port are placed into a split layout using a {@code JSplitPane}.
* <p>
* For centrally docked {@code Components}, the immediate child of the
* {@code DefaultDockingPort} may or may not be a {@code JTabbedPane}. If
* {@code isSingleTabAllowed()} returns {@code true} for the current
* {@code DefaultDockingPort}, then the immediate child returned by
* {@code getDockedComponent()} will return a {@code JTabbedPane} instance even
* if there is only one {@code Dockable} embedded within the port. If there is a
* single {@code Dockable} in the port, but {@code isSingleTabAllowed()} returns
* {@code false}, then {@code getDockedComponent()} will return the
* {@code Component} that backs the currently docked {@code Dockable}, returned
* by the {@code Dockable's} {@code getComponent()} method.
* {@code isSingleTabAllowed()} is a scoped property that may apply to this
* port, all ports across the JVM, or all ports within a user defined scope.
* {@code getDockedComponent()} will return a {@code JTabbedPane} at all times
* if there is more than one centrally docked {@code Dockable} within the port,
* and all docked {@code Components} will reside within the tabbed pane.
* <p>
* Components that are docked in the NORTH, SOUTH, EAST, or WEST regions are
* placed in a {@code JSplitPane} splitting the layout of the
* {@code DockingPort} between child components. Each region of the
* {@code JSplitPane} contains a new {@code DefaultDockingPort}, which, in
* turn, contains the docked components. In this situation,
* {@code getDockedComponent()} will return a {@code JSplitPane} reference.
* <p>
* A key concept that drives the {@code DefaultDockingPort}, then, is the
* notion that this {@code DockingPort} implementation may only ever have one
* single child component, which may or may not be a wrapper for other child
* components. Because {@code JSplitPane} contains child
* {@code DefaultDockingPorts}, each of those {@code DefaultDockingPorts} is
* available for further sub-docking operations.
* <p>
* Since a {@code DefaultDockingPort} may only contain one child component,
* there is a container hierarchy to manage tabbed interfaces, split layouts,
* and sub-docking. As components are removed from this hierarchy, the hierarchy
* itself must be reevaluated. Removing a component from a child
* {@code DefaultDockingPort} within a {@code JSplitPane} renders the child
* {@code DefaultDockingPort} unnecessary, which, in turn, renders the notion of
* splitting the layout with a {@code JSplitPane} unnecessary (since there are
* no longer two components to split the layout between). Likewise, removing a
* child component from a {@code JTabbedPane} such that there is only one child
* left within the {@code JTabbedPane} removes the need for a tabbed interface
* to begin with.
* <p>
* When the {@code DockingManager} removes a component from a
* {@code DockingPort} via {@code DockingManager.undock(Dockable dockable)} it
* uses a call to {@code undock()} on the current {@code DockingPort}.
* {@code undock()} automatically handles the reevaluation of the container
* hierarchy to keep wrapper-container usage at a minimum. Since
* {@code DockingManager} makes this callback automatic, developers normally
* will not need to call this method explicitly. However, when removing a
* component from a {@code DefaultDockingPort} using application code,
* developers should keep in mind to use {@code undock()} instead of
* {@code remove()}.
*
* Border management after docking and undocking operations are accomplished
* using a {@code BorderManager}. {@code setBorderManager()} may be used to set
* the border manager instance and customize border management.
*
* @author Christopher Butler
*
*/
public class DefaultDockingPort extends JPanel implements DockingPort,
DockingConstants {
protected class PortLayout implements LayoutManager2, Serializable {
/**
* Returns the amount of space the layout would like to have.
*
* @param parent
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's preferred size
*/
public Dimension preferredLayoutSize(Container parent) {
Dimension dd;
Insets i = getInsets();
if (dockedComponent != null) {
dd = dockedComponent.getPreferredSize();
} else {
dd = parent.getSize();
}
return new Dimension(dd.width + i.left + i.right, dd.height + i.top
+ i.bottom);
}
/**
* Returns the minimum amount of space the layout needs.
*
* @param parent
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's minimum size
*/
public Dimension minimumLayoutSize(Container parent) {
Dimension dd;
Insets i = getInsets();
if (dockedComponent != null) {
dd = dockedComponent.getMinimumSize();
} else {
dd = parent.getSize();
}
return new Dimension(dd.width + i.left + i.right, dd.height + i.top
+ i.bottom);
}
/**
* Returns the maximum amount of space the layout can use.
*
* @param target
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's maximum size
*/
public Dimension maximumLayoutSize(Container target) {
Dimension dd;
Insets i = getInsets();
if (dockedComponent != null) {
dd = dockedComponent.getMaximumSize();
} else {
// This is silly, but should stop an overflow error
dd = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE - i.top
- i.bottom);
}
return new Dimension(dd.width + i.left + i.right, dd.height + i.top
+ i.bottom);
}
/**
* Instructs the layout manager to perform the layout for the specified
* container.
*
* @param parent
* the Container for which this layout manager is being used
*/
public void layoutContainer(Container parent) {
Rectangle b = getBounds();
Insets i = getInsets();
int w = b.width - i.right - i.left;
int h = b.height - i.top - i.bottom;
if (dockedComponent != null) {
dockedComponent.setBounds(i.left, i.top, w, h);
}
}
public void addLayoutComponent(String name, Component comp) {
}
public void removeLayoutComponent(Component comp) {
}
public void addLayoutComponent(Component comp, Object constraints) {
}
public float getLayoutAlignmentX(Container target) {
return 0.0f;
}
public float getLayoutAlignmentY(Container target) {
return 0.0f;
}
public void invalidateLayout(Container target) {
}
}
private static final WeakHashMap COMPONENT_TITLES = new WeakHashMap();
protected ArrayList dockingListeners;
private Component dockedComponent;
private BorderManager borderManager;
private String persistentId;
private boolean tabsAsDragSource;
private boolean rootPort;
private BufferedImage dragImage;
private Timer timer;
private Object lock = new Object();
static {
// setup PropertyChangeListenerFactory to respond to
// DefaultDockingPort-specific
// events
PropertyChangeListenerFactory
.addFactory(new DockablePropertyChangeHandler.Factory());
}
/**
* Creates a new {@code DefaultDockingPort} with a persistent ID equal to
* the {@code String} value of this a random UUID.
*
* @see org.flexdock.util.UUID
*/
public DefaultDockingPort() {
this(UUID.randomUUID().toString());
}
/**
* Creates a new {@code DefaultDockingPort} with the specified persistent
* ID. If {@code id} is {@code null}, then the {@code String} value of this
* {@code Object's} hash code is used. The persistent ID will be the same
* value returned by invoking {@code getPersistentId()} for this
* {@code DefaultDockingPort}.
*
* @param id
* the persistent ID for the new {@code DefaultDockingPort}
* instance.
*/
public DefaultDockingPort(String id) {
setPersistentId(id);
dockingListeners = new ArrayList(2);
addDockingListener(this);
DockingPortPropertySet props = getDockingProperties();
props.setRegionChecker(new DefaultRegionChecker());
// check container hierarchy to track root dockingports
addHierarchyListener(DockingPortTracker.getInstance());
// start out as a root dockingport
rootPort = true;
// configure layout
setLayout(createLayout());
//configure the default border manager
setBorderManager(createBorderManager());
}
protected LayoutManager createLayout() {
return new PortLayout();
}
/**
* Creates a standard border manager for this docking port.
* <p>
* This method is called from the constructor.
*
* @return the border manager for this docking port.
*/
protected BorderManager createBorderManager() {
return new StandardBorderManager(new EmptyBorder(0, 0, 0, 0));
}
/**
* Overridden to set the currently docked component. Should not be called by
* application code.
*
* @param comp
* the component to be added
*/
public Component add(Component comp) {
return setComponent(comp);
}
/**
* Overridden to set the currently docked component. Should not be called by
* application code.
*
* @param comp
* the component to be added
* @param index
* the position at which to insert the component, or {@code -1}
* to append the component to the end
*/
public Component add(Component comp, int index) {
return setComponent(comp);
}
/**
* Overridden to set the currently docked component. Should not be called by
* application code.
*
* @param comp
* the component to be added
* @param constraints
* an object expressing layout contraints for this component
*/
public void add(Component comp, Object constraints) {
setComponent(comp);
}
/**
* Overridden to set the currently docked component. Should not be called by
* application code.
*
* @param comp
* the component to be added
* @param constraints
* an object expressing layout contraints for this
* @param index
* the position in the container's list at which to insert the
* component; {@code -1} means insert at the end
*/
public void add(Component comp, Object constraints, int index) {
setComponent(comp);
}
/**
* Overridden to set the currently docked component. Should not be called by
* application code.
*
* @param name
* the name of the {@code Component} to be added.
* @param comp
* the {@code Component} to add.
*/
public Component add(String name, Component comp) {
return setComponent(comp);
}
private void addCmp(DockingPort port, Component c) {
if (port instanceof Container)
((Container) port).add(c);
}
private void dockCmp(DockingPort port, Component c) {
port.dock(c, CENTER_REGION);
}
/**
* Returns {@code true} if docking is allowed for the specified
* {@code Component} within the supplied {@code region}, {@code false}
* otherwise. It is important to note that success of a docking operation
* relies on many factors and a return value of {@code true} from this
* method does not necessarily guarantee that a call to {@code dock()} will
* succeed. This method merely indicates that the current
* {@code DockingPort} does not have any outstanding reason to block a
* docking operation with respect to the specified {@code Component} and
* {@code region}.
* <p>
* If {@code comp} is {@code null} or {@code region} is invalid according to
* {@code DockingManager.isValidDockingRegion(String region)}, then this
* method returns {@code false}.
* <p>
* If this {@code DockingPort} is not already the parent {@code DockingPort}
* for the specified {@code Component}, then this method returns
* {@code true}.
* <p>
* If this {@code DockingPort} is already the parent {@code DockingPort} for
* the specified {@code Component}, then a check is performed to see if
* there is a tabbed layout. Tabbed layouts may contain multiple
* {@code Dockables}, and thus the tab ordering may be rearranged, or
* shifted into a split layout. If {@code comp} is the only docked
* {@code Component} within this {@code DockingPort}, then this method
* returns {@code false} since the layout cannot be rearranged. Otherwise,
* this method returns {@code true}.
*
* @param comp
* the {@code Component} whose docking availability is to be
* checked
* @param region
* the region to be checked for docking availability for the
* specified {@code Component}.
* @return {@code true} if docking is allowed for the specified
* {@code Component} within the supplied {@code region},
* {@code false} otherwise.
* @see DockingPort#isDockingAllowed(Component, String)
* @see DockingManager#isValidDockingRegion(String)
* @see #isParentDockingPort(Component)
*/
public boolean isDockingAllowed(Component comp, String region) {
if (comp == null || !isValidDockingRegion(region))
return false;
// allow any valid region if we're not already the parent
// of the component we're checking
if (!isParentDockingPort(comp))
return true;
// we already contain 'comp', so we're either a tabbed-layout, or
// we contain 'comp' directly. If we contain 'comp' directly, then we
// cannot logically move 'comp' to some other region within us, as it
// already fills up our entire space.
Component docked = getDockedComponent();
if (!(docked instanceof JTabbedPane))
// not a tabbed-layout, so we contain 'c' directly
return false;
JTabbedPane tabs = (JTabbedPane) docked;
// if there is only 1 tab, then we already fill up the entire
// dockingport space and cannot be moved elsewhere
if (tabs.getTabCount() < 2)
return false;
// there is more than 1 tab present, so re-ordering is possible,
// as well as changing regions
return true;
}
/**
* Checks the current state of the {@code DockingPort} and, if present,
* issues the appropriate call to the assigned {@code BorderManager}
* instance describing the container state. This method will issue a call to
* 1 of the 4 following methods on the assigned {@code BorderManager}
* instance, passing {@code this} as the method argument:
* <p>
* {@code managePortNullChild(DockingPort port)}
* {@code managePortSimpleChild(DockingPort port)}
* {@code managePortSplitChild(DockingPort port)}
* {@code managePortTabbedChild(DockingPort port)}
*/
final void evaluateDockingBorderStatus() {
if (borderManager == null)
return;
Component docked = getDockedComponent();
// check for the null-case
if (docked == null)
borderManager.managePortNullChild(this);
// check for a split layout
else if (docked instanceof JSplitPane)
borderManager.managePortSplitChild(this);
// check for a tabbed layout
else if (docked instanceof JTabbedPane)
borderManager.managePortTabbedChild(this);
// otherwise, we have a simple case of a regular component docked within
// us
else
borderManager.managePortSimpleChild(this);
}
/**
* Returns the docking region within this {@code DockingPort} that contains
* the specified {@code Point}. Valid return values are those regions
* defined in {@code DockingConstants} and include {@code NORTH_REGION},
* {@code SOUTH_REGION}, {@code EAST_REGION}, {@code WEST_REGION},
* {@code CENTER_REGION}, and {@code UNKNOWN_REGION}.
* <p>
* If {@code location} is {@code null}, then {@code UNKNOWN_REGION} is
* returned.
* <p>
* This method gets the {@code RegionChecker} for this {@code DockingPort}
* by calling {@code getRegionChecker()}. It then attempts to locate the
* {@code Dockable} at the specified {@code location} by calling
* {@code getDockableAt(Point location)}.
* <p>
* This method defers processing to {@code getRegion(Component c, Point p)}
* for the current {@code RegionChecker}. If a {@code Dockable} was found
* at the specified {@code Point}, then the location of the {@code Point}
* is translated to the coordinate system of the {@code Component} for the
* embedded {@code Dockable} and that {@code Component} and modified
* {@code Point} are passed into {@code getRegion(Component c, Point p)}}
* for the current {@code RegionChecker}. If no {@code Dockable} was found,
* then the specified {@code Point} is left unmodified and this
* {@code DockingPort} and the supplied {@code Point} are passed to
* {@code getRegion(Component c, Point p)}} for the current
* {@code RegionChecker}.
*
* @param location
* the location within this {@code DockingPort} to examine for a
* docking region.
* @return the docking region within this {@code DockingPort} that contains
* the specified {@code Point}
* @see #getRegionChecker()
* @see #getDockableAt(Point)
* @see Dockable#getComponent()
* @see RegionChecker#getRegion(Component, Point)
*/
public String getRegion(Point location) {
if (location == null)
return UNKNOWN_REGION;
RegionChecker regionChecker = getRegionChecker();
Dockable d = getDockableAt(location);
Component regionTest = this;
if (d != null) {
regionTest = d.getComponent();
location = SwingUtilities.convertPoint(this, location, regionTest);
}
return regionChecker.getRegion(regionTest, location);
}
/**
* Returns the {@code RegionChecker} currently used by this
* {@code DockingPort}. This method retrieves the
* {@code DockingPortPropertySet} instance for this {@code DockingPort} by
* calling {@code getDockingProperties()}. It then returns by invoking
* {@code getRegionChecker()} on the resolved {@code DockingPortPropertySet}.
*
* @return the {@code RegionChecker} currently used by this
* {@code DockingPort}.
* @see #getDockingProperties()
* @see DockingPortPropertySet#getRegionChecker()
*/
public RegionChecker getRegionChecker() {
return getDockingProperties().getRegionChecker();
}
/**
* Returns the direct child {@code Dockable} located at the specified
* {@code Point}. If {@code location} is {@code null}, or this
* {@code DockingPort} is empty, then a {@code null} reference is returned.
* <p>
* If this {@code DockingPort} contains a split layout, then any nested
* {@code Dockables} will be within a sub-{@code DockingPort} and not a
* direct child of this {@code DockingPort}. Therefore, if
* {@code getDockedComponent()} returns a {@code JSplitPane}, then this
* method will return a {@code null} reference.
* <p>
* If this {@code DockingPort} contains a tabbed layout, then the
* {@code JTabbedPane} returned by {@code getDockedComponent()} will be
* checked for a {@code Dockable} at the specified {@code Point}.
*
* @param location
* the location within the {@code DockingPort} to test for a
* {@code Dockable}.
* @return the direct child {@code Dockable} located at the specified
* {@code Point}.
* @see #getDockedComponent()
* @see DockingManager#getDockable(Component)
* @see JTabbedPane#getComponentAt(int x, int y)
*/
public Dockable getDockableAt(Point location) {
if (location == null)
return null;
Component docked = getDockedComponent();
if (docked == null || docked instanceof JSplitPane)
return null;
if (docked instanceof JTabbedPane) {
JTabbedPane tabs = (JTabbedPane) docked;
Component c = tabs.getComponentAt(location.x, location.y);
return c instanceof Dockable ? (Dockable) c : DockingManager
.getDockable(c);
}
return DockingManager.getDockable(docked);
}
/**
* Returns the {@code Component} currently docked within the specified
* {@code region}.
* <p>
* If this {@code DockingPort} has either a single child {@code Dockable} or
* a tabbed layout, then the supplied region must be {@code CENTER_REGION}
* or this method will return a {@code null} reference. If there is a single
* child {@code Dockable}, then this method will return the same
* {@code Component} as returned by {@code getDockedComponent()}. If there
* is a tabbed layout, then this method will return the {@code Component} in
* the currently selected tab.
* <p>
* If this {@code DockingPort} has a split layout, then a check for
* {@code CENTER_REGION} will return a {@code null} reference. For outer
* regions ({@code NORTH_REGION}, {@code SOUTH_REGION},
* {@code EAST_REGION}, or {@code WEST_REGION}), the supplied region
* parameter must match the orientation of the embedded {@code JSplitPane}.
* Thus for a vertically oriented split pane, checks for {@code EAST_REGION}
* and {@code WEST_REGION} will return a {@code null} reference. Likewise,
* for a horizontally oriented split pane, checks for {@code NORTH_REGION}
* and {@code SOUTH_REGION} will return a {@code null} reference.
* <p>
* Outer regions are mapped to corresponding split pane regions.
* {@code NORTH_REGION} maps to the split pane's top component,
* {@code SOUTH_REGION} maps to the bottom, {@code EAST_REGION} maps to the
* right, and {@code WEST_REGION} maps to the left. The sub-{@code DockingPort}
* for the split pane region that corresponds to the specified
* {@code region} parameter will be resolved and this method will return
* that {@code Component} retrieved by calling its
* {@code getDockedComponent()} method. <i>Note that the
* {@code getDockedComponent()} call to a sub- {@code DockingPort} implies
* that the {@code JTabbedPane} or {@code JSplitPane} for the sub-port may
* be returned if the sub-port contains multiple {@code Dockables}.</i>
* <p>
* If this {@code DockingPort} is empty, then this method returns a
* {@code null} reference.
*
* @param region
* the region to be checked for a docked {@code Component}
* @return the {@code Component} docked within the specified region.
* @see DockingPort#getComponent(String)
* @see #getDockedComponent()
*/
public Component getComponent(String region) {
Component docked = getDockedComponent();
if (docked == null)
return null;
if (docked instanceof JTabbedPane) {
// they can only get tabbed dockables if they were checking the
// CENTER region.
if (!CENTER_REGION.equals(region))
return null;
JTabbedPane tabs = (JTabbedPane) docked;
return tabs.getSelectedComponent();
}
if (docked instanceof JSplitPane) {
// they can only get split dockables if they were checking an outer
// region.
if (CENTER_REGION.equals(region))
return null;
JSplitPane split = (JSplitPane) docked;
// make sure the supplied regions correspond to the current
// splitpane orientation
boolean horizontal = split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT;
if (horizontal) {
if (NORTH_REGION.equals(region) || SOUTH_REGION.equals(region))
return null;
} else {
if (EAST_REGION.equals(region) || WEST_REGION.equals(region))
return null;
}
boolean left = NORTH_REGION.equals(region)
|| WEST_REGION.equals(region);
Component c = left ? split.getLeftComponent() : split
.getRightComponent();
// split panes only contain sub-dockingports. if 'c' is not a
// sub-dockingport,
// then something is really screwed up.
if (!(c instanceof DockingPort))
return null;
// get the dockable contained in the sub-dockingport
return ((DockingPort) c).getDockedComponent();
}
// we already checked the tabbed layout and split layout. all that's
// left is the direct-child component itself. this will only ever
// exist in the CENTER, so return it if they requested the CENTER
// region.
return CENTER_REGION.equals(region) ? docked : null;
}
/**
* Returns the {@code Dockable} currently docked within the specified
* {@code region}. This method dispatches to
* {@code getComponent(String region)} to retrieve the {@code Component}
* docked within the specified region and returns its associated
* {@code Dockable} via {@code DockingManager.getDockable(Component comp)}.
* <p>
* There are somewhat strict semantics associated with retrieving the
* {@code Component} in a particular docking region. API documentation for
* {@code getComponent(String region)} should be referenced for a listing of
* the rule set. If {@code region} is invalid according to
* {@code DockingManager.isValidDockingRegion(String region)}, then this
* method returns a {@code null} reference.
*
* @param region
* the region to be checked for a docked {@code Dockable}
* @return the {@code Dockable} docked within the specified region.
* @see DockingPort#getDockable(String)
* @see #getComponent(String)
* @see #getDockedComponent()
* @see DockingManager#getDockable(Component)
* @see DockingManager#isValidDockingRegion(String)
*/
public Dockable getDockable(String region) {
Component c = getComponent(region);
return DockingManager.getDockable(c);
}
/**
* If this method returns {@code null}, implementations may throw
* NullPointerExceptions. Do not expect NPE checking.
*
* @return a valid JTabbedPane.
*/
protected JTabbedPane createTabbedPane() {
Insets oldInsets = UIManager
.getInsets(LookAndFeelSettings.TAB_PANE_BORDER_INSETS);
int tabPlacement = getInitTabPlacement();
int edgeInset = LookAndFeelSettings.getTabEdgeInset(tabPlacement);
Insets newInsets = new Insets(0, 0, 0, 0);
switch (tabPlacement) {
case JTabbedPane.TOP:
newInsets.top = edgeInset >= 0 ? edgeInset : oldInsets.top;
break;
case JTabbedPane.LEFT:
newInsets.left = edgeInset >= 0 ? edgeInset : oldInsets.left;
break;
case JTabbedPane.BOTTOM:
newInsets.bottom = edgeInset >= 0 ? edgeInset : oldInsets.bottom;
break;
case JTabbedPane.RIGHT:
newInsets.right = edgeInset >= 0 ? edgeInset : oldInsets.right;
break;
}
UIManager.put(LookAndFeelSettings.TAB_PANE_BORDER_INSETS, newInsets);
JTabbedPane pane = new JTabbedPane();
pane.setTabPlacement(tabPlacement);
UIManager.put(LookAndFeelSettings.TAB_PANE_BORDER_INSETS, oldInsets);
TabbedDragListener tdl = new TabbedDragListener();
pane.addMouseListener(tdl);
pane.addMouseMotionListener(tdl);
return pane;
}
protected void updateTab(Dockable dockable) {
Component docked = getDockedComponent();
if (docked instanceof JTabbedPane) {
JTabbedPane tabs = (JTabbedPane) docked;
int index = tabs.indexOfComponent(dockable.getComponent());
if (index > -1) {
tabs.setIconAt(index, dockable.getDockingProperties()
.getTabIcon());
tabs.setTitleAt(index, dockable.getDockingProperties()
.getDockableDesc());
}
}
}
/**
* Returns the {@code DockingStrategy} used by this {@code DockingPort}.
* This method dispatches to {@code getDockingStrategy(Object obj)},
* passing {@code this} as an argument. By default,
* {@code DefaultDockingStrategy} is used unless a different
* {@code DockingStrategy} has been assigned by the end user for
* {@code DefaultDockingPort}.
*
* @return the {@code DockingStrategy} used by this {@code DockingPort}.
* @see DockingPort#getDockingStrategy()
* @see DockingManager#getDockingStrategy(Object)
*/
public DockingStrategy getDockingStrategy() {
return DockingManager.getDockingStrategy(this);
}
/**
* Removes all {@code Dockables} from this {@code DockingPort}. Internally,
* this method dispatches to {@code removeAll()}. This ensures that not
* only docked {@code Components} are removed, that that all wrapper
* containers such as {@code JTabbedPanes}, {@code JSplitPanes}, and sub-{@code DockingPorts}
* are removed as well.
*
* @see DockingPort#clear()
* @see #removeAll()
*/
public void clear() {
removeAll();
}
/**
* Docks the specified component within the specified region. This method
* attempts to resolve the {@code Dockable} associated with the specified
* {@code Component} by invoking
* {@code DockingManager.getDockable(Component comp)}. Processing is then
* dispatched to {@code dock(Dockable dockable, String region)}.
* <p>
* If no {@code Dockable} is resolved for the specified {@code Component},
* then this method attempts to register the {@code Component} as a
* {@code Dockable} automatically by calling
* {@code DockingManager.registerDockable(Component comp)}.
* <p>
* If either {@code comp} or {@code region} region are {@code null}, then
* this method returns {@code false}. Otherwise, this method returns a
* boolean indicating the success of the docking operation based upon
* {@code dock(Dockable dockable, String region)}.
*
* @param comp
* the {@code Component} to be docked within this
* {@code DockingPort}
* @param region
* the region within this {@code DockingPort} to dock the
* specified {@code Component}
* @return {@code true} if the docking operation was successful,
* {@code false} otherwise.
* @see DockingPort#dock(Component, String)
* @see #dock(Dockable, String)
* @see DockingManager#getDockable(Component)
* @see DockingManager#registerDockable(Component)
*/
public boolean dock(Component comp, String region) {
if (comp == null || region == null)
return false;
Dockable dockable = DockingManager.getDockable(comp);
if (dockable == null)
dockable = DockingManager.registerDockable(comp);
return dock(dockable, region);
}
/**
* Docks the specified {@code Dockable} within the specified region. The
* {@code Component} used for docking is returned by calling
* {@code getComponent()} on the specified {@code Dockable}. This method
* returns {@code false} immediately if the specified {@code Dockable} is
* {@code null} or if
* {@code isDockingAllowed(Component comp, String region)} returns
* {@code false}.
* <p>
* If this {@code DockingPort} is currently empty, then the {@code Dockable}
* is docked into the {@code CENTER_REGION}, regardless of the supplied
* {@code region} parameter's value.
* <p>
* If {@code isSingleTabAllowed()} returns {@code false} and the
* {@code DockingPort} is emtpy, then the {@code Dockable} will be added
* directly to the {@code DockingPort} and will take up all available space
* within the {@code DockingPort}. In this case, subsequent calls to
* {@code getDockedComponent()} will return the dockable {@code Component}.
* <p>
* If {@code isSingleTabAllowed()} returns {@code true} and the
* {@code DockingPort} is emtpy, then a {@code JTabbedPane} will be added
* directly to the {@code DockingPort} and will take up all available space
* within the {@code DockingPort}. The dockable {@code Component} will be
* added as a tab within the tabbed pane. In this case, subsequent calls to
* {@code getDockedComponent()} will return the {@code JTabbedPane}.
* <p>
* If the {@code DockingPort} is <b>not</b> empty, and the specified region
* is {@code CENTER_REGION}, then the dockable {@code Component} will be
* added to the {@code JTabbedPane} returned by {@code getDockedComponent()}.
* If this {@code DockingPort} only contained a single dockable
* {@code Component} without a tabbed pane, then the currently docked
* {@code Component} is removed, a {@code JTabbedPane} is created and added,
* and both the old {@code Component} and the new one are added to the
* {@code JTabbedPane}. In this case, subsequent calls to
* {@code getDockedComponent()} will return the {@code JTabbedPane}.
* <p>
* If the {@code DockingPort} is <b>not</b> empty, and the specified region
* is {@code NORTH_REGION}, {@code SOUTH_REGION}, {@code EAST_REGION}, or
* {@code WEST_REGION}, then the currently docked {@code Component} is
* removed and replaced with a {@code JSplitPane}. Two new
* {@code DefaultDockingPorts} are created as sub-ports and are added to
* each side of the {@code JSplitPane}. The previously docked
* {@code Component} is docked to the CENTER_REGION of one of the sub-ports
* and the new {@code Component} is added to the other. In this case,
* subsequent calls to {@code getDockedComponent()} will return the
* {@code JSplitPane}. In this fasion, the sub-ports will now be capable of
* handling further sub-docking within the layout.
* <p>
* {@code JSplitPane} and sub-{@code DockingPort} creation are delegated to
* the {@code DockingStrategy} returned by {@code getDockingStrategy()}.
* Initial splitpane divider location is also controlled by this
* {@code DockingStrategy}.
*
* @param dockable
* the {@code Dockable} to be docked within this
* {@code DockingPort}
* @param region
* the region within this {@code DockingPort} to dock the
* specified {@code Dockable}
* @return {@code true} if the docking operation was successful,
* {@code false} otherwise.
* @see DockingPort#dock(Dockable, String)
* @see #isDockingAllowed(Component, String)
* @see #getDockedComponent()
* @see #getDockingStrategy()
* @see DockingStrategy#createDockingPort(DockingPort)
* @see DockingStrategy#createSplitPane(DockingPort, String)
* @see DockingStrategy#getInitialDividerLocation(DockingPort, JSplitPane)
* @see DockingStrategy#getDividerProportion(DockingPort, JSplitPane)
*/
public boolean dock(Dockable dockable, String region) {
if (dockable == null)
return false;
Component comp = dockable.getComponent();
if (comp == null || !isDockingAllowed(comp, region))
return false;
// can't dock the same component twice. This will also keep them from
// moving CENTER to NORTH and that sort of thing, which would just be a
// headache to manage anyway.
Component docked = getDockedComponent();
if (comp == docked)
return false;
// if there is nothing currently in the docking port, then we can only
// dock into the CENTER region.
if (docked == null)
region = CENTER_REGION;
String tabTitle = DockingUtility.getTabText(dockable);
COMPONENT_TITLES.put(comp, tabTitle);
if (!isSingleTabAllowed() && docked == null) {
setComponent(comp);
evaluateDockingBorderStatus();
return true;
}
boolean success = CENTER_REGION.equals(region) ? dockInCenterRegion(comp)
: dockInOuterRegion(comp, region);
if (success) {
evaluateDockingBorderStatus();
// if we docked in an outer region, then there is a new JSplitPane.
// We'll want to divide it in half. this is done after
// evaluateDockingBorderStatus(), so we'll know any border
// modification that took place has already happened, and we can be
// relatively safe about assumptions regarding our current insets.
if (!CENTER_REGION.equals(region))
resetSplitDividerLocation();
}
return success;
}
private void resetSplitDividerLocation() {
Component c = getDockedComponent();
if (c instanceof JSplitPane)
deferSplitDividerReset((JSplitPane) c);
}
private void deferSplitDividerReset(final JSplitPane splitPane) {
applySplitDividerLocation(splitPane);
// we don't need to defer split divider location reset until after
// a DockingSplitPane has rendered, since that class is able to figure
// out its proper divider location by itself.
if (splitPane instanceof DockingSplitPane) {
return;
}
// check to see if we've rendered
int size = SwingUtility.getSplitPaneSize(splitPane);
if (splitPane.isVisible() && size > 0 && EventQueue.isDispatchThread()) {
// if so, apply the split divider location and return
applySplitDividerLocation(splitPane);
splitPane.validate();
return;
}
// otherwise, defer applying the divider location reset until
// the split pane is rendered.
EventQueue.invokeLater(new Runnable() {
public void run() {
deferSplitDividerReset(splitPane);
}
});
}
private void applySplitDividerLocation(JSplitPane splitPane) {
DockingStrategy strategy = DockingManager.getDockingStrategy(this);
int loc = strategy.getInitialDividerLocation(this, splitPane);
splitPane.setDividerLocation(loc);
}
private boolean dockInCenterRegion(Component comp) {
Component docked = getDockedComponent();
JTabbedPane tabs = null;
if (docked instanceof JTabbedPane) {
tabs = (JTabbedPane) docked;
addTab(tabs, comp);
tabs.revalidate();
tabs.setSelectedIndex(tabs.getTabCount() - 1);
return true;
}
tabs = createTabbedPane();
// createTabbedPane() is protected and may be overridden, so we'll have
// to check for a possible null case here. Though why anyone would
// return a null, I don't know. Maybe we should throw a
// NullPointerException instead.
if (tabs == null)
return false;
// remove the currently docked component and add it to the tabbed pane
if (docked != null) {
remove(docked);
addTab(tabs, docked);
}
// add the new component to the tabbed pane
addTab(tabs, comp);
// now add the tabbed pane back to the main container
setComponent(tabs);
tabs.setSelectedIndex(tabs.getTabCount() - 1);
return true;
}
private void addTab(JTabbedPane tabs, Component comp) {
String tabText = getValidTabTitle(tabs, comp);
tabs.add(comp, tabText);
Dockable d = DockingManager.getDockable(comp);
if (d == null)
return;
Icon icon = d.getDockingProperties().getTabIcon();
int indx = tabs.getTabCount() - 1;
tabs.setIconAt(indx, icon);
}
private boolean dockInOuterRegion(Component comp, String region) {
// cache the current size and cut it in half for later in the method.
Dimension halfSize = getSize();
halfSize.width /= 2;
halfSize.height /= 2;
// remove the old docked content. we'll be adding it to another
// dockingPort.
Component docked = getDockedComponent();
remove(docked);
// add the components to their new parents.
DockingStrategy strategy = getDockingStrategy();
DockingPort oldContent = strategy.createDockingPort(this);
DockingPort newContent = strategy.createDockingPort(this);
addCmp(oldContent, docked);
dockCmp(newContent, comp);
JSplitPane newDockedContent = strategy.createSplitPane(this, region);
// put the ports in the correct order and add them to a new wrapper
// panel
DockingPort[] ports = putPortsInOrder(oldContent, newContent, region);
if (ports[0] instanceof JComponent) {
((JComponent) ports[0]).setMinimumSize(new Dimension(0, 0));
}
if (ports[1] instanceof JComponent) {
((JComponent) ports[1]).setMinimumSize(new Dimension(0, 0));
}
if (ports[0] instanceof Component)
newDockedContent.setLeftComponent((Component) ports[0]);
if (ports[1] instanceof Component)
newDockedContent.setRightComponent((Component) ports[1]);
// set the split in the middle
double ratio = .5;
if (docked instanceof Dockable
&& newDockedContent instanceof DockingSplitPane) {
Float siblingRatio = ((Dockable) docked).getDockingProperties()
.getSiblingSize(region);
if (siblingRatio != null) {
ratio = siblingRatio.doubleValue();
}
((DockingSplitPane) newDockedContent).setInitialDividerRatio(ratio);
}
newDockedContent.setDividerLocation(ratio);
// now set the wrapper panel as the currently docked component
setComponent(newDockedContent);
// if we're currently showing, then we can exit now
if (isShowing())
return true;
// otherwise, we have unrealized components whose sizes cannot be
// determined until after we're visible. cache the desired size
// values now for use later during rendering.
double proportion = strategy.getDividerProportion(this,
newDockedContent);
SwingUtility.putClientProperty((Component) oldContent,
DefaultDockingStrategy.PREFERRED_PROPORTION, new Float(
proportion));
SwingUtility.putClientProperty((Component) newContent,
DefaultDockingStrategy.PREFERRED_PROPORTION, new Float(
1f - proportion));
return true;
}
/**
* Returns the child {@code Component} currently embedded within with
* {@code DockingPort}. If the {@code DockingPort} is empty, then this
* method returns a {@code null} reference. If there is a single
* {@code Dockable} docked within it with no tabbed layout, then the
* {@code Component} for that {@code Dockable} is returned per its
* {@code getComponent()} method. If there is a tabbed layout present, then
* a {@code JTabbedPane} is returned. If there is a split layout present,
* then a {@code JSplitPane} is returned.
*
* @see DockingPort#getDockedComponent()
*/
public Component getDockedComponent() {
return dockedComponent;
}
// private JSplitPane getDockedSplitPane() {
// Component docked = getDockedComponent();
// return docked instanceof JSplitPane? (JSplitPane)docked: null;
// }
/**
* Returns a {@code String} identifier that is unique to
* {@code DockingPorts} within a JVM instance, but persistent across JVM
* instances. This is used for configuration mangement, allowing the JVM to
* recognize a {@code DockingPort} instance within an application instance,
* persist the ID, and recall it in later application instances. The ID
* should be unique within an appliation instance so that there are no
* collisions with other {@code DockingPort} instances, but it should also
* be consistent from JVM to JVM so that the association between a
* {@code DockingPort} instance and its ID can be remembered from session to
* session.
* <p>
* The value returned by this method will come from the most recent call to
* {@code setPersistentId(String id)}. If
* {@code setPersistentId(String id)} was invoked with a {@code null}
* argument, then the {@code String} verion of this {@code DockingPort's}
* hash code is used. Therefore, this method will never return a
* {@code null} reference.
*
* @return the persistent ID for this {@code DockingPort}
* @see DockingPort#getPersistentId()
* @see #setPersistentId(String)
* @see DockingManager#getDockingPort(String)
*/
public String getPersistentId() {
return persistentId;
}
/**
* Sets the persisent ID to be used for this {@code DockingPort}. If
* {@code id} is {@code null}, then the {@code String} value of this
* {@code DockingPort's} hash code is used.
* <p>
* {@code DockingPorts} are tracked by persistent ID within
* {@code DockingManager}. Whenever this method is called, the
* {@code DockingManager's} tracking mechanism is automatically upated for
* this {@code DockingPort}.
*
* @param id
* the persistent ID to be used for this {@code DockingPort}
* @see #getPersistentId()
* @see DockingManager#getDockingPort(String)
* @see DockingPortTracker#updateIndex(DockingPort)
*/
public void setPersistentId(String id) {
if (id == null) {
id = UUID.randomUUID().toString();
}
persistentId = id;
DockingPortTracker.updateIndex(this);
}
private String getValidTabTitle(JTabbedPane tabs, Component comp) {
String title = (String) COMPONENT_TITLES.get(comp);
if (title == null || title.trim().length() == 0)
title = "null";
int tc = tabs.getTabCount();
int occurrances = 0;
HashSet titles = new HashSet();
String tmp = null;
for (int i = 0; i < tc; i++) {
tmp = tabs.getTitleAt(i).toLowerCase();
titles.add(tmp);
if (tmp.startsWith(title.toLowerCase()))
occurrances++;
}
if (titles.contains(title) && occurrances > 0)
title += occurrances;
COMPONENT_TITLES.put(comp, title);
return title;
}
/**
* Returns {@code true} if single tabs are allowed within this
* {@code DockingPort}, {@code false} otherwise.
* <p>
* Generally the tabbed interface does not appear until two or more
* {@code Dockables} are docked to the {@code CENTER_REGION} of the
* {@code DockingPort} and tabs are required to switch between them. When
* there is only a single {@code Dockable} within the {@code DockingPort},
* the default behavior for the dockable {@code Component} to take up all of
* the space within the {@code DockingPort}.
* <p>
* If this method returns {@code true}, then a single {@code Dockable}
* within this {@code DockingPort} will reside within a tabbed layout that
* contains only one tab.
* <p>
* The value returned by this method is a scoped property. This means there
* may be many different "scopes" at which the single-tab property may be
* set. For instance, a "global" setting may override the individual setting
* for this {@code DockingPort}, and this {@code DockingPort's} particular
* setting may override the global default setting.
* {@code org.flexdock.docking.props.PropertyManager} should be referenced
* for further information on scoped properties.
*
* @return {@code true} if single tabs are allowed within this
* {@code DockingPort}, {@code false} otherwise.
* @see #setSingleTabAllowed(boolean)
* @see DockingManager#isSingleTabsAllowed()
* @see DockingManager#setSingleTabsAllowed(boolean)
* @see PropertyManager
* @see DockingPortPropertySet#isSingleTabsAllowed()
* @see DockingPortPropertySet#setSingleTabsAllowed(boolean)
*/
public boolean isSingleTabAllowed() {
return getDockingProperties().isSingleTabsAllowed().booleanValue();
}
/**
* Sets the "single tab" property for this {@code DockingPort}, allowing or
* disallowing a single {@code Dockable} within the {@code DockingPort} to
* appear within a tabbed layout.
* <p>
* Generally the tabbed interface does not appear until two or more
* {@code Dockables} are docked to the {@code CENTER_REGION} of the
* {@code DockingPort} and tabs are required to switch between them. When
* there is only a single {@code Dockable} within the {@code DockingPort},
* the default behavior for the dockable {@code Component} to take up all of
* the space within the {@code DockingPort}.
* <p>
* If the single tab property is set to {@code true}, then a single
* {@code Dockable} within this {@code DockingPort} will reside within a
* tabbed layout that contains only one tab.
* <p>
* The single tab property is a scoped property. This means there may be
* many different "scopes" at which the single-tab property may be set. For
* instance, a "global" setting may override the individual setting for this
* {@code DockingPort}, and this {@code DockingPort's} particular setting
* may override the global default setting. <b>This method applied a value
* only to the local scope for this particular {@code DockingPort}.</b>
* {@code org.flexdock.docking.props.PropertyManager} should be referenced
* for further information on scoped properties.
*
* @param allowed
* {@code true} if a single-tabbed layout should be allowed,
* {@code false} otherwise
* @see #isSingleTabAllowed()
* @see DockingManager#setSingleTabsAllowed(boolean)
* @see DockingManager#isSingleTabsAllowed()
* @see PropertyManager
* @see DockingPortPropertySet#setSingleTabsAllowed(boolean)
* @see DockingPortPropertySet#isSingleTabsAllowed()
*/
public void setSingleTabAllowed(boolean allowed) {
getDockingProperties().setSingleTabsAllowed(allowed);
}
/**
* Indicates whether or not the specified component is docked somewhere
* within this {@code DefaultDockingPort}. This method returns {@code true}
* if the specified {@code Component} is a direct child of the
* {@code DefaultDockingPort} or is a direct child of a {@code JTabbedPane}
* or {@code JSplitPane}that is currently the {@code DefaultDockingPort's}docked
* component. Otherwise, this method returns {@code false}. If {@code comp}
* is {@code null}, then then this method return {@code false}
*
* @param comp
* the Component to be tested.
* @return a boolean indicating whether or not the specified component is
* docked somewhere within this {@code DefaultDockingPort}.
* @see DockingPort#isParentDockingPort(java.awt.Component)
* @see Component#getParent()
* @see #getDockedComponent()
*/
public boolean isParentDockingPort(Component comp) {
if (comp == null)
return false;
Container parent = comp.getParent();
// if the component has no parent, then it can't be docked within us
if (parent == null)
return false;
// if we're the direct parent of this component, then we're the parent
// docking port
if (parent == this)
return true;
// if the component is directly inside our docked component, then we're
// also considered its parent dockingPort
return parent == getDockedComponent();
}
protected boolean isValidDockingRegion(String region) {
return DockingManager.isValidDockingRegion(region);
}
private boolean isSingleComponentDocked() {
Component c = getDockedComponent();
// we have no docked component
if (c == null)
return false;
// we do have a docked component. It'll be a splitpane, a tabbedpane,
// or something else.
// if it's a splitpane, then we definitely have more than one component
// docked
if (c instanceof JSplitPane)
return false;
// if it's a tabbed pane, then check the number of tabs on the pane
if (c instanceof JTabbedPane) {
return ((JTabbedPane) c).getTabCount() == 1;
}
// splitpane and tabbed pane are the only two subcontainers that signify
// more than one docked component. if neither, then we only have one
// component docked.
return true;
}
protected Dockable getCenterDockable() {
// can't have a CENTER dockable if there's nothing in the center
if (!isSingleComponentDocked())
return null;
// get the component in the CENTER
Component c = getDockedComponent();
if (c instanceof JTabbedPane) {
// if in a tabbed pane, get the first component in there.
// (there will only be 1, since we've already passed the
// isSingleComponentDocked() test)
c = ((JTabbedPane) c).getComponent(0);
}
// return the Dockable instance associated with this component
return DockingManager.getDockable(c);
}
private DockingPort[] putPortsInOrder(DockingPort oldPort,
DockingPort newPort, String region) {
if (NORTH_REGION.equals(region) || WEST_REGION.equals(region))
return new DockingPort[] { newPort, oldPort };
return new DockingPort[] { oldPort, newPort };
}
/**
* This method completes with a call to
* {@code evaluateDockingBorderStatus()} to allow any installed
* {@code DefaultDockingStrategy} to handle container-state-related
* behavior.
*/
private void reevaluateContainerTree() {
reevaluateDockingWrapper();
reevaluateTabbedPane();
evaluateDockingBorderStatus();
}
private void reevaluateDockingWrapper() {
Component docked = getDockedComponent();
Container parent = getParent();
Container grandParent = parent == null ? null : parent.getParent();
// added grandparent check up here so we will be able to legally embed a
// DefaultDockingPort within a plain JSplitPane without triggering an
// unnecessary remove()
if (docked == null && parent instanceof JSplitPane
&& grandParent instanceof DefaultDockingPort) {
// in this case, the docked component has disappeared (removed) and
// our parent component is a wrapper for us and our child so that we
// can share the root docking port with another component. since our
// child is gone, there's no point in our being here anymore and our
// sibling component shouldn't have to share screen real estate with
// us anymore. we'll remove ourselves and notify the root docking
// port that the component tree has been modified.
parent.remove(this);
((DefaultDockingPort) grandParent).reevaluateContainerTree(); // LABEL
// 1
} else if (docked instanceof JSplitPane) {
// in this case, we're the parent of a docking wrapper. this implies
// that we're splitting our real estate between two components. (in
// practice, we're actually the parent that was called above at
// LABEL
// 1).
JSplitPane wrapper = (JSplitPane) docked;
Component left = wrapper.getLeftComponent();
Component right = wrapper.getRightComponent();
// first, check to make sure we do in fact have 2 components. if so,
// then we don't have
// to go any further.
if (left != null && right != null)
return;
// check to see if we have zero components. if so, remove everything
// and return.
if (left == right) {
removeAll();
return;
}
// if we got here, then one of our components has been removed (i.e.
// LABEL 1). In this
// case, we want to pull the remaining content out of its
// split-wrapper and add it
// as a direct child to ourselves.
Component comp = left == null ? right : left;
wrapper.remove(comp);
// do some cleanup on the wrapper before removing it
if (wrapper instanceof DockingSplitPane) {
((DockingSplitPane) wrapper).cleanup();
}
super.remove(wrapper);
if (comp instanceof DefaultDockingPort)
comp = ((DefaultDockingPort) comp).getDockedComponent();
if (comp != null)
setComponent(comp);
}
}
private void reevaluateTabbedPane() {
Component docked = getDockedComponent();
if (!(docked instanceof JTabbedPane))
return;
JTabbedPane tabs = (JTabbedPane) docked;
int tabCount = tabs.getTabCount();
// we don't have to do anything special here if there is more than the
// minimum number of allowable tabs
int minTabs = isSingleTabAllowed() ? 0 : 1;
if (tabCount > minTabs) {
return;
}
// otherwise, pull out the component in the remaining tab (if it
// exists), and add it to ourselves as a direct child (ditching the
// JTabbedPane).
Component comp = tabCount == 1 ? tabs.getComponentAt(0) : null;
removeAll();
if (comp != null)
setComponent(comp);
Container parent = getParent();
Container grandParent = parent == null ? null : parent.getParent();
// if our TabbedPane's last component was removed, then the TabbedPane
// itself has now been removed. if we're a child port within a
// JSplitPane
// within another DockingPort, then we ourselved need to be removed from
// the component tree, since we don't have any content.
if (comp == null && parent instanceof JSplitPane
&& grandParent instanceof DefaultDockingPort) {
parent.remove(this);
((DefaultDockingPort) grandParent).reevaluateContainerTree();
}
}
/**
* Overridden to decorate superclass method, keeping track of internal
* docked-component reference.
*
* @param index
* the index of the component to be removed.
* @see Container#remove(int)
*/
public void remove(int index) {
Component docked = getDockedComponent();
Component comp = getComponent(index);
super.remove(index);
if (docked == comp)
dockedComponent = null;
}
/**
* Overridden to decorate superclass method, keeping track of internal
* docked-component reference.
*
* @see Container#removeAll()
*/
public void removeAll() {
super.removeAll();
dockedComponent = null;
}
/**
* Sets the currently installed {@code BorderManager}. This method provides
* a means of customizing border managment following any successful call to
* {@code dock(Dockable dockable, String region)} or
* {@code undock(Component comp)}, allowing cleanup of borders for nested
* {@code Components} within the docking layout. {@code null} values are
* allowed.
*
* @param mgr
* the {@code BorderManager} assigned to to manage docked
* component borders.
* @see #getBorderManager()
* @see BorderManager
*/
public void setBorderManager(BorderManager mgr) {
borderManager = mgr;
}
/**
* Returns the currently intalled {@code BorderManager}. The
* {@code BorderManager} is used any time a successful call to
* {@code dock(Dockable dockable, String region)} or
* {@code undock(Component comp)} has been issued to clean up borders for
* nested {@code Components} within the docking layout. This method will
* return a {@code null} reference if there is no {@code BorderManager}
* installed.
*
* @return the currently installed {@code BorderManager}.
* @see #setBorderManager(BorderManager)
* @see BorderManager
*/
public BorderManager getBorderManager() {
return borderManager;
}
private Component setComponent(Component c) {
if (getDockedComponent() != null)
removeAll();
dockedComponent = c;
Component ret = super.add(dockedComponent);
// calling doLayout here to properly set the component's size
// validate throws an error
doLayout();
return ret;
}
/**
* Undocks the specified {@code Component} and returns a boolean indicating
* the success of the operation.
* <p>
* Since {@code DefaultDockingPort} may only contain one child component,
* there i s a container hierarchy to manage tabbed interfaces, split
* layouts, and sub-docking. As components are removed from this hierarchy,
* the hierarchy itself must be reevaluated. Removing a component from a
* child code>DefaultDockingPort} within a {@code JSplitPane} renders the
* child {@code DefaultDockingPort} unnecessary, which, in turn, renders the
* notion of splitting the layout with a {@code JSplitPane} unnecessary
* (since there are no longer two components to split the layout between).
* Likewise, removing a child component from a {@code JTabbedPane} such that
* there is only one child left within the {@code JTabbedPane} removes the
* need for a tabbed interface to begin with.
* <p>
* This method automatically handles the reevaluation of the container
* hierarchy to keep wrapper-container usage at a minimum. Since
* {@code DockingManager} makes this callback automatic, developers normally
* will not need to call this method explicitly. However, when removing a
* component from a {@code DefaultDockingPort} using application code,
* developers should keep in mind to use this method instead of
* {@code remove()}.
*
* @param comp
* the {@code Component} to be undocked.
* @return a boolean indicating the success of the operation
* @see DockingPort#undock(Component comp)
* @see DockingManager#undock(Dockable)
*/
public boolean undock(Component comp) {
// can't undock a component that isn't already docked within us
if (!isParentDockingPort(comp))
return false;
// remove the component
comp.getParent().remove(comp);
// reevaluate the container tree.
reevaluateContainerTree();
return true;
}
/**
* Returns all {@code Dockables} docked within this {@code DockingPort} and
* all sub-{@code DockingPorts}. The returned {@code Set} will contain
* {@code Dockable} instances. If there are no {@code Dockables} present, an
* empty {@code Set} will be returned. This method will never return a
* {@code null} reference.
*
* @return all {@code Dockables} docked within this {@code DockingPort} and
* all sub-{@code DockingPorts}.
* @see DockingPort#getDockables()
*/
public Set getDockables() {
// return ALL dockables, recursing to maximum depth
return getDockableSet(-1, 0, null);
}
protected Set getDockableSet(int depth, int level, Class desiredClass) {
Component c = getDockedComponent();
if (c instanceof JTabbedPane) {
JTabbedPane tabs = (JTabbedPane) c;
int len = tabs.getTabCount();
HashSet set = new HashSet(len);
for (int i = 0; i < len; i++) {
c = tabs.getComponentAt(i);
if (isValidDockableChild(c, desiredClass)) {
if (c instanceof Dockable)
set.add(c);
else
set.add(DockingManager.getDockable(c));
}
}
return set;
}
HashSet set = new HashSet(1);
// if we have a split-layout, then we need to decide whether to get the
// child viewSets. If 'depth' is less then zero, then it's implied we
// want to recurse to get ALL child viewsets no matter how deep. If
// 'depth' is greater than or equal to zero, we only want to go as deep
// as the specified depth.
if (c instanceof JSplitPane && (depth < 0 || level <= depth)) {
JSplitPane pane = (JSplitPane) c;
Component sub1 = pane.getLeftComponent();
Component sub2 = pane.getRightComponent();
if (sub1 instanceof DefaultDockingPort)
set.addAll(((DefaultDockingPort) sub1).getDockableSet(depth,
level + 1, desiredClass));
if (sub2 instanceof DefaultDockingPort)
set.addAll(((DefaultDockingPort) sub2).getDockableSet(depth,
level + 1, desiredClass));
}
if (isValidDockableChild(c, desiredClass)) {
if (c instanceof Dockable)
set.add(c);
else
set.add(DockingManager.getDockable(c));
}
return set;
}
protected boolean isValidDockableChild(Component c, Class desiredClass) {
return desiredClass == null ? DockingManager.getDockable(c) != null
: desiredClass.isAssignableFrom(c.getClass());
}
/**
* Adds a {@code DockingListener} to observe docking events for this
* {@code DockingPort}. {@code null} arguments are ignored.
*
* @param listener
* the {@code DockingListener} to add to this {@code DockingPort}.
* @see DockingMonitor#addDockingListener(DockingListener)
* @see #getDockingListeners()
* @see #removeDockingListener(DockingListener)
*/
public void addDockingListener(DockingListener listener) {
if (listener != null)
dockingListeners.add(listener);
}
/**
* Returns an array of all {@code DockingListeners} added to this
* {@code DockingPort}. If there are no listeners present for this
* {@code DockingPort}, then a zero-length array is returned.
*
* @return an array of all {@code DockingListeners} added to this
* {@code DockingPort}.
* @see DockingMonitor#getDockingListeners()
* @see #addDockingListener(DockingListener)
* @see #removeDockingListener(DockingListener)
*/
public DockingListener[] getDockingListeners() {
return (DockingListener[]) dockingListeners
.toArray(new DockingListener[0]);
}
/**
* Removes the specified {@code DockingListener} from this
* {@code DockingPort}. If the specified {@code DockingListener} is
* {@code null}, or the listener has not previously been added to this
* {@code DockingPort}, then no {@code Exception} is thrown and no action
* is taken.
*
* @param listener
* the {@code DockingListener} to remove from this
* {@code DockingPort}
* @see DockingMonitor#removeDockingListener(DockingListener)
* @see #addDockingListener(DockingListener)
* @see #getDockingListeners()
*/
public void removeDockingListener(DockingListener listener) {
if (listener != null)
dockingListeners.remove(listener);
}
/**
* No operation. Provided as a method stub to fulfull the
* {@code DockingListener} interface contract.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#dockingCanceled(DockingEvent)
*/
public void dockingCanceled(DockingEvent evt) {
}
/**
* Requests activation for the newly docked Dockable.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#dockingComplete(DockingEvent)
*/
public void dockingComplete(DockingEvent evt) {
Dockable dockable = evt.getDockable();
if (dockable == null || !isShowing() || evt.getNewDockingPort() != this)
return;
ActiveDockableTracker
.requestDockableActivation(dockable.getComponent());
}
/**
* No operation. Provided as a method stub to fulfull the
* {@code DockingListener} interface contract.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#dragStarted(DockingEvent)
*/
public void dragStarted(DockingEvent evt) {
}
/**
* No operation. Provided as a method stub to fulfull the
* {@code DockingListener} interface contract.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#dropStarted(DockingEvent)
*/
public void dropStarted(DockingEvent evt) {
}
/**
* No operation. Provided as a method stub to fulfull the
* {@code DockingListener} interface contract.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#undockingComplete(DockingEvent)
*/
public void undockingComplete(DockingEvent evt) {
}
/**
* No operation. Provided as a method stub to fulfull the
* {@code DockingListener} interface contract.
*
* @param evt
* the {@code DockingEvent} to respond to.
* @see DockingListener#undockingStarted(DockingEvent)
*/
public void undockingStarted(DockingEvent evt) {
}
/**
* Returns a {@code DockingPortPropertySet} instance associated with this
* {@code DockingPort}. This method returns the default implementation
* supplied by the framework by invoking
* {@code getDockingPortPropertySet(DockingPort port)} on
* {@code org.flexdock.docking.props.PropertyManager} and supplying an
* argument of {@code this}.
*
* @return the {@code DockingPortPropertySet} associated with this
* {@code DockingPort}. This method will not return a {@code null}
* reference.
* @see DockingPortPropertySet
* @see DockingPort#getDockingProperties()
* @see org.flexdock.docking.props.PropertyManager#getDockingPortPropertySet(DockingPort)
*/
public DockingPortPropertySet getDockingProperties() {
return PropertyManager.getDockingPortPropertySet(this);
}
/**
* Enables or disables drag support for docking operations on the tabs used
* within an embedded tabbed layout. If tab-drag-source is enabled, then the
* tab that corresponds to a {@code Dockable} within an embedded tabbed
* layout will respond to drag events as if the tab were a component
* included within the {@code List} returned by calling
* {@code getDragSources()} on the {@code Dockable}. This allows dragging a
* tab to initiate drag-to-dock operations.
*
* @param enabled
* {@code true} if drag-to-dock support should be enabled for
* tabs and their associated {@code Dockables}, {@code false}
* otherwise.
* @see #isTabsAsDragSource()
* @see Dockable#getDragSources()
*/
public void setTabsAsDragSource(boolean enabled) {
tabsAsDragSource = enabled;
}
/**
* Returns {@code true} if drag-to-dock support is enabled for tabs and
* their associated {@code Dockables}, {@code false} otherwise. If
* tab-drag-source is enabled, then the tab that corresponds to a
* {@code Dockable} within an embedded tabbed layout will respond to drag
* events as if the tab were a component included within the {@code List}
* returned by calling {@code getDragSources()} on the {@code Dockable}.
* This allows dragging a tab to initiate drag-to-dock operations.
*
* @return {@code true} if drag-to-dock support is enabled for tabs and
* their associated {@code Dockables}, {@code false} otherwise.
* @see #setTabsAsDragSource(boolean)
* @see Dockable#getDragSources()
*/
public boolean isTabsAsDragSource() {
return tabsAsDragSource;
}
protected int getInitTabPlacement() {
return getDockingProperties().getTabPlacement().intValue();
}
/**
* Returns a boolean indicating whether or not this {@code DockingPort} is
* nested within another {@code DockingPort}. If there are no other
* {@code DockingPorts} within this {@code DockingPort's} container ancestor
* hierarchy, then this method will return {@code true}. Otherwise, this
* method will return {@code false}. If the this {@code DockingPort} is not
* validated and/or is not part of a container hierarchy, this method should
* return {@code true}.
*
* @return {@code false} if this {@code DockingPort} is nested within
* another {@code DockingPort}, {@code true} otherwise.
* @see DockingPort#isRoot()
*/
public boolean isRoot() {
return rootPort;
}
/**
* This method is used internally by the framework to notify
* {@code DefaultDockingPorts} whether they are "root" {@code DockingPorts}
* according to the rules specified by {@code isRoot()} on the
* {@code DockingPort} interface. <b>This method should not be called by
* application-level developers.</b> It will most likely be removed in
* future versions and the logic contained herein will be managed by some
* type of change listener.
*
* @param root
* {@code true} if this is a "root" {@code DockingPort},
* {@code false} otherwise.
* @see #isRoot()
* @see DockingPort#isRoot()
*/
public void setRoot(boolean root) {
this.rootPort = root;
}
/**
* This method is used internally by the framework to notify
* {@code DefaultDockingPorts} whether a drag operation is or is not
* currently in progress and should not be called by application-level
* developers. It will most likely be removed in future versions and the
* logic contained herein will be managed by some type of change listener.
*
* @param inProgress
* {@code true} if a drag operation involving this
* {@code DockingPort} is currently in progress, {@code false}
* otherwise.
*/
public void setDragInProgress(boolean inProgress) {
if (inProgress && dragImage != null)
return;
if (!inProgress && dragImage == null)
return;
if (inProgress) {
dragImage = SwingUtility.createImage(this);
} else {
dragImage = null;
}
repaint();
}
/**
* Overridden to provide enhancements during drag operations. Some
* {@code DragPreview} implementations may by able to supply a
* {@code BufferedImage} for this {@code DockingPort} to use for painting
* operations. This may be useful for cases in which the dimensions of
* docked {@code Components} are altered in realtime during the drag
* operation to provide a "ghost" image for the {@code DragPreview}. In
* this case, visual feedback for altered subcomponents within this
* {@code DockingPort} may be blocked in favor of a temporary
* {@code BufferedImage} for the life of the drag operation.
*
* @param g
* the {@code Graphics} context in which to paint
* @see JComponent#paint(java.awt.Graphics)
*/
public void paint(Graphics g) {
if (dragImage == null) {
super.paint(g);
return;
}
g.drawImage(dragImage, 0, 0, this);
}
/**
* Returns a {@code LayoutNode} containing metadata that describes the
* current layout contained within this {@code DefaultDockingPort}. The
* {@code LayoutNode} returned by this method will be a
* {@code DockingPortNode} that constitutes the root of a tree structure
* containing various {@code DockingNode} implementations; specifically
* {@code SplitNode}, {@code DockableNode}, and {@code DockingPortNode}.
* Each of these nodes is {@code Serializable}, implying the
* {@code LayoutNode} itself may be written to external storage and later
* reloaded into this {@code DockingPort} via
* {@code importLayout(LayoutNode node)}.
*
* @return a {@code LayoutNode} representing the current layout state within
* this {@code DockingPort}
* @see DockingPort#importLayout(LayoutNode)
* @see #importLayout(LayoutNode)
* @see org.flexdock.docking.state.LayoutManager#createLayout(DockingPort)
* @see LayoutNode
* @see org.flexdock.docking.state.tree.DockingNode
* @see DockingPortNode
* @see SplitNode
* @see DockableNode
*/
public LayoutNode exportLayout() {
return DockingManager.getLayoutManager().createLayout(this);
}
/**
* Clears out the existing layout within this {@code DockingPort} and
* reconstructs a new layout based upon the specified {@code LayoutNode}.
* <p>
* At present, this method can only handle {@code LayoutNodes} that have
* been generated by {@code DefaultDockingPort's} {@code exportLayout()}
* method. If the specified {@code LayoutNode} is {@code null} or is
* otherwise <i>not</i> an instance of {@code DockingPortNode}, then this
* method returns immediately with no action taken.
* <p>
* Otherwise, the necessary {@code Dockables} are docked within this
* {@code DockingPort} and all subsequently generated sub-{@code DockingPorts}
* in a visual configuration mandated by the tree structure modeled by the
* specified {@code LayoutNode}.
*
* @param node
* the {@code LayoutNode} whose layout is to be instantiated
* within this {@code DockingPort}
* @see DockingPort#importLayout(LayoutNode)
* @see #exportLayout()
* @see LayoutNode
* @see org.flexdock.docking.state.tree.DockingNode
* @see DockingPortNode
* @see SplitNode
* @see DockableNode
*/
public void importLayout(LayoutNode node) {
if (!(node instanceof DockingPortNode))
return;
node.setUserObject(this);
ArrayList splitPaneResizeList = new ArrayList();
constructLayout(node, splitPaneResizeList);
deferSplitPaneValidation(splitPaneResizeList);
revalidate();
}
private void constructLayout(LayoutNode node, ArrayList splitPaneResizeList) {
// load the user object. this object isn't used here, but
// LayoutNode should have a lazy-load mechanism for loading of
// userObject at runtime. we just want to make sure the userObject has
// been loaded before we proceed.
Object obj = node.getUserObject();
if (node instanceof SplitNode)
splitPaneResizeList.add(node);
for (Enumeration en = node.children(); en.hasMoreElements();) {
LayoutNode child = (LayoutNode) en.nextElement();
constructLayout(child, splitPaneResizeList);
}
if (node instanceof SplitNode)
reconstruct((SplitNode) node);
else if (node instanceof DockingPortNode)
reconstruct((DockingPortNode) node);
}
private void reconstruct(DockingPortNode node) {
DefaultDockingPort port = (DefaultDockingPort) node.getDockingPort();
if (node.isSplit()) {
SplitNode child = (SplitNode) node.getChildAt(0);
JSplitPane split = child.getSplitPane();
// float percentage = child.getPercentage();
port.setComponent(split);
port.evaluateDockingBorderStatus();
return;
}
for (Enumeration en = node.children(); en.hasMoreElements();) {
LayoutNode child = (LayoutNode) en.nextElement();
if (child instanceof DockableNode) {
Dockable dockable = ((DockableNode) child).getDockable();
port.dock(dockable, CENTER_REGION);
}
}
}
private void reconstruct(SplitNode node) {
JSplitPane split = node.getSplitPane();
Component left = node.getLeftComponent();
Component right = node.getRightComponent();
split.setLeftComponent(left);
split.setRightComponent(right);
}
private void deferSplitPaneValidation(final ArrayList splitNodes) {
if (timer == null) {
timer = new Timer(15, new ActionListener() {
public void actionPerformed(ActionEvent e) {
Runnable r = new Runnable() {
public void run() {
synchronized (lock) {
if (timer != null) {
processImportedSplitPaneValidation(splitNodes);
}
}
}
};
EventQueue.invokeLater(r);
}
});
timer.setRepeats(true);
timer.start();
}
}
private void processImportedSplitPaneValidation(ArrayList splitNodes) {
synchronized (lock) {
int len = splitNodes.size();
if (len == 0) {
timer.stop();
timer = null;
return;
}
for (int i = 0; i < len; i++) {
((SplitNode) splitNodes.get(i)).getSplitPane().setVisible(false);
}
// first, check to see if we're ready for rendering
SplitNode node = (SplitNode) splitNodes.get(0);
JSplitPane split = node.getSplitPane();
int size = split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ? split.getWidth() : split.getHeight();
// if we're not ready to render, then defer processing again until later
if (!split.isValid() || size == 0) {
// try to validate first
if (!split.isValid())
split.validate();
// now redispatch
return;
}
timer.stop();
timer = null;
// if we're ready to render, then loop through all the splitNodes and
// set the split dividers to their appropriate locations.
for (int i = 0; i < len; i++) {
node = (SplitNode) splitNodes.get(i);
split = node.getSplitPane();
size = split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT ? split.getWidth() : split.getHeight();
float percent = node.getPercentage();
int divLoc = (int) ((float) size * percent);
// System.err.println("percent: " + percent + ", divLoc: " +
// divLoc);
split.setDividerLocation(percent);
// make sure to invoke the installed BorderManager how that we have
// a hierarchy of DockingPorts. otherwise, we may end up with some
// ugly nested borders.
DockingPort port = DockingUtility.getParentDockingPort(split);
if (port instanceof DefaultDockingPort) {
((DefaultDockingPort) port).evaluateDockingBorderStatus();
}
split.validate();
}
for (int i = 0; i < len; i++) {
((SplitNode) splitNodes.get(i)).getSplitPane().setVisible(true);
}
}
}
// --- maximization
// remember information to restore state after maximized dockable has left
// us again
private MaximizationInstallInfo maximizationInstallInfo;
public void installMaximizedDockable(Dockable dockable) {
if (maximizationInstallInfo != null) {
throw new IllegalStateException("Already maximized");
}
maximizationInstallInfo = new MaximizationInstallInfo(
getDockedComponent(), getBorder());
Component newComponent = dockable.getComponent();
setComponent(newComponent);
evaluateDockingBorderStatus();
revalidate();
}
public void uninstallMaximizedDockable() {
if (maximizationInstallInfo == null) {
throw new IllegalStateException("No dockable maximized.");
}
setComponent(maximizationInstallInfo.getContent());
setBorder(maximizationInstallInfo.getBorder());
maximizationInstallInfo = null;
revalidate();
}
// remember necessary information to restore state after maximized dockable
// returns
private MaximizationReleaseInfo maximizationReleaseInfo;
public void releaseForMaximization(Dockable dockable) {
if (maximizationReleaseInfo != null) {
throw new IllegalStateException(
"Already released a Dockable for maximization.");
}
Component comp = dockable.getComponent();
Border border = null;
if (comp instanceof JComponent) {
border = ((JComponent) comp).getBorder();
}
maximizationReleaseInfo = new MaximizationReleaseInfo(comp, border);
Component docked = getDockedComponent();
if (docked == null) {
throw new IllegalStateException("DefaultDockingPort is empty.");
} else if (docked instanceof JSplitPane) {
// this should never happen since in that case this DockingPort
// can't
// be the direct parent of any dockable requesting maximization
throw new IllegalStateException(
"DefaultDockingPort does not directly contain a Dockable");
} else if (docked instanceof JTabbedPane) {
// this is the tricky case, we have to store layout of tabbed pane
// to restore later
JTabbedPane tabs = (JTabbedPane) docked;
maximizationReleaseInfo.setTabIndex(getTabIndex(tabs, comp));
tabs.remove(comp);
} else {
// check if our component is the one that requested maximization
if (comp != docked) {
throw new IllegalStateException(
"Dockable requesting maximization is not the one docked in this DefaultDockingPort.");
}
remove(comp);
}
}
private int getTabIndex(JTabbedPane tabs, Component comp) {
int tabCount = tabs.getTabCount();
for (int i = 0; i < tabCount; i++) {
if (tabs.getComponentAt(i) == comp) {
return i;
}
}
return -1;
}
public void returnFromMaximization() {
Component comp = maximizationReleaseInfo.getContent();
if (comp instanceof JComponent) {
((JComponent) comp).setBorder(maximizationReleaseInfo.getBorder());
}
int tabIndex = maximizationReleaseInfo.getTabIndex();
maximizationReleaseInfo = null;
Component docked = getDockedComponent();
if (docked != null && docked instanceof JTabbedPane) {
// return dockable to its original (TODO) position in the
// JTabbedPane
JTabbedPane tabs = (JTabbedPane) docked;
tabs.add(comp, getValidTabTitle(tabs, comp), tabIndex);
tabs.setSelectedIndex(tabIndex);
} else {
// return dockable as direct child
setComponent(comp);
}
revalidate();
}
private static class MaximizationInstallInfo {
private final Component content;
private final Border border;
public MaximizationInstallInfo(Component content, Border border) {
this.content = content;
this.border = border;
}
public Border getBorder() {
return border;
}
public Component getContent() {
return content;
}
}
private static class MaximizationReleaseInfo extends
MaximizationInstallInfo {
private int tabIndex;
public MaximizationReleaseInfo(Component content, Border border) {
super(content, border);
}
public int getTabIndex() {
return tabIndex;
}
public void setTabIndex(int tabIndex) {
this.tabIndex = tabIndex;
}
}
}