blob: 2953f77fb8f532043512dae6c86e79b79549f096 [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;
/**
* 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 OrderedLayout {
/**
* Holds the necessary information for layout calculations.
*/
protected class WorkingData {
public Rectangle bounds[], area;
public IFigure row[];
public int rowHeight, rowWidth, rowCount, rowX, rowY, maxWidth;
}
/**
* Constant to specify components to be aligned on the left/top
*
* @deprecated Use {@link OrderedLayout#ALIGN_TOPLEFT} instead.
*/
public static final int ALIGN_LEFTTOP = ALIGN_TOPLEFT;
/**
* Constant to specify components to be aligned on the right/bottom
*
* @deprecated Use {@link OrderedLayout#ALIGN_BOTTOMRIGHT} instead.
*/
public static final int ALIGN_RIGHTBOTTOM = ALIGN_BOTTOMRIGHT;
protected WorkingData data = null;
/**
* 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.
*
* @deprecated Use {@link OrderedLayout#setStretchMinorAxis(boolean)} and
* {@link OrderedLayout#isStretchMinorAxis()} instead.
*/
protected boolean fill;
/**
* The alignment along the major axis.
*
* @deprecated Use {@link #getMajorAlignment()} and
* {@link #setMajorAlignment(int)} instead.
*/
protected int majorAlignment = ALIGN_TOPLEFT;
/**
* The spacing along the major axis.
*
* @deprecated Use {@link #getMajorSpacing()} and
* {@link #setMajorSpacing(int)} instead.
*/
protected int majorSpacing = 5;
/**
* The spacing along the minor axis.
*
* @deprecated Use {@link #getMinorSpacing()} and
* {@link #setMinorSpacing(int)} instead.
*/
protected int minorSpacing = 5;
/**
* Constructs a FlowLayout with horizontal orientation.
*
* @since 2.0
*/
public FlowLayout() {
setStretchMinorAxis(false);
}
/**
* 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);
setStretchMinorAxis(false);
}
/**
* @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 {@link PositionConstants#HORIZONTAL} by default.
*
* @see org.eclipse.draw2d.OrderedLayout#getDefaultOrientation()
*/
protected int getDefaultOrientation() {
return PositionConstants.HORIZONTAL;
}
/**
* 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 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;
}
/**
* @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();
}
/**
* Overwritten to guarantee backwards compatibility with {@link #fill}
* field.
*
* @see org.eclipse.draw2d.OrderedLayout#isStretchMinorAxis()
*/
public boolean isStretchMinorAxis() {
return fill;
}
/**
* @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();
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++;
}
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 = getMajorAlignment();
int correctMinorAlignment = getMinorAlignment();
majorAdjustment = data.area.width - data.rowWidth + getMinorSpacing();
switch (correctMajorAlignment) {
case ALIGN_TOPLEFT:
majorAdjustment = 0;
break;
case ALIGN_CENTER:
majorAdjustment /= 2;
break;
case ALIGN_BOTTOMRIGHT:
break;
}
for (int j = 0; j < data.rowCount; j++) {
if (isStretchMinorAxis()) {
data.bounds[j].height = data.rowHeight;
} else {
minorAdjustment = data.rowHeight - data.bounds[j].height;
switch (correctMinorAlignment) {
case ALIGN_TOPLEFT:
minorAdjustment = 0;
break;
case ALIGN_CENTER:
minorAdjustment /= 2;
break;
case ALIGN_BOTTOMRIGHT:
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.getSINGLETON());
bounds.translate(Rectangle.getSINGLETON().x, Rectangle.getSINGLETON().y);
child.setBounds(bounds);
}
/**
* 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 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;
}
/**
* Overwritten to guarantee backwards compatibility with {@link #fill}
* field.
*
* @see org.eclipse.draw2d.OrderedLayout#setStretchMinorAxis(boolean)
*/
public void setStretchMinorAxis(boolean value) {
fill = value;
}
}