📄 defaultdockingstrategy.java
字号:
/*
* Created on Mar 14, 2005
*/
package org.flexdock.docking.defaults;
import java.awt.Component;
import java.awt.Container;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Map;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
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.drag.DragManager;
import org.flexdock.docking.drag.DragOperation;
import org.flexdock.docking.event.DockingEvent;
import org.flexdock.docking.floating.frames.DockingFrame;
import org.flexdock.docking.floating.frames.FloatingDockingPort;
import org.flexdock.docking.props.DockablePropertySet;
import org.flexdock.docking.state.FloatManager;
import org.flexdock.event.EventManager;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.RootWindow;
import org.flexdock.util.SwingUtility;
/**
* @author Christopher Butler
*/
public class DefaultDockingStrategy implements DockingStrategy,
DockingConstants {
public static final String PREFERRED_PROPORTION = "DefaultDockingStrategy.PREFERRED_PROPORTION";
/**
* Returns the specified {@code Dockable's} sibling {@code Dockable} within
* the current docking layout. This method checks the parent
* {@code DockingPort} of a given {@code Dockable} to see if it is split
* equally with another {@code Dockable}. If so, the immediate sibling
* {@code Dockable} is returned. If there are more than two
* {@code Dockables} within the split layout, then the closest sibling
* region is determined and this method dispatches to
* {@code getSibling(Dockable dockable, String region)}.
* <p>
* If the specified {@code Dockable} is {@code null}, or there are no
* siblings available in the docking layout, then this methdo returns a
* {@code null} reference. If the specified {@code Dockable} is not
* currently docked within a {@code DockingPort}, then this method returns
* a {@code null} reference.
*
* @param dockable
* the {@code Dockable} whose sibling is to be returned
* @return the sibling of the specified {@code Dockable} within the current
* docking layout.
* @see Dockable#getDockingPort()
* @see #getSibling(Dockable, String)
*/
public static Dockable getSibling(Dockable dockable) {
if (dockable == null)
return null;
DockingPort port = dockable.getDockingPort();
String startRegion = findRegion(dockable.getComponent());
String region = DockingUtility.flipRegion(startRegion);
Dockable sibling = findDockable(port, dockable.getComponent(), region,
startRegion);
return sibling;
}
/**
* Returns the sibling {@code Dockable} relative to the specified
* {@code Dockable's} supplied region in the current docking layout. If
* {@code dockable} is {@code null} or {@code region} is either invalid or
* equal to {@code CENTER_REGION}, then this method returns a {@code null}
* reference.
* <p>
* If the specified {@code Dockable} is in a {@code DockingPort} that
* equally splits the layout between two {@code Dockables} in a fashion that
* matches up with the specified region, then the immediate sibling
* {@code Dockable} is returned. Otherwise, a fuzzy search is performed
* throughout the docking layout for a {@code Dockable} that "looks like" it
* is docked to the supplied region of the specified {@code Dockable} from a
* visual standpoint.
* <p>
* For instance, a docking layout may consist of four quadrants <i>Dockable1</i>
* (top-left), <i>Dockable2</i> (top-right), <i>Dockable3</i>
* (bottom-left) and <i>Dockable4</i> (bottom-right). The layout is built
* by docking <i>Dockable2>/i> to the {@code EAST_REGION} of <i>Dockable1</i>,
* <i>Dockable3</i> to the {@code SOUTH_REGION} of <i>Dockable1</i>, and
* <i>Dockable4</i> to the {@code SOUTH_REGION} of <i>Dockable2</i>.
* Within this layout, <i>Dockable1</i> and <i>Dockable3</i> are immediate
* siblings, as are <i>Dockable2</i> and <i>Dockable4</i>. Thus,
* requesting sibling NORTH_REGION of <i>Dockable3</i> will easily yield
* <i>Dockable1</i>. However, <i>Dockable3</i> has no immediate
* {@code EAST_REGION} sibling. In this case, a fuzzy search through the
* layout is performed to determine the visual sibling, and this method
* returns <i>Dockable4</i>. Likewise, this method will return a
* {@code null} reference for the {@code WEST_REGION} sibling of
* <i>Dockable3}, since there are no {@code Dockables} in the visual layout
* to the west of this {@code Dockable}.
*
* @param dockable
* the {@code Dockable} whose sibling is to be returned
* @param region
* the region of the specified {@code Dockable} whose visual
* sibling is to be returned
* @return the {@code Dockable} in the supplied region relative to the
* specified {@code Dockable}
*/
public static Dockable getSibling(Dockable dockable, String region) {
if (dockable == null || !DockingManager.isValidDockingRegion(region)
|| CENTER_REGION.equals(region))
return null;
DockingPort port = dockable.getDockingPort();
String startRegion = findRegion(dockable.getComponent());
Dockable sibling = findDockable(port, dockable.getComponent(), region,
startRegion);
return sibling;
}
private static Dockable findDockable(DockingPort port, Component self,
String region, String startRegion) {
if (port == null)
return null;
Component docked = port.getDockedComponent();
// if we're not a split port, then there is no concept of 'outer
// regions'.
// jump up a level to find the parent split port
if (!(docked instanceof JSplitPane)) {
DockingPort superPort = DockingManager
.getDockingPort((Component) port);
return findDockable(superPort, self, region, startRegion);
}
Component sibling = port.getComponent(region);
if (sibling == self) {
if (!(self instanceof JSplitPane)) {
DockingPort superPort = DockingManager
.getDockingPort((Component) port);
return findDockable(superPort, self, region, startRegion);
}
return null;
}
if (sibling instanceof JSplitPane) {
// go one level deeper
DockingPort subPort = DockingManager.getDockingPort(sibling);
Component other = port.getComponent(DockingUtility
.flipRegion(region));
String subRegion = findSubRegion((JSplitPane) sibling, other,
region, startRegion);
return findDockable(subPort, self, subRegion, startRegion);
}
// if we have no direct sibling in the specified region, the jump
// up a level.
if (sibling == null) {
DockingPort superPort = DockingManager
.getDockingPort((Component) port);
self = port.getDockedComponent();
return findDockable(superPort, self, region, startRegion);
}
return DockingManager.getDockable(sibling);
}
private static String findSubRegion(JSplitPane split, Component other,
String targetRegion, String baseRegion) {
String region = DockingUtility.translateRegionAxis(split, targetRegion);
if (!(other instanceof JSplitPane))
return region;
boolean translated = !targetRegion.equals(region);
if (translated && !DockingUtility.isAxisEquivalent(region, baseRegion)) {
region = DockingUtility.flipRegion(region);
}
return region;
}
/**
* Returns the docking region within the current split docking layout
* containing the specified {@code Component}. If {@code comp} is
* {@code null}, then a {@code null} reference is returned. If {@code comp}
* is not in a split layout, then {@code CENTER_REGION} is returned.
* <p>
* This method resolves the associated {@code Dockable} and
* {@code DockingPort} for the specified {@code Component} and backtracks
* through the docking layout to find a split layout. If a split layout is
* found, then the region retured by this method is calculated relative to
* its sibling in the layout.
*
* @param comp
* the {@code Component} whose region is to be returned
* @return the region of the current split layout containing the specified
* {@code Dockable}
*/
public static String findRegion(Component comp) {
if (comp == null)
return null;
DockingPort port = DockingManager.getDockingPort(comp);
Component docked = port.getDockedComponent();
if (!(docked instanceof JSplitPane)) {
// we didn't find a split pane, to check the grandparent dockingport
DockingPort superPort = DockingManager
.getDockingPort((Component) port);
// if there was no grandparent DockingPort, then we're stuck with
// the docked
// component we already found. this can happen on the root
// dockingport.
docked = superPort == null ? docked : superPort
.getDockedComponent();
}
if (!(docked instanceof JSplitPane))
return CENTER_REGION;
JSplitPane split = (JSplitPane) docked;
boolean horiz = split.getOrientation() == JSplitPane.HORIZONTAL_SPLIT;
Component left = split.getLeftComponent();
if (left == port) {
return horiz ? WEST_REGION : NORTH_REGION;
}
return horiz ? EAST_REGION : SOUTH_REGION;
}
/**
* Docks the specified {@code Dockable} into the supplied {@code region} of
* the specified {@code DockingPort}. This method is meant for programmatic
* docking, as opposed to realtime, event-based docking operations. As such,
* it defers processing to
* {@code dock(Dockable dockable, DockingPort port, String region, DragOperation token)},
* passing a {@code null} argument for the {@code DragOperation} parameter.
* This implies that there is no event-based drag operation currently in
* progress to control the semantics of the docking operation, only that an
* attempt should be made to dock the specified {@code Dockable} into the
* specified {@code DockingPort}.
* <p>
* This method will return {@code false} if {@code dockable} or {@code port}
* are {@code null}, or if {@code region} is not a valid region according
* to the specified {@code DockingPort}. If a {@code Dockable} is currently
* docked within the specified {@code DockingPort}, then that
* {@code Dockable's} territorial properties are also checked and this
* method may return {@code false} if the territory is blocked. Finally,
* this method will return {@code false} if the specified {@code Dockable}
* is already docked within the supplied region of the specified
* <code.DockingPort}.
*
* @param dockable
* the {@code Dockable} we wish to dock
* @param port
* the {@code DockingPort} into which we wish to dock
* @param region
* the region of the specified {@code DockingPort} into which we
* wish to dock.
* @return {@code true} if the docking operation was successful,
* {@code false}. otherwise.
* @see #dock(Dockable, DockingPort, String, DragOperation)
* @see Dockable#getDockingProperties()
* @see DockablePropertySet#isTerritoryBlocked(String)
*/
public boolean dock(Dockable dockable, DockingPort port, String region) {
return dock(dockable, port, region, null);
}
/**
* Docks the specified {@code Dockable} into the supplied {@code region} of
* the specified {@code DockingPort}. This method is meant for realtime,
* event-based docking based on an in-progress drag operation. It is not
* recommended for developers to call this method programmatically, except
* to pass in a {@code null} {@code DragOperation} argument. *
* <p>
* The {@code DragOperation} parameter, if present, will control the
* semantics of the docking operation based upon current mouse position,
* drag threshold, and a customizable drag context {@code Map}. For
* instance, the {@code DragOperation} may contain information regarding the
* {@code Dockable} over which the mouse is currently hovered, whether the
* user is attempting to drag a {@code Dockable} outside the bounds of any
* existing windows (perhaps in an attempt to float the {@code Dockable}),
* or whether the current distance offset from the original drag point
* sufficiently warrants a valid docking operation.
* <p>
* If the {@code DragOperation} is {@code null}, then this method will
* attempt to programmatically dock the specified {@code Dockable} into the
* supplied {@code region} of the specified {@code DockingPort} without
* regard to external event-based criteria. This is in accordance with the
* behavior specified by
* {@code dock(Dockable dockable, DockingPort port, String region)}.
*
* This method will return {@code false} if {@code dockable} or {@code port}
* are {@code null}, or if {@code region} is not a valid region according
* to the specified {@code DockingPort}. If a {@code Dockable} is currently
* docked within the specified {@code DockingPort}, then that
* {@code Dockable's} territorial properties are also checked and this
* method may return {@code false} if the territory is blocked. If a
* {@code DragOperation} is present, then this method will return
* {@code false} if the required drag threshold has not been exceeded.
* Finally, this method will return {@code false} if the specified
* {@code Dockable} is already docked within the supplied region of the
* specified <code.DockingPort}.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -