blob: 754037022b2e202085b9850e4ad0f51b00a04a92 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 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
*******************************************************************************/
package org.eclipse.ui.internal.layout;
import java.util.List;
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.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.Tree;
/**
* Caches the preferred size of an SWT control
*
* @since 3.0
*/
public class SizeCache {
private Control control;
private Point preferredSize;
private Point cachedWidth;
private Point cachedHeight;
/**
* True iff we should recursively flush all children on the next layout
*/
private boolean flushChildren;
/**
* True iff changing the height hint does not affect the preferred width and changing
* the width hint does not change the preferred height
*/
private boolean independentDimensions = false;
/**
* True iff the preferred height for any hint larger than the preferred width will not
* change the preferred height.
*/
private boolean preferredWidthOrLargerIsMinimumHeight = false;
// HACK: these values estimate how much to subtract from the width and height
// hints that get passed into computeSize, in order to produce a result
// that is exactly the desired size. To be removed once bug 46112 is fixed (note:
// bug 46112 is currently flagged as a duplicate, but there is still no workaround).
private int widthAdjustment = 0;
private int heightAdjustment = 0;
// END OF HACK
public SizeCache() {
this(null);
}
/**
* Creates a cache for size computations on the given control
*
* @param control the control for which sizes will be calculated,
* or null to always return (0,0)
*/
public SizeCache(Control control) {
setControl(control);
}
/**
* Sets the control whose size is being cached. Does nothing (will not
* even flush the cache) if this is the same control as last time.
*
* @param newControl the control whose size is being cached, or null to always return (0,0)
*/
public void setControl(Control newControl) {
if (newControl != control) {
control = newControl;
if (control == null) {
independentDimensions = true;
preferredWidthOrLargerIsMinimumHeight = false;
widthAdjustment = 0;
heightAdjustment = 0;
} else {
independentDimensions = independentLengthAndWidth(control);
preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control);
computeHintOffset(control);
flush();
}
}
}
/**
* Returns the control whose size is being cached
*
* @return the control whose size is being cached, or null if this cache always returns (0,0)
*/
public Control getControl() {
return control;
}
/**
* Flush the cache (should be called if the control's contents may have changed since the
* last query)
*/
public void flush() {
flush(true);
}
public void flush(boolean recursive) {
preferredSize = null;
cachedWidth = null;
cachedHeight = null;
this.flushChildren = recursive;
}
private Point getPreferredSize() {
if (preferredSize == null) {
preferredSize = computeSize(control, SWT.DEFAULT, SWT.DEFAULT);
}
return preferredSize;
}
/**
* Computes the preferred size of the control.
*
* @param widthHint the known width of the control (pixels) or SWT.DEFAULT if unknown
* @param heightHint the known height of the control (pixels) or SWT.DEFAULT if unknown
* @return the preferred size of the control
*/
public Point computeSize(int widthHint, int heightHint) {
if (control == null) {
return new Point(0, 0);
}
// If both dimensions were supplied in the input, return them verbatim
if (widthHint != SWT.DEFAULT && heightHint != SWT.DEFAULT) {
return new Point(widthHint, heightHint);
}
// No hints given -- find the preferred size
if (widthHint == SWT.DEFAULT && heightHint == SWT.DEFAULT) {
return Geometry.copy(getPreferredSize());
}
// If the length and width are independent, compute the preferred size
// and adjust whatever dimension was supplied in the input
if (independentDimensions) {
Point result = Geometry.copy(getPreferredSize());
if (widthHint != SWT.DEFAULT) {
result.x = widthHint;
}
if (heightHint != SWT.DEFAULT) {
result.y = heightHint;
}
return result;
}
// Computing a height
if (heightHint == SWT.DEFAULT) {
// If we know the control's preferred size
if (preferredSize != null) {
// If the given width is the preferred width, then return the preferred size
if (widthHint == preferredSize.x) {
return Geometry.copy(preferredSize);
}
}
// If we have a cached height measurement
if (cachedHeight != null) {
// If this was measured with the same width hint
if (cachedHeight.x == widthHint) {
return Geometry.copy(cachedHeight);
}
}
// If this is a control where any hint larger than the
// preferred width results in the minimum height, determine if
// we can compute the result based on the preferred height
if (preferredWidthOrLargerIsMinimumHeight) {
// Computed the preferred size (if we don't already know it)
getPreferredSize();
// If the width hint is larger than the preferred width, then
// we can compute the result from the preferred width
if (widthHint >= preferredSize.x) {
Point result = Geometry.copy(preferredSize);
result.x = widthHint;
return result;
}
}
// Else we can't find an existing size in the cache, so recompute
// it from scratch.
cachedHeight = computeSize(control, widthHint, heightHint);
return Geometry.copy(cachedHeight);
}
// Computing a width
if (widthHint == SWT.DEFAULT) {
// If we know the control's preferred size
if (preferredSize != null) {
// If the given height is the preferred height, then return the preferred size
if (heightHint == preferredSize.y) {
return Geometry.copy(preferredSize);
}
}
// If we have a cached width measurement
if (cachedWidth != null) {
// If this was measured with the same height hint
if (cachedWidth.y == heightHint) {
return Geometry.copy(cachedWidth);
}
}
cachedWidth = computeSize(control, widthHint, heightHint);
return Geometry.copy(cachedWidth);
}
return computeSize(control, widthHint, heightHint);
}
/**
* Compute the control's size, and ensure that non-default hints are returned verbatim
* (this tries to compensate for SWT's hints, which aren't really the outer width of the
* control).
*
* @param control
* @param widthHint
* @param heightHint
* @return
*/
private Point computeSize(Control control, int widthHint, int heightHint) {
int adjustedWidthHint = widthHint == SWT.DEFAULT ? SWT.DEFAULT : Math
.max(0, widthHint - widthAdjustment);
int adjustedHeightHint = heightHint == SWT.DEFAULT ? SWT.DEFAULT : Math
.max(0, heightHint - heightAdjustment);
Point result = control.computeSize(adjustedWidthHint,
adjustedHeightHint, flushChildren);
flushChildren = false;
// If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then
// manually adjust the result to ensure that a non-default hint will return that result verbatim.
if (widthHint != SWT.DEFAULT) {
result.x = widthHint;
}
if (heightHint != SWT.DEFAULT) {
result.y = heightHint;
}
return result;
}
/**
* Returns true if the preferred length of the given control is
* independent of the width and visa-versa. If this returns true,
* then changing the widthHint argument to control.computeSize will
* never change the resulting height and changing the heightHint
* will never change the resulting width. Returns false if unknown.
* <p>
* This information can be used to improve caching. Incorrectly returning
* a value of false may decrease performance, but incorrectly returning
* a value of true will generate incorrect layouts... so always return
* false if unsure.
* </p>
*
* @param control
* @return
*/
static boolean independentLengthAndWidth(Control control) {
if (control == null) {
return true;
}
if (control instanceof Button || control instanceof ProgressBar
|| control instanceof Sash || control instanceof Scale
|| control instanceof Slider || control instanceof List
|| control instanceof Combo || control instanceof Tree) {
return true;
}
if (control instanceof Label || control instanceof Text) {
return (control.getStyle() & SWT.WRAP) == 0;
}
// Unless we're certain that the control has this property, we should
// return false.
return false;
}
/**
* Try to figure out how much we need to subtract from the hints that we
* pass into the given control's computeSize(...) method. This tries to
* compensate for bug 46112. To be removed once SWT provides an "official"
* way to compute one dimension of a control's size given the other known
* dimension.
*
* @param control
*/
private void computeHintOffset(Control control) {
if (control instanceof Composite) {
// For composites, subtract off the trim size
Composite composite = (Composite) control;
Rectangle trim = composite.computeTrim(0, 0, 0, 0);
widthAdjustment = trim.width;
heightAdjustment = trim.height;
} else {
// For non-composites, subtract off 2 * the border size
widthAdjustment = control.getBorderWidth() * 2;
heightAdjustment = widthAdjustment;
}
}
/**
* Returns true only if the control will return a constant height for any
* width hint larger than the preferred width. Returns false if there is
* any situation in which the control does not have this property.
*
* <p>
* Note: this method is only important for wrapping controls, and it can
* safely return false for anything else. AFAIK, all SWT controls have this
* property, but to be safe they will only be added to the list once the
* property has been confirmed.
* </p>
*
* @param control
* @return
*/
private static boolean isPreferredWidthMaximum(Control control) {
return (control instanceof ToolBar
//|| control instanceof CoolBar
|| control instanceof Label);
}
}