001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 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.forms.widgets;
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.Layout;
024: import org.eclipse.swt.widgets.ProgressBar;
025: import org.eclipse.swt.widgets.Sash;
026: import org.eclipse.swt.widgets.Scale;
027: import org.eclipse.swt.widgets.Scrollable;
028: import org.eclipse.swt.widgets.Slider;
029: import org.eclipse.swt.widgets.Text;
030: import org.eclipse.swt.widgets.ToolBar;
031: import org.eclipse.swt.widgets.Tree;
032: import org.eclipse.ui.internal.forms.widgets.FormUtil;
033:
034: /**
035: * Caches the preferred size of an SWT control
036: *
037: * @since 3.0
038: */
039: public class SizeCache {
040: private Control control;
041:
042: private Point preferredSize;
043:
044: private int cachedWidthQuery;
045: private int cachedWidthResult;
046:
047: private int cachedHeightQuery;
048: private int cachedHeightResult;
049:
050: private int minimumWidth;
051: private int heightAtMinimumWidth = -1;
052: private int maximumWidth;
053:
054: /**
055: * True iff we should recursively flush all children on the next layout
056: */
057: private boolean flushChildren;
058:
059: /**
060: * True iff changing the height hint does not affect the preferred width and changing
061: * the width hint does not change the preferred height
062: */
063: private boolean independentDimensions = false;
064:
065: /**
066: * True iff the preferred height for any hint larger than the preferred width will not
067: * change the preferred height.
068: */
069: private boolean preferredWidthOrLargerIsMinimumHeight = false;
070:
071: // HACK: these values estimate how much to subtract from the width and height
072: // hints that get passed into computeSize, in order to produce a result
073: // that is exactly the desired size. To be removed once bug 46112 is fixed (note:
074: // bug 46112 is currently flagged as a duplicate, but there is still no workaround).
075: private int widthAdjustment = 0;
076:
077: private int heightAdjustment = 0;
078:
079: private int minimumHeight;
080:
081: private int widthAtMinimumHeight = -1;
082:
083: // If the layout is dirty, this is the size of the control at the time its
084: // layout was dirtied. null if the layout is not dirty.
085: private Point dirtySize = null;
086:
087: // END OF HACK
088:
089: public SizeCache() {
090: this (null);
091: }
092:
093: /**
094: * Creates a cache for size computations on the given control
095: *
096: * @param control the control for which sizes will be calculated,
097: * or null to always return (0,0)
098: */
099: public SizeCache(Control control) {
100: setControl(control);
101: }
102:
103: /**
104: * Sets the control whose size is being cached. Does nothing (will not
105: * even flush the cache) if this is the same control as last time.
106: *
107: * @param newControl the control whose size is being cached, or null to always return (0,0)
108: */
109: public void setControl(Control newControl) {
110: if (newControl != control) {
111: control = newControl;
112: if (control == null) {
113: independentDimensions = true;
114: preferredWidthOrLargerIsMinimumHeight = false;
115: widthAdjustment = 0;
116: heightAdjustment = 0;
117: } else {
118: independentDimensions = independentLengthAndWidth(control);
119: preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control);
120: computeHintOffset(control);
121: flush();
122: }
123: }
124: }
125:
126: /**
127: * Returns the control whose size is being cached
128: *
129: * @return the control whose size is being cached, or null if this cache always returns (0,0)
130: */
131: public Control getControl() {
132: return control;
133: }
134:
135: /**
136: * Flush the cache (should be called if the control's contents may have changed since the
137: * last query)
138: */
139: public void flush() {
140: flush(true);
141: }
142:
143: public void flush(boolean recursive) {
144: preferredSize = null;
145: cachedWidthQuery = -1;
146: cachedWidthResult = -1;
147: cachedHeightQuery = -1;
148: cachedHeightResult = -1;
149: minimumWidth = -1;
150: maximumWidth = -1;
151: minimumHeight = -1;
152: heightAtMinimumWidth = -1;
153: widthAtMinimumHeight = -1;
154:
155: if (recursive || dirtySize != null) {
156: if (control == null || control.isDisposed()) {
157: dirtySize = new Point(0, 0);
158: control = null;
159: } else {
160: dirtySize = control.getSize();
161: }
162: }
163:
164: this .flushChildren = this .flushChildren || recursive;
165: }
166:
167: private Point getPreferredSize() {
168: if (preferredSize == null) {
169: preferredSize = controlComputeSize(SWT.DEFAULT, SWT.DEFAULT);
170: }
171:
172: return preferredSize;
173: }
174:
175: /**
176: * Computes the preferred size of the control.
177: *
178: * @param widthHint the known width of the control (pixels) or SWT.DEFAULT if unknown
179: * @param heightHint the known height of the control (pixels) or SWT.DEFAULT if unknown
180: * @return the preferred size of the control
181: */
182: public Point computeSize(int widthHint, int heightHint) {
183: if (control == null || control.isDisposed()) {
184: return new Point(0, 0);
185: }
186:
187: // If we're asking for a result smaller than the minimum width
188: int minWidth = computeMinimumWidth();
189:
190: if (widthHint != SWT.DEFAULT
191: && widthHint + widthAdjustment < minWidth) {
192: if (heightHint == SWT.DEFAULT) {
193: return new Point(minWidth,
194: computeHeightAtMinimumWidth());
195: }
196:
197: widthHint = minWidth - widthAdjustment;
198: }
199:
200: // If we're asking for a result smaller than the minimum height
201: int minHeight = computeMinimumHeight();
202:
203: if (heightHint != SWT.DEFAULT
204: && heightHint + heightAdjustment < minHeight) {
205: if (widthHint == SWT.DEFAULT) {
206: return new Point(computeWidthAtMinimumHeight(),
207: minHeight);
208: }
209:
210: heightHint = minHeight - heightAdjustment;
211: }
212:
213: // If both dimensions were supplied in the input, compute the trivial result
214: if (widthHint != SWT.DEFAULT && heightHint != SWT.DEFAULT) {
215: return new Point(widthHint + widthAdjustment, heightHint
216: + heightAdjustment);
217: }
218:
219: // No hints given -- find the preferred size
220: if (widthHint == SWT.DEFAULT && heightHint == SWT.DEFAULT) {
221: return Geometry.copy(getPreferredSize());
222: }
223:
224: // If the length and width are independent, compute the preferred size
225: // and adjust whatever dimension was supplied in the input
226: if (independentDimensions) {
227: Point result = Geometry.copy(getPreferredSize());
228:
229: if (widthHint != SWT.DEFAULT) {
230: result.x = widthHint + widthAdjustment;
231: }
232:
233: if (heightHint != SWT.DEFAULT) {
234: result.y = heightHint + heightAdjustment;
235: }
236:
237: return result;
238: }
239:
240: // Computing a height
241: if (heightHint == SWT.DEFAULT) {
242: // If we know the control's preferred size
243: if (preferredSize != null) {
244: // If the given width is the preferred width, then return the preferred size
245: if (widthHint + widthAdjustment == preferredSize.x) {
246: return Geometry.copy(preferredSize);
247: }
248: }
249:
250: // If we have a cached height measurement
251: if (cachedHeightQuery != -1) {
252: // If this was measured with the same width hint
253: if (cachedHeightQuery == widthHint) {
254: return new Point(widthHint + widthAdjustment,
255: cachedHeightResult);
256: }
257: }
258:
259: // If this is a control where any hint larger than the
260: // preferred width results in the minimum height, determine if
261: // we can compute the result based on the preferred height
262: if (preferredWidthOrLargerIsMinimumHeight) {
263: // Computed the preferred size (if we don't already know it)
264: getPreferredSize();
265:
266: // If the width hint is larger than the preferred width, then
267: // we can compute the result from the preferred width
268: if (widthHint + widthAdjustment >= preferredSize.x) {
269: return new Point(widthHint + widthAdjustment,
270: preferredSize.y);
271: }
272: }
273:
274: // Else we can't find an existing size in the cache, so recompute
275: // it from scratch.
276: Point newHeight = controlComputeSize(widthHint
277: - widthAdjustment, SWT.DEFAULT);
278:
279: cachedHeightQuery = heightHint;
280: cachedHeightResult = newHeight.y;
281:
282: return newHeight;
283: }
284:
285: // Computing a width
286: if (widthHint == SWT.DEFAULT) {
287: // If we know the control's preferred size
288: if (preferredSize != null) {
289: // If the given height is the preferred height, then return the preferred size
290: if (heightHint + heightAdjustment == preferredSize.y) {
291: return Geometry.copy(preferredSize);
292: }
293: }
294:
295: // If we have a cached width measurement with the same height hint
296: if (cachedWidthQuery == heightHint) {
297: return new Point(cachedWidthResult, heightHint
298: + heightAdjustment);
299: }
300:
301: Point widthResult = controlComputeSize(SWT.DEFAULT,
302: heightHint - heightAdjustment);
303:
304: cachedWidthQuery = heightHint;
305: cachedWidthResult = widthResult.x;
306:
307: return widthResult;
308: }
309:
310: return controlComputeSize(widthHint, heightHint);
311: }
312:
313: /**
314: * Compute the control's size, and ensure that non-default hints are returned verbatim
315: * (this tries to compensate for SWT's hints, which aren't really the outer width of the
316: * control).
317: *
318: * @param widthHint the horizontal hint
319: * @param heightHint the vertical hint
320: * @return the control's size
321: */
322: public Point computeAdjustedSize(int widthHint, int heightHint) {
323: int adjustedWidthHint = widthHint == SWT.DEFAULT ? SWT.DEFAULT
324: : Math.max(0, widthHint - widthAdjustment);
325: int adjustedHeightHint = heightHint == SWT.DEFAULT ? SWT.DEFAULT
326: : Math.max(0, heightHint - heightAdjustment);
327:
328: Point result = computeSize(adjustedWidthHint,
329: adjustedHeightHint);
330:
331: // If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then
332: // manually adjust the result to ensure that a non-default hint will return that result verbatim.
333:
334: return result;
335: }
336:
337: /**
338: * Returns true if the preferred length of the given control is
339: * independent of the width and visa-versa. If this returns true,
340: * then changing the widthHint argument to control.computeSize will
341: * never change the resulting height and changing the heightHint
342: * will never change the resulting width. Returns false if unknown.
343: * <p>
344: * This information can be used to improve caching. Incorrectly returning
345: * a value of false may decrease performance, but incorrectly returning
346: * a value of true will generate incorrect layouts... so always return
347: * false if unsure.
348: * </p>
349: *
350: * @param control
351: * @return
352: */
353: static boolean independentLengthAndWidth(Control control) {
354: if (control == null || control.isDisposed()) {
355: return true;
356: }
357:
358: if (control instanceof Button || control instanceof ProgressBar
359: || control instanceof Sash || control instanceof Scale
360: || control instanceof Slider || control instanceof List
361: || control instanceof Combo || control instanceof Tree) {
362: return true;
363: }
364:
365: if (control instanceof Label || control instanceof Text) {
366: return (control.getStyle() & SWT.WRAP) == 0;
367: }
368:
369: // Unless we're certain that the control has this property, we should
370: // return false.
371:
372: return false;
373: }
374:
375: /**
376: * Try to figure out how much we need to subtract from the hints that we
377: * pass into the given control's computeSize(...) method. This tries to
378: * compensate for bug 46112. To be removed once SWT provides an "official"
379: * way to compute one dimension of a control's size given the other known
380: * dimension.
381: *
382: * @param control
383: */
384: private void computeHintOffset(Control control) {
385: if (control instanceof Scrollable) {
386: // For scrollables, subtract off the trim size
387: Scrollable scrollable = (Scrollable) control;
388: Rectangle trim = scrollable.computeTrim(0, 0, 0, 0);
389:
390: widthAdjustment = trim.width;
391: heightAdjustment = trim.height;
392: } else {
393: // For non-composites, subtract off 2 * the border size
394: widthAdjustment = control.getBorderWidth() * 2;
395: heightAdjustment = widthAdjustment;
396: }
397: }
398:
399: private Point controlComputeSize(int widthHint, int heightHint) {
400: Point result = control.computeSize(widthHint, heightHint,
401: flushChildren);
402: flushChildren = false;
403:
404: return result;
405: }
406:
407: /**
408: * Returns true only if the control will return a constant height for any
409: * width hint larger than the preferred width. Returns false if there is
410: * any situation in which the control does not have this property.
411: *
412: * <p>
413: * Note: this method is only important for wrapping controls, and it can
414: * safely return false for anything else. AFAIK, all SWT controls have this
415: * property, but to be safe they will only be added to the list once the
416: * property has been confirmed.
417: * </p>
418: *
419: * @param control
420: * @return
421: */
422: private static boolean isPreferredWidthMaximum(Control control) {
423: return (control instanceof ToolBar
424: //|| control instanceof CoolBar
425: || control instanceof Label);
426: }
427:
428: public int computeMinimumWidth() {
429: if (minimumWidth == -1) {
430: if (control instanceof Composite) {
431: Layout layout = ((Composite) control).getLayout();
432: if (layout instanceof ILayoutExtension) {
433: minimumWidth = ((ILayoutExtension) layout)
434: .computeMinimumWidth((Composite) control,
435: flushChildren);
436: flushChildren = false;
437: }
438: }
439: }
440:
441: if (minimumWidth == -1) {
442: Point minWidth = controlComputeSize(FormUtil.getWidthHint(
443: 5, control), SWT.DEFAULT);
444: minimumWidth = minWidth.x;
445: heightAtMinimumWidth = minWidth.y;
446: }
447:
448: return minimumWidth;
449: }
450:
451: public int computeMaximumWidth() {
452: if (maximumWidth == -1) {
453: if (control instanceof Composite) {
454: Layout layout = ((Composite) control).getLayout();
455: if (layout instanceof ILayoutExtension) {
456: maximumWidth = ((ILayoutExtension) layout)
457: .computeMaximumWidth((Composite) control,
458: flushChildren);
459: flushChildren = false;
460: }
461: }
462: }
463:
464: if (maximumWidth == -1) {
465: maximumWidth = getPreferredSize().x;
466: }
467:
468: return maximumWidth;
469: }
470:
471: private int computeHeightAtMinimumWidth() {
472: int minimumWidth = computeMinimumWidth();
473:
474: if (heightAtMinimumWidth == -1) {
475: heightAtMinimumWidth = controlComputeSize(minimumWidth
476: - widthAdjustment, SWT.DEFAULT).y;
477: }
478:
479: return heightAtMinimumWidth;
480: }
481:
482: private int computeWidthAtMinimumHeight() {
483: int minimumHeight = computeMinimumHeight();
484:
485: if (widthAtMinimumHeight == -1) {
486: widthAtMinimumHeight = controlComputeSize(SWT.DEFAULT,
487: minimumHeight - heightAdjustment).x;
488: }
489:
490: return widthAtMinimumHeight;
491: }
492:
493: private int computeMinimumHeight() {
494: if (minimumHeight == -1) {
495: Point sizeAtMinHeight = controlComputeSize(SWT.DEFAULT, 0);
496:
497: minimumHeight = sizeAtMinHeight.y;
498: widthAtMinimumHeight = sizeAtMinHeight.x;
499: }
500:
501: return minimumHeight;
502: }
503:
504: public Point computeMinimumSize() {
505: return new Point(computeMinimumWidth(), computeMinimumHeight());
506: }
507:
508: public void setSize(Point newSize) {
509: if (control != null) {
510: control.setSize(newSize);
511: }
512:
513: layoutIfNecessary();
514: }
515:
516: public void setSize(int width, int height) {
517: if (control != null) {
518: control.setSize(width, height);
519: }
520:
521: layoutIfNecessary();
522: }
523:
524: public void setBounds(int x, int y, int width, int height) {
525: if (control != null) {
526: control.setBounds(x, y, width, height);
527: }
528:
529: layoutIfNecessary();
530: }
531:
532: public void setBounds(Rectangle bounds) {
533: if (control != null) {
534: control.setBounds(bounds);
535: }
536:
537: layoutIfNecessary();
538: }
539:
540: public void layoutIfNecessary() {
541: if (dirtySize != null && control != null
542: && control instanceof Composite) {
543: if (control.getSize().equals(dirtySize)) {
544: ((Composite) control).layout(flushChildren);
545: flushChildren = false;
546: }
547: }
548: dirtySize = null;
549: }
550: }
|