private void checkLayout(Node root) { if (root instanceof Split) { Split split = (Split) root; if (split.getChildren().size() <= 2) { throwInvalidLayout("Split must have > 2 children", root); } Iterator<Node> splitChildren = split.getChildren().iterator(); double weight = 0.0; while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); if (splitChild instanceof Divider) { throwInvalidLayout("expected a Split or Leaf Node", splitChild); } if (splitChildren.hasNext()) { Node dividerChild = splitChildren.next(); if (!(dividerChild instanceof Divider)) { throwInvalidLayout("expected a Divider Node", dividerChild); } } weight += splitChild.getWeight(); checkLayout(splitChild); } if (weight > 1.0 + 0.000000001) { /* add some epsilon to a double check */ throwInvalidLayout("Split children's total weight > 1.0", root); } } }
private Dimension minimumNodeSize(Node root) { if (root instanceof Leaf) { Component child = childForNode(root); return (child != null) ? child.getMinimumSize() : new Dimension(0, 0); } else if (root instanceof Divider) { int dividerSize = getDividerSize(); return new Dimension(dividerSize, dividerSize); } else { Split split = (Split) root; List<Node> splitChildren = split.getChildren(); int width = 0; int height = 0; if (split.isRowLayout()) { for (Node splitChild : splitChildren) { Dimension size = minimumNodeSize(splitChild); width += size.width; height = Math.max(height, size.height); } } else { for (Node splitChild : splitChildren) { Dimension size = minimumNodeSize(splitChild); width = Math.max(width, size.width); height += size.height; } } return new Dimension(width, height); } }
private Dimension preferredNodeSize(Node root) { if (root instanceof Leaf) return preferredComponentSize(root); else if (root instanceof Divider) { int dividerSize = getDividerSize(); return new Dimension(dividerSize, dividerSize); } else { Split split = (Split) root; List<Node> splitChildren = split.getChildren(); int width = 0; int height = 0; if (split.isRowLayout()) { for (Node splitChild : splitChildren) { Dimension size = preferredNodeSize(splitChild); width += size.width; height = Math.max(height, size.height); } } else { for (Node splitChild : splitChildren) { Dimension size = preferredNodeSize(splitChild); width = Math.max(width, size.width); height += size.height; } } return new Dimension(width, height); } }
private Node siblingAtOffset(int offset) { Split parent = parent_get(); if (parent == null) return null; List<Node> siblings = parent.getChildren(); int index = siblings.indexOf(this); if (index == -1) return null; index += offset; return ((index > -1) && (index < siblings.size())) ? siblings.get(index) : null; }
private static void addSplitChild(Split parent, Node child) { List<Node> children = new ArrayList<Node>(parent.getChildren()); if (children.size() == 0) { children.add(child); } else { children.add(new Divider()); children.add(child); } parent.setChildren(children); }
private static void printModel(String indent, Node root) { if (root instanceof Split) { Split split = (Split) root; System.out.println(indent + split); for (Node child : split.getChildren()) { printModel(indent + " ", child); } } else { System.out.println(indent + root); } }
private Divider dividerAt(Node root, int x, int y) { if (root instanceof Divider) { Divider divider = (Divider) root; return (divider.getBounds().contains(x, y)) ? divider : null; } else if (root instanceof Split) { Split split = (Split) root; for (Node child : split.getChildren()) { if (child.getBounds().contains(x, y)) return dividerAt(child, x, y); } } return null; }
private static Node parseModel(Reader r) { StreamTokenizer st = new StreamTokenizer(r); try { Split root = new Split(); parseSplit(st, root); return root.getChildren().get(0); } catch (Exception e) { Main.error(e); } finally { Utils.close(r); } return null; }
private void minimizeSplitBounds(Split split, Rectangle bounds) { Rectangle splitBounds = new Rectangle(bounds.x, bounds.y, 0, 0); List<Node> splitChildren = split.getChildren(); Node lastChild = splitChildren.get(splitChildren.size() - 1); Rectangle lastChildBounds = lastChild.getBounds(); if (split.isRowLayout()) { int lastChildMaxX = lastChildBounds.x + lastChildBounds.width; splitBounds.add(lastChildMaxX, bounds.y + bounds.height); } else { int lastChildMaxY = lastChildBounds.y + lastChildBounds.height; splitBounds.add(bounds.x + bounds.width, lastChildMaxY); } split.setBounds(splitBounds); }
private static Node parseModel(Reader r) { StreamTokenizer st = new StreamTokenizer(r); try { Split root = new Split(); parseSplit(st, root); return root.getChildren().get(0); } catch (Exception e) { System.err.println(e); } finally { try { r.close(); } catch (IOException ignore) { } } return null; }
/* First pass of the layout algorithm. * * If the Dividers are "floating" then set the bounds of each * node to accomodate the preferred size of all of the * Leaf's java.awt.Components. Otherwise, just set the bounds * of each Leaf/Split node so that it's to the left of (for * Split.isRowLayout() Split children) or directly above * the Divider that follows. * * This pass sets the bounds of each Node in the layout model. It * does not resize any of the parent Container's * (java.awt.Component) children. That's done in the second pass, * see layoutGrow() and layoutShrink(). */ private void layout1(Node root, Rectangle bounds) { if (root instanceof Leaf) { root.setBounds(bounds); } else if (root instanceof Split) { Split split = (Split) root; Iterator<Node> splitChildren = split.getChildren().iterator(); Rectangle childBounds = null; int dividerSize = getDividerSize(); /* Layout the Split's child Nodes' along the X axis. The bounds * of each child will have the same y coordinate and height as the * layout1() bounds argument. * * Note: the column layout code - that's the "else" clause below * this if, is identical to the X axis (rowLayout) code below. */ if (split.isRowLayout()) { double x = bounds.getX(); while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Divider dividerChild = (splitChildren.hasNext()) ? (Divider) (splitChildren.next()) : null; double childWidth = 0.0; if (getFloatingDividers()) { childWidth = preferredNodeSize(splitChild).getWidth(); } else { if (dividerChild != null) { childWidth = dividerChild.getBounds().getX() - x; } else { childWidth = split.getBounds().getMaxX() - x; } } childBounds = boundsWithXandWidth(bounds, x, childWidth); layout1(splitChild, childBounds); if (getFloatingDividers() && (dividerChild != null)) { double dividerX = childBounds.getMaxX(); Rectangle dividerBounds = boundsWithXandWidth(bounds, dividerX, dividerSize); dividerChild.setBounds(dividerBounds); } if (dividerChild != null) { x = dividerChild.getBounds().getMaxX(); } } } /* Layout the Split's child Nodes' along the Y axis. The bounds * of each child will have the same x coordinate and width as the * layout1() bounds argument. The algorithm is identical to what's * explained above, for the X axis case. */ else { double y = bounds.getY(); while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Divider dividerChild = (splitChildren.hasNext()) ? (Divider) (splitChildren.next()) : null; double childHeight = 0.0; if (getFloatingDividers()) { childHeight = preferredNodeSize(splitChild).getHeight(); } else { if (dividerChild != null) { childHeight = dividerChild.getBounds().getY() - y; } else { childHeight = split.getBounds().getMaxY() - y; } } childBounds = boundsWithYandHeight(bounds, y, childHeight); layout1(splitChild, childBounds); if (getFloatingDividers() && (dividerChild != null)) { double dividerY = childBounds.getMaxY(); Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, dividerSize); dividerChild.setBounds(dividerBounds); } if (dividerChild != null) { y = dividerChild.getBounds().getMaxY(); } } } /* The bounds of the Split node root are set to be just * big enough to contain all of its children, but only * along the axis it's allocating space on. That's * X for rows, Y for columns. The second pass of the * layout algorithm - see layoutShrink()/layoutGrow() * allocates extra space. */ minimizeSplitBounds(split, bounds); } }
private void layoutGrow(Split split, Rectangle bounds) { Rectangle splitBounds = split.getBounds(); ListIterator<Node> splitChildren = split.getChildren().listIterator(); Node lastWeightedChild = split.lastWeightedChild(); /* Layout the Split's child Nodes' along the X axis. The bounds * of each child will have the same y coordinate and height as the * layoutGrow() bounds argument. Extra width is allocated to the * to each child with a non-zero weight: * newWidth = currentWidth + (extraWidth * splitChild.getWeight()) * Any extraWidth "left over" (that's availableWidth in the loop * below) is given to the last child. Note that Dividers always * have a weight of zero, and they're never the last child. */ if (split.isRowLayout()) { double x = bounds.getX(); double extraWidth = bounds.getWidth() - splitBounds.getWidth(); double availableWidth = extraWidth; while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Rectangle splitChildBounds = splitChild.getBounds(); double splitChildWeight = splitChild.getWeight(); if (!splitChildren.hasNext()) { double newWidth = bounds.getMaxX() - x; Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); } else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) { double allocatedWidth = (splitChild.equals(lastWeightedChild)) ? availableWidth : Math.rint(splitChildWeight * extraWidth); double newWidth = splitChildBounds.getWidth() + allocatedWidth; Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); availableWidth -= allocatedWidth; } else { double existingWidth = splitChildBounds.getWidth(); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth); layout2(splitChild, newSplitChildBounds); } x = splitChild.getBounds().getMaxX(); } } /* Layout the Split's child Nodes' along the Y axis. The bounds * of each child will have the same x coordinate and width as the * layoutGrow() bounds argument. Extra height is allocated to the * to each child with a non-zero weight: * newHeight = currentHeight + (extraHeight * splitChild.getWeight()) * Any extraHeight "left over" (that's availableHeight in the loop * below) is given to the last child. Note that Dividers always * have a weight of zero, and they're never the last child. */ else { double y = bounds.getY(); double extraHeight = bounds.getMaxY() - splitBounds.getHeight(); double availableHeight = extraHeight; while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Rectangle splitChildBounds = splitChild.getBounds(); double splitChildWeight = splitChild.getWeight(); if (!splitChildren.hasNext()) { double newHeight = bounds.getMaxY() - y; Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); } else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) { double allocatedHeight = (splitChild.equals(lastWeightedChild)) ? availableHeight : Math.rint(splitChildWeight * extraHeight); double newHeight = splitChildBounds.getHeight() + allocatedHeight; Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= allocatedHeight; } else { double existingHeight = splitChildBounds.getHeight(); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight); layout2(splitChild, newSplitChildBounds); } y = splitChild.getBounds().getMaxY(); } } }
private void layoutShrink(Split split, Rectangle bounds) { Rectangle splitBounds = split.getBounds(); ListIterator<Node> splitChildren = split.getChildren().listIterator(); if (split.isRowLayout()) { int totalWidth = 0; // sum of the children's widths int minWeightedWidth = 0; // sum of the weighted childrens' min widths int totalWeightedWidth = 0; // sum of the weighted childrens' widths for (Node splitChild : split.getChildren()) { int nodeWidth = splitChild.getBounds().width; int nodeMinWidth = Math.min(nodeWidth, minimumNodeSize(splitChild).width); totalWidth += nodeWidth; if (splitChild.getWeight() > 0.0) { minWeightedWidth += nodeMinWidth; totalWeightedWidth += nodeWidth; } } double x = bounds.getX(); double extraWidth = splitBounds.getWidth() - bounds.getWidth(); double availableWidth = extraWidth; boolean onlyShrinkWeightedComponents = (totalWeightedWidth - minWeightedWidth) > extraWidth; while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Rectangle splitChildBounds = splitChild.getBounds(); double minSplitChildWidth = minimumNodeSize(splitChild).getWidth(); double splitChildWeight = (onlyShrinkWeightedComponents) ? splitChild.getWeight() : (splitChildBounds.getWidth() / totalWidth); if (!splitChildren.hasNext()) { double newWidth = Math.max(minSplitChildWidth, bounds.getMaxX() - x); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); } else if ((availableWidth > 0.0) && (splitChildWeight > 0.0)) { double allocatedWidth = Math.rint(splitChildWeight * extraWidth); double oldWidth = splitChildBounds.getWidth(); double newWidth = Math.max(minSplitChildWidth, oldWidth - allocatedWidth); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, newWidth); layout2(splitChild, newSplitChildBounds); availableWidth -= (oldWidth - splitChild.getBounds().getWidth()); } else { double existingWidth = splitChildBounds.getWidth(); Rectangle newSplitChildBounds = boundsWithXandWidth(bounds, x, existingWidth); layout2(splitChild, newSplitChildBounds); } x = splitChild.getBounds().getMaxX(); } } else { int totalHeight = 0; // sum of the children's heights int minWeightedHeight = 0; // sum of the weighted childrens' min heights int totalWeightedHeight = 0; // sum of the weighted childrens' heights for (Node splitChild : split.getChildren()) { int nodeHeight = splitChild.getBounds().height; int nodeMinHeight = Math.min(nodeHeight, minimumNodeSize(splitChild).height); totalHeight += nodeHeight; if (splitChild.getWeight() > 0.0) { minWeightedHeight += nodeMinHeight; totalWeightedHeight += nodeHeight; } } double y = bounds.getY(); double extraHeight = splitBounds.getHeight() - bounds.getHeight(); double availableHeight = extraHeight; boolean onlyShrinkWeightedComponents = (totalWeightedHeight - minWeightedHeight) > extraHeight; while (splitChildren.hasNext()) { Node splitChild = splitChildren.next(); Rectangle splitChildBounds = splitChild.getBounds(); double minSplitChildHeight = minimumNodeSize(splitChild).getHeight(); double splitChildWeight = (onlyShrinkWeightedComponents) ? splitChild.getWeight() : (splitChildBounds.getHeight() / totalHeight); if (!splitChildren.hasNext()) { double oldHeight = splitChildBounds.getHeight(); double newHeight = Math.max(minSplitChildHeight, bounds.getMaxY() - y); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= (oldHeight - splitChild.getBounds().getHeight()); } else if ((availableHeight > 0.0) && (splitChildWeight > 0.0)) { double allocatedHeight = Math.rint(splitChildWeight * extraHeight); double oldHeight = splitChildBounds.getHeight(); double newHeight = Math.max(minSplitChildHeight, oldHeight - allocatedHeight); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, newHeight); layout2(splitChild, newSplitChildBounds); availableHeight -= (oldHeight - splitChild.getBounds().getHeight()); } else { double existingHeight = splitChildBounds.getHeight(); Rectangle newSplitChildBounds = boundsWithYandHeight(bounds, y, existingHeight); layout2(splitChild, newSplitChildBounds); } y = splitChild.getBounds().getMaxY(); } } /* The bounds of the Split node root are set to be * big enough to contain all of its children. Since * Leaf children can't be reduced below their * (corresponding java.awt.Component) minimum sizes, * the size of the Split's bounds maybe be larger than * the bounds we were asked to fit within. */ minimizeSplitBounds(split, bounds); }