/****************************************************************************** * 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; } }