Mercurial > projects > dwt2
diff org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/SizeCache.d @ 12:bc29606a740c
Added dwt-addons in original directory structure of eclipse.org
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 14 Mar 2009 18:23:29 +0100 |
parents | |
children | dbfb303e8fb0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/SizeCache.d Sat Mar 14 18:23:29 2009 +0100 @@ -0,0 +1,539 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module org.eclipse.ui.forms.widgets.SizeCache; + +import org.eclipse.ui.forms.widgets.ILayoutExtension; + +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.List; +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.Scrollable; +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.jface.util.Geometry; +import org.eclipse.ui.internal.forms.widgets.FormUtil; + +import java.lang.all; +import java.util.List; +import java.util.Set; + +/** + * 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 bool 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 bool independentDimensions = false; + + /** + * True iff the preferred height for any hint larger than the preferred width will not + * change the preferred height. + */ + private bool 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 this() { + 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 this(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 !is control) { + control = newControl; + if (control is 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(bool recursive) { + preferredSize = null; + cachedWidthQuery = -1; + cachedWidthResult = -1; + cachedHeightQuery = -1; + cachedHeightResult = -1; + minimumWidth = -1; + maximumWidth = -1; + minimumHeight = -1; + heightAtMinimumWidth = -1; + widthAtMinimumHeight = -1; + + if (recursive || dirtySize !is null) { + if (control is null || control.isDisposed()) { + dirtySize = new Point(0,0); + control = null; + } else { + dirtySize = control.getSize(); + } + } + + this.flushChildren = this.flushChildren || recursive; + } + + private Point getPreferredSize() { + if (preferredSize is 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 is 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 !is SWT.DEFAULT && widthHint + widthAdjustment < minWidth) { + if (heightHint is 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 !is SWT.DEFAULT && heightHint + heightAdjustment < minHeight) { + if (widthHint is SWT.DEFAULT) { + return new Point(computeWidthAtMinimumHeight(), minHeight); + } + + heightHint = minHeight - heightAdjustment; + } + + // If both dimensions were supplied in the input, compute the trivial result + if (widthHint !is SWT.DEFAULT && heightHint !is SWT.DEFAULT) { + return new Point(widthHint + widthAdjustment, heightHint + heightAdjustment); + } + + // No hints given -- find the preferred size + if (widthHint is SWT.DEFAULT && heightHint is 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 !is SWT.DEFAULT) { + result.x = widthHint + widthAdjustment; + } + + if (heightHint !is SWT.DEFAULT) { + result.y = heightHint + heightAdjustment; + } + + return result; + } + + // Computing a height + if (heightHint is SWT.DEFAULT) { + // If we know the control's preferred size + if (preferredSize !is null) { + // If the given width is the preferred width, then return the preferred size + if (widthHint + widthAdjustment is preferredSize.x) { + return Geometry.copy(preferredSize); + } + } + + // If we have a cached height measurement + if (cachedHeightQuery !is -1) { + // If this was measured with the same width hint + if (cachedHeightQuery is 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 - widthAdjustment, SWT.DEFAULT); + + cachedHeightQuery = heightHint; + cachedHeightResult = newHeight.y; + + return newHeight; + } + + // Computing a width + if (widthHint is SWT.DEFAULT) { + // If we know the control's preferred size + if (preferredSize !is null) { + // If the given height is the preferred height, then return the preferred size + if (heightHint + heightAdjustment is preferredSize.y) { + return Geometry.copy(preferredSize); + } + } + + // If we have a cached width measurement with the same height hint + if (cachedWidthQuery is heightHint) { + return new Point(cachedWidthResult, heightHint + heightAdjustment); + } + + Point widthResult = controlComputeSize(SWT.DEFAULT, heightHint - heightAdjustment); + + 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 is SWT.DEFAULT ? SWT.DEFAULT : Math + .max(0, widthHint - widthAdjustment); + int adjustedHeightHint = heightHint is 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 bool independentLengthAndWidth(Control control) { + if (control is null || control.isDisposed()) { + return true; + } + + if (null !is cast(Button)control || null !is cast(ProgressBar)control + || null !is cast(Sash)control || null !is cast(Scale)control + || null !is cast(Slider)control || null !is cast(List)control + || null !is cast(Combo)control || null !is cast(Tree)control ) { + return true; + } + + if (null !is cast(Label)control || null !is cast(Text)control ) { + return (control.getStyle() & SWT.WRAP) is 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 (null !is cast(Scrollable)control ) { + // For scrollables, subtract off the trim size + Scrollable scrollable = cast(Scrollable) control; + Rectangle trim = scrollable.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 bool isPreferredWidthMaximum(Control control) { + return (null !is cast(ToolBar)control + //|| control instanceof CoolBar + || null !is cast(Label)control ); + } + + public int computeMinimumWidth() { + if (minimumWidth is -1) { + if (null !is cast(Composite)control ) { + Layout layout = (cast(Composite)control).getLayout(); + if (null !is cast(ILayoutExtension)layout ) { + minimumWidth = (cast(ILayoutExtension)layout).computeMinimumWidth(cast(Composite)control, flushChildren); + flushChildren = false; + } + } + } + + if (minimumWidth is -1) { + Point minWidth = controlComputeSize(FormUtil.getWidthHint(5, control), SWT.DEFAULT); + minimumWidth = minWidth.x; + heightAtMinimumWidth = minWidth.y; + } + + return minimumWidth; + } + + public int computeMaximumWidth() { + if (maximumWidth is -1) { + if (null !is cast(Composite)control ) { + Layout layout = (cast(Composite)control).getLayout(); + if (null !is cast(ILayoutExtension)layout ) { + maximumWidth = (cast(ILayoutExtension)layout).computeMaximumWidth(cast(Composite)control, flushChildren); + flushChildren = false; + } + } + } + + if (maximumWidth is -1) { + maximumWidth = getPreferredSize().x; + } + + return maximumWidth; + } + + private int computeHeightAtMinimumWidth() { + int minimumWidth = computeMinimumWidth(); + + if (heightAtMinimumWidth is -1) { + heightAtMinimumWidth = controlComputeSize(minimumWidth - widthAdjustment, SWT.DEFAULT).y; + } + + return heightAtMinimumWidth; + } + + private int computeWidthAtMinimumHeight() { + int minimumHeight = computeMinimumHeight(); + + if (widthAtMinimumHeight is -1) { + widthAtMinimumHeight = controlComputeSize(SWT.DEFAULT, minimumHeight - heightAdjustment).x; + } + + return widthAtMinimumHeight; + } + + private int computeMinimumHeight() { + if (minimumHeight is -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 !is null) { + control.setSize(newSize); + } + + layoutIfNecessary(); + } + + public void setSize(int width, int height) { + if (control !is null) { + control.setSize(width, height); + } + + layoutIfNecessary(); + } + + public void setBounds(int x, int y, int width, int height) { + if (control !is null) { + control.setBounds(x, y, width, height); + } + + layoutIfNecessary(); + } + + public void setBounds(Rectangle bounds) { + if (control !is null) { + control.setBounds(bounds); + } + + layoutIfNecessary(); + } + + public void layoutIfNecessary() { + if (dirtySize !is null && control !is null && null !is cast(Composite)control ) { + if (control.getSize().opEquals(dirtySize)) { + (cast(Composite)control).layout(flushChildren); + flushChildren = false; + } + } + dirtySize = null; + } +}