blob: 9bf39b358e32958812185e4fbf1001ad056ea9c4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 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.draw2d;
import java.util.Iterator;
import java.util.List;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.geometry.Transposer;
/**
* Lays out children in rows or columns, wrapping when the current row/column is
* filled. The aligment and spacing of rows in the parent can be configured. The
* aligment and spacing of children within a row can be configured.
*/
public class FlowLayout extends AbstractHintLayout {
/** Constant to specify components to be aligned in the center */
public static final int ALIGN_CENTER = 0;
/** Constant to specify components to be aligned on the left/top */
public static final int ALIGN_LEFTTOP = 1;
/** Constant to specify components to be aligned on the right/bottom */
public static final int ALIGN_RIGHTBOTTOM = 2;
/** Constant to specify components should be layed out horizontally */
public static final boolean HORIZONTAL = true;
/** Constant to specify components should be layed out vertically */
public static final boolean VERTICAL = false;
/** The horizontal property. */
protected boolean horizontal = true;
/**
* The property that determines whether leftover space at the end of a
* row/column should be filled by the last item in that row/column.
*/
protected boolean fill = false;
/** The transposer used in converting horizontal layout to vertical. */
protected Transposer transposer;
{
transposer = new Transposer();
transposer.setEnabled(!horizontal);
}
/** The alignment along the major axis. */
protected int majorAlignment = ALIGN_LEFTTOP;
/** The alignment along the minor axis. */
protected int minorAlignment = ALIGN_LEFTTOP;
/** The spacing along the minor axis. */
protected int minorSpacing = 5;
/** The spacing along the major axis. */
protected int majorSpacing = 5;
protected WorkingData data = null;
/**
* Holds the necessary information for layout calculations.
*/
protected class WorkingData {
public int rowHeight, rowWidth, rowCount, rowX, rowY, maxWidth;
public Rectangle bounds[], area;
public IFigure row[];
}
/**
* Constructs a FlowLayout with horizontal orientation.
*
* @since 2.0
*/
public FlowLayout() {
}
/**
* Constructs a FlowLayout whose orientation is given in the input.
*
* @param isHorizontal
* <code>true</code> if the layout should be horizontal
* @since 2.0
*/
public FlowLayout(boolean isHorizontal) {
setHorizontal(isHorizontal);
}
/**
* @see org.eclipse.draw2d.AbstractLayout#calculatePreferredSize(IFigure,
* int, int)
*/
protected Dimension calculatePreferredSize(IFigure container, int wHint,
int hHint) {
// Subtract out the insets from the hints
if (wHint > -1)
wHint = Math.max(0, wHint - container.getInsets().getWidth());
if (hHint > -1)
hHint = Math.max(0, hHint - container.getInsets().getHeight());
// Figure out the new hint that we are interested in based on the
// orientation
// Ignore the other hint (by setting it to -1). NOTE: The children of
// the
// parent figure will then be asked to ignore that hint as well.
int maxWidth;
if (isHorizontal()) {
maxWidth = wHint;
hHint = -1;
} else {
maxWidth = hHint;
wHint = -1;
}
if (maxWidth < 0) {
maxWidth = Integer.MAX_VALUE;
}
// The preferred dimension that is to be calculated and returned
Dimension prefSize = new Dimension();
List children = container.getChildren();
int width = 0;
int height = 0;
IFigure child;
Dimension childSize;
// Build the sizes for each row, and update prefSize accordingly
for (int i = 0; i < children.size(); i++) {
child = (IFigure) children.get(i);
childSize = transposer.t(getChildSize(child, wHint, hHint));
if (i == 0) {
width = childSize.width;
height = childSize.height;
} else if (width + childSize.width + getMinorSpacing() > maxWidth) {
// The current row is full, start a new row.
prefSize.height += height + getMajorSpacing();
prefSize.width = Math.max(prefSize.width, width);
width = childSize.width;
height = childSize.height;
} else {
// The current row can fit another child.
width += childSize.width + getMinorSpacing();
height = Math.max(height, childSize.height);
}
}
// Flush out the last row's data
prefSize.height += height;
prefSize.width = Math.max(prefSize.width, width);
// Transpose the dimension back, and compensate for the border.
prefSize = transposer.t(prefSize);
prefSize.width += container.getInsets().getWidth();
prefSize.height += container.getInsets().getHeight();
prefSize.union(getBorderPreferredSize(container));
return prefSize;
}
/**
* Provides the given child's preferred size.
*
* @param child
* the Figure whose preferred size needs to be calculated
* @param wHint
* the width hint
* @param hHint
* the height hint
* @return the child's preferred size
*/
protected Dimension getChildSize(IFigure child, int wHint, int hHint) {
return child.getPreferredSize(wHint, hHint);
}
/**
* Returns the alignment used for an entire row/column.
* <P>
* Possible values are :
* <ul>
* <li>{@link #ALIGN_CENTER}
* <li>{@link #ALIGN_LEFTTOP}
* <li>{@link #ALIGN_RIGHTBOTTOM}
* </ul>
*
* @return the major alignment
* @since 2.0
*/
public int getMajorAlignment() {
return majorAlignment;
}
/**
* Returns the spacing in pixels to be used between children in the
* direction parallel to the layout's orientation.
*
* @return the major spacing
*/
public int getMajorSpacing() {
return majorSpacing;
}
/**
* Returns the alignment used for children within a row/column.
* <P>
* Possible values are :
* <ul>
* <li>{@link #ALIGN_CENTER}
* <li>{@link #ALIGN_LEFTTOP}
* <li>{@link #ALIGN_RIGHTBOTTOM}
* </ul>
*
* @return the minor alignment
* @since 2.0
*/
public int getMinorAlignment() {
return minorAlignment;
}
/**
* Returns the spacing to be used between children within a row/column.
*
* @return the minor spacing
*/
public int getMinorSpacing() {
return minorSpacing;
}
/**
* Initializes the state of row data, which is internal to the layout
* process.
*/
protected void initRow() {
data.rowX = 0;
data.rowHeight = 0;
data.rowWidth = 0;
data.rowCount = 0;
}
/**
* Initializes state data for laying out children, based on the Figure given
* as input.
*
* @param parent
* the parent figure
* @since 2.0
*/
protected void initVariables(IFigure parent) {
data.row = new IFigure[parent.getChildren().size()];
data.bounds = new Rectangle[data.row.length];
data.maxWidth = data.area.width;
}
/**
* Returns <code>true</code> if the orientation of the layout is horizontal.
*
* @return <code>true</code> if the orientation of the layout is horizontal
* @since 2.0
*/
public boolean isHorizontal() {
return horizontal;
}
/**
* @see org.eclipse.draw2d.AbstractHintLayout#isSensitiveHorizontally(IFigure)
*/
protected boolean isSensitiveHorizontally(IFigure parent) {
return isHorizontal();
}
/**
* @see org.eclipse.draw2d.AbstractHintLayout#isSensitiveVertically(IFigure)
*/
protected boolean isSensitiveVertically(IFigure parent) {
return !isHorizontal();
}
/**
* @see org.eclipse.draw2d.LayoutManager#layout(IFigure)
*/
public void layout(IFigure parent) {
data = new WorkingData();
Rectangle relativeArea = parent.getClientArea();
data.area = transposer.t(relativeArea);
Iterator iterator = parent.getChildren().iterator();
int dx;
// Calculate the hints to be passed to children
int wHint = -1;
int hHint = -1;
if (isHorizontal())
wHint = parent.getClientArea().width;
else
hHint = parent.getClientArea().height;
initVariables(parent);
initRow();
int i = 0;
while (iterator.hasNext()) {
IFigure f = (IFigure) iterator.next();
Dimension pref = transposer.t(getChildSize(f, wHint, hHint));
Rectangle r = new Rectangle(0, 0, pref.width, pref.height);
if (data.rowCount > 0) {
if (data.rowWidth + pref.width > data.maxWidth)
layoutRow(parent);
}
r.x = data.rowX;
r.y = data.rowY;
dx = r.width + getMinorSpacing();
data.rowX += dx;
data.rowWidth += dx;
data.rowHeight = Math.max(data.rowHeight, r.height);
data.row[data.rowCount] = f;
data.bounds[data.rowCount] = r;
data.rowCount++;
i++;
}
if (data.rowCount != 0)
layoutRow(parent);
data = null;
}
/**
* Layouts one row of components. This is done based on the layout's
* orientation, minor alignment and major alignment.
*
* @param parent
* the parent figure
* @since 2.0
*/
protected void layoutRow(IFigure parent) {
int majorAdjustment = 0;
int minorAdjustment = 0;
int correctMajorAlignment = majorAlignment;
int correctMinorAlignment = minorAlignment;
majorAdjustment = data.area.width - data.rowWidth + getMinorSpacing();
switch (correctMajorAlignment) {
case ALIGN_LEFTTOP:
majorAdjustment = 0;
break;
case ALIGN_CENTER:
majorAdjustment /= 2;
break;
case ALIGN_RIGHTBOTTOM:
break;
}
for (int j = 0; j < data.rowCount; j++) {
if (fill) {
data.bounds[j].height = data.rowHeight;
} else {
minorAdjustment = data.rowHeight - data.bounds[j].height;
switch (correctMinorAlignment) {
case ALIGN_LEFTTOP:
minorAdjustment = 0;
break;
case ALIGN_CENTER:
minorAdjustment /= 2;
break;
case ALIGN_RIGHTBOTTOM:
break;
}
data.bounds[j].y += minorAdjustment;
}
data.bounds[j].x += majorAdjustment;
setBoundsOfChild(parent, data.row[j], transposer.t(data.bounds[j]));
}
data.rowY += getMajorSpacing() + data.rowHeight;
initRow();
}
/**
* Sets the given bounds for the child figure input.
*
* @param parent
* the parent figure
* @param child
* the child figure
* @param bounds
* the size of the child to be set
* @since 2.0
*/
protected void setBoundsOfChild(IFigure parent, IFigure child,
Rectangle bounds) {
parent.getClientArea(Rectangle.SINGLETON);
bounds.translate(Rectangle.SINGLETON.x, Rectangle.SINGLETON.y);
child.setBounds(bounds);
}
/**
* Sets flag based on layout orientation. If in horizontal orientation, all
* figures will have the same height. If in vertical orientation, all
* figures will have the same width.
*
* @param value
* fill state desired
* @since 2.0
*/
public void setStretchMinorAxis(boolean value) {
fill = value;
}
/**
* Sets the orientation of the layout.
*
* @param flag
* <code>true</code> if this layout should be horizontal
* @since 2.0
*/
public void setHorizontal(boolean flag) {
if (horizontal == flag)
return;
invalidate();
horizontal = flag;
transposer.setEnabled(!horizontal);
}
/**
* Sets the alignment for an entire row/column within the parent figure.
* <P>
* Possible values are :
* <ul>
* <li>{@link #ALIGN_CENTER}
* <li>{@link #ALIGN_LEFTTOP}
* <li>{@link #ALIGN_RIGHTBOTTOM}
* </ul>
*
* @param align
* the major alignment
* @since 2.0
*/
public void setMajorAlignment(int align) {
majorAlignment = align;
}
/**
* Sets the spacing in pixels to be used between children in the direction
* parallel to the layout's orientation.
*
* @param n
* the major spacing
* @since 2.0
*/
public void setMajorSpacing(int n) {
majorSpacing = n;
}
/**
* Sets the alignment to be used within a row/column.
* <P>
* Possible values are :
* <ul>
* <li>{@link #ALIGN_CENTER}
* <li>{@link #ALIGN_LEFTTOP}
* <li>{@link #ALIGN_RIGHTBOTTOM}
* </ul>
*
* @param align
* the minor alignment
* @since 2.0
*/
public void setMinorAlignment(int align) {
minorAlignment = align;
}
/**
* Sets the spacing to be used between children within a row/column.
*
* @param n
* the minor spacing
* @since 2.0
*/
public void setMinorSpacing(int n) {
minorSpacing = n;
}
}