001: /*******************************************************************************
002: * Copyright (c) 2004, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.layout;
011:
012: import java.util.List;
013:
014: import org.eclipse.jface.util.Geometry;
015: import org.eclipse.swt.SWT;
016: import org.eclipse.swt.graphics.Point;
017: import org.eclipse.swt.graphics.Rectangle;
018: import org.eclipse.swt.widgets.Button;
019: import org.eclipse.swt.widgets.Combo;
020: import org.eclipse.swt.widgets.Composite;
021: import org.eclipse.swt.widgets.Control;
022: import org.eclipse.swt.widgets.Label;
023: import org.eclipse.swt.widgets.ProgressBar;
024: import org.eclipse.swt.widgets.Sash;
025: import org.eclipse.swt.widgets.Scale;
026: import org.eclipse.swt.widgets.Slider;
027: import org.eclipse.swt.widgets.Text;
028: import org.eclipse.swt.widgets.ToolBar;
029: import org.eclipse.swt.widgets.Tree;
030:
031: /**
032: * Caches the preferred size of an SWT control
033: *
034: * @since 3.0
035: */
036: public class SizeCache {
037: private Control control;
038:
039: private Point preferredSize;
040:
041: private Point cachedWidth;
042:
043: private Point cachedHeight;
044:
045: /**
046: * True iff we should recursively flush all children on the next layout
047: */
048: private boolean flushChildren;
049:
050: /**
051: * True iff changing the height hint does not affect the preferred width and changing
052: * the width hint does not change the preferred height
053: */
054: private boolean independentDimensions = false;
055:
056: /**
057: * True iff the preferred height for any hint larger than the preferred width will not
058: * change the preferred height.
059: */
060: private boolean preferredWidthOrLargerIsMinimumHeight = false;
061:
062: // HACK: these values estimate how much to subtract from the width and height
063: // hints that get passed into computeSize, in order to produce a result
064: // that is exactly the desired size. To be removed once bug 46112 is fixed (note:
065: // bug 46112 is currently flagged as a duplicate, but there is still no workaround).
066: private int widthAdjustment = 0;
067:
068: private int heightAdjustment = 0;
069:
070: // END OF HACK
071:
072: public SizeCache() {
073: this (null);
074: }
075:
076: /**
077: * Creates a cache for size computations on the given control
078: *
079: * @param control the control for which sizes will be calculated,
080: * or null to always return (0,0)
081: */
082: public SizeCache(Control control) {
083: setControl(control);
084: }
085:
086: /**
087: * Sets the control whose size is being cached. Does nothing (will not
088: * even flush the cache) if this is the same control as last time.
089: *
090: * @param newControl the control whose size is being cached, or null to always return (0,0)
091: */
092: public void setControl(Control newControl) {
093: if (newControl != control) {
094: control = newControl;
095: if (control == null) {
096: independentDimensions = true;
097: preferredWidthOrLargerIsMinimumHeight = false;
098: widthAdjustment = 0;
099: heightAdjustment = 0;
100: } else {
101: independentDimensions = independentLengthAndWidth(control);
102: preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control);
103: computeHintOffset(control);
104: flush();
105: }
106: }
107: }
108:
109: /**
110: * Returns the control whose size is being cached
111: *
112: * @return the control whose size is being cached, or null if this cache always returns (0,0)
113: */
114: public Control getControl() {
115: return control;
116: }
117:
118: /**
119: * Flush the cache (should be called if the control's contents may have changed since the
120: * last query)
121: */
122: public void flush() {
123: flush(true);
124: }
125:
126: public void flush(boolean recursive) {
127: preferredSize = null;
128: cachedWidth = null;
129: cachedHeight = null;
130: this .flushChildren = recursive;
131: }
132:
133: private Point getPreferredSize() {
134: if (preferredSize == null) {
135: preferredSize = computeSize(control, SWT.DEFAULT,
136: SWT.DEFAULT);
137: }
138:
139: return preferredSize;
140: }
141:
142: /**
143: * Computes the preferred size of the control.
144: *
145: * @param widthHint the known width of the control (pixels) or SWT.DEFAULT if unknown
146: * @param heightHint the known height of the control (pixels) or SWT.DEFAULT if unknown
147: * @return the preferred size of the control
148: */
149: public Point computeSize(int widthHint, int heightHint) {
150: if (control == null) {
151: return new Point(0, 0);
152: }
153:
154: // If both dimensions were supplied in the input, return them verbatim
155: if (widthHint != SWT.DEFAULT && heightHint != SWT.DEFAULT) {
156: return new Point(widthHint, heightHint);
157: }
158:
159: // No hints given -- find the preferred size
160: if (widthHint == SWT.DEFAULT && heightHint == SWT.DEFAULT) {
161: return Geometry.copy(getPreferredSize());
162: }
163:
164: // If the length and width are independent, compute the preferred size
165: // and adjust whatever dimension was supplied in the input
166: if (independentDimensions) {
167: Point result = Geometry.copy(getPreferredSize());
168:
169: if (widthHint != SWT.DEFAULT) {
170: result.x = widthHint;
171: }
172:
173: if (heightHint != SWT.DEFAULT) {
174: result.y = heightHint;
175: }
176:
177: return result;
178: }
179:
180: // Computing a height
181: if (heightHint == SWT.DEFAULT) {
182: // If we know the control's preferred size
183: if (preferredSize != null) {
184: // If the given width is the preferred width, then return the preferred size
185: if (widthHint == preferredSize.x) {
186: return Geometry.copy(preferredSize);
187: }
188: }
189:
190: // If we have a cached height measurement
191: if (cachedHeight != null) {
192: // If this was measured with the same width hint
193: if (cachedHeight.x == widthHint) {
194: return Geometry.copy(cachedHeight);
195: }
196: }
197:
198: // If this is a control where any hint larger than the
199: // preferred width results in the minimum height, determine if
200: // we can compute the result based on the preferred height
201: if (preferredWidthOrLargerIsMinimumHeight) {
202: // Computed the preferred size (if we don't already know it)
203: getPreferredSize();
204:
205: // If the width hint is larger than the preferred width, then
206: // we can compute the result from the preferred width
207: if (widthHint >= preferredSize.x) {
208: Point result = Geometry.copy(preferredSize);
209: result.x = widthHint;
210: return result;
211: }
212: }
213:
214: // Else we can't find an existing size in the cache, so recompute
215: // it from scratch.
216: cachedHeight = computeSize(control, widthHint, heightHint);
217:
218: return Geometry.copy(cachedHeight);
219: }
220:
221: // Computing a width
222: if (widthHint == SWT.DEFAULT) {
223: // If we know the control's preferred size
224: if (preferredSize != null) {
225: // If the given height is the preferred height, then return the preferred size
226: if (heightHint == preferredSize.y) {
227: return Geometry.copy(preferredSize);
228: }
229: }
230:
231: // If we have a cached width measurement
232: if (cachedWidth != null) {
233: // If this was measured with the same height hint
234: if (cachedWidth.y == heightHint) {
235: return Geometry.copy(cachedWidth);
236: }
237: }
238:
239: cachedWidth = computeSize(control, widthHint, heightHint);
240:
241: return Geometry.copy(cachedWidth);
242: }
243:
244: return computeSize(control, widthHint, heightHint);
245: }
246:
247: /**
248: * Compute the control's size, and ensure that non-default hints are returned verbatim
249: * (this tries to compensate for SWT's hints, which aren't really the outer width of the
250: * control).
251: *
252: * @param control
253: * @param widthHint
254: * @param heightHint
255: * @return
256: */
257: private Point computeSize(Control control, int widthHint,
258: int heightHint) {
259: int adjustedWidthHint = widthHint == SWT.DEFAULT ? SWT.DEFAULT
260: : Math.max(0, widthHint - widthAdjustment);
261: int adjustedHeightHint = heightHint == SWT.DEFAULT ? SWT.DEFAULT
262: : Math.max(0, heightHint - heightAdjustment);
263:
264: Point result = control.computeSize(adjustedWidthHint,
265: adjustedHeightHint, flushChildren);
266: flushChildren = false;
267:
268: // If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then
269: // manually adjust the result to ensure that a non-default hint will return that result verbatim.
270:
271: if (widthHint != SWT.DEFAULT) {
272: result.x = widthHint;
273: }
274:
275: if (heightHint != SWT.DEFAULT) {
276: result.y = heightHint;
277: }
278:
279: return result;
280: }
281:
282: /**
283: * Returns true if the preferred length of the given control is
284: * independent of the width and visa-versa. If this returns true,
285: * then changing the widthHint argument to control.computeSize will
286: * never change the resulting height and changing the heightHint
287: * will never change the resulting width. Returns false if unknown.
288: * <p>
289: * This information can be used to improve caching. Incorrectly returning
290: * a value of false may decrease performance, but incorrectly returning
291: * a value of true will generate incorrect layouts... so always return
292: * false if unsure.
293: * </p>
294: *
295: * @param control
296: * @return
297: */
298: static boolean independentLengthAndWidth(Control control) {
299: if (control == null) {
300: return true;
301: }
302:
303: if (control instanceof Button || control instanceof ProgressBar
304: || control instanceof Sash || control instanceof Scale
305: || control instanceof Slider || control instanceof List
306: || control instanceof Combo || control instanceof Tree) {
307: return true;
308: }
309:
310: if (control instanceof Label || control instanceof Text) {
311: return (control.getStyle() & SWT.WRAP) == 0;
312: }
313:
314: // Unless we're certain that the control has this property, we should
315: // return false.
316:
317: return false;
318: }
319:
320: /**
321: * Try to figure out how much we need to subtract from the hints that we
322: * pass into the given control's computeSize(...) method. This tries to
323: * compensate for bug 46112. To be removed once SWT provides an "official"
324: * way to compute one dimension of a control's size given the other known
325: * dimension.
326: *
327: * @param control
328: */
329: private void computeHintOffset(Control control) {
330: if (control instanceof Composite) {
331: // For composites, subtract off the trim size
332: Composite composite = (Composite) control;
333: Rectangle trim = composite.computeTrim(0, 0, 0, 0);
334:
335: widthAdjustment = trim.width;
336: heightAdjustment = trim.height;
337: } else {
338: // For non-composites, subtract off 2 * the border size
339: widthAdjustment = control.getBorderWidth() * 2;
340: heightAdjustment = widthAdjustment;
341: }
342: }
343:
344: /**
345: * Returns true only if the control will return a constant height for any
346: * width hint larger than the preferred width. Returns false if there is
347: * any situation in which the control does not have this property.
348: *
349: * <p>
350: * Note: this method is only important for wrapping controls, and it can
351: * safely return false for anything else. AFAIK, all SWT controls have this
352: * property, but to be safe they will only be added to the list once the
353: * property has been confirmed.
354: * </p>
355: *
356: * @param control
357: * @return
358: */
359: private static boolean isPreferredWidthMaximum(Control control) {
360: return (control instanceof ToolBar
361: //|| control instanceof CoolBar
362: || control instanceof Label);
363: }
364:
365: }
|