/******************************************************************************* * Copyright (c) 2000, 2015 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 * dinko.ivanov@sap.com - patch #70790 * RasmussenJamie@comcast.net - patch for Bug 184345 *******************************************************************************/ package org.eclipse.ui.forms.widgets; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Layout; import org.eclipse.ui.internal.forms.widgets.ColumnLayoutUtils; import org.eclipse.ui.internal.forms.widgets.FormUtil; /** * This layout manager arranges children of the composite parent in vertical * columns. All the columns are identical size and children are stretched * horizontally to fill the column width. The goal is to give layout some * reasonable range of column numbers to allow it to handle various parent * widths. That way, column number will drop to the lowest number in the range * when width decreases, and grow up to the highest number in the range when * allowed by the parent width. * <p> * In addition, the layout attempts to 'fill the space' equally i.e. to avoid * large gaps at the and of the last column. * <p> * Child controls are layed out according to their 'natural' (preferred) size. * For 'stretchy' controls that do not have natural preferred size, it is * possible to set width and/or height hints using ColumnLayoutData objects. * * @see ColumnLayoutData * @since 3.0 */ public final class ColumnLayout extends Layout implements ILayoutExtension { /** * Minimum number of columns (default is 1). */ public int minNumColumns = 1; /** * Maximum number of columns (default is 3). */ public int maxNumColumns = 3; /** * Horizontal spacing between columns (default is 5). */ public int horizontalSpacing = 5; /** * Vertical spacing between controls (default is 5). */ public int verticalSpacing = 5; /** * Top margin (default is 5). */ public int topMargin = 5; /** * Left margin (default is 5). */ public int leftMargin = 5; /** * Bottom margin (default is 5). */ public int bottomMargin = 5; /** * Right margin (default is 5). */ public int rightMargin = 5; private LayoutCache cache = new LayoutCache(); private final static int MIN_SIZE = -2; /** * Creates a new instance of the column layout. */ public ColumnLayout() { } private void updateCache(Composite composite, boolean flushCache) { Control[] children = composite.getChildren(); if (flushCache) { cache.flush(); } cache.setControls(children); } @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { updateCache(composite, flushCache); return computeSize(composite, wHint, hHint); } /** * Given a desired number of columns, this returns a clamped result that falls * within the range specified by the minimum and maximum number of columns. */ private int clampNumColumns(Composite parent, int desiredNumColumns) { int ncolumns = desiredNumColumns; ncolumns = Math.min(ncolumns, parent.getChildren().length); ncolumns = Math.min(ncolumns, maxNumColumns); ncolumns = Math.max(ncolumns, minNumColumns); ncolumns = Math.max(ncolumns, 1); return ncolumns; } private int computeOptimalNumColumnsForWidth(Composite parent, int width) { if (minNumColumns >= maxNumColumns || parent.getChildren().length <= minNumColumns) { return clampNumColumns(parent, minNumColumns); } Control[] children = parent.getChildren(); int minColWidth = 0; for (int i = 0; i < children.length; i++) { // To maximize the number of columns: int nextWidth = computeMinimumWidth(i); // To minimize the number of columns: // int nextWidth = computeControlSize(i, SWT.DEFAULT).x; minColWidth = Math.max(minColWidth, nextWidth); } return clampNumColumns(parent, (width - leftMargin - rightMargin + horizontalSpacing) / (minColWidth + horizontalSpacing)); } private int computeColumnWidthForNumColumns(int layoutWidth, int numColumns) { return ((layoutWidth - leftMargin - rightMargin) - (numColumns - 1) * horizontalSpacing) / numColumns; } private Point computeSize(Composite parent, int wHint, int hHint) { Control[] children = parent.getChildren(); int cheight = 0; Point[] sizes = new Point[children.length]; int columnWidth = 0; int nColumns; if (wHint == SWT.DEFAULT) { nColumns = clampNumColumns(parent, maxNumColumns); for (int i = 0; i < children.length; i++) { columnWidth = Math.max(columnWidth, computeControlSize(i, SWT.DEFAULT).x); } } else if (wHint == MIN_SIZE) { nColumns = clampNumColumns(parent, 0); for (int i = 0; i < children.length; i++) { columnWidth = Math.max(columnWidth, computeMinimumWidth(i)); } } else { nColumns = computeOptimalNumColumnsForWidth(parent, wHint); columnWidth = computeColumnWidthForNumColumns(wHint, nColumns); } for (int i = 0; i < children.length; i++) { sizes[i] = computeControlSize(i, columnWidth); cheight += sizes[i].y; } int perColHeight = ColumnLayoutUtils.computeColumnHeight(nColumns, sizes, cheight, verticalSpacing); int colHeight = 0; int[] heights = new int[nColumns]; int ncol = 0; boolean fillIn = false; for (int i = 0; i < sizes.length; i++) { int childHeight = sizes[i].y; if (i>0 && colHeight + childHeight > perColHeight) { heights[ncol] = colHeight; ncol++; if (ncol == nColumns || fillIn) { // overflow - start filling in fillIn = true; ncol = findShortestColumn(heights); } colHeight = heights[ncol]; } if (colHeight > 0) colHeight += verticalSpacing; colHeight += childHeight; } heights[ncol] = Math.max(heights[ncol],colHeight); Point size = new Point(0, 0); for (int i = 0; i < nColumns; i++) { size.y = Math.max(size.y, heights[i]); } size.x = columnWidth * nColumns + (nColumns - 1) * horizontalSpacing; size.x += leftMargin + rightMargin; size.y += topMargin + bottomMargin; if (hHint != SWT.DEFAULT) { size.y = hHint; } return size; } private int computeMinimumWidth(int i) { SizeCache sc = cache.getCache(i); return sc.computeMinimumWidth(); } private Point computeControlSize(int controlIndex, int wHint) { SizeCache sizeCache = cache.getCache(controlIndex); Control c = sizeCache.getControl(); ColumnLayoutData cd = (ColumnLayoutData) c.getLayoutData(); if (cd != null) { return FormUtil.computeControlSize(sizeCache, wHint, cd.widthHint, cd.heightHint, cd.horizontalAlignment == ColumnLayoutData.FILL); } return FormUtil.computeControlSize(sizeCache, wHint, SWT.DEFAULT, SWT.DEFAULT, true); } private int findShortestColumn(int[] heights) { int result = 0; int height = Integer.MAX_VALUE; for (int i = 0; i < heights.length; i++) { if (height > heights[i]) { height = heights[i]; result = i; } } return result; } @Override protected void layout(Composite parent, boolean flushCache) { updateCache(parent, flushCache); Control[] children = parent.getChildren(); Rectangle carea = parent.getClientArea(); int nColumns = computeOptimalNumColumnsForWidth(parent, carea.width); int columnWidth = computeColumnWidthForNumColumns(carea.width, nColumns); int cheight = 0; Point[] sizes = new Point[children.length]; for (int i = 0; i < children.length; i++) { sizes[i] = computeControlSize(i, columnWidth); cheight += sizes[i].y; } int perColHeight = ColumnLayoutUtils.computeColumnHeight(nColumns, sizes, cheight, verticalSpacing); int colHeight = 0; int[] heights = new int[nColumns]; int ncol = 0; int x = leftMargin; boolean fillIn = false; for (int i = 0; i < sizes.length; i++) { Control child = children[i]; Point csize = sizes[i]; ColumnLayoutData cd = (ColumnLayoutData) child.getLayoutData(); int align = cd != null ? cd.horizontalAlignment : ColumnLayoutData.FILL; int childWidth = csize.x; if (i>0 && colHeight + csize.y > perColHeight) { heights[ncol] = colHeight; if (fillIn || ncol == nColumns - 1) { // overflow - start filling in fillIn = true; ncol = findShortestColumn(heights); x = leftMargin + ncol * (columnWidth + horizontalSpacing); } else { ncol++; x += columnWidth + horizontalSpacing; } colHeight = heights[ncol]; } if (colHeight > 0) colHeight += verticalSpacing; switch (align) { case ColumnLayoutData.LEFT : case ColumnLayoutData.FILL : child.setBounds(x, topMargin+colHeight, childWidth, csize.y); break; case ColumnLayoutData.RIGHT : child.setBounds(x + columnWidth - childWidth, topMargin + colHeight, childWidth, csize.y); break; case ColumnLayoutData.CENTER : child.setBounds(x + columnWidth / 2 - childWidth / 2, topMargin + colHeight, childWidth, csize.y); break; } colHeight += csize.y; } } @Override public int computeMaximumWidth(Composite parent, boolean changed) { return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x; } @Override public int computeMinimumWidth(Composite parent, boolean changed) { updateCache(parent, changed); return computeSize(parent, MIN_SIZE, SWT.DEFAULT).x; } }