blob: 182197c6d3e09944185e3427fbca088c4446ec81 [file] [log] [blame]
/*******************************************************************************
* 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$
}
}