| /******************************************************************************* |
| * Copyright (c) 2000, 2008 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Randy Hudson <hudsonr@us.ibm.com> |
| * - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views |
| * Cagatay Kavukcuoglu <cagatayk@acm.org> |
| * - Fix for bug 10025 - Resizing views should not use height ratios |
| * Matthew Hatem Matthew_Hatem@notesdev.ibm.com Bug 189953 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal; |
| |
| import java.util.ArrayList; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.jface.util.Geometry; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Sash; |
| import org.eclipse.ui.IPageLayout; |
| |
| /** |
| * Implementation of a tree node. The node represents a |
| * sash and it allways has two children. |
| */ |
| public class LayoutTreeNode extends LayoutTree { |
| |
| static class ChildSizes { |
| int left; |
| int right; |
| boolean resizable = true; |
| |
| public ChildSizes (int l, int r, boolean resize) { |
| left = l; |
| right = r; |
| resizable = resize; |
| } |
| } |
| |
| /* The node children witch may be another node or a leaf */ |
| private LayoutTree children[] = new LayoutTree[2]; |
| |
| /** |
| * Initialize this tree with its sash. |
| */ |
| public LayoutTreeNode(LayoutPartSash sash) { |
| super(sash); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.internal.LayoutTree#flushChildren() |
| */ |
| public void flushChildren() { |
| super.flushChildren(); |
| |
| children[0].flushChildren(); |
| children[1].flushChildren(); |
| } |
| |
| /** |
| * Traverses the tree to find the part that intersects the given point |
| * |
| * @param toFind |
| * @return the part that intersects the given point |
| */ |
| public LayoutPart findPart(Point toFind) { |
| if (!children[0].isVisible()) { |
| if (!children[1].isVisible()) { |
| return null; |
| } |
| |
| return children[1].findPart(toFind); |
| } else { |
| if (!children[1].isVisible()) { |
| return children[0].findPart(toFind); |
| } |
| } |
| |
| LayoutPartSash sash = getSash(); |
| |
| Rectangle bounds = sash.getBounds(); |
| |
| if (sash.isVertical()) { |
| if (toFind.x < bounds.x + (bounds.width / 2)) { |
| return children[0].findPart(toFind); |
| } |
| return children[1].findPart(toFind); |
| } else { |
| if (toFind.y < bounds.y + (bounds.height / 2)) { |
| return children[0].findPart(toFind); |
| } |
| return children[1].findPart(toFind); |
| } |
| } |
| |
| /** |
| * Add the relation ship between the children in the list |
| * and returns the left children. |
| */ |
| public LayoutPart computeRelation(ArrayList relations) { |
| PartSashContainer.RelationshipInfo r = new PartSashContainer.RelationshipInfo(); |
| r.relative = children[0].computeRelation(relations); |
| r.part = children[1].computeRelation(relations); |
| r.left = getSash().getLeft(); |
| r.right = getSash().getRight(); |
| r.relationship = getSash().isVertical() ? IPageLayout.RIGHT |
| : IPageLayout.BOTTOM; |
| relations.add(0, r); |
| return r.relative; |
| } |
| |
| /** |
| * Dispose all Sashs in this tree |
| */ |
| public void disposeSashes() { |
| children[0].disposeSashes(); |
| children[1].disposeSashes(); |
| getSash().dispose(); |
| } |
| |
| /** |
| * Find a LayoutPart in the tree and return its sub-tree. Returns |
| * null if the child is not found. |
| */ |
| public LayoutTree find(LayoutPart child) { |
| LayoutTree node = children[0].find(child); |
| if (node != null) { |
| return node; |
| } |
| node = children[1].find(child); |
| return node; |
| } |
| |
| /** |
| * Find the part that is in the bottom right position. |
| */ |
| public LayoutPart findBottomRight() { |
| if (children[1].isVisible()) { |
| return children[1].findBottomRight(); |
| } |
| return children[0].findBottomRight(); |
| } |
| |
| /** |
| * Go up in the tree finding a parent that is common of both children. |
| * Return the subtree. |
| */ |
| public LayoutTreeNode findCommonParent(LayoutPart child1, LayoutPart child2) { |
| return findCommonParent(child1, child2, false, false); |
| } |
| |
| /** |
| * Go up in the tree finding a parent that is common of both children. |
| * Return the subtree. |
| */ |
| LayoutTreeNode findCommonParent(LayoutPart child1, LayoutPart child2, |
| boolean foundChild1, boolean foundChild2) { |
| if (!foundChild1) { |
| foundChild1 = find(child1) != null; |
| } |
| if (!foundChild2) { |
| foundChild2 = find(child2) != null; |
| } |
| if (foundChild1 && foundChild2) { |
| return this; |
| } |
| if (parent == null) { |
| return null; |
| } |
| return parent |
| .findCommonParent(child1, child2, foundChild1, foundChild2); |
| } |
| |
| /** |
| * Find a sash in the tree and return its sub-tree. Returns |
| * null if the sash is not found. |
| */ |
| public LayoutTreeNode findSash(LayoutPartSash sash) { |
| if (this.getSash() == sash) { |
| return this; |
| } |
| LayoutTreeNode node = children[0].findSash(sash); |
| if (node != null) { |
| return node; |
| } |
| node = children[1].findSash(sash); |
| if (node != null) { |
| return node; |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the elements in the array of sashes with the |
| * Left,Rigth,Top and Botton sashes. The elements |
| * may be null depending whether there is a shash |
| * beside the <code>part</code> |
| */ |
| void findSashes(LayoutTree child, PartPane.Sashes sashes) { |
| Sash sash = (Sash) getSash().getControl(); |
| boolean leftOrTop = children[0] == child; |
| if (sash != null) { |
| LayoutPartSash partSash = getSash(); |
| //If the child is in the left, the sash |
| //is in the rigth and so on. |
| if (leftOrTop) { |
| if (partSash.isVertical()) { |
| if (sashes.right == null) { |
| sashes.right = sash; |
| } |
| } else { |
| if (sashes.bottom == null) { |
| sashes.bottom = sash; |
| } |
| } |
| } else { |
| if (partSash.isVertical()) { |
| if (sashes.left == null) { |
| sashes.left = sash; |
| } |
| } else { |
| if (sashes.top == null) { |
| sashes.top = sash; |
| } |
| } |
| } |
| } |
| if (getParent() != null) { |
| getParent().findSashes(this, sashes); |
| } |
| } |
| |
| /** |
| * Returns the sash of this node. |
| */ |
| public LayoutPartSash getSash() { |
| return (LayoutPartSash) part; |
| } |
| |
| private int getSashSize() { |
| return getSash().getSashSize(); |
| } |
| |
| /** |
| * Returns true if this tree has visible parts otherwise returns false. |
| */ |
| public boolean isVisible() { |
| return children[0].isVisible() || children[1].isVisible(); |
| } |
| |
| /** |
| * Remove the child and this node from the tree |
| */ |
| LayoutTree remove(LayoutTree child) { |
| getSash().dispose(); |
| if (parent == null) { |
| //This is the root. Return the other child to be the new root. |
| if (children[0] == child) { |
| children[1].setParent(null); |
| return children[1]; |
| } |
| children[0].setParent(null); |
| return children[0]; |
| } |
| |
| LayoutTreeNode oldParent = parent; |
| if (children[0] == child) { |
| oldParent.replaceChild(this, children[1]); |
| } else { |
| oldParent.replaceChild(this, children[0]); |
| } |
| return oldParent; |
| } |
| |
| /** |
| * Replace a child with a new child and sets the new child's parent. |
| */ |
| void replaceChild(LayoutTree oldChild, LayoutTree newChild) { |
| if (children[0] == oldChild) { |
| children[0] = newChild; |
| } else if (children[1] == oldChild) { |
| children[1] = newChild; |
| } |
| newChild.setParent(this); |
| if (!children[0].isVisible() || !children[0].isVisible()) { |
| getSash().dispose(); |
| } |
| |
| flushCache(); |
| } |
| |
| /** |
| * Go up from the subtree and return true if all the sash are |
| * in the direction specified by <code>isVertical</code> |
| */ |
| public boolean sameDirection(boolean isVertical, LayoutTreeNode subTree) { |
| boolean treeVertical = getSash().isVertical(); |
| if (treeVertical != isVertical) { |
| return false; |
| } |
| while (subTree != null) { |
| if (this == subTree) { |
| return true; |
| } |
| if (subTree.children[0].isVisible() |
| && subTree.children[1].isVisible()) { |
| if (subTree.getSash().isVertical() != isVertical) { |
| return false; |
| } |
| } |
| subTree = subTree.getParent(); |
| } |
| return true; |
| } |
| |
| public int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) { |
| assertValidSize(availablePerpendicular); |
| assertValidSize(availableParallel); |
| assertValidSize(preferredParallel); |
| |
| // If one child is invisible, defer to the other child |
| if (!children[0].isVisible()) { |
| return children[1].computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel); |
| } |
| |
| if (!children[1].isVisible()) { |
| return children[0].computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel); |
| } |
| |
| if (availableParallel == 0) { |
| return 0; |
| } |
| |
| // If computing the dimension perpendicular to our sash |
| if (width == getSash().isVertical()) { |
| // Compute the child sizes |
| ChildSizes sizes = computeChildSizes(availableParallel, availablePerpendicular, |
| getSash().getLeft(), getSash().getRight(), preferredParallel); |
| |
| // Return the sum of the child sizes plus the sash size |
| return add(sizes.left, add(sizes.right, getSashSize())); |
| } else { |
| // Computing the dimension parallel to the sash. We will compute and return the preferred size |
| // of whichever child is closest to the ideal size. |
| |
| ChildSizes sizes; |
| // First compute the dimension of the child sizes perpendicular to the sash |
| sizes = computeChildSizes(availablePerpendicular, availableParallel, |
| getSash().getLeft(), getSash().getRight(), availablePerpendicular); |
| |
| // Use this information to compute the dimension of the child sizes parallel to the sash. |
| // Return the preferred size of whichever child is largest |
| int leftSize = children[0].computePreferredSize(width, availableParallel, sizes.left, preferredParallel); |
| |
| // Compute the preferred size of the right child |
| int rightSize = children[1].computePreferredSize(width, availableParallel, sizes.right, preferredParallel); |
| |
| // Return leftSize or rightSize: whichever one is largest |
| int result = rightSize; |
| if (leftSize > rightSize) { |
| result = leftSize; |
| } |
| |
| assertValidSize(result); |
| |
| return result; |
| } |
| } |
| |
| /** |
| * Computes the pixel sizes of this node's children, given the available |
| * space for this node. Note that "width" and "height" actually refer |
| * to the distance perpendicular and parallel to the sash respectively. |
| * That is, their meaning is reversed when computing a horizontal sash. |
| * |
| * @param width the pixel width of a vertical node, or the pixel height |
| * of a horizontal node (INFINITE if unbounded) |
| * @param height the pixel height of a vertical node, or the pixel width |
| * of a horizontal node (INFINITE if unbounded) |
| * @return a struct describing the pixel sizes of the left and right children |
| * (this is a width for horizontal nodes and a height for vertical nodes) |
| */ |
| ChildSizes computeChildSizes(int width, int height, int left, int right, int preferredWidth) { |
| Assert.isTrue(children[0].isVisible()); |
| Assert.isTrue(children[1].isVisible()); |
| assertValidSize(width); |
| assertValidSize(height); |
| assertValidSize(preferredWidth); |
| Assert.isTrue(left >= 0); |
| Assert.isTrue(right >= 0); |
| Assert.isTrue(preferredWidth >= 0); |
| Assert.isTrue(preferredWidth <= width); |
| boolean vertical = getSash().isVertical(); |
| |
| if (width <= getSashSize()) { |
| return new ChildSizes(0,0, false); |
| } |
| |
| if (width == INFINITE) { |
| if (preferredWidth == INFINITE) { |
| return new ChildSizes(children[0].computeMaximumSize(vertical, height), |
| children[1].computeMaximumSize(vertical, height), false); |
| } |
| |
| if (preferredWidth == 0) { |
| return new ChildSizes(children[0].computeMinimumSize(vertical, height), |
| children[1].computeMinimumSize(vertical, height), false); |
| } |
| } |
| |
| int total = left + right; |
| |
| // Use all-or-none weighting |
| double wLeft = left, wRight = right; |
| switch (getCompressionBias()) { |
| case -1: |
| wLeft = 0.0; |
| break; |
| case 1: |
| wRight = 0.0; |
| break; |
| default: |
| break; |
| } |
| double wTotal = wLeft + wRight; |
| |
| // Subtract the SASH_WIDTH from preferredWidth and width. From here on, we'll deal with the |
| // width available to the controls and neglect the space used by the sash. |
| preferredWidth = Math.max(0, subtract(preferredWidth, getSashSize())); |
| width = Math.max(0, subtract(width, getSashSize())); |
| |
| int redistribute = subtract(preferredWidth, total); |
| |
| // Compute the minimum and maximum sizes for each child |
| int leftMinimum = children[0].computeMinimumSize(vertical, height); |
| int rightMinimum = children[1].computeMinimumSize(vertical, height); |
| int leftMaximum = children[0].computeMaximumSize(vertical, height); |
| int rightMaximum = children[1].computeMaximumSize(vertical, height); |
| |
| // Keep track of the available space for each child, given the minimum size of the other child |
| int leftAvailable = Math.min(leftMaximum, Math.max(0, subtract(width, rightMinimum))); |
| int rightAvailable = Math.min(rightMaximum, Math.max(0, subtract(width, leftMinimum))); |
| |
| // Figure out the ideal size of the left child |
| int idealLeft = Math.max(leftMinimum, Math.min(preferredWidth, |
| left + (int) Math.round(redistribute * wLeft / wTotal))); |
| |
| // If the right child can't use all its available space, let the left child fill it in |
| idealLeft = Math.max(idealLeft, preferredWidth - rightAvailable); |
| // Ensure the left child doesn't get larger than its available space |
| idealLeft = Math.min(idealLeft, leftAvailable); |
| |
| // Check if the left child would prefer to be a different size |
| idealLeft = children[0].computePreferredSize(vertical, leftAvailable, height, idealLeft); |
| |
| // Ensure that the left child is larger than its minimum size |
| idealLeft = Math.max(idealLeft, leftMinimum); |
| idealLeft = Math.min(idealLeft, leftAvailable); |
| |
| // Compute the right child width |
| int idealRight = Math.max(rightMinimum, preferredWidth - idealLeft); |
| |
| rightAvailable = Math.max(0, Math.min(rightAvailable, subtract(width, idealLeft))); |
| idealRight = Math.min(idealRight, rightAvailable); |
| idealRight = children[1].computePreferredSize(vertical, rightAvailable, height, idealRight); |
| idealRight = Math.max(idealRight, rightMinimum); |
| |
| return new ChildSizes(idealLeft, idealRight, leftMaximum > leftMinimum |
| && rightMaximum > rightMinimum |
| && leftMinimum + rightMinimum < width); |
| } |
| |
| protected int doGetSizeFlags(boolean width) { |
| if (!children[0].isVisible()) { |
| return children[1].getSizeFlags(width); |
| } |
| |
| if (!children[1].isVisible()) { |
| return children[0].getSizeFlags(width); |
| } |
| |
| int leftFlags = children[0].getSizeFlags(width); |
| int rightFlags = children[1].getSizeFlags(width); |
| |
| return ((leftFlags | rightFlags) & ~SWT.MAX) | (leftFlags & rightFlags & SWT.MAX); |
| } |
| |
| /** |
| * Resize the parts on this tree to fit in <code>bounds</code>. |
| */ |
| public void doSetBounds(Rectangle bounds) { |
| if (!children[0].isVisible()) { |
| children[1].setBounds(bounds); |
| getSash().setVisible(false); |
| return; |
| } |
| if (!children[1].isVisible()) { |
| children[0].setBounds(bounds); |
| getSash().setVisible(false); |
| return; |
| } |
| |
| bounds = Geometry.copy(bounds); |
| |
| boolean vertical = getSash().isVertical(); |
| |
| // If this is a horizontal sash, flip coordinate systems so |
| // that we can eliminate special cases |
| if (!vertical) { |
| Geometry.flipXY(bounds); |
| } |
| |
| ChildSizes childSizes = computeChildSizes(bounds.width, bounds.height, getSash().getLeft(), getSash().getRight(), bounds.width); |
| |
| getSash().setVisible(true); |
| getSash().setEnabled(childSizes.resizable); |
| |
| Rectangle leftBounds = new Rectangle(bounds.x, bounds.y, childSizes.left, bounds.height); |
| Rectangle sashBounds = new Rectangle(leftBounds.x + leftBounds.width, bounds.y, this.getSashSize(), bounds.height); |
| Rectangle rightBounds = new Rectangle(sashBounds.x + sashBounds.width, bounds.y, childSizes.right, bounds.height); |
| |
| if (!vertical) { |
| Geometry.flipXY(leftBounds); |
| Geometry.flipXY(sashBounds); |
| Geometry.flipXY(rightBounds); |
| } |
| |
| getSash().setBounds(sashBounds); |
| children[0].setBounds(leftBounds); |
| children[1].setBounds(rightBounds); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.ui.internal.LayoutTree#createControl(org.eclipse.swt.widgets.Composite) |
| */ |
| public void createControl(Composite parent) { |
| children[0].createControl(parent); |
| children[1].createControl(parent); |
| getSash().createControl(parent); |
| |
| super.createControl(parent); |
| } |
| |
| //Added by hudsonr@us.ibm.com - bug 19524 |
| |
| public boolean isCompressible() { |
| return children[0].isCompressible() || children[1].isCompressible(); |
| } |
| |
| /** |
| * Returns 0 if there is no bias. Returns -1 if the first child should be of |
| * fixed size, and the second child should be compressed. Returns 1 if the |
| * second child should be of fixed size. |
| * @return the bias |
| */ |
| public int getCompressionBias() { |
| boolean left = children[0].isCompressible(); |
| boolean right = children[1].isCompressible(); |
| if (left == right) { |
| return 0; |
| } |
| if (right) { |
| return -1; |
| } |
| return 1; |
| } |
| |
| boolean isLeftChild(LayoutTree toTest) { |
| return children[0] == toTest; |
| } |
| |
| LayoutTree getChild(boolean left) { |
| int index = left ? 0 : 1; |
| return (children[index]); |
| } |
| |
| /** |
| * Sets a child in this node |
| */ |
| void setChild(boolean left, LayoutPart part) { |
| LayoutTree child = new LayoutTree(part); |
| setChild(left, child); |
| flushCache(); |
| } |
| |
| /** |
| * Sets a child in this node |
| */ |
| void setChild(boolean left, LayoutTree child) { |
| int index = left ? 0 : 1; |
| children[index] = child; |
| child.setParent(this); |
| flushCache(); |
| } |
| |
| /** |
| * Returns a string representation of this object. |
| */ |
| public String toString() { |
| String s = "<null>\n";//$NON-NLS-1$ |
| if (part.getControl() != null) { |
| s = "<@" + part.getControl().hashCode() + ">\n";//$NON-NLS-2$//$NON-NLS-1$ |
| } |
| String result = "["; //$NON-NLS-1$ |
| if (children[0].getParent() != this) { |
| result = result + "{" + children[0] + "}" + s;//$NON-NLS-2$//$NON-NLS-1$ |
| } else { |
| result = result + children[0] + s; |
| } |
| |
| if (children[1].getParent() != this) { |
| result = result + "{" + children[1] + "}]";//$NON-NLS-2$//$NON-NLS-1$ |
| } else { |
| result = result + children[1] + "]";//$NON-NLS-1$ |
| } |
| return result; |
| } |
| |
| /** |
| * Create the sashes if the children are visible |
| * and dispose it if they are not. |
| */ |
| // public void updateSashes(Composite parent) { |
| // if (parent == null) |
| // return; |
| // children[0].updateSashes(parent); |
| // children[1].updateSashes(parent); |
| // if (children[0].isVisible() && children[1].isVisible()) |
| // getSash().createControl(parent); |
| // else |
| // getSash().dispose(); |
| // } |
| |
| /** |
| * Writes a description of the layout to the given string buffer. |
| * This is used for drag-drop test suites to determine if two layouts are the |
| * same. Like a hash code, the description should compare as equal iff the |
| * layouts are the same. However, it should be user-readable in order to |
| * help debug failed tests. Although these are english readable strings, |
| * they should not be translated or equality tests will fail. |
| * |
| * @param buf |
| */ |
| public void describeLayout(StringBuffer buf) { |
| if (!(children[0].isVisible())) { |
| if (!children[1].isVisible()) { |
| return; |
| } |
| |
| children[1].describeLayout(buf); |
| return; |
| } |
| |
| if (!children[1].isVisible()) { |
| children[0].describeLayout(buf); |
| return; |
| } |
| |
| buf.append("("); //$NON-NLS-1$ |
| children[0].describeLayout(buf); |
| |
| buf.append(getSash().isVertical() ? "|" : "-"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| children[1].describeLayout(buf); |
| buf.append(")"); //$NON-NLS-1$ |
| } |
| |
| } |