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.swt.widgets.Sash;
025: import org.eclipse.ui.IPageLayout;
026:
027: /**
028: * Implementation of a tree node. The node represents a
029: * sash and it allways has two children.
030: */
031: public class LayoutTreeNode extends LayoutTree {
032:
033: static class ChildSizes {
034: int left;
035: int right;
036: boolean resizable = true;
037:
038: public ChildSizes(int l, int r, boolean resize) {
039: left = l;
040: right = r;
041: resizable = resize;
042: }
043: }
044:
045: /* The node children witch may be another node or a leaf */
046: private LayoutTree children[] = new LayoutTree[2];
047:
048: /* The sash's width when vertical and hight on horizontal */
049: final static int SASH_WIDTH = 3;
050:
051: /**
052: * Initialize this tree with its sash.
053: */
054: public LayoutTreeNode(LayoutPartSash sash) {
055: super (sash);
056: }
057:
058: /* (non-Javadoc)
059: * @see org.eclipse.ui.internal.LayoutTree#flushChildren()
060: */
061: public void flushChildren() {
062: super .flushChildren();
063:
064: children[0].flushChildren();
065: children[1].flushChildren();
066: }
067:
068: /**
069: * Traverses the tree to find the part that intersects the given point
070: *
071: * @param toFind
072: * @return the part that intersects the given point
073: */
074: public LayoutPart findPart(Point toFind) {
075: if (!children[0].isVisible()) {
076: if (!children[1].isVisible()) {
077: return null;
078: }
079:
080: return children[1].findPart(toFind);
081: } else {
082: if (!children[1].isVisible()) {
083: return children[0].findPart(toFind);
084: }
085: }
086:
087: LayoutPartSash sash = getSash();
088:
089: Rectangle bounds = sash.getBounds();
090:
091: if (sash.isVertical()) {
092: if (toFind.x < bounds.x + (bounds.width / 2)) {
093: return children[0].findPart(toFind);
094: }
095: return children[1].findPart(toFind);
096: } else {
097: if (toFind.y < bounds.y + (bounds.height / 2)) {
098: return children[0].findPart(toFind);
099: }
100: return children[1].findPart(toFind);
101: }
102: }
103:
104: /**
105: * Add the relation ship between the children in the list
106: * and returns the left children.
107: */
108: public LayoutPart computeRelation(ArrayList relations) {
109: PartSashContainer.RelationshipInfo r = new PartSashContainer.RelationshipInfo();
110: r.relative = children[0].computeRelation(relations);
111: r.part = children[1].computeRelation(relations);
112: r.left = getSash().getLeft();
113: r.right = getSash().getRight();
114: r.relationship = getSash().isVertical() ? IPageLayout.RIGHT
115: : IPageLayout.BOTTOM;
116: relations.add(0, r);
117: return r.relative;
118: }
119:
120: /**
121: * Dispose all Sashs in this tree
122: */
123: public void disposeSashes() {
124: children[0].disposeSashes();
125: children[1].disposeSashes();
126: getSash().dispose();
127: }
128:
129: /**
130: * Find a LayoutPart in the tree and return its sub-tree. Returns
131: * null if the child is not found.
132: */
133: public LayoutTree find(LayoutPart child) {
134: LayoutTree node = children[0].find(child);
135: if (node != null) {
136: return node;
137: }
138: node = children[1].find(child);
139: return node;
140: }
141:
142: /**
143: * Find the part that is in the bottom right position.
144: */
145: public LayoutPart findBottomRight() {
146: if (children[1].isVisible()) {
147: return children[1].findBottomRight();
148: }
149: return children[0].findBottomRight();
150: }
151:
152: /**
153: * Go up in the tree finding a parent that is common of both children.
154: * Return the subtree.
155: */
156: public LayoutTreeNode findCommonParent(LayoutPart child1,
157: LayoutPart child2) {
158: return findCommonParent(child1, child2, false, false);
159: }
160:
161: /**
162: * Go up in the tree finding a parent that is common of both children.
163: * Return the subtree.
164: */
165: LayoutTreeNode findCommonParent(LayoutPart child1,
166: LayoutPart child2, boolean foundChild1, boolean foundChild2) {
167: if (!foundChild1) {
168: foundChild1 = find(child1) != null;
169: }
170: if (!foundChild2) {
171: foundChild2 = find(child2) != null;
172: }
173: if (foundChild1 && foundChild2) {
174: return this ;
175: }
176: if (parent == null) {
177: return null;
178: }
179: return parent.findCommonParent(child1, child2, foundChild1,
180: foundChild2);
181: }
182:
183: /**
184: * Find a sash in the tree and return its sub-tree. Returns
185: * null if the sash is not found.
186: */
187: public LayoutTreeNode findSash(LayoutPartSash sash) {
188: if (this .getSash() == sash) {
189: return this ;
190: }
191: LayoutTreeNode node = children[0].findSash(sash);
192: if (node != null) {
193: return node;
194: }
195: node = children[1].findSash(sash);
196: if (node != null) {
197: return node;
198: }
199: return null;
200: }
201:
202: /**
203: * Sets the elements in the array of sashes with the
204: * Left,Rigth,Top and Botton sashes. The elements
205: * may be null depending whether there is a shash
206: * beside the <code>part</code>
207: */
208: void findSashes(LayoutTree child, PartPane.Sashes sashes) {
209: Sash sash = (Sash) getSash().getControl();
210: boolean leftOrTop = children[0] == child;
211: if (sash != null) {
212: LayoutPartSash partSash = getSash();
213: //If the child is in the left, the sash
214: //is in the rigth and so on.
215: if (leftOrTop) {
216: if (partSash.isVertical()) {
217: if (sashes.right == null) {
218: sashes.right = sash;
219: }
220: } else {
221: if (sashes.bottom == null) {
222: sashes.bottom = sash;
223: }
224: }
225: } else {
226: if (partSash.isVertical()) {
227: if (sashes.left == null) {
228: sashes.left = sash;
229: }
230: } else {
231: if (sashes.top == null) {
232: sashes.top = sash;
233: }
234: }
235: }
236: }
237: if (getParent() != null) {
238: getParent().findSashes(this , sashes);
239: }
240: }
241:
242: /**
243: * Returns the sash of this node.
244: */
245: public LayoutPartSash getSash() {
246: return (LayoutPartSash) part;
247: }
248:
249: /**
250: * Returns true if this tree has visible parts otherwise returns false.
251: */
252: public boolean isVisible() {
253: return children[0].isVisible() || children[1].isVisible();
254: }
255:
256: /**
257: * Remove the child and this node from the tree
258: */
259: LayoutTree remove(LayoutTree child) {
260: getSash().dispose();
261: if (parent == null) {
262: //This is the root. Return the other child to be the new root.
263: if (children[0] == child) {
264: children[1].setParent(null);
265: return children[1];
266: }
267: children[0].setParent(null);
268: return children[0];
269: }
270:
271: LayoutTreeNode oldParent = parent;
272: if (children[0] == child) {
273: oldParent.replaceChild(this , children[1]);
274: } else {
275: oldParent.replaceChild(this , children[0]);
276: }
277: return oldParent;
278: }
279:
280: /**
281: * Replace a child with a new child and sets the new child's parent.
282: */
283: void replaceChild(LayoutTree oldChild, LayoutTree newChild) {
284: if (children[0] == oldChild) {
285: children[0] = newChild;
286: } else if (children[1] == oldChild) {
287: children[1] = newChild;
288: }
289: newChild.setParent(this );
290: if (!children[0].isVisible() || !children[0].isVisible()) {
291: getSash().dispose();
292: }
293:
294: flushCache();
295: }
296:
297: /**
298: * Go up from the subtree and return true if all the sash are
299: * in the direction specified by <code>isVertical</code>
300: */
301: public boolean sameDirection(boolean isVertical,
302: LayoutTreeNode subTree) {
303: boolean treeVertical = getSash().isVertical();
304: if (treeVertical != isVertical) {
305: return false;
306: }
307: while (subTree != null) {
308: if (this == subTree) {
309: return true;
310: }
311: if (subTree.children[0].isVisible()
312: && subTree.children[1].isVisible()) {
313: if (subTree.getSash().isVertical() != isVertical) {
314: return false;
315: }
316: }
317: subTree = subTree.getParent();
318: }
319: return true;
320: }
321:
322: public int doComputePreferredSize(boolean width,
323: int availableParallel, int availablePerpendicular,
324: int preferredParallel) {
325: assertValidSize(availablePerpendicular);
326: assertValidSize(availableParallel);
327: assertValidSize(preferredParallel);
328:
329: // If one child is invisible, defer to the other child
330: if (!children[0].isVisible()) {
331: return children[1].computePreferredSize(width,
332: availableParallel, availablePerpendicular,
333: preferredParallel);
334: }
335:
336: if (!children[1].isVisible()) {
337: return children[0].computePreferredSize(width,
338: availableParallel, availablePerpendicular,
339: preferredParallel);
340: }
341:
342: if (availableParallel == 0) {
343: return 0;
344: }
345:
346: // If computing the dimension perpendicular to our sash
347: if (width == getSash().isVertical()) {
348: // Compute the child sizes
349: ChildSizes sizes = computeChildSizes(availableParallel,
350: availablePerpendicular, getSash().getLeft(),
351: getSash().getRight(), preferredParallel);
352:
353: // Return the sum of the child sizes plus the sash size
354: return add(sizes.left, add(sizes.right, SASH_WIDTH));
355: } else {
356: // Computing the dimension parallel to the sash. We will compute and return the preferred size
357: // of whichever child is closest to the ideal size.
358:
359: ChildSizes sizes;
360: // First compute the dimension of the child sizes perpendicular to the sash
361: sizes = computeChildSizes(availablePerpendicular,
362: availableParallel, getSash().getLeft(), getSash()
363: .getRight(), availablePerpendicular);
364:
365: // Use this information to compute the dimension of the child sizes parallel to the sash.
366: // Return the preferred size of whichever child is largest
367: int leftSize = children[0].computePreferredSize(width,
368: availableParallel, sizes.left, preferredParallel);
369:
370: // Compute the preferred size of the right child
371: int rightSize = children[1].computePreferredSize(width,
372: availableParallel, sizes.right, preferredParallel);
373:
374: // Return leftSize or rightSize: whichever one is largest
375: int result = rightSize;
376: if (leftSize > rightSize) {
377: result = leftSize;
378: }
379:
380: assertValidSize(result);
381:
382: return result;
383: }
384: }
385:
386: /**
387: * Computes the pixel sizes of this node's children, given the available
388: * space for this node. Note that "width" and "height" actually refer
389: * to the distance perpendicular and parallel to the sash respectively.
390: * That is, their meaning is reversed when computing a horizontal sash.
391: *
392: * @param width the pixel width of a vertical node, or the pixel height
393: * of a horizontal node (INFINITE if unbounded)
394: * @param height the pixel height of a vertical node, or the pixel width
395: * of a horizontal node (INFINITE if unbounded)
396: * @return a struct describing the pixel sizes of the left and right children
397: * (this is a width for horizontal nodes and a height for vertical nodes)
398: */
399: ChildSizes computeChildSizes(int width, int height, int left,
400: int right, int preferredWidth) {
401: Assert.isTrue(children[0].isVisible());
402: Assert.isTrue(children[1].isVisible());
403: assertValidSize(width);
404: assertValidSize(height);
405: assertValidSize(preferredWidth);
406: Assert.isTrue(left >= 0);
407: Assert.isTrue(right >= 0);
408: Assert.isTrue(preferredWidth >= 0);
409: Assert.isTrue(preferredWidth <= width);
410: boolean vertical = getSash().isVertical();
411:
412: if (width <= SASH_WIDTH) {
413: return new ChildSizes(0, 0, false);
414: }
415:
416: if (width == INFINITE) {
417: if (preferredWidth == INFINITE) {
418: return new ChildSizes(children[0].computeMaximumSize(
419: vertical, height), children[1]
420: .computeMaximumSize(vertical, height), false);
421: }
422:
423: if (preferredWidth == 0) {
424: return new ChildSizes(children[0].computeMinimumSize(
425: vertical, height), children[1]
426: .computeMinimumSize(vertical, height), false);
427: }
428: }
429:
430: int total = left + right;
431:
432: // Use all-or-none weighting
433: double wLeft = left, wRight = right;
434: switch (getCompressionBias()) {
435: case -1:
436: wLeft = 0.0;
437: break;
438: case 1:
439: wRight = 0.0;
440: break;
441: default:
442: break;
443: }
444: double wTotal = wLeft + wRight;
445:
446: // Subtract the SASH_WIDTH from preferredWidth and width. From here on, we'll deal with the
447: // width available to the controls and neglect the space used by the sash.
448: preferredWidth = Math.max(0, subtract(preferredWidth,
449: SASH_WIDTH));
450: width = Math.max(0, subtract(width, SASH_WIDTH));
451:
452: int redistribute = subtract(preferredWidth, total);
453:
454: // Compute the minimum and maximum sizes for each child
455: int leftMinimum = children[0].computeMinimumSize(vertical,
456: height);
457: int rightMinimum = children[1].computeMinimumSize(vertical,
458: height);
459: int leftMaximum = children[0].computeMaximumSize(vertical,
460: height);
461: int rightMaximum = children[1].computeMaximumSize(vertical,
462: height);
463:
464: // Keep track of the available space for each child, given the minimum size of the other child
465: int leftAvailable = Math.min(leftMaximum, Math.max(0, subtract(
466: width, rightMinimum)));
467: int rightAvailable = Math.min(rightMaximum, Math.max(0,
468: subtract(width, leftMinimum)));
469:
470: // Figure out the ideal size of the left child
471: int idealLeft = Math
472: .max(leftMinimum, Math.min(preferredWidth, left
473: + (int) Math.round(redistribute * wLeft
474: / wTotal)));
475:
476: // If the right child can't use all its available space, let the left child fill it in
477: idealLeft = Math
478: .max(idealLeft, preferredWidth - rightAvailable);
479: // Ensure the left child doesn't get larger than its available space
480: idealLeft = Math.min(idealLeft, leftAvailable);
481:
482: // Check if the left child would prefer to be a different size
483: idealLeft = children[0].computePreferredSize(vertical,
484: leftAvailable, height, idealLeft);
485:
486: // Ensure that the left child is larger than its minimum size
487: idealLeft = Math.max(idealLeft, leftMinimum);
488: idealLeft = Math.min(idealLeft, leftAvailable);
489:
490: // Compute the right child width
491: int idealRight = Math.max(rightMinimum, preferredWidth
492: - idealLeft);
493:
494: rightAvailable = Math.max(0, Math.min(rightAvailable, subtract(
495: width, idealLeft)));
496: idealRight = Math.min(idealRight, rightAvailable);
497: idealRight = children[1].computePreferredSize(vertical,
498: rightAvailable, height, idealRight);
499: idealRight = Math.max(idealRight, rightMinimum);
500:
501: return new ChildSizes(idealLeft, idealRight,
502: leftMaximum > leftMinimum
503: && rightMaximum > rightMinimum
504: && leftMinimum + rightMinimum < width);
505: }
506:
507: protected int doGetSizeFlags(boolean width) {
508: if (!children[0].isVisible()) {
509: return children[1].getSizeFlags(width);
510: }
511:
512: if (!children[1].isVisible()) {
513: return children[0].getSizeFlags(width);
514: }
515:
516: int leftFlags = children[0].getSizeFlags(width);
517: int rightFlags = children[1].getSizeFlags(width);
518:
519: return ((leftFlags | rightFlags) & ~SWT.MAX)
520: | (leftFlags & rightFlags & SWT.MAX);
521: }
522:
523: /**
524: * Resize the parts on this tree to fit in <code>bounds</code>.
525: */
526: public void doSetBounds(Rectangle bounds) {
527: if (!children[0].isVisible()) {
528: children[1].setBounds(bounds);
529: getSash().setVisible(false);
530: return;
531: }
532: if (!children[1].isVisible()) {
533: children[0].setBounds(bounds);
534: getSash().setVisible(false);
535: return;
536: }
537:
538: bounds = Geometry.copy(bounds);
539:
540: boolean vertical = getSash().isVertical();
541:
542: // If this is a horizontal sash, flip coordinate systems so
543: // that we can eliminate special cases
544: if (!vertical) {
545: Geometry.flipXY(bounds);
546: }
547:
548: ChildSizes childSizes = computeChildSizes(bounds.width,
549: bounds.height, getSash().getLeft(), getSash()
550: .getRight(), bounds.width);
551:
552: getSash().setVisible(true);
553: getSash().setEnabled(childSizes.resizable);
554:
555: Rectangle leftBounds = new Rectangle(bounds.x, bounds.y,
556: childSizes.left, bounds.height);
557: Rectangle sashBounds = new Rectangle(leftBounds.x
558: + leftBounds.width, bounds.y, SASH_WIDTH, bounds.height);
559: Rectangle rightBounds = new Rectangle(sashBounds.x
560: + sashBounds.width, bounds.y, childSizes.right,
561: bounds.height);
562:
563: if (!vertical) {
564: Geometry.flipXY(leftBounds);
565: Geometry.flipXY(sashBounds);
566: Geometry.flipXY(rightBounds);
567: }
568:
569: getSash().setBounds(sashBounds);
570: children[0].setBounds(leftBounds);
571: children[1].setBounds(rightBounds);
572: }
573:
574: /* (non-Javadoc)
575: * @see org.eclipse.ui.internal.LayoutTree#createControl(org.eclipse.swt.widgets.Composite)
576: */
577: public void createControl(Composite parent) {
578: children[0].createControl(parent);
579: children[1].createControl(parent);
580: getSash().createControl(parent);
581:
582: super .createControl(parent);
583: }
584:
585: //Added by hudsonr@us.ibm.com - bug 19524
586:
587: public boolean isCompressible() {
588: return children[0].isCompressible()
589: || children[1].isCompressible();
590: }
591:
592: /**
593: * Returns 0 if there is no bias. Returns -1 if the first child should be of
594: * fixed size, and the second child should be compressed. Returns 1 if the
595: * second child should be of fixed size.
596: * @return the bias
597: */
598: public int getCompressionBias() {
599: boolean left = children[0].isCompressible();
600: boolean right = children[1].isCompressible();
601: if (left == right) {
602: return 0;
603: }
604: if (right) {
605: return -1;
606: }
607: return 1;
608: }
609:
610: boolean isLeftChild(LayoutTree toTest) {
611: return children[0] == toTest;
612: }
613:
614: LayoutTree getChild(boolean left) {
615: int index = left ? 0 : 1;
616: return (children[index]);
617: }
618:
619: /**
620: * Sets a child in this node
621: */
622: void setChild(boolean left, LayoutPart part) {
623: LayoutTree child = new LayoutTree(part);
624: setChild(left, child);
625: flushCache();
626: }
627:
628: /**
629: * Sets a child in this node
630: */
631: void setChild(boolean left, LayoutTree child) {
632: int index = left ? 0 : 1;
633: children[index] = child;
634: child.setParent(this );
635: flushCache();
636: }
637:
638: /**
639: * Returns a string representation of this object.
640: */
641: public String toString() {
642: String s = "<null>\n";//$NON-NLS-1$
643: if (part.getControl() != null) {
644: s = "<@" + part.getControl().hashCode() + ">\n";//$NON-NLS-2$//$NON-NLS-1$
645: }
646: String result = "["; //$NON-NLS-1$
647: if (children[0].getParent() != this ) {
648: result = result + "{" + children[0] + "}" + s;//$NON-NLS-2$//$NON-NLS-1$
649: } else {
650: result = result + children[0] + s;
651: }
652:
653: if (children[1].getParent() != this ) {
654: result = result + "{" + children[1] + "}]";//$NON-NLS-2$//$NON-NLS-1$
655: } else {
656: result = result + children[1] + "]";//$NON-NLS-1$
657: }
658: return result;
659: }
660:
661: /**
662: * Create the sashes if the children are visible
663: * and dispose it if they are not.
664: */
665: // public void updateSashes(Composite parent) {
666: // if (parent == null)
667: // return;
668: // children[0].updateSashes(parent);
669: // children[1].updateSashes(parent);
670: // if (children[0].isVisible() && children[1].isVisible())
671: // getSash().createControl(parent);
672: // else
673: // getSash().dispose();
674: // }
675: /**
676: * Writes a description of the layout to the given string buffer.
677: * This is used for drag-drop test suites to determine if two layouts are the
678: * same. Like a hash code, the description should compare as equal iff the
679: * layouts are the same. However, it should be user-readable in order to
680: * help debug failed tests. Although these are english readable strings,
681: * they should not be translated or equality tests will fail.
682: *
683: * @param buf
684: */
685: public void describeLayout(StringBuffer buf) {
686: if (!(children[0].isVisible())) {
687: if (!children[1].isVisible()) {
688: return;
689: }
690:
691: children[1].describeLayout(buf);
692: return;
693: }
694:
695: if (!children[1].isVisible()) {
696: children[0].describeLayout(buf);
697: return;
698: }
699:
700: buf.append("("); //$NON-NLS-1$
701: children[0].describeLayout(buf);
702:
703: buf.append(getSash().isVertical() ? "|" : "-"); //$NON-NLS-1$ //$NON-NLS-2$
704:
705: children[1].describeLayout(buf);
706: buf.append(")"); //$NON-NLS-1$
707: }
708:
709: }
|