| /******************************************************************************* |
| * Copyright (c) 2004, 2005 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.forms.widgets; |
| |
| 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.Layout; |
| 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; |
| import org.eclipse.ui.internal.forms.widgets.FormUtil; |
| |
| /** |
| * Caches the preferred size of an SWT control |
| * |
| * @since 3.0 |
| */ |
| public class SizeCache { |
| private Control control; |
| |
| private Point preferredSize; |
| |
| private int cachedWidthQuery; |
| private int cachedWidthResult; |
| |
| private int cachedHeightQuery; |
| private int cachedHeightResult; |
| |
| private int minimumWidth; |
| private int heightAtMinimumWidth = -1; |
| private int maximumWidth; |
| |
| /** |
| * 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; |
| |
| private int minimumHeight; |
| |
| private int widthAtMinimumHeight = -1; |
| |
| // If the layout is dirty, this is the size of the control at the time its |
| // layout was dirtied. null if the layout is not dirty. |
| private Point dirtySize = null; |
| |
| |
| // 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; |
| cachedWidthQuery = -1; |
| cachedWidthResult = -1; |
| cachedHeightQuery = -1; |
| cachedHeightResult = -1; |
| minimumWidth = -1; |
| maximumWidth = -1; |
| minimumHeight = -1; |
| heightAtMinimumWidth = -1; |
| widthAtMinimumHeight = -1; |
| |
| if (recursive || dirtySize != null) { |
| if (control == null || control.isDisposed()) { |
| dirtySize = new Point(0,0); |
| control = null; |
| } else { |
| dirtySize = control.getSize(); |
| } |
| } |
| |
| this.flushChildren = this.flushChildren || recursive; |
| } |
| |
| private Point getPreferredSize() { |
| if (preferredSize == null) { |
| preferredSize = controlComputeSize(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 || control.isDisposed()) { |
| return new Point(0, 0); |
| } |
| |
| // If we're asking for a result smaller than the minimum width |
| int minWidth = computeMinimumWidth(); |
| |
| if (widthHint != SWT.DEFAULT && widthHint + widthAdjustment < minWidth) { |
| if (heightHint == SWT.DEFAULT) { |
| return new Point(minWidth, computeHeightAtMinimumWidth()); |
| } |
| |
| widthHint = minWidth - widthAdjustment; |
| } |
| |
| // If we're asking for a result smaller than the minimum height |
| int minHeight = computeMinimumHeight(); |
| |
| if (heightHint != SWT.DEFAULT && heightHint + heightAdjustment < minHeight) { |
| if (widthHint == SWT.DEFAULT) { |
| return new Point(computeWidthAtMinimumHeight(), minHeight); |
| } |
| |
| heightHint = minHeight - heightAdjustment; |
| } |
| |
| // If both dimensions were supplied in the input, compute the trivial result |
| if (widthHint != SWT.DEFAULT && heightHint != SWT.DEFAULT) { |
| return new Point(widthHint + widthAdjustment, heightHint + heightAdjustment); |
| } |
| |
| // 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 + widthAdjustment; |
| } |
| |
| if (heightHint != SWT.DEFAULT) { |
| result.y = heightHint + heightAdjustment; |
| } |
| |
| 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 + widthAdjustment == preferredSize.x) { |
| return Geometry.copy(preferredSize); |
| } |
| } |
| |
| // If we have a cached height measurement |
| if (cachedHeightQuery != -1) { |
| // If this was measured with the same width hint |
| if (cachedHeightQuery == widthHint) { |
| return new Point(widthHint + widthAdjustment, cachedHeightResult); |
| } |
| } |
| |
| // 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 + widthAdjustment >= preferredSize.x) { |
| return new Point(widthHint + widthAdjustment, preferredSize.y); |
| } |
| } |
| |
| // Else we can't find an existing size in the cache, so recompute |
| // it from scratch. |
| Point newHeight = controlComputeSize(widthHint, heightHint); |
| |
| cachedHeightQuery = heightHint; |
| cachedHeightResult = newHeight.y; |
| |
| return newHeight; |
| } |
| |
| // 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 + heightAdjustment == preferredSize.y) { |
| return Geometry.copy(preferredSize); |
| } |
| } |
| |
| // If we have a cached width measurement with the same height hint |
| if (cachedWidthQuery == heightHint) { |
| return new Point(cachedWidthResult, heightHint + heightAdjustment); |
| } |
| |
| Point widthResult = controlComputeSize(widthHint, heightHint); |
| |
| cachedWidthQuery = heightHint; |
| cachedWidthResult = widthResult.x; |
| |
| return widthResult; |
| } |
| |
| return controlComputeSize(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 widthHint the horizontal hint |
| * @param heightHint the vertical hint |
| * @return the control's size |
| */ |
| public Point computeAdjustedSize(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 = computeSize(adjustedWidthHint, adjustedHeightHint); |
| |
| // 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. |
| |
| 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 || control.isDisposed()) { |
| 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; |
| } |
| } |
| |
| private Point controlComputeSize(int widthHint, int heightHint) { |
| Point result = control.computeSize(widthHint, heightHint, flushChildren); |
| flushChildren = false; |
| |
| return result; |
| } |
| |
| /** |
| * 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); |
| } |
| |
| public int computeMinimumWidth() { |
| if (minimumWidth == -1) { |
| if (control instanceof Composite) { |
| Layout layout = ((Composite)control).getLayout(); |
| if (layout instanceof ILayoutExtension) { |
| minimumWidth = ((ILayoutExtension)layout).computeMinimumWidth((Composite)control, flushChildren); |
| flushChildren = false; |
| } |
| } |
| } |
| |
| if (minimumWidth == -1) { |
| Point minWidth = controlComputeSize(FormUtil.getWidthHint(5, control), SWT.DEFAULT); |
| minimumWidth = minWidth.x; |
| heightAtMinimumWidth = minWidth.y; |
| } |
| |
| return minimumWidth; |
| } |
| |
| public int computeMaximumWidth() { |
| if (maximumWidth == -1) { |
| if (control instanceof Composite) { |
| Layout layout = ((Composite)control).getLayout(); |
| if (layout instanceof ILayoutExtension) { |
| maximumWidth = ((ILayoutExtension)layout).computeMaximumWidth((Composite)control, flushChildren); |
| flushChildren = false; |
| } |
| } |
| } |
| |
| if (maximumWidth == -1) { |
| maximumWidth = getPreferredSize().x; |
| } |
| |
| return maximumWidth; |
| } |
| |
| private int computeHeightAtMinimumWidth() { |
| int minimumWidth = computeMinimumWidth(); |
| |
| if (heightAtMinimumWidth == -1) { |
| heightAtMinimumWidth = controlComputeSize(minimumWidth - widthAdjustment, SWT.DEFAULT).y; |
| } |
| |
| return heightAtMinimumWidth; |
| } |
| |
| private int computeWidthAtMinimumHeight() { |
| int minimumHeight = computeMinimumHeight(); |
| |
| if (widthAtMinimumHeight == -1) { |
| widthAtMinimumHeight = controlComputeSize(SWT.DEFAULT, minimumHeight - heightAdjustment).x; |
| } |
| |
| return widthAtMinimumHeight; |
| } |
| |
| private int computeMinimumHeight() { |
| if (minimumHeight == -1) { |
| Point sizeAtMinHeight = controlComputeSize(SWT.DEFAULT, 0); |
| |
| minimumHeight = sizeAtMinHeight.y; |
| widthAtMinimumHeight = sizeAtMinHeight.x; |
| } |
| |
| return minimumHeight; |
| } |
| |
| public Point computeMinimumSize() { |
| return new Point(computeMinimumWidth(), computeMinimumHeight()); |
| } |
| |
| public void setSize(Point newSize) { |
| if (control != null) { |
| control.setSize(newSize); |
| } |
| |
| layoutIfNecessary(); |
| } |
| |
| public void setSize(int width, int height) { |
| if (control != null) { |
| control.setSize(width, height); |
| } |
| |
| layoutIfNecessary(); |
| } |
| |
| public void setBounds(int x, int y, int width, int height) { |
| if (control != null) { |
| control.setBounds(x, y, width, height); |
| } |
| |
| layoutIfNecessary(); |
| } |
| |
| public void setBounds(Rectangle bounds) { |
| if (control != null) { |
| control.setBounds(bounds); |
| } |
| |
| layoutIfNecessary(); |
| } |
| |
| public void layoutIfNecessary() { |
| if (dirtySize != null && control != null && control instanceof Composite) { |
| if (control.getSize().equals(dirtySize)) { |
| ((Composite)control).layout(flushChildren); |
| flushChildren = false; |
| } |
| } |
| dirtySize = null; |
| } |
| } |