/*
* Created on Apr 28, 2005
*/
package org.flexdock.docking.state;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Window;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.defaults.DockingSplitPane;
import org.flexdock.docking.state.tree.SplitNode;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.SwingUtility;
/**
* @author Christopher Butler
*/
public class DockingPath implements Cloneable, DockingConstants, Serializable {
public static final String RESTORE_PATH_KEY = "DockingPath.RESTORE_PATH_KEY";
private transient String stringForm;
private String rootPortId;
private ArrayList nodes; // contains SplitNode objects
private String siblingId;
private boolean tabbed;
public DockingPath() {
nodes = new ArrayList();
}
public static DockingPath create(String dockableId) {
Dockable dockable = findDockable(dockableId);
return create(dockable);
}
public static DockingPath create(Dockable dockable) {
if(dockable==null || !isDocked(dockable))
return null;
DockingPath path = new DockingPath(dockable);
Component comp = dockable.getComponent();
Container parent = comp.getParent();
while(!isDockingRoot(parent)) {
if(parent instanceof DockingPort) {
SplitNode node = createNode((DockingPort)parent);
path.addNode(node);
}
parent = parent.getParent();
}
if(isDockingRoot(parent))
path.setRootPortId(((DockingPort)parent).getPersistentId());
path.initialize();
return path;
}
public static SplitNode createNode(Dockable dockable) {
if(dockable==null)
return null;
Container parent = dockable.getComponent().getParent();
return parent instanceof DockingPort? createNode((DockingPort)parent): null;
}
public static SplitNode createNode(DockingPort port) {
if(port==null)
return null;
Component c = ((Component)port).getParent();
JSplitPane split = c instanceof JSplitPane? (JSplitPane)c: null;
if(split==null)
return null;
return createNode(port, split);
}
private static SplitNode createNode(DockingPort port, JSplitPane split) {
int orientation = split.getOrientation();
boolean topLeft = split.getLeftComponent()==port? true: false;
int region = 0;
String siblingId = null;
if(topLeft) {
region = orientation==JSplitPane.VERTICAL_SPLIT? TOP: LEFT;
siblingId = getSiblingId(split.getRightComponent());
} else {
region = orientation==JSplitPane.VERTICAL_SPLIT? BOTTOM: RIGHT;
siblingId = getSiblingId(split.getLeftComponent());
}
int size = orientation==JSplitPane.VERTICAL_SPLIT? split.getHeight(): split.getWidth();
int divLoc = split.getDividerLocation();
int testSize = 0;
if (orientation == JSplitPane.VERTICAL_SPLIT) {
testSize += split.getTopComponent().getHeight() + split.getBottomComponent().getHeight() + split.getDividerSize();
} else {
testSize += split.getLeftComponent().getWidth() + split.getRightComponent().getWidth() + split.getDividerSize();
}
float percentage;
if (split instanceof DockingSplitPane && ((DockingSplitPane) split).getPercent() != -1) {
percentage = (float) ((DockingSplitPane) split).getPercent();
} else {
percentage = (float)divLoc / (float)size;
}
return new SplitNode(orientation, region, percentage, siblingId);
}
private static String getSiblingId(Component c) {
if(c instanceof DockingPort)
c = ((DockingPort)c).getDockedComponent();
Dockable dockable = findDockable(c);
return dockable==null? null: dockable.getPersistentId();
}
private static boolean isDockingRoot(Container c) {
return c instanceof DockingPort && ((DockingPort)c).isRoot();
}
public static DockingPath getRestorePath(Dockable dockable) {
Object obj = dockable==null? null: dockable.getClientProperty(RESTORE_PATH_KEY);
return obj instanceof DockingPath? (DockingPath)obj: null;
}
public static DockingPath updateRestorePath_(Dockable dockable, DockingPath restorePath) {
if(dockable==null || restorePath==null)
return null;
dockable.putClientProperty(RESTORE_PATH_KEY, restorePath);
return restorePath;
}
private DockingPath(Dockable dockable) {
siblingId = findSiblingId(dockable);
tabbed = dockable.getComponent().getParent() instanceof JTabbedPane;
nodes = new ArrayList();
}
public boolean isTabbed() {
return this.tabbed;
}
public void setTabbed(boolean isTabbed) {
this.tabbed = isTabbed;
}
public String getSiblingId() {
return this.siblingId;
}
public void setSiblingId(String siblingId) {
this.siblingId = siblingId;
}
private DockingPath(String parent, boolean tabs, ArrayList nodeList) {
siblingId = parent;
tabbed = tabs;
nodes = nodeList;
}
public List getNodes() {
return nodes;
}
public DockingPort getRootPort() {
return DockingManager.getDockingPort(rootPortId);
}
public String getRootPortId() {
return this.rootPortId;
}
public void setRootPortId(String portId) {
rootPortId = portId;
}
private void addNode(SplitNode node) {
nodes.add(node);
}
private void initialize() {
Collections.reverse(nodes);
}
private String findSiblingId(Dockable dockable) {
Component comp = dockable.getComponent();
JSplitPane split = comp.getParent() instanceof JSplitPane? (JSplitPane)comp.getParent(): null;
if(split==null)
return null;
Component sibling = split.getLeftComponent();
if(comp==sibling)
sibling = split.getRightComponent();
Dockable d = findDockable(sibling);
return d==null? null: d.getPersistentId();
}
public String toString() {
if(stringForm==null) {
StringBuffer sb = new StringBuffer("/RootPort[id=").append(rootPortId).append("]");
for(Iterator it=nodes.iterator(); it.hasNext();) {
SplitNode node = (SplitNode)it.next();
sb.append("/").append(node.toString());
}
sb.append("/Dockable");
stringForm = sb.toString();
}
return stringForm;
}
public boolean restore(String dockable) {
return restore(DockingManager.getDockable(dockable));
}
private DockingPort getRootDockingPort() {
DockingPort port = DockingManager.getDockingPort(rootPortId);
if(port!=null)
return port;
Window activeWindow = SwingUtility.getActiveWindow();
return DockingManager.getRootDockingPort(activeWindow);
}
public boolean restore(Dockable dockable) {
if(dockable==null || isDocked(dockable))
return false;
DockingPort rootPort = getRootDockingPort();
String region = CENTER_REGION;
if(nodes.size()==0) {
return dockFullPath(dockable, rootPort, region);
}
DockingPort port = rootPort;
for(Iterator it=nodes.iterator(); it.hasNext();) {
SplitNode node = (SplitNode)it.next();
Component comp = port.getDockedComponent();
region = getRegion(node, comp);
JSplitPane splitPane = comp instanceof JSplitPane? (JSplitPane)comp: null;
// path was broken. we have no SplitPane, or the SplitPane doesn't
// match the orientation of the current node, meaning the path was
// altered at this point.
if(splitPane==null || splitPane.getOrientation()!=node.getOrientation()) {
return dockBrokenPath(dockable, port, region, node);
}
// assume there is a transient sub-dockingPort in the split pane
comp = node.getRegion()==LEFT || node.getRegion()==TOP? splitPane.getLeftComponent(): splitPane.getRightComponent();
port = (DockingPort)comp;
// move on to the next node
}
return dockFullPath(dockable, port, region);
}
private boolean dockBrokenPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
Component current = port.getDockedComponent();
if(current instanceof JSplitPane) {
return dockExtendedPath(dockable, port, region, ctrlNode);
}
if(current instanceof JTabbedPane) {
return dock(dockable, port, CENTER_REGION, null);
}
Dockable embedded = findDockable(current);
if(embedded==null || tabbed) {
return dock(dockable, port, CENTER_REGION, null);
}
String embedId = embedded.getPersistentId();
SplitNode lastNode = getLastNode();
if(embedId.equals(lastNode.getSiblingId())) {
region = getRegion(lastNode, current);
ctrlNode = lastNode;
}
return dock(dockable, port, region, ctrlNode);
}
private boolean dockFullPath(Dockable dockable, DockingPort port, String region) {
// the docking layout was altered since the last time our dockable we embedded within
// it, and we were able to fill out the full docking path. this means there is already
// something within the target dockingPort where we expect to dock our dockable.
// first, check to see if we need to use a tabbed layout
Component current = port.getDockedComponent();
if(current instanceof JTabbedPane) {
return dock(dockable, port, CENTER_REGION, null);
}
// check to see if we dock outside the current port or outside of it
Dockable docked = findDockable(current);
if(docked!=null) {
Component comp = dockable.getComponent();
if(port.isDockingAllowed(comp, CENTER_REGION)) {
return dock(dockable, port, CENTER_REGION, null);
}
DockingPort superPort = (DockingPort)SwingUtilities.getAncestorOfClass(DockingPort.class, (Component)port);
if(superPort!=null)
port = superPort;
return dock(dockable, port, region, getLastNode());
}
// if we were't able to dock above, then the path changes means our current path
// does not extend all the way down into to docking layout. try to determine
// an extended path and dock into it
return dockExtendedPath(dockable, port, region, getLastNode());
}
private boolean dockExtendedPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
Component docked = port.getDockedComponent();
//I don't think this code will matter any more, given the null check, but leaving for now.
//null is returned when a dockingport is empty, so we need to dock to an empty port
// if 'docked' is not a split pane, then I don't know what it is. let's print a
// stacktrace and see who sends in an error report.
if(docked != null && !(docked instanceof JSplitPane)) {
Throwable t = new Throwable("Docked: " + docked);
System.err.println("Exception: "+t.getMessage());
return false;
}
//begin code that matters.
SplitNode lastNode = getLastNode();
String lastSibling = lastNode==null? null: lastNode.getSiblingId();
Set dockables = port.getDockables();
for(Iterator it=dockables.iterator(); lastSibling!=null && it.hasNext();) {
Dockable d = (Dockable)it.next();
if(d.getPersistentId().equals(lastSibling)) {
DockingPort embedPort = d.getDockingPort();
String embedRegion = getRegion(lastNode, d.getComponent());
return dock(dockable, embedPort, embedRegion, ctrlNode);
}
}
return dock(dockable, port, region, ctrlNode);
}
private String getRegion(SplitNode node, Component dockedComponent) {
if(dockedComponent==null)
return CENTER_REGION;
return DockingUtility.getRegion(node.getRegion());
}
public SplitNode getLastNode() {
return nodes.size()==0? null: (SplitNode)nodes.get(nodes.size()-1);
}
private boolean dock(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
boolean ret = DockingManager.dock(dockable, port, region);
if(tabbed || ctrlNode==null)
return ret;
final float percent = ctrlNode.getPercentage();
final Component docked = dockable.getComponent();
EventQueue.invokeLater(new Runnable() {
public void run() {
resizeSplitPane(docked, percent);
}
});
return ret;
}
private void resizeSplitPane(Component comp, float percentage) {
Container parent = comp.getParent();
Container grandParent = parent==null? null: parent.getParent();
if(!(grandParent instanceof JSplitPane))
return;
JSplitPane split = (JSplitPane)grandParent;
// int splitSize = split.getOrientation()==DockingConstants.VERTICAL? split.getHeight(): split.getWidth();
// int divLoc = (int)(percentage * (float)splitSize);
split.setDividerLocation(percentage);
}
private static Dockable findDockable(Component c) {
return DockingManager.getDockable(c);
}
private static Dockable findDockable(String id) {
return DockingManager.getDockable(id);
}
private static boolean isDocked(Dockable dockable) {
return DockingManager.isDocked(dockable);
}
public int getDepth() {
return nodes.size();
}
public SplitNode getNode(int indx) {
return indx<0 || indx>=getDepth()? null: (SplitNode)nodes.get(indx);
}
public Object clone() {
ArrayList nodeList = null;
if(nodes!=null) {
nodeList = new ArrayList(nodes.size());
for(Iterator it=nodes.iterator(); it.hasNext();) {
SplitNode node = (SplitNode)it.next();
nodeList.add(node.clone());
}
}
DockingPath path = new DockingPath(siblingId, tabbed, nodeList);
path.rootPortId = rootPortId;
return path;
}
}