| /****************************************************************************** |
| * Copyright (c) 2002, 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 |
| ****************************************************************************/ |
| |
| package org.eclipse.gmf.runtime.draw2d.ui.figures; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.ToolbarLayout; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Insets; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| |
| /** |
| * An extended toolbar layout that supports the following additional features: |
| * 1- The ability to stretch the major axis |
| * 2- The ability to reverse the children in layout |
| * 3- The ability to ignore invisible children |
| * 4- The ability to set ratio constraints on children (in major axis) |
| * |
| * @author melaasar |
| */ |
| public class ConstrainedToolbarLayout extends ToolbarLayout { |
| |
| /** |
| * Whether to stretch the major axis |
| */ |
| private boolean stretchMajorAxis = true; |
| /** |
| * Whether to reverse children for layout |
| */ |
| private boolean reversed = false; |
| /** |
| * Whether to ignore invisible children |
| */ |
| private boolean ignoreInvisibleChildren = true; |
| /** |
| * The constrains map |
| */ |
| private Map constraints; |
| |
| /** |
| * Creates a new vertical ConstrainedToolbarLayout |
| */ |
| public ConstrainedToolbarLayout() { |
| this(false); |
| } |
| |
| /** |
| * Creates a new ConstrainedToolbarLayout with a given orientation |
| * |
| * @param isHorizontal Whether the layout is horizontal |
| */ |
| public ConstrainedToolbarLayout(boolean isHorizontal) { |
| super(isHorizontal); |
| setStretchMinorAxis(true); |
| setStretchMajorAxis(true); |
| setMinorAlignment(ALIGN_CENTER); |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.AbstractLayout#calculatePreferredSize(org.eclipse.draw2d.IFigure, int, int) |
| */ |
| protected Dimension calculatePreferredSize( |
| IFigure container, |
| int wHint, |
| int hHint) { |
| Insets insets = container.getInsets(); |
| if (!container.isVisible()) |
| return new Dimension(insets.getWidth(),insets.getHeight()); |
| if (isHorizontal()) { |
| wHint = -1; |
| if (hHint >= 0) |
| hHint = Math.max(0, hHint - insets.getHeight()); |
| } else { |
| hHint = -1; |
| if (wHint >= 0) |
| wHint = Math.max(0, wHint - insets.getWidth()); |
| } |
| |
| List children = getChildren(container); |
| Dimension prefSize = |
| calculateChildrenSize(children, wHint, hHint, true); |
| // Do a second pass, if necessary |
| if (wHint >= 0 && prefSize.width > wHint) { |
| prefSize = |
| calculateChildrenSize(children, prefSize.width, hHint, true); |
| } else if (hHint >= 0 && prefSize.width > hHint) { |
| prefSize = |
| calculateChildrenSize(children, wHint, prefSize.width, true); |
| } |
| |
| prefSize.height += Math.max(0, children.size() - 1) * spacing; |
| return transposer |
| .t(prefSize) |
| .expand(insets.getWidth(), insets.getHeight()) |
| .union(getBorderPreferredSize(container)); |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.AbstractHintLayout#calculateMinimumSize(org.eclipse.draw2d.IFigure, int, int) |
| */ |
| public Dimension calculateMinimumSize( |
| IFigure container, |
| int wHint, |
| int hHint) { |
| Insets insets = container.getInsets(); |
| if (!container.isVisible()) |
| return new Dimension(insets.getWidth(),insets.getHeight()); |
| |
| if (isHorizontal()) { |
| wHint = -1; |
| if (hHint >= 0) |
| hHint = Math.max(0, hHint - insets.getHeight()); |
| } else { |
| hHint = -1; |
| if (wHint >= 0) |
| wHint = Math.max(0, wHint - insets.getWidth()); |
| } |
| |
| List children = getChildren(container); |
| Dimension minSize = |
| calculateChildrenSize(children, wHint, hHint, false); |
| // Do a second pass, if necessary |
| if (wHint >= 0 && minSize.width > wHint) { |
| minSize = |
| calculateChildrenSize(children, minSize.width, hHint, false); |
| } else if (hHint >= 0 && minSize.width > hHint) { |
| minSize = |
| calculateChildrenSize(children, wHint, minSize.width, false); |
| } |
| |
| minSize.height += Math.max(0, children.size() - 1) * spacing; |
| return transposer |
| .t(minSize) |
| .expand(insets.getWidth(), insets.getHeight()) |
| .union(getBorderPreferredSize(container)); |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.LayoutManager#layout(IFigure) |
| */ |
| public void layout(IFigure parent) { |
| if (!parent.isVisible()) |
| return; |
| List children = getChildren(parent); |
| int numChildren = children.size(); |
| Rectangle clientArea = transposer.t(parent.getClientArea()); |
| int x = clientArea.x; |
| int y = clientArea.y; |
| int availableHeight = clientArea.height; |
| |
| Dimension prefSizes[] = new Dimension[numChildren]; |
| Dimension minSizes[] = new Dimension[numChildren]; |
| Dimension maxSizes[] = new Dimension[numChildren]; |
| |
| // Calculate the width and height hints. If it's a vertical ToolBarLayout, |
| // then ignore the height hint (set it to -1); otherwise, ignore the |
| // width hint. These hints will be passed to the children of the parent |
| // figure when getting their preferred size. |
| int wHint = -1; |
| int hHint = -1; |
| if (isHorizontal()) { |
| hHint = parent.getClientArea(Rectangle.SINGLETON).height; |
| } else { |
| wHint = parent.getClientArea(Rectangle.SINGLETON).width; |
| } |
| |
| /* |
| * Calculate sum of preferred heights of all children(totalHeight). |
| * Calculate sum of minimum heights of all children(minHeight). |
| * Cache Preferred Sizes and Minimum Sizes of all children. |
| * |
| * totalHeight is the sum of the preferred heights of all children |
| * totalMinHeight is the sum of the minimum heights of all children |
| * prefMinSumHeight is the sum of the difference between all children's |
| * preferred heights and minimum heights. (This is used as a ratio to |
| * calculate how much each child will shrink). |
| */ |
| IFigure child; |
| int totalHeight = 0; |
| int totalMinHeight = 0; |
| double totalMaxHeight = 0; |
| int prefMinSumHeight = 0; |
| double prefMaxSumHeight = 0; |
| |
| for (int i = 0; i < numChildren; i++) { |
| child = (IFigure) children.get(i); |
| |
| prefSizes[i] = transposer.t(child.getPreferredSize(wHint, hHint)); |
| minSizes[i] = transposer.t(child.getMinimumSize(wHint, hHint)); |
| maxSizes[i] = transposer.t(child.getMaximumSize()); |
| |
| if (getConstraint(child) != null) { |
| double ratio = ((Double) getConstraint(child)).doubleValue(); |
| int prefHeight = (int) (ratio * availableHeight); |
| prefHeight = Math.max(prefHeight, minSizes[i].height); |
| prefHeight = Math.min(prefHeight, maxSizes[i].height); |
| prefSizes[i].height = prefHeight; |
| } |
| |
| totalHeight += prefSizes[i].height; |
| totalMinHeight += minSizes[i].height; |
| totalMaxHeight += maxSizes[i].height; |
| } |
| totalHeight += (numChildren - 1) * spacing; |
| totalMinHeight += (numChildren - 1) * spacing; |
| totalMaxHeight += (numChildren - 1) * spacing; |
| prefMinSumHeight = totalHeight - totalMinHeight; |
| prefMaxSumHeight = totalMaxHeight - totalHeight; |
| |
| /* |
| * The total amount that the children must be shrunk is the |
| * sum of the preferred Heights of the children minus |
| * Max(the available area and the sum of the minimum heights of the children). |
| * |
| * amntShrinkHeight is the combined amount that the children must shrink |
| * amntShrinkCurrentHeight is the amount each child will shrink respectively |
| */ |
| int amntShrinkHeight = |
| totalHeight - Math.max(availableHeight, totalMinHeight); |
| |
| for (int i = 0; i < numChildren; i++) { |
| int amntShrinkCurrentHeight = 0; |
| int prefHeight = prefSizes[i].height; |
| int minHeight = minSizes[i].height; |
| int maxHeight = maxSizes[i].height; |
| int prefWidth = prefSizes[i].width; |
| int minWidth = minSizes[i].width; |
| int maxWidth = maxSizes[i].width; |
| Rectangle newBounds = new Rectangle(x, y, prefWidth, prefHeight); |
| |
| child = (IFigure) children.get(i); |
| if (getStretchMajorAxis()) { |
| if (amntShrinkHeight > 0 && prefMinSumHeight != 0) |
| amntShrinkCurrentHeight = (int) ((long) (prefHeight - minHeight) |
| * amntShrinkHeight / (prefMinSumHeight)); |
| else if (amntShrinkHeight < 0 && totalHeight != 0) |
| amntShrinkCurrentHeight = |
| (int) (((maxHeight - prefHeight) / prefMaxSumHeight) |
| * amntShrinkHeight); |
| } |
| |
| int width = Math.min(prefWidth, maxWidth); |
| if (matchWidth) |
| width = maxWidth; |
| width = Math.max(minWidth, Math.min(clientArea.width, width)); |
| newBounds.width = width; |
| |
| int adjust = clientArea.width - width; |
| switch (minorAlignment) { |
| case ALIGN_TOPLEFT : |
| adjust = 0; |
| break; |
| case ALIGN_CENTER : |
| adjust /= 2; |
| break; |
| case ALIGN_BOTTOMRIGHT : |
| break; |
| } |
| newBounds.x += adjust; |
| if (newBounds.height - amntShrinkCurrentHeight > maxHeight) |
| amntShrinkCurrentHeight = newBounds.height - maxHeight; |
| newBounds.height -= amntShrinkCurrentHeight; |
| child.setBounds(transposer.t(newBounds)); |
| |
| amntShrinkHeight -= amntShrinkCurrentHeight; |
| prefMinSumHeight -= (prefHeight - minHeight); |
| prefMaxSumHeight -= (maxHeight - prefHeight); |
| totalHeight -= prefHeight; |
| y += newBounds.height + spacing; |
| } |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.LayoutManager#getConstraint(org.eclipse.draw2d.IFigure) |
| */ |
| public Object getConstraint(IFigure child) { |
| if (constraints != null) |
| return constraints.get(child); |
| return null; |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.LayoutManager#setConstraint(org.eclipse.draw2d.IFigure, java.lang.Object) |
| */ |
| public void setConstraint(IFigure child, Object constraint) { |
| if (!(constraint instanceof Double)) |
| return; |
| if (constraint instanceof Double) { |
| Double c = (Double) constraint; |
| super.setConstraint(child, constraint); |
| if (constraints == null) |
| constraints = new HashMap(); |
| if (constraint == null || c.doubleValue() <= 0) { |
| if (constraints.containsKey(child)) |
| constraints.remove(child); |
| } else |
| constraints.put(child, constraint); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.draw2d.LayoutManager#remove(org.eclipse.draw2d.IFigure) |
| */ |
| public void remove(IFigure child) { |
| super.remove(child); |
| setConstraint(child, null); |
| } |
| |
| /** |
| * Sets whether to stretch the major axis or not |
| * |
| * @param stretch Whether to stretch the major axis or not |
| */ |
| public void setStretchMajorAxis(boolean stretch) { |
| stretchMajorAxis = stretch; |
| } |
| |
| /** |
| * @return Whether the stretch major axis is on |
| */ |
| public boolean getStretchMajorAxis() { |
| return stretchMajorAxis; |
| } |
| |
| /** |
| * Sets whether to reverse children or not |
| * |
| * @param reversed Whether to reverse children or not |
| */ |
| public void setReversed(boolean reversed) { |
| this.reversed = reversed; |
| } |
| |
| /** |
| * @return Whether the reverse children or not |
| */ |
| public boolean isReversed() { |
| return reversed; |
| } |
| |
| /** |
| * Sets whether to ignore invisible children or not |
| * |
| * @param ignoreInvisibleChildren Whether to ignore invisible children or not |
| */ |
| public void setIgnoreInvisibleChildren(boolean ignoreInvisibleChildren) { |
| this.ignoreInvisibleChildren = ignoreInvisibleChildren; |
| } |
| |
| /** |
| * @return Whether to ignore invisible children or not |
| */ |
| public boolean getIgnoreInvisibleChildren() { |
| return ignoreInvisibleChildren; |
| } |
| |
| /** |
| * Calculates either the preferred or minimum children size |
| */ |
| private Dimension calculateChildrenSize( |
| List children, |
| int wHint, |
| int hHint, |
| boolean preferred) { |
| Dimension childSize; |
| IFigure child; |
| int height = 0, width = 0; |
| for (int i = 0; i < children.size(); i++) { |
| child = (IFigure) children.get(i); |
| childSize = |
| transposer.t( |
| preferred |
| ? child.getPreferredSize(wHint, hHint) |
| : child.getMinimumSize(wHint, hHint)); |
| height += childSize.height; |
| width = Math.max(width, childSize.width); |
| } |
| return new Dimension(width, height); |
| } |
| |
| /** |
| * Gets the list of children after applying the layout options of |
| * ignore invisible children & reverse children |
| */ |
| private List getChildren(IFigure container) { |
| List children = new ArrayList(container.getChildren()); |
| if (getIgnoreInvisibleChildren()) { |
| Iterator iter = children.iterator(); |
| while (iter.hasNext()) { |
| IFigure f = (IFigure) iter.next(); |
| if (!f.isVisible()) |
| iter.remove(); |
| } |
| } |
| if (isReversed()) |
| Collections.reverse(children); |
| return children; |
| } |
| |
| } |