LayoutTree.java :  » IDE-Eclipse » ui-workbench » org » eclipse » ui » internal » Java Open Source

Java Open Source » IDE Eclipse » ui workbench 
ui workbench » org » eclipse » ui » internal » LayoutTree.java
/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Randy Hudson <hudsonr@us.ibm.com>
 *     - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views
 *     Cagatay Kavukcuoglu <cagatayk@acm.org>
 *     - Fix for bug 10025 - Resizing views should not use height ratios
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.ArrayList;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.ISizeProvider;

/**
 * Implementation of a tree where the node is always a sash
 * and it always has two children. If a children is removed
 * the sash, i.e. the node, is removed as well and its other children
 * placed on its parent.
 */
public class LayoutTree implements ISizeProvider {
    /* The parent of this tree or null if it is the root */
    LayoutTreeNode parent;

    /* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */
    LayoutPart part;
    
    // Cached information
    private int cachedMinimumWidthHint = SWT.DEFAULT;
    private int cachedMinimumWidth = SWT.DEFAULT;
    private int cachedMinimumHeightHint = SWT.DEFAULT;
    private int cachedMinimumHeight = SWT.DEFAULT;
    private int cachedMaximumWidthHint = SWT.DEFAULT;
    private int cachedMaximumWidth = SWT.DEFAULT;
    private int cachedMaximumHeightHint = SWT.DEFAULT;
    private int cachedMaximumHeight = SWT.DEFAULT;
    
    // Cached size flags
    private boolean sizeFlagsDirty = true;
    private int widthSizeFlags = 0;
    private int heightSizeFlags = 0;
    
    // Cache statistics. For use in benchmarks and test suites only!
    public static int minCacheHits;
    public static int minCacheMisses;
    public static int maxCacheHits;
    public static int maxCacheMisses;
    
    private boolean forceLayout = true;
    private Rectangle currentBounds = new Rectangle(0,0,0,0);
    
    /**
     * Initialize this tree with its part.
     */
    public LayoutTree(LayoutPart part) {
        this.part = part;
    }

    /**
     * Add the relation ship between the children in the list
     * and returns the left children.
     */
    public LayoutPart computeRelation(ArrayList relations) {
        return part;
    }

    /**
     * Locates the part that intersects the given point
     * 
     * @param toFind
     * @return
     */
    public LayoutPart findPart(Point toFind) {
        return part;
    }
    
    /**
     * Dispose all Sashs in this tree
     */
    public void disposeSashes() {
    }
    
    /**
     * Find a LayoutPart in the tree and return its sub-tree. Returns
     * null if the child is not found.
     */
    public LayoutTree find(LayoutPart child) {
        if (part != child) {
      return null;
    }
        return this;
    }

    /**
     * Find the Left,Right,Top and Botton 
     * sashes around this tree and set them
     * in <code>sashes</code>
     */
    public void findSashes(PartPane.Sashes sashes) {
        if (getParent() == null) {
      return;
    }
        getParent().findSashes(this, sashes);
    }

    /**
     * Find the part that is in the bottom rigth possition.
     */
    public LayoutPart findBottomRight() {
        return part;
    }
    
    /**
     * Find a sash in the tree and return its sub-tree. Returns
     * null if the sash is not found.
     */
    public LayoutTreeNode findSash(LayoutPartSash sash) {
        return null;
    }

    /**
     * Return the bounds of this tree which is the rectangle that
     * contains all Controls in this tree.
     */
    public final Rectangle getBounds() {
        return Geometry.copy(currentBounds);
    }
    
    /**
     * Subtracts two integers. If a is INFINITE, this is treated as
     * positive infinity. 
     * 
     * @param a a positive integer or INFINITE indicating positive infinity
     * @param b a positive integer (may not be INFINITE)
     * @return a - b, or INFINITE if a == INFINITE
     * @since 3.1
     */
    public static int subtract(int a, int b) {
        Assert.isTrue(b >= 0 && b < INFINITE);
        
      return add(a, -b);
    }
    
    /**
     * Adds two positive integers. Treates INFINITE as positive infinity.
     * 
     * @param a a positive integer
     * @param b a positive integer
     * @return a + b, or INFINITE if a or b are positive infinity
     * @since 3.1
     */
    public static int add(int a, int b) {
      if (a == INFINITE || b == INFINITE) {
        return INFINITE;
      }
      
      return a + b;
    }
    
    /**
     * Asserts that toCheck is a positive integer less than INFINITE / 2 or equal
     * to INFINITE. Many of the methods of this class use positive integers as sizes,
     * with INFINITE indicating positive infinity. This picks up accidental addition or
     * subtraction from infinity. 
     * 
     * @param toCheck integer to validate
     * @since 3.1
     */
    public static void assertValidSize(int toCheck) {
      Assert.isTrue(toCheck >= 0 && (toCheck == INFINITE || toCheck < INFINITE / 2));
    }
    
    /**
     * Computes the preferred size for this object. The interpretation of the result depends on the flags returned
     * by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize
     * or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to 
     * doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or
     * doComputePreferredSize to specialize the return value. 
     * 
     * @see LayoutPart#computePreferredSize(boolean, int, int, int)
     */
    public final int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
      assertValidSize(availableParallel);
      assertValidSize(availablePerpendicular);
      assertValidSize(preferredParallel);
      
      if (!isVisible()) {
        return 0;
      }

      if (availableParallel == 0) {
        return 0;
      }

      if (preferredParallel == 0) {
        return Math.min(availableParallel, computeMinimumSize(width, availablePerpendicular));
      } else if (preferredParallel == INFINITE && availableParallel == INFINITE) {
        return computeMaximumSize(width, availablePerpendicular);
      }
      
      // Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum
      // size, simply return the preferred size
      if (!hasSizeFlag(width, SWT.FILL)) {
          return preferredParallel;
      }
      
      int result = doComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);

      return result;
    }
    
    /**
     * Returns the size flags for this tree. 
     * 
     * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
     * 
   * @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false)
   * @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean)
   */
  protected int doGetSizeFlags(boolean width) {
    return part.getSizeFlags(width);
  }

  /**
   * Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int)
   * 
   * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
   * 
   * @since 3.1
   */
  protected int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
      int result = Math.min(availableParallel, 
          part.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel));

      assertValidSize(result);
      return result;      
    }
    
  /**
   * Returns the minimum size for this subtree. Equivalent to calling 
   * computePreferredSize(width, INFINITE, availablePerpendicular, 0).
   * Returns a cached value if possible or defers to doComputeMinimumSize otherwise.
   * Subclasses should overload doComputeMinimumSize if they want to specialize the
   * return value.
   * 
   * @param width true iff computing the minimum width, false iff computing the minimum height
   * @param availablePerpendicular available space (pixels) perpendicular to the dimension 
   * being computed. This is a height when computing a width, or a width when computing a height.
   * 
   * @see LayoutPart#computePreferredSize(boolean, int, int, int)
   */
    public final int computeMinimumSize(boolean width, int availablePerpendicular) {
      assertValidSize(availablePerpendicular);
      
      // Optimization: if this subtree has no minimum size, then always return 0 as its
      // minimum size.
        if (!hasSizeFlag(width, SWT.MIN)) {
            return 0;
        }
      
      // If this subtree doesn't contain any wrapping controls (ie: they don't care
      // about their perpendicular size) then force the perpendicular
      // size to be INFINITE. This ensures that we will get a cache hit
      // every time for non-wrapping controls.
      if (!hasSizeFlag(width, SWT.WRAP)) {
        availablePerpendicular = INFINITE;
      }
      
      if (width) {
          // Check if we have a cached width measurement (we can only return a cached
          // value if we computed it for the same height)
        if (cachedMinimumWidthHint == availablePerpendicular) {
          minCacheHits++;
          return cachedMinimumWidth;
        }
        
        // Recompute the minimum width and store it in the cache
        
        minCacheMisses++;
         
        int result = doComputeMinimumSize(width, availablePerpendicular);
        cachedMinimumWidth = result;
        cachedMinimumWidthHint = availablePerpendicular;
        return result;
        
      } else {
          // Check if we have a cached height measurement (we can only return a cached
          // value if we computed it for the same width)
        if (cachedMinimumHeightHint == availablePerpendicular) {
          minCacheHits++;
          return cachedMinimumHeight;
        }
        
        // Recompute the minimum width and store it in the cache
        minCacheMisses++;
        
        int result = doComputeMinimumSize(width, availablePerpendicular);
        cachedMinimumHeight = result;
        cachedMinimumHeightHint = availablePerpendicular;
        return result;
      }
    }
    
    /**
     * For use in benchmarks and test suites only. Displays cache utilization statistics for all
     * LayoutTree instances.
     * 
     * @since 3.1
     */
    public static void printCacheStatistics() {
      System.out.println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
          minCacheHits * 100 / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$
      System.out.println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
          maxCacheHits * 100 / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$
    }
    
  public int doComputeMinimumSize(boolean width, int availablePerpendicular) {
    int result = doComputePreferredSize(width, INFINITE, availablePerpendicular, 0);
    assertValidSize(result);
    return result;
  }

    public final int computeMaximumSize(boolean width, int availablePerpendicular) {
      assertValidSize(availablePerpendicular);
      
      // Optimization: if this subtree has no maximum size, then always return INFINITE as its
      // maximum size.
        if (!hasSizeFlag(width, SWT.MAX)) {
            return INFINITE;
        }
      
      // If this subtree doesn't contain any wrapping controls (ie: they don't care
      // about their perpendicular size) then force the perpendicular
      // size to be INFINITE. This ensures that we will get a cache hit
      // every time.
      if (!hasSizeFlag(width, SWT.WRAP)) {
        availablePerpendicular = INFINITE;
      }
      
      if (width) {          
          // Check if we have a cached width measurement (we can only return a cached
          // value if we computed it for the same height)
        if (cachedMaximumWidthHint == availablePerpendicular) {
          maxCacheHits++;
          return cachedMaximumWidth;
        }
        
        maxCacheMisses++;
        
        // Recompute the maximum width and store it in the cache
        int result = doComputeMaximumSize(width, availablePerpendicular);
        cachedMaximumWidth = result;
        cachedMaximumWidthHint = availablePerpendicular;
        return result;
        
      } else {
          // Check if we have a cached height measurement
        if (cachedMaximumHeightHint == availablePerpendicular) {
          maxCacheHits++;
          return cachedMaximumHeight;
        }
        
        maxCacheMisses++;
        
        // Recompute the maximum height and store it in the cache
        int result = doComputeMaximumSize(width, availablePerpendicular);
        cachedMaximumHeight = result;
        cachedMaximumHeightHint = availablePerpendicular;
        return result;
      }
    }
    
    protected int doComputeMaximumSize(boolean width, int availablePerpendicular) {        
      return doComputePreferredSize(width, INFINITE, availablePerpendicular, INFINITE);
    }
    
    /**
     * Called to flush any cached information in this tree and its parents.
     */
    public void flushNode() {
        
        // Clear cached sizes
        cachedMinimumWidthHint = SWT.DEFAULT;
        cachedMinimumWidth = SWT.DEFAULT;
        cachedMinimumHeightHint = SWT.DEFAULT;
        cachedMinimumHeight = SWT.DEFAULT;
        cachedMaximumWidthHint = SWT.DEFAULT;
        cachedMaximumWidth = SWT.DEFAULT;
        cachedMaximumHeightHint = SWT.DEFAULT;
        cachedMaximumHeight = SWT.DEFAULT;
        
        // Flags may have changed. Ensure that they are recomputed the next time around
        sizeFlagsDirty = true;
        
        // The next setBounds call should trigger a layout even if set to the same bounds since
        // one of the children has changed.
        forceLayout = true;
    }
    
    /**
     * Flushes all cached information about this node and all of its children.
     * This should be called if something may have caused all children to become
     * out of synch with their cached information (for example, if a lot of changes
     * may have happened without calling flushCache after each change)
     * 
     * @since 3.1
     */
    public void flushChildren() {
        flushNode();
    }
    
    /**
     * Flushes all cached information about this node and all of its ancestors.
     * This should be called when a single child changes.
     * 
     * @since 3.1
     */
    public final void flushCache() {
        flushNode();
        
      if (parent != null) {
        parent.flushCache();
      }        
    }
    
    public final int getSizeFlags(boolean width) {
        if (sizeFlagsDirty) {
            widthSizeFlags = doGetSizeFlags(true);
            heightSizeFlags = doGetSizeFlags(false);
            sizeFlagsDirty = false;
        }
        
        return width ? widthSizeFlags : heightSizeFlags;
    }
        
    /**
     * Returns the parent of this tree or null if it is the root.
     */
    public LayoutTreeNode getParent() {
        return parent;
    }

    /**
     * Inserts a new child on the tree. The child will be placed beside 
     * the <code>relative</code> child. Returns the new root of the tree.
     */
    public LayoutTree insert(LayoutPart child, boolean left,
            LayoutPartSash sash, LayoutPart relative) {
        LayoutTree relativeChild = find(relative);
        LayoutTreeNode node = new LayoutTreeNode(sash);
        if (relativeChild == null) {
            //Did not find the relative part. Insert beside the root.
            node.setChild(left, child);
            node.setChild(!left, this);
            return node;
        } else {
            LayoutTreeNode oldParent = relativeChild.getParent();
            node.setChild(left, child);
            node.setChild(!left, relativeChild);
            if (oldParent == null) {
                //It was the root. Return a new root.
                return node;
            }
            oldParent.replaceChild(relativeChild, node);
            return this;
        }
    }

    /**
     * Returns true if this tree can be compressed and expanded.
     * @return true if springy
     */
    public boolean isCompressible() {
        //Added for bug 19524
        return part.isCompressible();
    }

    /**
     * Returns true if this tree has visible parts otherwise returns false.
     */
    public boolean isVisible() {
        return !(part instanceof PartPlaceholder);
    }

    /**
     * Recompute the ratios in this tree.
     */
    public void recomputeRatio() {
    }

    /**
     * Find a child in the tree and remove it and its parent.
     * The other child of its parent is placed on the parent's parent.
     * Returns the new root of the tree.
     */
    public LayoutTree remove(LayoutPart child) {
        LayoutTree tree = find(child);
        if (tree == null) {
      return this;
    }
        LayoutTreeNode oldParent = tree.getParent();
        if (oldParent == null) {
            //It was the root and the only child of this tree
            return null;
        }
        if (oldParent.getParent() == null) {
      return oldParent.remove(tree);
    }

        oldParent.remove(tree);
        return this;
    }

    /**
     * Sets the bounds of this node. If the bounds have changed or any children have
     * changed then the children will be recursively layed out. This implementation
     * filters out redundant calls and delegates to doSetBounds to layout the children. 
     * Subclasses should overload doSetBounds to lay out their children.  
     * 
     * @param bounds new bounds of the tree
     */
    public final void setBounds(Rectangle bounds) {
        if (!bounds.equals(currentBounds) || forceLayout) {
            currentBounds = Geometry.copy(bounds);
            
            doSetBounds(currentBounds);
            forceLayout = false;
        } 
    }
    
    /**
     * Resize the parts on this tree to fit in <code>bounds</code>.
     */
    protected void doSetBounds(Rectangle bounds) {
        part.setBounds(bounds);
    }

    /**
     * Set the parent of this tree.
     */
    void setParent(LayoutTreeNode parent) {
        this.parent = parent;
    }

    /**
     * Set the part of this leaf
     */
    void setPart(LayoutPart part) {
        this.part = part;
        flushCache();
    }

    /**
     * Returns a string representation of this object.
     */
    public String toString() {
        return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$
    }

    /**
     * Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the 
     * LayoutParts that are being arranged by the LayoutTree. 
     * 
     * @param parent
     * @since 3.1
     */
    public void createControl(Composite parent) {        
    }
        
    /**
     * Writes a description of the layout to the given string buffer.
     * This is used for drag-drop test suites to determine if two layouts are the
     * same. Like a hash code, the description should compare as equal iff the
     * layouts are the same. However, it should be user-readable in order to
     * help debug failed tests. Although these are english readable strings,
     * they should not be translated or equality tests will fail.
     * <p>
     * This is only intended for use by test suites.
     * </p>
     * 
     * @param buf
     */
    public void describeLayout(StringBuffer buf) {
        part.describeLayout(buf);
    }

    /**
     * This is a shorthand method that checks if the tree contains the
     * given size flag. For example, hasSizeFlag(false, SWT.MIN) returns
     * true iff the receiver enforces a minimum height, or 
     * hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to
     * know its height when computing its preferred width.
     * 
     * @param vertical 
     * @return
     * @since 3.1
     */
    public final boolean hasSizeFlag(boolean width, int flag) {        
        return (getSizeFlags(width) & flag) != 0;
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.