/******************************************************************************* * Copyright (c) 2005 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.bpel.common.ui.layouts; import java.util.Iterator; import java.util.List; import org.eclipse.draw2d.AbstractHintLayout; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.geometry.Transposer; /** A version of FlowLayout that doesn't wrap the children * and allows aligning in both orientations * as well as alignment of the children relative to each other * * some parameters you can set: * * horizontal - orientation of children * if true, children laid across, and width respected * if false. children laid downwards, height respected. * * fill - if true and layout is horizontal, height not respected * if false and layout is horizontal, height respected * if true and layout is vertical, width not respected * * fillParent - if true, tries to fill the child to the container size * * horizontal or vertical alignment * specifies which 1 of 3 areas the children are placed into * * ALIGN_BEGIN means either left or top * ALIGN_END means either right or bottom * ALIGN_CENTER means in the middle * * secondaryAlignment - how the children are lined up in secondary axis * if horizontal - specfies if children are aligned to their tops, center or bottom * if vertical - specifies if children are aligned to left, right or center. * */ public class AlignedFlowLayout extends AbstractHintLayout { /* * Constants defining the alignment of the components */ public static final int ALIGN_CENTER = 0, ALIGN_BEGIN = 1, ALIGN_END = 2; public static final boolean HORIZONTAL = true, VERTICAL = false; protected boolean horizontal = true; protected boolean fill = false; protected boolean fillParent = false; protected Transposer transposer; { transposer = new Transposer(); transposer.setEnabled(!horizontal); } /* * Internal state */ protected int horizontalAlignment = ALIGN_BEGIN; protected int verticalAlignment = ALIGN_BEGIN; protected int secondaryAlignment = ALIGN_BEGIN; protected int horizontalSpacing = 5, verticalSpacing = 5; private WorkingData data = null; /** * Holds the necessary information for layout calculations. */ class WorkingData { int rowHeight, rowWidth, rowCount, rowX, rowY; Rectangle bounds[], area; IFigure row[]; Dimension spacing; } public AlignedFlowLayout() { } public AlignedFlowLayout(boolean isHorizontal) { setHorizontal(isHorizontal); } @Override protected Dimension calculatePreferredSize(IFigure container, int wHint, int hHint) { int cHorizontalSpacing = horizontalSpacing; if (!isHorizontal()) { cHorizontalSpacing = verticalSpacing; } // 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 { // fit another child. width += childSize.width + cHorizontalSpacing; 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 to be used when calculating the child's preferred size * @param hHint The height hint to be used when calculating the child's preferred size * @return the child's preferred size */ protected Dimension getChildSize(IFigure child, int wHint, int hHint) { return child.getPreferredSize(wHint, hHint); } public int getHorizontalAlignment() { return horizontalAlignment; } public int getVerticalSpacing() { return verticalSpacing; } public int getVerticalAlignment() { return verticalAlignment; } public int getHorizontalSpacing() { return horizontalSpacing; } /** * Initializes the state of row data, which is internal * to the layout process. */ private 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 Figure for which the children are to * be arranged. * @since 2.0 */ private void initVariables(IFigure parent) { data.row = new IFigure[parent.getChildren().size()]; data.bounds = new Rectangle[data.row.length]; } public boolean isHorizontal() { return horizontal; } @Override protected boolean isSensitiveHorizontally(IFigure parent) { return isHorizontal(); } @Override protected boolean isSensitiveVertically(IFigure parent) { return !isHorizontal(); } public void layout(IFigure parent) { data = new WorkingData(); Rectangle relativeArea = parent.getClientArea(); data.area = transposer.t(relativeArea); data.spacing = transposer.t(new Dimension(horizontalSpacing, verticalSpacing)); 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); r.x = data.rowX; r.y = data.rowY; dx = r.width + data.spacing.width; data.rowX += dx; data.rowWidth += dx; data.rowHeight = Math.max(data.rowHeight, r.height); if (fillParent) data.rowHeight = Math.max(data.area.height, 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 Figure whose children are to be placed. * @since 2.0 */ protected void layoutRow(IFigure parent) { int majorAdjustment = 0; int minorAdjustment = 0; int justification = 0; int correctHorizontalAlignment = horizontalAlignment; int correctVerticalAlignment = verticalAlignment; majorAdjustment = data.area.width - data.rowWidth; if (!isHorizontal()) { correctHorizontalAlignment = verticalAlignment; correctVerticalAlignment = horizontalAlignment; } switch (correctHorizontalAlignment) { case ALIGN_BEGIN : majorAdjustment = 0; break; case ALIGN_CENTER : majorAdjustment /= 2; break; case ALIGN_END : break; } minorAdjustment = data.area.height - data.rowHeight; switch (correctVerticalAlignment) { case ALIGN_BEGIN : minorAdjustment = 0; break; case ALIGN_CENTER : minorAdjustment /= 2; break; case ALIGN_END : break; } for (int j = 0; j < data.rowCount; j++) { if (fill) { data.bounds[j].height = data.rowHeight; } else { justification = data.rowHeight - data.bounds[j].height; switch (secondaryAlignment) { case ALIGN_BEGIN : justification = 0; break; case ALIGN_CENTER : justification /= 2; break; case ALIGN_END : break; } data.bounds[j].y += minorAdjustment + justification; } data.bounds[j].x += majorAdjustment; setBoundsOfChild(parent, data.row[j], transposer.t(data.bounds[j])); } data.rowY += data.spacing.height + data.rowHeight; initRow(); } /** * Sets the given bounds for the child figure input. * * @param parent Parent Figure which holds the child. * @param child Child Figure whose bounds are to be set. * @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; } public void setHorizontal(boolean flag) { if (horizontal == flag) return; invalidate(); horizontal = flag; transposer.setEnabled(!horizontal); } public void setHorizontalAlignment(int align) { horizontalAlignment = align; } /** * Sets the spacing in pixels to be used between children in * the direction parallel to the layout's orientation. * * @param n Amount of major space. * @see #setHorizontalSpacing(int) * @since 2.0 */ public void setVerticalSpacing(int n) { verticalSpacing = n; } public void setVerticalAlignment(int align) { verticalAlignment = align; } public void setHorizontalSpacing(int n) { horizontalSpacing = n; } public int getSecondaryAlignment() { return secondaryAlignment; } public void setSecondaryAlignment(int i) { secondaryAlignment = i; } /** * * @param fillParent - if setStretchMinorAxis is true, setting * this flag will make the stretch fill the client area of the container */ public void setStretchMinorAxisToParent(boolean fillParent) { this.fillParent = fillParent; } }