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