75
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2004, 2007 IBM Corporation and others.
|
|
3 * All rights reserved. This program and the accompanying materials
|
|
4 * are made available under the terms of the Eclipse Public License v1.0
|
|
5 * which accompanies this distribution, and is available at
|
|
6 * http://www.eclipse.org/legal/epl-v10.html
|
|
7 *
|
|
8 * Contributors:
|
|
9 * IBM Corporation - initial API and implementation
|
|
10 * Port to the D programming language:
|
|
11 * Frank Benoit <benoit@tionex.de>
|
|
12 *******************************************************************************/
|
|
13 module dwtx.ui.forms.widgets.SizeCache;
|
|
14
|
|
15 import dwtx.ui.forms.widgets.ILayoutExtension;
|
|
16
|
|
17 import dwt.DWT;
|
|
18 import dwt.graphics.Point;
|
|
19 import dwt.graphics.Rectangle;
|
|
20 import dwt.widgets.Button;
|
|
21 import dwt.widgets.Combo;
|
|
22 import dwt.widgets.Composite;
|
|
23 import dwt.widgets.Control;
|
|
24 import dwt.widgets.Label;
|
|
25 import dwt.widgets.List;
|
|
26 import dwt.widgets.Layout;
|
|
27 import dwt.widgets.ProgressBar;
|
|
28 import dwt.widgets.Sash;
|
|
29 import dwt.widgets.Scale;
|
|
30 import dwt.widgets.Scrollable;
|
|
31 import dwt.widgets.Slider;
|
|
32 import dwt.widgets.Text;
|
|
33 import dwt.widgets.ToolBar;
|
|
34 import dwt.widgets.Tree;
|
|
35 import dwtx.jface.util.Geometry;
|
|
36 import dwtx.ui.internal.forms.widgets.FormUtil;
|
|
37
|
|
38 import dwt.dwthelper.utils;
|
|
39 import tango.util.collection.ArraySeq;
|
|
40 /**
|
|
41 * Caches the preferred size of an DWT control
|
|
42 *
|
|
43 * @since 3.0
|
|
44 */
|
|
45 public class SizeCache {
|
|
46 private Control control;
|
|
47
|
|
48 private Point preferredSize;
|
|
49
|
|
50 private int cachedWidthQuery;
|
|
51 private int cachedWidthResult;
|
|
52
|
|
53 private int cachedHeightQuery;
|
|
54 private int cachedHeightResult;
|
|
55
|
|
56 private int minimumWidth;
|
|
57 private int heightAtMinimumWidth = -1;
|
|
58 private int maximumWidth;
|
|
59
|
|
60 /**
|
|
61 * True iff we should recursively flush all children on the next layout
|
|
62 */
|
|
63 private bool flushChildren;
|
|
64
|
|
65 /**
|
|
66 * True iff changing the height hint does not affect the preferred width and changing
|
|
67 * the width hint does not change the preferred height
|
|
68 */
|
|
69 private bool independentDimensions = false;
|
|
70
|
|
71 /**
|
|
72 * True iff the preferred height for any hint larger than the preferred width will not
|
|
73 * change the preferred height.
|
|
74 */
|
|
75 private bool preferredWidthOrLargerIsMinimumHeight = false;
|
|
76
|
|
77 // HACK: these values estimate how much to subtract from the width and height
|
|
78 // hints that get passed into computeSize, in order to produce a result
|
|
79 // that is exactly the desired size. To be removed once bug 46112 is fixed (note:
|
|
80 // bug 46112 is currently flagged as a duplicate, but there is still no workaround).
|
|
81 private int widthAdjustment = 0;
|
|
82
|
|
83 private int heightAdjustment = 0;
|
|
84
|
|
85 private int minimumHeight;
|
|
86
|
|
87 private int widthAtMinimumHeight = -1;
|
|
88
|
|
89 // If the layout is dirty, this is the size of the control at the time its
|
|
90 // layout was dirtied. null if the layout is not dirty.
|
|
91 private Point dirtySize = null;
|
|
92
|
|
93
|
|
94 // END OF HACK
|
|
95
|
|
96 public this() {
|
|
97 this(null);
|
|
98 }
|
|
99
|
|
100 /**
|
|
101 * Creates a cache for size computations on the given control
|
|
102 *
|
|
103 * @param control the control for which sizes will be calculated,
|
|
104 * or null to always return (0,0)
|
|
105 */
|
|
106 public this(Control control) {
|
|
107 setControl(control);
|
|
108 }
|
|
109
|
|
110 /**
|
|
111 * Sets the control whose size is being cached. Does nothing (will not
|
|
112 * even flush the cache) if this is the same control as last time.
|
|
113 *
|
|
114 * @param newControl the control whose size is being cached, or null to always return (0,0)
|
|
115 */
|
|
116 public void setControl(Control newControl) {
|
|
117 if (newControl !is control) {
|
|
118 control = newControl;
|
|
119 if (control is null) {
|
|
120 independentDimensions = true;
|
|
121 preferredWidthOrLargerIsMinimumHeight = false;
|
|
122 widthAdjustment = 0;
|
|
123 heightAdjustment = 0;
|
|
124 } else {
|
|
125 independentDimensions = independentLengthAndWidth(control);
|
|
126 preferredWidthOrLargerIsMinimumHeight = isPreferredWidthMaximum(control);
|
|
127 computeHintOffset(control);
|
|
128 flush();
|
|
129 }
|
|
130 }
|
|
131 }
|
|
132
|
|
133 /**
|
|
134 * Returns the control whose size is being cached
|
|
135 *
|
|
136 * @return the control whose size is being cached, or null if this cache always returns (0,0)
|
|
137 */
|
|
138 public Control getControl() {
|
|
139 return control;
|
|
140 }
|
|
141
|
|
142 /**
|
|
143 * Flush the cache (should be called if the control's contents may have changed since the
|
|
144 * last query)
|
|
145 */
|
|
146 public void flush() {
|
|
147 flush(true);
|
|
148 }
|
|
149
|
|
150 public void flush(bool recursive) {
|
|
151 preferredSize = null;
|
|
152 cachedWidthQuery = -1;
|
|
153 cachedWidthResult = -1;
|
|
154 cachedHeightQuery = -1;
|
|
155 cachedHeightResult = -1;
|
|
156 minimumWidth = -1;
|
|
157 maximumWidth = -1;
|
|
158 minimumHeight = -1;
|
|
159 heightAtMinimumWidth = -1;
|
|
160 widthAtMinimumHeight = -1;
|
|
161
|
|
162 if (recursive || dirtySize !is null) {
|
|
163 if (control is null || control.isDisposed()) {
|
|
164 dirtySize = new Point(0,0);
|
|
165 control = null;
|
|
166 } else {
|
|
167 dirtySize = control.getSize();
|
|
168 }
|
|
169 }
|
|
170
|
|
171 this.flushChildren = this.flushChildren || recursive;
|
|
172 }
|
|
173
|
|
174 private Point getPreferredSize() {
|
|
175 if (preferredSize is null) {
|
|
176 preferredSize = controlComputeSize(DWT.DEFAULT, DWT.DEFAULT);
|
|
177 }
|
|
178
|
|
179 return preferredSize;
|
|
180 }
|
|
181
|
|
182 /**
|
|
183 * Computes the preferred size of the control.
|
|
184 *
|
|
185 * @param widthHint the known width of the control (pixels) or DWT.DEFAULT if unknown
|
|
186 * @param heightHint the known height of the control (pixels) or DWT.DEFAULT if unknown
|
|
187 * @return the preferred size of the control
|
|
188 */
|
|
189 public Point computeSize(int widthHint, int heightHint) {
|
|
190 if (control is null || control.isDisposed()) {
|
|
191 return new Point(0, 0);
|
|
192 }
|
|
193
|
|
194 // If we're asking for a result smaller than the minimum width
|
|
195 int minWidth = computeMinimumWidth();
|
|
196
|
|
197 if (widthHint !is DWT.DEFAULT && widthHint + widthAdjustment < minWidth) {
|
|
198 if (heightHint is DWT.DEFAULT) {
|
|
199 return new Point(minWidth, computeHeightAtMinimumWidth());
|
|
200 }
|
|
201
|
|
202 widthHint = minWidth - widthAdjustment;
|
|
203 }
|
|
204
|
|
205 // If we're asking for a result smaller than the minimum height
|
|
206 int minHeight = computeMinimumHeight();
|
|
207
|
|
208 if (heightHint !is DWT.DEFAULT && heightHint + heightAdjustment < minHeight) {
|
|
209 if (widthHint is DWT.DEFAULT) {
|
|
210 return new Point(computeWidthAtMinimumHeight(), minHeight);
|
|
211 }
|
|
212
|
|
213 heightHint = minHeight - heightAdjustment;
|
|
214 }
|
|
215
|
|
216 // If both dimensions were supplied in the input, compute the trivial result
|
|
217 if (widthHint !is DWT.DEFAULT && heightHint !is DWT.DEFAULT) {
|
|
218 return new Point(widthHint + widthAdjustment, heightHint + heightAdjustment);
|
|
219 }
|
|
220
|
|
221 // No hints given -- find the preferred size
|
|
222 if (widthHint is DWT.DEFAULT && heightHint is DWT.DEFAULT) {
|
|
223 return Geometry.copy(getPreferredSize());
|
|
224 }
|
|
225
|
|
226 // If the length and width are independent, compute the preferred size
|
|
227 // and adjust whatever dimension was supplied in the input
|
|
228 if (independentDimensions) {
|
|
229 Point result = Geometry.copy(getPreferredSize());
|
|
230
|
|
231 if (widthHint !is DWT.DEFAULT) {
|
|
232 result.x = widthHint + widthAdjustment;
|
|
233 }
|
|
234
|
|
235 if (heightHint !is DWT.DEFAULT) {
|
|
236 result.y = heightHint + heightAdjustment;
|
|
237 }
|
|
238
|
|
239 return result;
|
|
240 }
|
|
241
|
|
242 // Computing a height
|
|
243 if (heightHint is DWT.DEFAULT) {
|
|
244 // If we know the control's preferred size
|
|
245 if (preferredSize !is null) {
|
|
246 // If the given width is the preferred width, then return the preferred size
|
|
247 if (widthHint + widthAdjustment is preferredSize.x) {
|
|
248 return Geometry.copy(preferredSize);
|
|
249 }
|
|
250 }
|
|
251
|
|
252 // If we have a cached height measurement
|
|
253 if (cachedHeightQuery !is -1) {
|
|
254 // If this was measured with the same width hint
|
|
255 if (cachedHeightQuery is widthHint) {
|
|
256 return new Point(widthHint + widthAdjustment, cachedHeightResult);
|
|
257 }
|
|
258 }
|
|
259
|
|
260 // If this is a control where any hint larger than the
|
|
261 // preferred width results in the minimum height, determine if
|
|
262 // we can compute the result based on the preferred height
|
|
263 if (preferredWidthOrLargerIsMinimumHeight) {
|
|
264 // Computed the preferred size (if we don't already know it)
|
|
265 getPreferredSize();
|
|
266
|
|
267 // If the width hint is larger than the preferred width, then
|
|
268 // we can compute the result from the preferred width
|
|
269 if (widthHint + widthAdjustment >= preferredSize.x) {
|
|
270 return new Point(widthHint + widthAdjustment, 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 - widthAdjustment, DWT.DEFAULT);
|
|
277
|
|
278 cachedHeightQuery = heightHint;
|
|
279 cachedHeightResult = newHeight.y;
|
|
280
|
|
281 return newHeight;
|
|
282 }
|
|
283
|
|
284 // Computing a width
|
|
285 if (widthHint is DWT.DEFAULT) {
|
|
286 // If we know the control's preferred size
|
|
287 if (preferredSize !is null) {
|
|
288 // If the given height is the preferred height, then return the preferred size
|
|
289 if (heightHint + heightAdjustment is preferredSize.y) {
|
|
290 return Geometry.copy(preferredSize);
|
|
291 }
|
|
292 }
|
|
293
|
|
294 // If we have a cached width measurement with the same height hint
|
|
295 if (cachedWidthQuery is heightHint) {
|
|
296 return new Point(cachedWidthResult, heightHint + heightAdjustment);
|
|
297 }
|
|
298
|
|
299 Point widthResult = controlComputeSize(DWT.DEFAULT, heightHint - heightAdjustment);
|
|
300
|
|
301 cachedWidthQuery = heightHint;
|
|
302 cachedWidthResult = widthResult.x;
|
|
303
|
|
304 return widthResult;
|
|
305 }
|
|
306
|
|
307 return controlComputeSize(widthHint, heightHint);
|
|
308 }
|
|
309
|
|
310 /**
|
|
311 * Compute the control's size, and ensure that non-default hints are returned verbatim
|
|
312 * (this tries to compensate for DWT's hints, which aren't really the outer width of the
|
|
313 * control).
|
|
314 *
|
|
315 * @param widthHint the horizontal hint
|
|
316 * @param heightHint the vertical hint
|
|
317 * @return the control's size
|
|
318 */
|
|
319 public Point computeAdjustedSize(int widthHint, int heightHint) {
|
|
320 int adjustedWidthHint = widthHint is DWT.DEFAULT ? DWT.DEFAULT : Math
|
|
321 .max(0, widthHint - widthAdjustment);
|
|
322 int adjustedHeightHint = heightHint is DWT.DEFAULT ? DWT.DEFAULT : Math
|
|
323 .max(0, heightHint - heightAdjustment);
|
|
324
|
|
325 Point result = computeSize(adjustedWidthHint, adjustedHeightHint);
|
|
326
|
|
327 // If the amounts we subtracted off the widthHint and heightHint didn't do the trick, then
|
|
328 // manually adjust the result to ensure that a non-default hint will return that result verbatim.
|
|
329
|
|
330 return result;
|
|
331 }
|
|
332
|
|
333 /**
|
|
334 * Returns true if the preferred length of the given control is
|
|
335 * independent of the width and visa-versa. If this returns true,
|
|
336 * then changing the widthHint argument to control.computeSize will
|
|
337 * never change the resulting height and changing the heightHint
|
|
338 * will never change the resulting width. Returns false if unknown.
|
|
339 * <p>
|
|
340 * This information can be used to improve caching. Incorrectly returning
|
|
341 * a value of false may decrease performance, but incorrectly returning
|
|
342 * a value of true will generate incorrect layouts... so always return
|
|
343 * false if unsure.
|
|
344 * </p>
|
|
345 *
|
|
346 * @param control
|
|
347 * @return
|
|
348 */
|
|
349 static bool independentLengthAndWidth(Control control) {
|
|
350 if (control is null || control.isDisposed()) {
|
|
351 return true;
|
|
352 }
|
|
353
|
|
354 if (null !is cast(Button)control || null !is cast(ProgressBar)control
|
|
355 || null !is cast(Sash)control || null !is cast(Scale)control
|
|
356 || null !is cast(Slider)control || null !is cast(List)control
|
|
357 || null !is cast(Combo)control || null !is cast(Tree)control ) {
|
|
358 return true;
|
|
359 }
|
|
360
|
|
361 if (null !is cast(Label)control || null !is cast(Text)control ) {
|
|
362 return (control.getStyle() & DWT.WRAP) is 0;
|
|
363 }
|
|
364
|
|
365 // Unless we're certain that the control has this property, we should
|
|
366 // return false.
|
|
367
|
|
368 return false;
|
|
369 }
|
|
370
|
|
371 /**
|
|
372 * Try to figure out how much we need to subtract from the hints that we
|
|
373 * pass into the given control's computeSize(...) method. This tries to
|
|
374 * compensate for bug 46112. To be removed once DWT provides an "official"
|
|
375 * way to compute one dimension of a control's size given the other known
|
|
376 * dimension.
|
|
377 *
|
|
378 * @param control
|
|
379 */
|
|
380 private void computeHintOffset(Control control) {
|
|
381 if (null !is cast(Scrollable)control ) {
|
|
382 // For scrollables, subtract off the trim size
|
|
383 Scrollable scrollable = cast(Scrollable) control;
|
|
384 Rectangle trim = scrollable.computeTrim(0, 0, 0, 0);
|
|
385
|
|
386 widthAdjustment = trim.width;
|
|
387 heightAdjustment = trim.height;
|
|
388 } else {
|
|
389 // For non-composites, subtract off 2 * the border size
|
|
390 widthAdjustment = control.getBorderWidth() * 2;
|
|
391 heightAdjustment = widthAdjustment;
|
|
392 }
|
|
393 }
|
|
394
|
|
395 private Point controlComputeSize(int widthHint, int heightHint) {
|
|
396 Point result = control.computeSize(widthHint, heightHint, flushChildren);
|
|
397 flushChildren = false;
|
|
398
|
|
399 return result;
|
|
400 }
|
|
401
|
|
402 /**
|
|
403 * Returns true only if the control will return a constant height for any
|
|
404 * width hint larger than the preferred width. Returns false if there is
|
|
405 * any situation in which the control does not have this property.
|
|
406 *
|
|
407 * <p>
|
|
408 * Note: this method is only important for wrapping controls, and it can
|
|
409 * safely return false for anything else. AFAIK, all DWT controls have this
|
|
410 * property, but to be safe they will only be added to the list once the
|
|
411 * property has been confirmed.
|
|
412 * </p>
|
|
413 *
|
|
414 * @param control
|
|
415 * @return
|
|
416 */
|
|
417 private static bool isPreferredWidthMaximum(Control control) {
|
|
418 return (null !is cast(ToolBar)control
|
|
419 //|| control instanceof CoolBar
|
|
420 || null !is cast(Label)control );
|
|
421 }
|
|
422
|
|
423 public int computeMinimumWidth() {
|
|
424 if (minimumWidth is -1) {
|
|
425 if (null !is cast(Composite)control ) {
|
|
426 Layout layout = (cast(Composite)control).getLayout();
|
|
427 if (null !is cast(ILayoutExtension)layout ) {
|
|
428 minimumWidth = (cast(ILayoutExtension)layout).computeMinimumWidth(cast(Composite)control, flushChildren);
|
|
429 flushChildren = false;
|
|
430 }
|
|
431 }
|
|
432 }
|
|
433
|
|
434 if (minimumWidth is -1) {
|
|
435 Point minWidth = controlComputeSize(FormUtil.getWidthHint(5, control), DWT.DEFAULT);
|
|
436 minimumWidth = minWidth.x;
|
|
437 heightAtMinimumWidth = minWidth.y;
|
|
438 }
|
|
439
|
|
440 return minimumWidth;
|
|
441 }
|
|
442
|
|
443 public int computeMaximumWidth() {
|
|
444 if (maximumWidth is -1) {
|
|
445 if (null !is cast(Composite)control ) {
|
|
446 Layout layout = (cast(Composite)control).getLayout();
|
|
447 if (null !is cast(ILayoutExtension)layout ) {
|
|
448 maximumWidth = (cast(ILayoutExtension)layout).computeMaximumWidth(cast(Composite)control, flushChildren);
|
|
449 flushChildren = false;
|
|
450 }
|
|
451 }
|
|
452 }
|
|
453
|
|
454 if (maximumWidth is -1) {
|
|
455 maximumWidth = getPreferredSize().x;
|
|
456 }
|
|
457
|
|
458 return maximumWidth;
|
|
459 }
|
|
460
|
|
461 private int computeHeightAtMinimumWidth() {
|
|
462 int minimumWidth = computeMinimumWidth();
|
|
463
|
|
464 if (heightAtMinimumWidth is -1) {
|
|
465 heightAtMinimumWidth = controlComputeSize(minimumWidth - widthAdjustment, DWT.DEFAULT).y;
|
|
466 }
|
|
467
|
|
468 return heightAtMinimumWidth;
|
|
469 }
|
|
470
|
|
471 private int computeWidthAtMinimumHeight() {
|
|
472 int minimumHeight = computeMinimumHeight();
|
|
473
|
|
474 if (widthAtMinimumHeight is -1) {
|
|
475 widthAtMinimumHeight = controlComputeSize(DWT.DEFAULT, minimumHeight - heightAdjustment).x;
|
|
476 }
|
|
477
|
|
478 return widthAtMinimumHeight;
|
|
479 }
|
|
480
|
|
481 private int computeMinimumHeight() {
|
|
482 if (minimumHeight is -1) {
|
|
483 Point sizeAtMinHeight = controlComputeSize(DWT.DEFAULT, 0);
|
|
484
|
|
485 minimumHeight = sizeAtMinHeight.y;
|
|
486 widthAtMinimumHeight = sizeAtMinHeight.x;
|
|
487 }
|
|
488
|
|
489 return minimumHeight;
|
|
490 }
|
|
491
|
|
492 public Point computeMinimumSize() {
|
|
493 return new Point(computeMinimumWidth(), computeMinimumHeight());
|
|
494 }
|
|
495
|
|
496 public void setSize(Point newSize) {
|
|
497 if (control !is null) {
|
|
498 control.setSize(newSize);
|
|
499 }
|
|
500
|
|
501 layoutIfNecessary();
|
|
502 }
|
|
503
|
|
504 public void setSize(int width, int height) {
|
|
505 if (control !is null) {
|
|
506 control.setSize(width, height);
|
|
507 }
|
|
508
|
|
509 layoutIfNecessary();
|
|
510 }
|
|
511
|
|
512 public void setBounds(int x, int y, int width, int height) {
|
|
513 if (control !is null) {
|
|
514 control.setBounds(x, y, width, height);
|
|
515 }
|
|
516
|
|
517 layoutIfNecessary();
|
|
518 }
|
|
519
|
|
520 public void setBounds(Rectangle bounds) {
|
|
521 if (control !is null) {
|
|
522 control.setBounds(bounds);
|
|
523 }
|
|
524
|
|
525 layoutIfNecessary();
|
|
526 }
|
|
527
|
|
528 public void layoutIfNecessary() {
|
|
529 if (dirtySize !is null && control !is null && null !is cast(Composite)control ) {
|
|
530 if (control.getSize().opEquals(dirtySize)) {
|
|
531 (cast(Composite)control).layout(flushChildren);
|
|
532 flushChildren = false;
|
|
533 }
|
|
534 }
|
|
535 dirtySize = null;
|
|
536 }
|
|
537 }
|