001: /*******************************************************************************
002: * Copyright (c) 2000, 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: * Randy Hudson <hudsonr@us.ibm.com>
011: * - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views
012: * Cagatay Kavukcuoglu <cagatayk@acm.org>
013: * - Fix for bug 10025 - Resizing views should not use height ratios
014: *******************************************************************************/package org.eclipse.ui.internal;
015:
016: import java.util.ArrayList;
017:
018: import org.eclipse.core.runtime.Assert;
019: import org.eclipse.jface.util.Geometry;
020: import org.eclipse.swt.SWT;
021: import org.eclipse.swt.graphics.Point;
022: import org.eclipse.swt.graphics.Rectangle;
023: import org.eclipse.swt.widgets.Composite;
024: import org.eclipse.ui.ISizeProvider;
025:
026: /**
027: * Implementation of a tree where the node is allways a sash
028: * and it allways has two chidren. If a children is removed
029: * the sash, ie the node, is removed as well and its other children
030: * placed on its parent.
031: */
032: public class LayoutTree implements ISizeProvider {
033: /* The parent of this tree or null if it is the root */
034: LayoutTreeNode parent;
035:
036: /* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */
037: LayoutPart part;
038:
039: // Cached information
040: private int cachedMinimumWidthHint = SWT.DEFAULT;
041: private int cachedMinimumWidth = SWT.DEFAULT;
042: private int cachedMinimumHeightHint = SWT.DEFAULT;
043: private int cachedMinimumHeight = SWT.DEFAULT;
044: private int cachedMaximumWidthHint = SWT.DEFAULT;
045: private int cachedMaximumWidth = SWT.DEFAULT;
046: private int cachedMaximumHeightHint = SWT.DEFAULT;
047: private int cachedMaximumHeight = SWT.DEFAULT;
048:
049: // Cached size flags
050: private boolean sizeFlagsDirty = true;
051: private int widthSizeFlags = 0;
052: private int heightSizeFlags = 0;
053:
054: // Cache statistics. For use in benchmarks and test suites only!
055: public static int minCacheHits;
056: public static int minCacheMisses;
057: public static int maxCacheHits;
058: public static int maxCacheMisses;
059:
060: private boolean forceLayout = true;
061: private Rectangle currentBounds = new Rectangle(0, 0, 0, 0);
062:
063: /**
064: * Initialize this tree with its part.
065: */
066: public LayoutTree(LayoutPart part) {
067: this .part = part;
068: }
069:
070: /**
071: * Add the relation ship between the children in the list
072: * and returns the left children.
073: */
074: public LayoutPart computeRelation(ArrayList relations) {
075: return part;
076: }
077:
078: /**
079: * Locates the part that intersects the given point
080: *
081: * @param toFind
082: * @return
083: */
084: public LayoutPart findPart(Point toFind) {
085: return part;
086: }
087:
088: /**
089: * Dispose all Sashs in this tree
090: */
091: public void disposeSashes() {
092: }
093:
094: /**
095: * Find a LayoutPart in the tree and return its sub-tree. Returns
096: * null if the child is not found.
097: */
098: public LayoutTree find(LayoutPart child) {
099: if (part != child) {
100: return null;
101: }
102: return this ;
103: }
104:
105: /**
106: * Find the Left,Right,Top and Botton
107: * sashes around this tree and set them
108: * in <code>sashes</code>
109: */
110: public void findSashes(PartPane.Sashes sashes) {
111: if (getParent() == null) {
112: return;
113: }
114: getParent().findSashes(this , sashes);
115: }
116:
117: /**
118: * Find the part that is in the bottom rigth possition.
119: */
120: public LayoutPart findBottomRight() {
121: return part;
122: }
123:
124: /**
125: * Find a sash in the tree and return its sub-tree. Returns
126: * null if the sash is not found.
127: */
128: public LayoutTreeNode findSash(LayoutPartSash sash) {
129: return null;
130: }
131:
132: /**
133: * Return the bounds of this tree which is the rectangle that
134: * contains all Controls in this tree.
135: */
136: public final Rectangle getBounds() {
137: return Geometry.copy(currentBounds);
138: }
139:
140: /**
141: * Subtracts two integers. If a is INFINITE, this is treated as
142: * positive infinity.
143: *
144: * @param a a positive integer or INFINITE indicating positive infinity
145: * @param b a positive integer (may not be INFINITE)
146: * @return a - b, or INFINITE if a == INFINITE
147: * @since 3.1
148: */
149: public static int subtract(int a, int b) {
150: Assert.isTrue(b >= 0 && b < INFINITE);
151:
152: return add(a, -b);
153: }
154:
155: /**
156: * Adds two positive integers. Treates INFINITE as positive infinity.
157: *
158: * @param a a positive integer
159: * @param b a positive integer
160: * @return a + b, or INFINITE if a or b are positive infinity
161: * @since 3.1
162: */
163: public static int add(int a, int b) {
164: if (a == INFINITE || b == INFINITE) {
165: return INFINITE;
166: }
167:
168: return a + b;
169: }
170:
171: /**
172: * Asserts that toCheck is a positive integer less than INFINITE / 2 or equal
173: * to INFINITE. Many of the methods of this class use positive integers as sizes,
174: * with INFINITE indicating positive infinity. This picks up accidental addition or
175: * subtraction from infinity.
176: *
177: * @param toCheck integer to validate
178: * @since 3.1
179: */
180: public static void assertValidSize(int toCheck) {
181: Assert.isTrue(toCheck >= 0
182: && (toCheck == INFINITE || toCheck < INFINITE / 2));
183: }
184:
185: /**
186: * Computes the preferred size for this object. The interpretation of the result depends on the flags returned
187: * by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize
188: * or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to
189: * doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or
190: * doComputePreferredSize to specialize the return value.
191: *
192: * @see LayoutPart#computePreferredSize(boolean, int, int, int)
193: */
194: public final int computePreferredSize(boolean width,
195: int availableParallel, int availablePerpendicular,
196: int preferredParallel) {
197: assertValidSize(availableParallel);
198: assertValidSize(availablePerpendicular);
199: assertValidSize(preferredParallel);
200:
201: if (!isVisible()) {
202: return 0;
203: }
204:
205: if (availableParallel == 0) {
206: return 0;
207: }
208:
209: if (preferredParallel == 0) {
210: return Math.min(availableParallel, computeMinimumSize(
211: width, availablePerpendicular));
212: } else if (preferredParallel == INFINITE
213: && availableParallel == INFINITE) {
214: return computeMaximumSize(width, availablePerpendicular);
215: }
216:
217: // Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum
218: // size, simply return the preferred size
219: if (!hasSizeFlag(width, SWT.FILL)) {
220: return preferredParallel;
221: }
222:
223: int result = doComputePreferredSize(width, availableParallel,
224: availablePerpendicular, preferredParallel);
225:
226: return result;
227: }
228:
229: /**
230: * Returns the size flags for this tree.
231: *
232: * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
233: *
234: * @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false)
235: * @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean)
236: */
237: protected int doGetSizeFlags(boolean width) {
238: return part.getSizeFlags(width);
239: }
240:
241: /**
242: * Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int)
243: *
244: * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
245: *
246: * @since 3.1
247: */
248: protected int doComputePreferredSize(boolean width,
249: int availableParallel, int availablePerpendicular,
250: int preferredParallel) {
251: int result = Math.min(availableParallel, part
252: .computePreferredSize(width, availableParallel,
253: availablePerpendicular, preferredParallel));
254:
255: assertValidSize(result);
256: return result;
257: }
258:
259: /**
260: * Returns the minimum size for this subtree. Equivalent to calling
261: * computePreferredSize(width, INFINITE, availablePerpendicular, 0).
262: * Returns a cached value if possible or defers to doComputeMinimumSize otherwise.
263: * Subclasses should overload doComputeMinimumSize if they want to specialize the
264: * return value.
265: *
266: * @param width true iff computing the minimum width, false iff computing the minimum height
267: * @param availablePerpendicular available space (pixels) perpendicular to the dimension
268: * being computed. This is a height when computing a width, or a width when computing a height.
269: *
270: * @see LayoutPart#computePreferredSize(boolean, int, int, int)
271: */
272: public final int computeMinimumSize(boolean width,
273: int availablePerpendicular) {
274: assertValidSize(availablePerpendicular);
275:
276: // Optimization: if this subtree has no minimum size, then always return 0 as its
277: // minimum size.
278: if (!hasSizeFlag(width, SWT.MIN)) {
279: return 0;
280: }
281:
282: // If this subtree doesn't contain any wrapping controls (ie: they don't care
283: // about their perpendicular size) then force the perpendicular
284: // size to be INFINITE. This ensures that we will get a cache hit
285: // every time for non-wrapping controls.
286: if (!hasSizeFlag(width, SWT.WRAP)) {
287: availablePerpendicular = INFINITE;
288: }
289:
290: if (width) {
291: // Check if we have a cached width measurement (we can only return a cached
292: // value if we computed it for the same height)
293: if (cachedMinimumWidthHint == availablePerpendicular) {
294: minCacheHits++;
295: return cachedMinimumWidth;
296: }
297:
298: // Recompute the minimum width and store it in the cache
299:
300: minCacheMisses++;
301:
302: int result = doComputeMinimumSize(width,
303: availablePerpendicular);
304: cachedMinimumWidth = result;
305: cachedMinimumWidthHint = availablePerpendicular;
306: return result;
307:
308: } else {
309: // Check if we have a cached height measurement (we can only return a cached
310: // value if we computed it for the same width)
311: if (cachedMinimumHeightHint == availablePerpendicular) {
312: minCacheHits++;
313: return cachedMinimumHeight;
314: }
315:
316: // Recompute the minimum width and store it in the cache
317: minCacheMisses++;
318:
319: int result = doComputeMinimumSize(width,
320: availablePerpendicular);
321: cachedMinimumHeight = result;
322: cachedMinimumHeightHint = availablePerpendicular;
323: return result;
324: }
325: }
326:
327: /**
328: * For use in benchmarks and test suites only. Displays cache utilization statistics for all
329: * LayoutTree instances.
330: *
331: * @since 3.1
332: */
333: public static void printCacheStatistics() {
334: System.out
335: .println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
336: minCacheHits * 100
337: / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$
338: System.out
339: .println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
340: maxCacheHits * 100
341: / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$
342: }
343:
344: public int doComputeMinimumSize(boolean width,
345: int availablePerpendicular) {
346: int result = doComputePreferredSize(width, INFINITE,
347: availablePerpendicular, 0);
348: assertValidSize(result);
349: return result;
350: }
351:
352: public final int computeMaximumSize(boolean width,
353: int availablePerpendicular) {
354: assertValidSize(availablePerpendicular);
355:
356: // Optimization: if this subtree has no maximum size, then always return INFINITE as its
357: // maximum size.
358: if (!hasSizeFlag(width, SWT.MAX)) {
359: return INFINITE;
360: }
361:
362: // If this subtree doesn't contain any wrapping controls (ie: they don't care
363: // about their perpendicular size) then force the perpendicular
364: // size to be INFINITE. This ensures that we will get a cache hit
365: // every time.
366: if (!hasSizeFlag(width, SWT.WRAP)) {
367: availablePerpendicular = INFINITE;
368: }
369:
370: if (width) {
371: // Check if we have a cached width measurement (we can only return a cached
372: // value if we computed it for the same height)
373: if (cachedMaximumWidthHint == availablePerpendicular) {
374: maxCacheHits++;
375: return cachedMaximumWidth;
376: }
377:
378: maxCacheMisses++;
379:
380: // Recompute the maximum width and store it in the cache
381: int result = doComputeMaximumSize(width,
382: availablePerpendicular);
383: cachedMaximumWidth = result;
384: cachedMaximumWidthHint = availablePerpendicular;
385: return result;
386:
387: } else {
388: // Check if we have a cached height measurement
389: if (cachedMaximumHeightHint == availablePerpendicular) {
390: maxCacheHits++;
391: return cachedMaximumHeight;
392: }
393:
394: maxCacheMisses++;
395:
396: // Recompute the maximum height and store it in the cache
397: int result = doComputeMaximumSize(width,
398: availablePerpendicular);
399: cachedMaximumHeight = result;
400: cachedMaximumHeightHint = availablePerpendicular;
401: return result;
402: }
403: }
404:
405: protected int doComputeMaximumSize(boolean width,
406: int availablePerpendicular) {
407: return doComputePreferredSize(width, INFINITE,
408: availablePerpendicular, INFINITE);
409: }
410:
411: /**
412: * Called to flush any cached information in this tree and its parents.
413: */
414: public void flushNode() {
415:
416: // Clear cached sizes
417: cachedMinimumWidthHint = SWT.DEFAULT;
418: cachedMinimumWidth = SWT.DEFAULT;
419: cachedMinimumHeightHint = SWT.DEFAULT;
420: cachedMinimumHeight = SWT.DEFAULT;
421: cachedMaximumWidthHint = SWT.DEFAULT;
422: cachedMaximumWidth = SWT.DEFAULT;
423: cachedMaximumHeightHint = SWT.DEFAULT;
424: cachedMaximumHeight = SWT.DEFAULT;
425:
426: // Flags may have changed. Ensure that they are recomputed the next time around
427: sizeFlagsDirty = true;
428:
429: // The next setBounds call should trigger a layout even if set to the same bounds since
430: // one of the children has changed.
431: forceLayout = true;
432: }
433:
434: /**
435: * Flushes all cached information about this node and all of its children.
436: * This should be called if something may have caused all children to become
437: * out of synch with their cached information (for example, if a lot of changes
438: * may have happened without calling flushCache after each change)
439: *
440: * @since 3.1
441: */
442: public void flushChildren() {
443: flushNode();
444: }
445:
446: /**
447: * Flushes all cached information about this node and all of its ancestors.
448: * This should be called when a single child changes.
449: *
450: * @since 3.1
451: */
452: public final void flushCache() {
453: flushNode();
454:
455: if (parent != null) {
456: parent.flushCache();
457: }
458: }
459:
460: public final int getSizeFlags(boolean width) {
461: if (sizeFlagsDirty) {
462: widthSizeFlags = doGetSizeFlags(true);
463: heightSizeFlags = doGetSizeFlags(false);
464: sizeFlagsDirty = false;
465: }
466:
467: return width ? widthSizeFlags : heightSizeFlags;
468: }
469:
470: /**
471: * Returns the parent of this tree or null if it is the root.
472: */
473: public LayoutTreeNode getParent() {
474: return parent;
475: }
476:
477: /**
478: * Inserts a new child on the tree. The child will be placed beside
479: * the <code>relative</code> child. Returns the new root of the tree.
480: */
481: public LayoutTree insert(LayoutPart child, boolean left,
482: LayoutPartSash sash, LayoutPart relative) {
483: LayoutTree relativeChild = find(relative);
484: LayoutTreeNode node = new LayoutTreeNode(sash);
485: if (relativeChild == null) {
486: //Did not find the relative part. Insert beside the root.
487: node.setChild(left, child);
488: node.setChild(!left, this );
489: return node;
490: } else {
491: LayoutTreeNode oldParent = relativeChild.getParent();
492: node.setChild(left, child);
493: node.setChild(!left, relativeChild);
494: if (oldParent == null) {
495: //It was the root. Return a new root.
496: return node;
497: }
498: oldParent.replaceChild(relativeChild, node);
499: return this ;
500: }
501: }
502:
503: /**
504: * Returns true if this tree can be compressed and expanded.
505: * @return true if springy
506: */
507: public boolean isCompressible() {
508: //Added for bug 19524
509: return part.isCompressible();
510: }
511:
512: /**
513: * Returns true if this tree has visible parts otherwise returns false.
514: */
515: public boolean isVisible() {
516: return !(part instanceof PartPlaceholder);
517: }
518:
519: /**
520: * Recompute the ratios in this tree.
521: */
522: public void recomputeRatio() {
523: }
524:
525: /**
526: * Find a child in the tree and remove it and its parent.
527: * The other child of its parent is placed on the parent's parent.
528: * Returns the new root of the tree.
529: */
530: public LayoutTree remove(LayoutPart child) {
531: LayoutTree tree = find(child);
532: if (tree == null) {
533: return this ;
534: }
535: LayoutTreeNode oldParent = tree.getParent();
536: if (oldParent == null) {
537: //It was the root and the only child of this tree
538: return null;
539: }
540: if (oldParent.getParent() == null) {
541: return oldParent.remove(tree);
542: }
543:
544: oldParent.remove(tree);
545: return this ;
546: }
547:
548: /**
549: * Sets the bounds of this node. If the bounds have changed or any children have
550: * changed then the children will be recursively layed out. This implementation
551: * filters out redundant calls and delegates to doSetBounds to layout the children.
552: * Subclasses should overload doSetBounds to lay out their children.
553: *
554: * @param bounds new bounds of the tree
555: */
556: public final void setBounds(Rectangle bounds) {
557: if (!bounds.equals(currentBounds) || forceLayout) {
558: currentBounds = Geometry.copy(bounds);
559:
560: doSetBounds(currentBounds);
561: forceLayout = false;
562: }
563: }
564:
565: /**
566: * Resize the parts on this tree to fit in <code>bounds</code>.
567: */
568: protected void doSetBounds(Rectangle bounds) {
569: part.setBounds(bounds);
570: }
571:
572: /**
573: * Set the parent of this tree.
574: */
575: void setParent(LayoutTreeNode parent) {
576: this .parent = parent;
577: }
578:
579: /**
580: * Set the part of this leaf
581: */
582: void setPart(LayoutPart part) {
583: this .part = part;
584: flushCache();
585: }
586:
587: /**
588: * Returns a string representation of this object.
589: */
590: public String toString() {
591: return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$
592: }
593:
594: /**
595: * Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the
596: * LayoutParts that are being arranged by the LayoutTree.
597: *
598: * @param parent
599: * @since 3.1
600: */
601: public void createControl(Composite parent) {
602: }
603:
604: /**
605: * Writes a description of the layout to the given string buffer.
606: * This is used for drag-drop test suites to determine if two layouts are the
607: * same. Like a hash code, the description should compare as equal iff the
608: * layouts are the same. However, it should be user-readable in order to
609: * help debug failed tests. Although these are english readable strings,
610: * they should not be translated or equality tests will fail.
611: * <p>
612: * This is only intended for use by test suites.
613: * </p>
614: *
615: * @param buf
616: */
617: public void describeLayout(StringBuffer buf) {
618: part.describeLayout(buf);
619: }
620:
621: /**
622: * This is a shorthand method that checks if the tree contains the
623: * given size flag. For example, hasSizeFlag(false, SWT.MIN) returns
624: * true iff the receiver enforces a minimum height, or
625: * hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to
626: * know its height when computing its preferred width.
627: *
628: * @param vertical
629: * @return
630: * @since 3.1
631: */
632: public final boolean hasSizeFlag(boolean width, int flag) {
633: return (getSizeFlags(width) & flag) != 0;
634: }
635:
636: }
|