/*
* Created on Mar 11, 2005
*/
package org.flexdock.docking.defaults;
import java.awt.Component;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.RegionChecker;
/**
* @author Christopher Butler
*/
public class DefaultRegionChecker implements RegionChecker, DockingConstants {
/**
* Returns the docking region of the supplied {@code Component} that
* contains the coordinates of the specified {@code Point}. If either
* {@code comp} or {@code point} is {@code null}, then
* {@code UNKNOWN_REGION} is returned. If the specified {@code Component}
* bounds do not contain the supplied {@code Point}, then
* {@code UNKNOWN_REGION} is returned.
* <p>
* This implementation assumes that {@code comp} is a {@code Component}
* embedded within a {@code DockingPort}. If {@code comp} is itself a
* {@code DockingPort}, then {@code CENTER_REGION} is returned. Otherwise,
* the returned region is based upon a section of the bounds of the
* specified {@code Component} relative to the containing
* {@code DockingPort}.
* <p>
* This method divides the specified {@code Component's} bounds into four
* {@code Rectangles} determined by {@code getNorthRegion(Component c)},
* {@code getSouthRegion(Component c)}, {@code getEastRegion(Component c)},
* and {@code getWestRegion(Component c)}, respectively. Each
* {@code Rectangle} is then checked to see if it contains the specified
* {@code Point}. The order of precedence is NORTH, SOUTH, EAST, and then
* WEST. If the specified {@code Point} is contained by the
* {@code Component} bounds but none of the sub-{@code Rectangles}, then
* {@code CENTER_REGION} is returned.
* <p>
* For NORTH and SOUTH {@code Rectangles}, the distance is checked between
* the top/bottom and left or right edge of the regional bounds. If the
* horizontal distance to the regional edge is smaller than the vertical
* distance, then EAST or WEST takes precendence of NORTH or SOUTH. This
* allows for proper determination between "northeast", "northwest",
* "southeast", and "southwest" cases.
*
* @param comp
* the {@code Component} whose region is to be examined.
* @param point
* the coordinates whose region is to be determined.
* @return the docking region containing the specified {@code Point}.
* @see RegionChecker#getRegion(Component, Point)
* @see #getNorthRegion(Component)
* @see #getSouthRegion(Component)
* @see #getEastRegion(Component)
* @see #getWestRegion(Component)
*/
public String getRegion(Component comp, Point point) {
if (comp == null || point == null)
return UNKNOWN_REGION;
// make sure the point is actually inside of the target dockingport
Rectangle targetArea = comp.getBounds();
// if our target component is the dockingport itself, then getBounds()
// would
// have returned a target area relative to the dockingport's parent.
// reset
// relative to the dockingport.
if (comp instanceof DockingPort)
targetArea.setLocation(0, 0);
if (!targetArea.contains(point))
return UNKNOWN_REGION;
// if our target component is the dockingport, then the dockingport is
// currently empty and all points within it are in the CENTER
if (comp instanceof DockingPort)
return CENTER_REGION;
// start with the north region
Rectangle north = getNorthRegion(comp);
int rightX = north.x + north.width;
if (north.contains(point)) {
// check NORTH_WEST
Rectangle west = getWestRegion(comp);
if (west.contains(point)) {
Polygon westPoly = new Polygon();
westPoly.addPoint(0, 0);
westPoly.addPoint(0, north.height);
westPoly.addPoint(west.width, north.height);
return westPoly.contains(point) ? WEST_REGION : NORTH_REGION;
}
// check NORTH_EAST
Rectangle east = getEastRegion(comp);
if (east.contains(point)) {
Polygon eastPoly = new Polygon();
eastPoly.addPoint(rightX, 0);
eastPoly.addPoint(rightX, north.height);
eastPoly.addPoint(east.x, north.height);
return eastPoly.contains(point) ? EAST_REGION : NORTH_REGION;
}
return NORTH_REGION;
}
// check with the south region
Rectangle south = getSouthRegion(comp);
int bottomY = south.y + south.height;
if (south.contains(point)) {
// check SOUTH_WEST
Rectangle west = getWestRegion(comp);
if (west.contains(point)) {
Polygon westPoly = new Polygon();
westPoly.addPoint(0, south.y);
westPoly.addPoint(west.width, south.y);
westPoly.addPoint(0, bottomY);
return westPoly.contains(point) ? WEST_REGION : SOUTH_REGION;
}
// check SOUTH_EAST
Rectangle east = getEastRegion(comp);
if (east.contains(point)) {
Polygon eastPoly = new Polygon();
eastPoly.addPoint(east.y, south.y);
eastPoly.addPoint(rightX, south.y);
eastPoly.addPoint(rightX, bottomY);
return eastPoly.contains(point) ? EAST_REGION : SOUTH_REGION;
}
return SOUTH_REGION;
}
// Now check EAST and WEST. We've already checked NORTH and SOUTH, so we
// don't have to
// check for NE, SE, NW, and SW anymore.
Rectangle east = getEastRegion(comp);
if (east.contains(point))
return EAST_REGION;
Rectangle west = getWestRegion(comp);
if (west.contains(point))
return WEST_REGION;
// not in any of the outer regions, so return CENTER.
return CENTER_REGION;
}
/**
* Returns the rectangular bounds within the specified component that
* represent it's {@code DockingConstants.NORTH_REGION}. This method
* dispatches to {@code getRegionBounds(Component c, String region)},
* passing an argument of {@code DockingConstants.NORTH_REGION} for the
* region parameter. If the specified {@code Component} is {@code null},
* then a {@code null} reference is returned.
*
* @param c
* the {@code Component} whose north region is to be returned.
* @return the bounds containing the north region of the specified
* {@code Component}.
* @see RegionChecker#getNorthRegion(Component)
* @see #getRegionBounds(Component, String)
*/
public Rectangle getNorthRegion(Component c) {
return getRegionBounds(c, NORTH_REGION);
}
/**
* Returns the rectangular bounds within the specified component that
* represent it's {@code DockingConstants.SOUTH_REGION}. This method
* dispatches to {@code getRegionBounds(Component c, String region)},
* passing an argument of {@code DockingConstants.SOUTH_REGION} for the
* region parameter. If the specified {@code Component} is {@code null},
* then a {@code null} reference is returned.
*
* @param c
* the {@code Component} whose south region is to be returned.
* @return the bounds containing the north region of the specified
* {@code Component}.
* @see RegionChecker#getSouthRegion(Component)
* @see #getRegionBounds(Component, String)
*/
public Rectangle getSouthRegion(Component c) {
return getRegionBounds(c, SOUTH_REGION);
}
/**
* Returns the rectangular bounds within the specified component that
* represent it's {@code DockingConstants.EAST_REGION}. This method
* dispatches to {@code getRegionBounds(Component c, String region)},
* passing an argument of {@code DockingConstants.EAST_REGION} for the
* region parameter. If the specified {@code Component} is {@code null},
* then a {@code null} reference is returned.
*
* @param c
* the {@code Component} whose east region is to be returned.
* @return the bounds containing the north region of the specified
* {@code Component}.
* @see RegionChecker#getEastRegion(Component)
* @see #getRegionBounds(Component, String)
*/
public Rectangle getEastRegion(Component c) {
return getRegionBounds(c, EAST_REGION);
}
/**
* Returns the rectangular bounds within the specified component that
* represent it's {@code DockingConstants.WEST_REGION}. This method
* dispatches to {@code getRegionBounds(Component c, String region)},
* passing an argument of {@code DockingConstants.WEST_REGION} for the
* region parameter. If the specified {@code Component} is {@code null},
* then a {@code null} reference is returned.
*
* @param c
* the {@code Component} whose west region is to be returned.
* @return the bounds containing the north region of the specified
* {@code Component}.
* @see RegionChecker#getWestRegion(Component)
* @see #getRegionBounds(Component, String)
*/
public Rectangle getWestRegion(Component c) {
return getRegionBounds(c, WEST_REGION);
}
/**
* Returns the bounding {@code Rectangle} within the specified component
* that represents the specified region. If {@code c} or {@code region} are
* null, then this method returns a {@code null} reference.
* <p>
* This method dispatches to
* {@code getRegionSize(Component c, String region)} to determine the
* proportional size of the specified {@code Component} dedicated to the
* specified region. It then multiplies this value by the relevant
* {@code Component} dimension (<i>{@code width} for east/west,
* {@code height} for north/south</i>) and returns a {@code Rectangle} with
* the resulting dimension, spanning the {@code Component} edge for the
* specified region.
*
* @param c
* the {@code Component} whose region bounds are to be returned.
* @param region
* the specified region that is to be examined.
* @return the bounds containing the supplied region of the specified
* {@code Component}.
* @see RegionChecker#getRegionBounds(Component, String)
* @see #getRegionSize(Component, String)
*/
public Rectangle getRegionBounds(Component c, String region) {
if (c != null && region != null) {
float size = getRegionSize(c, region);
return calculateRegionalBounds(c, region, size);
}
return null;
}
/**
* Returns the bounding {@code Rectangle} within the specified component
* that represents the desired area to be allotted for sibling
* {@code Components} in the specified region. If {@code c} or
* {@code region} are null, then this method returns a {@code null}
* reference.
* <p>
* This method dispatches to
* {@code getSiblingSize(Component c, String region)} to determine the
* proportional size of the specified {@code Component} dedicated to
* siblings in the specified region. It then multiplies this value by the
* relevant {@code Component} dimension (<i>{@code width} for east/west,
* {@code height} for north/south</i>) and returns a {@code Rectangle} with
* the resulting dimension, spanning the {@code Component} edge for the
* specified region.
*
* @param c
* the {@code Component} whose sibling bounds are to be returned.
* @param region
* the specified region that is to be examined.
* @return the bounds representing the allotted sibling area for the
* supplied region of the specified {@code Component}.
* @see RegionChecker#getSiblingBounds(Component, String)
* @see #getSiblingSize(Component, String)
*/
public Rectangle getSiblingBounds(Component c, String region) {
if (c != null && region != null) {
float size = getSiblingSize(c, region);
return calculateRegionalBounds(c, region, size);
}
return null;
}
protected Rectangle calculateRegionalBounds(Component c, String region,
float size) {
if (c == null || region == null)
return null;
Rectangle bounds = c.getBounds();
if (NORTH_REGION.equals(region) || SOUTH_REGION.equals(region)) {
int h = (int) ((float) bounds.height * size);
int y = NORTH_REGION.equals(region) ? 0 : bounds.height - h;
return new Rectangle(0, y, bounds.width, h);
}
if (WEST_REGION.equals(region) || EAST_REGION.equals(region)) {
int w = (int) ((float) bounds.width * size);
int x = WEST_REGION.equals(region) ? 0 : bounds.width - w;
return new Rectangle(x, 0, w, bounds.height);
}
return null;
}
/**
* Returns a percentage (0.0F through 1.0F) representing the amount of space
* allotted for the specified region within the specified {@code Component}.
* <p>
* This method resolves the {@code Dockable} associated with the specified
* {@code Component} and dispatches to
* {@code getRegionPreference(Dockable d, String region)}.
* {@code getRegionPreference(Dockable d, String region)} attempts to invoke
* {@code getDockingProperties()} on the {@code Dockable} to resolve a
* {@code DockablePropertySet} instance and return from its
* {@code getRegionInset(String region)} method.
* <p>
* If the specified {@code Component} is {@code null}, no {@code Dockable}
* can be resolved, or no value is specified in the {@code Dockable's}
* associated {@code DockingProps} instance, then the default value of
* {@code RegionChecker.DEFAULT_REGION_SIZE} is returned.
*
* @param c
* the {@code Component} whose region is to be examined.
* @param region
* the specified region that is to be examined.
* @return the percentage of the specified {@code Component} allotted for
* the specified region.
* @see RegionChecker#getRegionSize(Component, String)
* @see DockingManager#getDockable(Component)
* @see #getRegionPreference(Dockable, String)
* @see Dockable#getDockingProperties()
*/
public float getRegionSize(Component c, String region) {
Dockable d = DockingManager.getDockable(c);
return getRegionPreference(d, region);
}
/**
* Returns a percentage (0.0F through 1.0F) representing the amount of space
* allotted for sibling {@code Component} docked to the specified region
* within the specified {@code Component}.
* <p>
* This method resolves the {@code Dockable} associated with the specified
* {@code Component} and dispatches to
* {@code getSiblingPreference(Dockable d, String region)}.
* {@code getSiblingPreference(Dockable d, String region)} attempts to
* invoke {@code getDockingProperties()} on the {@code Dockable} to resolve
* a {@code DockablePropertySet} instance and return from its
* {@code getSiblingSize(String region)} method.
* <p>
* If the specified {@code Component} is {@code null}, no {@code Dockable}
* can be resolved, or no value is specified in the {@code Dockable's}
* associated {@code DockingProps} instance, then the default value of
* {@code RegionChecker.DEFAULT_SIBLING_SIZE} is returned.
*
* @param c
* the {@code Component} whose sibling size is to be examined.
* @param region
* the specified region that is to be examined.
* @return the percentage of the specified {@code Component} allotted for
* the siblings within the specified region.
* @see DockingManager#getDockable(Component)
* @see #getSiblingPreference(Dockable, String)
* @see Dockable#getDockingProperties()
*/
public float getSiblingSize(Component c, String region) {
Dockable d = DockingManager.getDockable(c);
return getSiblingPreference(d, region);
}
protected static float getDockingInset(Float value, float defaultVal,
float max, float min) {
float f = value == null ? -1 : value.floatValue();
if (f == -1)
f = defaultVal;
return checkBounds(f, max, min);
}
protected static float checkBounds(float val, float max, float min) {
val = Math.min(val, max);
return Math.max(val, min);
}
/**
* Returns {@code size} if it is between the values
* {@code RegionChecker.MIN_REGION_SIZE} and
* {@code RegionChecker.MAX_REGION_SIZE}. If {@code size} is less than
* {@code RegionChecker.MIN_REGION_SIZE}, then
* {@code RegionChecker.MIN_REGION_SIZE} is returned. If {@code size} is
* greater than {@code RegionChecker.MAX_REGION_SIZE}, then
* {@code RegionChecker.MAX_REGION_SIZE} is returned.
*
* @return a valid {@code size} value between
* {@code RegionChecker.MIN_REGION_SIZE} and
* {@code RegionChecker.MAX_REGION_SIZE}, inclusive.
*/
public static float validateRegionSize(float size) {
return checkBounds(size, MAX_REGION_SIZE, MIN_REGION_SIZE);
}
/**
* Returns {@code size} if it is between the values
* {@code RegionChecker.MIN_SIBILNG_SIZE} and
* {@code RegionChecker.MAX_SIBILNG_SIZE}. If {@code size} is less than
* {@code RegionChecker.MIN_SIBILNG_SIZE}, then
* {@code RegionChecker.MIN_SIBILNG_SIZE} is returned. If {@code size} is
* greater than {@code RegionChecker.MAX_SIBILNG_SIZE}, then
* {@code RegionChecker.MAX_SIBILNG_SIZE} is returned.
*
* @return a valid {@code size} value between
* {@code RegionChecker.MIN_SIBILNG_SIZE} and
* {@code RegionChecker.MAX_SIBILNG_SIZE}, inclusive.
*/
public static float validateSiblingSize(float size) {
return checkBounds(size, MAX_SIBILNG_SIZE, MIN_SIBILNG_SIZE);
}
/**
* Returns a percentage (0.0F through 1.0F) representing the amount of space
* allotted for the specified region within the specified {@code Dockable}.
* <p>
* This method calls {@code getDockingProperties()} on the {@code Dockable}
* to resolve a {@code DockablePropertySet} instance. It then invokes
* {@code getRegionInset(String region)} on the {@code DockablePropertySet}
* to retrieve the preferred region size. If the {@code Dockable} is
* {@code null} or no region preference can be found, then the default value
* of {@code RegionChecker.DEFAULT_REGION_SIZE} is returned. Otherwise, the
* retrieved region preference is passed through
* {@code validateRegionSize(float size)} and returned.
*
* @param d
* the {@code Dockable} whose region is to be checked
* @param region
* the region of the specified {@code Dockable} to be checked
* @return a percentage (0.0F through 1.0F) representing the amount of space
* allotted for the specified region within the specified
* {@code Dockable}.
* @see Dockable#getDockingProperties()
* @see RegionChecker#DEFAULT_REGION_SIZE
* @see #validateRegionSize(float)
*/
public static float getRegionPreference(Dockable d, String region) {
Float inset = d == null ? null : d.getDockingProperties()
.getRegionInset(region);
return getDockingInset(inset, DEFAULT_REGION_SIZE, MAX_REGION_SIZE,
MIN_REGION_SIZE);
}
/**
* Returns a percentage (0.0F through 1.0F) representing the amount of space
* allotted for sibling {@code Components} docked to the specified region
* within the specified {@code Dockable}.
* <p>
* This method calls {@code getDockingProperties()} on the {@code Dockable}
* to resolve a {@code DockablePropertySet} instance. It then invokes
* {@code getSiblingSize(String region)} on the {@code DockablePropertySet}
* to retrieve the preferred sibling size. If the {@code Dockable} is
* {@code null} or no sibling preference can be found, then the default
* value of {@code RegionChecker.DEFAULT_SIBLING_SIZE} is returned.
* Otherwise, the retrieved region preference is passed through
* {@code validateSiblingSize(float size)} and returned.
*
* @param d
* the {@code Dockable} whose sibling size is to be checked
* @param region
* the region of the specified {@code Dockable} to be checked
* @return a percentage (0.0F through 1.0F) representing the amount of space
* allotted for sibling {@code Components} docked to the specified
* region within the specified {@code Dockable}.
* @see Dockable#getDockingProperties()
* @see RegionChecker#DEFAULT_SIBLING_SIZE
* @see #validateSiblingSize(float)
*/
public static float getSiblingPreference(Dockable d, String region) {
Float size = d == null ? null : d.getDockingProperties()
.getSiblingSize(region);
return getDockingInset(size, DockingManager.getDefaultSiblingSize(),
MAX_SIBILNG_SIZE, MIN_SIBILNG_SIZE);
}
}