blob: ec330740a39595b449747fed53d44ac9304bc248 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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
*******************************************************************************/
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.ui.ISizeProvider;
/**
* Implementation of a tree where the node is allways a sash
* and it allways has two chidren. If a children is removed
* the sash, ie the node, is removed as well and its other children
* placed on its parent.
*/
public class LayoutTree implements ISizeProvider {
/* The parent of this tree or null if it is the root */
LayoutTreeNode parent;
/* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */
LayoutPart part;
// Cached information
private int cachedMinimumWidthHint = SWT.DEFAULT;
private int cachedMinimumWidth = SWT.DEFAULT;
private int cachedMinimumHeightHint = SWT.DEFAULT;
private int cachedMinimumHeight = SWT.DEFAULT;
private int cachedMaximumWidthHint = SWT.DEFAULT;
private int cachedMaximumWidth = SWT.DEFAULT;
private int cachedMaximumHeightHint = SWT.DEFAULT;
private int cachedMaximumHeight = SWT.DEFAULT;
// Cached size flags
private boolean sizeFlagsDirty = true;
private int widthSizeFlags = 0;
private int heightSizeFlags = 0;
// Cache statistics. For use in benchmarks and test suites only!
public static int minCacheHits;
public static int minCacheMisses;
public static int maxCacheHits;
public static int maxCacheMisses;
private boolean forceLayout = true;
private Rectangle currentBounds = new Rectangle(0,0,0,0);
/**
* Initialize this tree with its part.
*/
public LayoutTree(LayoutPart part) {
this.part = part;
}
/**
* Add the relation ship between the children in the list
* and returns the left children.
*/
public LayoutPart computeRelation(ArrayList relations) {
return part;
}
/**
* Locates the part that intersects the given point
*
* @param toFind
* @return
*/
public LayoutPart findPart(Point toFind) {
return part;
}
/**
* Dispose all Sashs in this tree
*/
public void disposeSashes() {
}
/**
* Find a LayoutPart in the tree and return its sub-tree. Returns
* null if the child is not found.
*/
public LayoutTree find(LayoutPart child) {
if (part != child) {
return null;
}
return this;
}
/**
* Find the Left,Right,Top and Botton
* sashes around this tree and set them
* in <code>sashes</code>
*/
public void findSashes(PartPane.Sashes sashes) {
if (getParent() == null) {
return;
}
getParent().findSashes(this, sashes);
}
/**
* Find the part that is in the bottom rigth possition.
*/
public LayoutPart findBottomRight() {
return part;
}
/**
* Find a sash in the tree and return its sub-tree. Returns
* null if the sash is not found.
*/
public LayoutTreeNode findSash(LayoutPartSash sash) {
return null;
}
/**
* Return the bounds of this tree which is the rectangle that
* contains all Controls in this tree.
*/
public final Rectangle getBounds() {
return Geometry.copy(currentBounds);
}
/**
* Subtracts two integers. If a is INFINITE, this is treated as
* positive infinity.
*
* @param a a positive integer or INFINITE indicating positive infinity
* @param b a positive integer (may not be INFINITE)
* @return a - b, or INFINITE if a == INFINITE
* @since 3.1
*/
public static int subtract(int a, int b) {
Assert.isTrue(b >= 0 && b < INFINITE);
return add(a, -b);
}
/**
* Adds two positive integers. Treates INFINITE as positive infinity.
*
* @param a a positive integer
* @param b a positive integer
* @return a + b, or INFINITE if a or b are positive infinity
* @since 3.1
*/
public static int add(int a, int b) {
if (a == INFINITE || b == INFINITE) {
return INFINITE;
}
return a + b;
}
/**
* Asserts that toCheck is a positive integer less than INFINITE / 2 or equal
* to INFINITE. Many of the methods of this class use positive integers as sizes,
* with INFINITE indicating positive infinity. This picks up accidental addition or
* subtraction from infinity.
*
* @param toCheck integer to validate
* @since 3.1
*/
public static void assertValidSize(int toCheck) {
Assert.isTrue(toCheck >= 0 && (toCheck == INFINITE || toCheck < INFINITE / 2));
}
/**
* Computes the preferred size for this object. The interpretation of the result depends on the flags returned
* by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize
* or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to
* doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or
* doComputePreferredSize to specialize the return value.
*
* @see LayoutPart#computePreferredSize(boolean, int, int, int)
*/
public final int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
assertValidSize(availableParallel);
assertValidSize(availablePerpendicular);
assertValidSize(preferredParallel);
if (!isVisible()) {
return 0;
}
if (availableParallel == 0) {
return 0;
}
if (preferredParallel == 0) {
return Math.min(availableParallel, computeMinimumSize(width, availablePerpendicular));
} else if (preferredParallel == INFINITE && availableParallel == INFINITE) {
return computeMaximumSize(width, availablePerpendicular);
}
// Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum
// size, simply return the preferred size
if (!hasSizeFlag(width, SWT.FILL)) {
return preferredParallel;
}
int result = doComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);
return result;
}
/**
* Returns the size flags for this tree.
*
* @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
*
* @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false)
* @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean)
*/
protected int doGetSizeFlags(boolean width) {
return part.getSizeFlags(width);
}
/**
* Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int)
*
* @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
*
* @since 3.1
*/
protected int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
int result = Math.min(availableParallel,
part.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel));
assertValidSize(result);
return result;
}
/**
* Returns the minimum size for this subtree. Equivalent to calling
* computePreferredSize(width, INFINITE, availablePerpendicular, 0).
* Returns a cached value if possible or defers to doComputeMinimumSize otherwise.
* Subclasses should overload doComputeMinimumSize if they want to specialize the
* return value.
*
* @param width true iff computing the minimum width, false iff computing the minimum height
* @param availablePerpendicular available space (pixels) perpendicular to the dimension
* being computed. This is a height when computing a width, or a width when computing a height.
*
* @see LayoutPart#computePreferredSize(boolean, int, int, int)
*/
public final int computeMinimumSize(boolean width, int availablePerpendicular) {
assertValidSize(availablePerpendicular);
// Optimization: if this subtree has no minimum size, then always return 0 as its
// minimum size.
if (!hasSizeFlag(width, SWT.MIN)) {
return 0;
}
// If this subtree doesn't contain any wrapping controls (ie: they don't care
// about their perpendicular size) then force the perpendicular
// size to be INFINITE. This ensures that we will get a cache hit
// every time for non-wrapping controls.
if (!hasSizeFlag(width, SWT.WRAP)) {
availablePerpendicular = INFINITE;
}
if (width) {
// Check if we have a cached width measurement (we can only return a cached
// value if we computed it for the same height)
if (cachedMinimumWidthHint == availablePerpendicular) {
minCacheHits++;
return cachedMinimumWidth;
}
// Recompute the minimum width and store it in the cache
minCacheMisses++;
int result = doComputeMinimumSize(width, availablePerpendicular);
cachedMinimumWidth = result;
cachedMinimumWidthHint = availablePerpendicular;
return result;
} else {
// Check if we have a cached height measurement (we can only return a cached
// value if we computed it for the same width)
if (cachedMinimumHeightHint == availablePerpendicular) {
minCacheHits++;
return cachedMinimumHeight;
}
// Recompute the minimum width and store it in the cache
minCacheMisses++;
int result = doComputeMinimumSize(width, availablePerpendicular);
cachedMinimumHeight = result;
cachedMinimumHeightHint = availablePerpendicular;
return result;
}
}
/**
* For use in benchmarks and test suites only. Displays cache utilization statistics for all
* LayoutTree instances.
*
* @since 3.1
*/
public static void printCacheStatistics() {
System.out.println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
minCacheHits * 100 / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$
System.out.println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
maxCacheHits * 100 / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$
}
public int doComputeMinimumSize(boolean width, int availablePerpendicular) {
int result = doComputePreferredSize(width, INFINITE, availablePerpendicular, 0);
assertValidSize(result);
return result;
}
public final int computeMaximumSize(boolean width, int availablePerpendicular) {
assertValidSize(availablePerpendicular);
// Optimization: if this subtree has no maximum size, then always return INFINITE as its
// maximum size.
if (!hasSizeFlag(width, SWT.MAX)) {
return INFINITE;
}
// If this subtree doesn't contain any wrapping controls (ie: they don't care
// about their perpendicular size) then force the perpendicular
// size to be INFINITE. This ensures that we will get a cache hit
// every time.
if (!hasSizeFlag(width, SWT.WRAP)) {
availablePerpendicular = INFINITE;
}
if (width) {
// Check if we have a cached width measurement (we can only return a cached
// value if we computed it for the same height)
if (cachedMaximumWidthHint == availablePerpendicular) {
maxCacheHits++;
return cachedMaximumWidth;
}
maxCacheMisses++;
// Recompute the maximum width and store it in the cache
int result = doComputeMaximumSize(width, availablePerpendicular);
cachedMaximumWidth = result;
cachedMaximumWidthHint = availablePerpendicular;
return result;
} else {
// Check if we have a cached height measurement
if (cachedMaximumHeightHint == availablePerpendicular) {
maxCacheHits++;
return cachedMaximumHeight;
}
maxCacheMisses++;
// Recompute the maximum height and store it in the cache
int result = doComputeMaximumSize(width, availablePerpendicular);
cachedMaximumHeight = result;
cachedMaximumHeightHint = availablePerpendicular;
return result;
}
}
protected int doComputeMaximumSize(boolean width, int availablePerpendicular) {
return doComputePreferredSize(width, INFINITE, availablePerpendicular, INFINITE);
}
/**
* Called to flush any cached information in this tree and its parents.
*/
public void flushNode() {
// Clear cached sizes
cachedMinimumWidthHint = SWT.DEFAULT;
cachedMinimumWidth = SWT.DEFAULT;
cachedMinimumHeightHint = SWT.DEFAULT;
cachedMinimumHeight = SWT.DEFAULT;
cachedMaximumWidthHint = SWT.DEFAULT;
cachedMaximumWidth = SWT.DEFAULT;
cachedMaximumHeightHint = SWT.DEFAULT;
cachedMaximumHeight = SWT.DEFAULT;
// Flags may have changed. Ensure that they are recomputed the next time around
sizeFlagsDirty = true;
// The next setBounds call should trigger a layout even if set to the same bounds since
// one of the children has changed.
forceLayout = true;
}
/**
* Flushes all cached information about this node and all of its children.
* This should be called if something may have caused all children to become
* out of synch with their cached information (for example, if a lot of changes
* may have happened without calling flushCache after each change)
*
* @since 3.1
*/
public void flushChildren() {
flushNode();
}
/**
* Flushes all cached information about this node and all of its ancestors.
* This should be called when a single child changes.
*
* @since 3.1
*/
public final void flushCache() {
flushNode();
if (parent != null) {
parent.flushCache();
}
}
public final int getSizeFlags(boolean width) {
if (sizeFlagsDirty) {
widthSizeFlags = doGetSizeFlags(true);
heightSizeFlags = doGetSizeFlags(false);
sizeFlagsDirty = false;
}
return width ? widthSizeFlags : heightSizeFlags;
}
/**
* Returns the parent of this tree or null if it is the root.
*/
public LayoutTreeNode getParent() {
return parent;
}
/**
* Inserts a new child on the tree. The child will be placed beside
* the <code>relative</code> child. Returns the new root of the tree.
*/
public LayoutTree insert(LayoutPart child, boolean left,
LayoutPartSash sash, LayoutPart relative) {
LayoutTree relativeChild = find(relative);
LayoutTreeNode node = new LayoutTreeNode(sash);
if (relativeChild == null) {
//Did not find the relative part. Insert beside the root.
node.setChild(left, child);
node.setChild(!left, this);
return node;
} else {
LayoutTreeNode oldParent = relativeChild.getParent();
node.setChild(left, child);
node.setChild(!left, relativeChild);
if (oldParent == null) {
//It was the root. Return a new root.
return node;
}
oldParent.replaceChild(relativeChild, node);
return this;
}
}
/**
* Returns true if this tree can be compressed and expanded.
* @return true if springy
*/
public boolean isCompressible() {
//Added for bug 19524
return part.isCompressible();
}
/**
* Returns true if this tree has visible parts otherwise returns false.
*/
public boolean isVisible() {
return !(part instanceof PartPlaceholder);
}
/**
* Recompute the ratios in this tree.
*/
public void recomputeRatio() {
}
/**
* Find a child in the tree and remove it and its parent.
* The other child of its parent is placed on the parent's parent.
* Returns the new root of the tree.
*/
public LayoutTree remove(LayoutPart child) {
LayoutTree tree = find(child);
if (tree == null) {
return this;
}
LayoutTreeNode oldParent = tree.getParent();
if (oldParent == null) {
//It was the root and the only child of this tree
return null;
}
if (oldParent.getParent() == null) {
return oldParent.remove(tree);
}
oldParent.remove(tree);
return this;
}
/**
* Sets the bounds of this node. If the bounds have changed or any children have
* changed then the children will be recursively layed out. This implementation
* filters out redundant calls and delegates to doSetBounds to layout the children.
* Subclasses should overload doSetBounds to lay out their children.
*
* @param bounds new bounds of the tree
*/
public final void setBounds(Rectangle bounds) {
if (!bounds.equals(currentBounds) || forceLayout) {
currentBounds = Geometry.copy(bounds);
doSetBounds(currentBounds);
forceLayout = false;
}
}
/**
* Resize the parts on this tree to fit in <code>bounds</code>.
*/
protected void doSetBounds(Rectangle bounds) {
part.setBounds(bounds);
}
/**
* Set the parent of this tree.
*/
void setParent(LayoutTreeNode parent) {
this.parent = parent;
}
/**
* Set the part of this leaf
*/
void setPart(LayoutPart part) {
this.part = part;
flushCache();
}
/**
* Returns a string representation of this object.
*/
public String toString() {
return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$
}
/**
* Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the
* LayoutParts that are being arranged by the LayoutTree.
*
* @param parent
* @since 3.1
*/
public void createControl(Composite parent) {
}
/**
* 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.
* <p>
* This is only intended for use by test suites.
* </p>
*
* @param buf
*/
public void describeLayout(StringBuffer buf) {
part.describeLayout(buf);
}
/**
* This is a shorthand method that checks if the tree contains the
* given size flag. For example, hasSizeFlag(false, SWT.MIN) returns
* true iff the receiver enforces a minimum height, or
* hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to
* know its height when computing its preferred width.
*
* @param vertical
* @return
* @since 3.1
*/
public final boolean hasSizeFlag(boolean width, int flag) {
return (getSizeFlags(width) & flag) != 0;
}
}