1 /*
2  * Created on Apr 28, 2005
3  */
4 package org.flexdock.docking.state;
5 
6 import java.awt.Component;
7 import java.awt.Container;
8 import java.awt.EventQueue;
9 import java.awt.Window;
10 import java.io.Serializable;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Set;
16 
17 import javax.swing.JSplitPane;
18 import javax.swing.JTabbedPane;
19 import javax.swing.SwingUtilities;
20 
21 
22 
23 import org.flexdock.docking.Dockable;
24 import org.flexdock.docking.DockingConstants;
25 import org.flexdock.docking.DockingManager;
26 import org.flexdock.docking.DockingPort;
27 import org.flexdock.docking.defaults.DockingSplitPane;
28 import org.flexdock.docking.state.tree.SplitNode;
29 import org.flexdock.util.DockingUtility;
30 import org.flexdock.util.SwingUtility;
31 
32 /**
33  * @author Christopher Butler
34  */
35 @SuppressWarnings(value = { "serial" })
36 public class DockingPath implements Cloneable, DockingConstants, Serializable {
37 
38     public static final String RESTORE_PATH_KEY = "DockingPath.RESTORE_PATH_KEY";
39 
40     private transient String stringForm;
41     private String rootPortId;
42     private ArrayList nodes; // contains SplitNode objects
43     private String siblingId;
44     private boolean tabbed;
45 
DockingPath()46     public DockingPath() {
47         nodes = new ArrayList();
48     }
49 
create(String dockableId)50     public static DockingPath create(String dockableId) {
51         Dockable dockable = findDockable(dockableId);
52         return create(dockable);
53     }
54 
create(Dockable dockable)55     public static DockingPath create(Dockable dockable) {
56         if(dockable==null || !isDocked(dockable))
57             return null;
58 
59         DockingPath path = new DockingPath(dockable);
60         Component comp = dockable.getComponent();
61 
62         Container parent = comp.getParent();
63         while(!isDockingRoot(parent)) {
64             if(parent instanceof DockingPort) {
65                 SplitNode node = createNode((DockingPort)parent);
66                 path.addNode(node);
67             }
68             parent = parent.getParent();
69         }
70         if(isDockingRoot(parent))
71             path.setRootPortId(((DockingPort)parent).getPersistentId());
72 
73         path.initialize();
74         return path;
75     }
76 
createNode(Dockable dockable)77     public static SplitNode createNode(Dockable dockable) {
78         if(dockable==null)
79             return null;
80 
81         Container parent = dockable.getComponent().getParent();
82         return parent instanceof DockingPort? createNode((DockingPort)parent): null;
83     }
84 
createNode(DockingPort port)85     public static SplitNode createNode(DockingPort port) {
86         if(port==null)
87             return null;
88 
89         Component c = ((Component)port).getParent();
90         JSplitPane split = c instanceof JSplitPane? (JSplitPane)c: null;
91         if(split==null)
92             return null;
93 
94         return createNode(port, split);
95     }
96 
createNode(DockingPort port, JSplitPane split)97     private static SplitNode createNode(DockingPort port, JSplitPane split) {
98         int orientation = split.getOrientation();
99         boolean topLeft = split.getLeftComponent()==port? true: false;
100 
101         int region = 0;
102         String siblingId = null;
103         if(topLeft) {
104             region = orientation==JSplitPane.VERTICAL_SPLIT? TOP: LEFT;
105             siblingId = getSiblingId(split.getRightComponent());
106         } else {
107             region = orientation==JSplitPane.VERTICAL_SPLIT? BOTTOM: RIGHT;
108             siblingId = getSiblingId(split.getLeftComponent());
109         }
110 
111         int size = orientation==JSplitPane.VERTICAL_SPLIT? split.getHeight(): split.getWidth();
112         int divLoc = split.getDividerLocation();
113 
114         int testSize = 0;
115         if (orientation == JSplitPane.VERTICAL_SPLIT) {
116             testSize += split.getTopComponent().getHeight() + split.getBottomComponent().getHeight() + split.getDividerSize();
117         } else {
118             testSize += split.getLeftComponent().getWidth() + split.getRightComponent().getWidth() + split.getDividerSize();
119         }
120 
121         float percentage;
122         if (split instanceof DockingSplitPane && ((DockingSplitPane) split).getPercent() != -1) {
123             percentage = (float) ((DockingSplitPane) split).getPercent();
124         } else {
125             percentage = (float)divLoc / (float)size;
126         }
127 
128         return new SplitNode(orientation, region, percentage, siblingId);
129     }
130 
getSiblingId(Component c)131     private static String getSiblingId(Component c) {
132         if(c instanceof DockingPort)
133             c = ((DockingPort)c).getDockedComponent();
134 
135         Dockable dockable = findDockable(c);
136         return dockable==null? null: dockable.getPersistentId();
137     }
138 
139 
140 
isDockingRoot(Container c)141     private static boolean isDockingRoot(Container c) {
142         return c instanceof DockingPort && ((DockingPort)c).isRoot();
143     }
144 
getRestorePath(Dockable dockable)145     public static DockingPath getRestorePath(Dockable dockable) {
146         Object obj = dockable==null? null: dockable.getClientProperty(RESTORE_PATH_KEY);
147         return obj instanceof DockingPath? (DockingPath)obj: null;
148     }
149 
updateRestorePath_(Dockable dockable, DockingPath restorePath)150     public static DockingPath updateRestorePath_(Dockable dockable, DockingPath restorePath) {
151         if(dockable==null || restorePath==null)
152             return null;
153         dockable.putClientProperty(RESTORE_PATH_KEY, restorePath);
154         return restorePath;
155     }
156 
DockingPath(Dockable dockable)157     private DockingPath(Dockable dockable) {
158         siblingId = findSiblingId(dockable);
159         tabbed = dockable.getComponent().getParent() instanceof JTabbedPane;
160         nodes = new ArrayList();
161     }
162 
isTabbed()163     public boolean isTabbed() {
164         return this.tabbed;
165     }
166 
setTabbed(boolean isTabbed)167     public void setTabbed(boolean isTabbed) {
168         this.tabbed = isTabbed;
169     }
170 
getSiblingId()171     public String getSiblingId() {
172         return this.siblingId;
173     }
174 
setSiblingId(String siblingId)175     public void setSiblingId(String siblingId) {
176         this.siblingId = siblingId;
177     }
178 
DockingPath(String parent, boolean tabs, ArrayList nodeList)179     private DockingPath(String parent, boolean tabs, ArrayList nodeList) {
180         siblingId = parent;
181         tabbed = tabs;
182         nodes = nodeList;
183     }
184 
getNodes()185     public List getNodes() {
186         return nodes;
187     }
188 
getRootPort()189     public DockingPort getRootPort() {
190         return DockingManager.getDockingPort(rootPortId);
191     }
192 
getRootPortId()193     public String getRootPortId() {
194         return this.rootPortId;
195     }
196 
setRootPortId(String portId)197     public void setRootPortId(String portId) {
198         rootPortId = portId;
199     }
200 
addNode(SplitNode node)201     private void addNode(SplitNode node) {
202         nodes.add(node);
203     }
204 
initialize()205     private void initialize() {
206         Collections.reverse(nodes);
207     }
208 
findSiblingId(Dockable dockable)209     private String findSiblingId(Dockable dockable) {
210         Component comp = dockable.getComponent();
211         JSplitPane split = comp.getParent() instanceof JSplitPane? (JSplitPane)comp.getParent(): null;
212         if(split==null)
213             return null;
214 
215         Component sibling = split.getLeftComponent();
216         if(comp==sibling)
217             sibling = split.getRightComponent();
218 
219         Dockable d = findDockable(sibling);
220         return d==null? null: d.getPersistentId();
221     }
222 
toString()223     public String toString() {
224         if(stringForm==null) {
225             StringBuffer sb = new StringBuffer("/RootPort[id=").append(rootPortId).append("]");
226             for(Iterator it=nodes.iterator(); it.hasNext();) {
227                 SplitNode node = (SplitNode)it.next();
228                 sb.append("/").append(node.toString());
229             }
230             sb.append("/Dockable");
231             stringForm = sb.toString();
232         }
233         return stringForm;
234     }
235 
restore(String dockable)236     public boolean restore(String dockable) {
237         return restore(DockingManager.getDockable(dockable));
238     }
239 
getRootDockingPort()240     private DockingPort getRootDockingPort() {
241         DockingPort port = DockingManager.getDockingPort(rootPortId);
242         if(port!=null)
243             return port;
244 
245         Window activeWindow = SwingUtility.getActiveWindow();
246         return DockingManager.getRootDockingPort(activeWindow);
247     }
248 
restore(Dockable dockable)249     public boolean restore(Dockable dockable) {
250         if(dockable==null || isDocked(dockable))
251             return false;
252 
253         DockingPort rootPort = getRootDockingPort();
254         String region = CENTER_REGION;
255         if(nodes.size()==0) {
256             return dockFullPath(dockable, rootPort, region);
257         }
258 
259         DockingPort port = rootPort;
260         for(Iterator it=nodes.iterator(); it.hasNext();) {
261             SplitNode node = (SplitNode)it.next();
262             Component comp = port.getDockedComponent();
263             region = getRegion(node, comp);
264 
265             JSplitPane splitPane = comp instanceof JSplitPane? (JSplitPane)comp: null;
266             // path was broken.  we have no SplitPane, or the SplitPane doesn't
267             // match the orientation of the current node, meaning the path was
268             // altered at this point.
269             if(splitPane==null || splitPane.getOrientation()!=node.getOrientation()) {
270                 return dockBrokenPath(dockable, port, region, node);
271             }
272 
273             // assume there is a transient sub-dockingPort in the split pane
274             comp = node.getRegion()==LEFT || node.getRegion()==TOP? splitPane.getLeftComponent(): splitPane.getRightComponent();
275             port = (DockingPort)comp;
276 
277             // move on to the next node
278         }
279 
280         return dockFullPath(dockable, port, region);
281     }
282 
283 
284 
dockBrokenPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode)285     private boolean dockBrokenPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
286         Component current = port.getDockedComponent();
287         if(current instanceof JSplitPane) {
288             return dockExtendedPath(dockable, port, region, ctrlNode);
289         }
290 
291         if(current instanceof JTabbedPane) {
292             return dock(dockable, port, CENTER_REGION, null);
293         }
294 
295         Dockable embedded = findDockable(current);
296         if(embedded==null || tabbed) {
297             return dock(dockable, port, CENTER_REGION, null);
298         }
299 
300         String embedId = embedded.getPersistentId();
301         SplitNode lastNode = getLastNode();
302         if(embedId.equals(lastNode.getSiblingId())) {
303             region = getRegion(lastNode, current);
304             ctrlNode = lastNode;
305         }
306 
307         return dock(dockable, port, region, ctrlNode);
308     }
309 
dockFullPath(Dockable dockable, DockingPort port, String region)310     private boolean dockFullPath(Dockable dockable, DockingPort port, String region) {
311         // the docking layout was altered since the last time our dockable we embedded within
312         // it, and we were able to fill out the full docking path.  this means there is already
313         // something within the target dockingPort where we expect to dock our dockable.
314 
315         // first, check to see if we need to use a tabbed layout
316         Component current = port.getDockedComponent();
317         if(current instanceof JTabbedPane) {
318             return dock(dockable, port, CENTER_REGION, null);
319         }
320 
321         // check to see if we dock outside the current port or outside of it
322         Dockable docked = findDockable(current);
323         if(docked!=null) {
324             Component comp = dockable.getComponent();
325             if(port.isDockingAllowed(comp, CENTER_REGION)) {
326                 return dock(dockable, port, CENTER_REGION, null);
327             }
328             DockingPort superPort = (DockingPort)SwingUtilities.getAncestorOfClass(DockingPort.class, (Component)port);
329             if(superPort!=null)
330                 port = superPort;
331             return dock(dockable, port, region, getLastNode());
332         }
333 
334         // if we were't able to dock above, then the path changes means our current path
335         // does not extend all the way down into to docking layout.  try to determine
336         // an extended path and dock into it
337         return dockExtendedPath(dockable, port, region, getLastNode());
338     }
339 
dockExtendedPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode)340     private boolean dockExtendedPath(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
341         Component docked = port.getDockedComponent();
342 
343         //I don't think this code will matter any more, given the null check, but leaving for now.
344         //null is returned when a dockingport is empty, so we need to dock to an empty port
345 
346         // if 'docked' is not a split pane, then I don't know what it is.  let's print a
347         // stacktrace and see who sends in an error report.
348         if(docked != null && !(docked instanceof JSplitPane)) {
349             Throwable t = new Throwable("Docked: " + docked);
350             System.err.println("Exception: "+t.getMessage());
351             return false;
352         }
353 
354         //begin code that matters.
355 
356         SplitNode lastNode = getLastNode();
357         String lastSibling = lastNode==null? null: lastNode.getSiblingId();
358 
359         Set dockables = port.getDockables();
360         for(Iterator it=dockables.iterator(); lastSibling!=null && it.hasNext();) {
361             Dockable d = (Dockable)it.next();
362             if(d.getPersistentId().equals(lastSibling)) {
363                 DockingPort embedPort = d.getDockingPort();
364                 String embedRegion = getRegion(lastNode, d.getComponent());
365                 return dock(dockable, embedPort, embedRegion, ctrlNode);
366             }
367         }
368 
369 
370         return dock(dockable, port, region, ctrlNode);
371     }
372 
getRegion(SplitNode node, Component dockedComponent)373     private String getRegion(SplitNode node, Component dockedComponent) {
374         if(dockedComponent==null)
375             return CENTER_REGION;
376         return DockingUtility.getRegion(node.getRegion());
377     }
378 
getLastNode()379     public SplitNode getLastNode() {
380         return nodes.size()==0? null: (SplitNode)nodes.get(nodes.size()-1);
381     }
382 
dock(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode)383     private boolean dock(Dockable dockable, DockingPort port, String region, SplitNode ctrlNode) {
384         boolean ret = DockingManager.dock(dockable, port, region);
385         if(tabbed || ctrlNode==null)
386             return ret;
387 
388         final float percent = ctrlNode.getPercentage();
389         final Component docked = dockable.getComponent();
390 
391         EventQueue.invokeLater(new Runnable() {
392             public void run() {
393                 resizeSplitPane(docked, percent);
394             }
395         });
396         return ret;
397     }
398 
resizeSplitPane(Component comp, float percentage)399     private void resizeSplitPane(Component comp, float percentage) {
400         Container parent = comp.getParent();
401         Container grandParent = parent==null? null: parent.getParent();
402         if(!(grandParent instanceof JSplitPane))
403             return;
404 
405         JSplitPane split = (JSplitPane)grandParent;
406 //              int splitSize = split.getOrientation()==DockingConstants.VERTICAL? split.getHeight(): split.getWidth();
407 //              int divLoc = (int)(percentage * (float)splitSize);
408         split.setDividerLocation(percentage);
409     }
410 
findDockable(Component c)411     private static Dockable findDockable(Component c) {
412         return DockingManager.getDockable(c);
413     }
414 
findDockable(String id)415     private static Dockable findDockable(String id) {
416         return DockingManager.getDockable(id);
417     }
418 
isDocked(Dockable dockable)419     private static boolean isDocked(Dockable dockable) {
420         return DockingManager.isDocked(dockable);
421     }
422 
getDepth()423     public int getDepth() {
424         return nodes.size();
425     }
426 
getNode(int indx)427     public SplitNode getNode(int indx) {
428         return indx<0 || indx>=getDepth()? null: (SplitNode)nodes.get(indx);
429     }
430 
clone()431     public Object clone() {
432         ArrayList nodeList = null;
433         if(nodes!=null) {
434             nodeList = new ArrayList(nodes.size());
435             for(Iterator it=nodes.iterator(); it.hasNext();) {
436                 SplitNode node = (SplitNode)it.next();
437                 nodeList.add(node.clone());
438             }
439         }
440 
441         DockingPath path = new DockingPath(siblingId, tabbed, nodeList);
442         path.rootPortId = rootPortId;
443         return path;
444     }
445 
446 }
447