| /******************************************************************************* |
| * 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); |
| } |
| |
| } |