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