/******************************************************************************* * Copyright (c) 2000, 2008, 2012 IBM Corporation, Gerhardt Informatics Kft. 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 * Gerhardt Informatics Kft. - GEFGWT port *******************************************************************************/ package org.eclipse.swt.layout; 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.swt.widgets.Scrollable; /** * Instances of this class control the position and size of the children of a * composite control by using <code>FormAttachments</code> to optionally * configure the left, top, right and bottom edges of each child. * <p> * The following example code creates a <code>FormLayout</code> and then sets it * into a <code>Shell</code>: * * <pre> * Display display = new Display(); * Shell shell = new Shell(display); * FormLayout layout = new FormLayout(); * layout.marginWidth = 3; * layout.marginHeight = 3; * shell.setLayout(layout); * </pre> * * </p> * <p> * To use a <code>FormLayout</code>, create a <code>FormData</code> with * <code>FormAttachment</code> for each child of <code>Composite</code>. The * following example code attaches <code>button1</code> to the top and left edge * of the composite and <code>button2</code> to the right edge of * <code>button1</code> and the top and right edges of the composite: * * <pre> * FormData data1 = new FormData(); * data1.left = new FormAttachment(0, 0); * data1.top = new FormAttachment(0, 0); * button1.setLayoutData(data1); * FormData data2 = new FormData(); * data2.left = new FormAttachment(button1); * data2.top = new FormAttachment(0, 0); * data2.right = new FormAttachment(100, 0); * button2.setLayoutData(data2); * </pre> * * </p> * <p> * Each side of a child control can be attached to a position in the parent * composite, or to other controls within the <code>Composite</code> by creating * instances of <code>FormAttachment</code> and setting them into the top, * bottom, left, and right fields of the child's <code>FormData</code>. * </p> * <p> * If a side is not given an attachment, it is defined as not being attached to * anything, causing the child to remain at its preferred size. If a child is * given no attachment on either the left or the right or top or bottom, it is * automatically attached to the left and top of the composite respectively. The * following code positions <code>button1</code> and <code>button2</code> but * relies on default attachments: * * <pre> * FormData data2 = new FormData(); * data2.left = new FormAttachment(button1); * data2.right = new FormAttachment(100, 0); * button2.setLayoutData(data2); * </pre> * * </p> * <p> * IMPORTANT: Do not define circular attachments. For example, do not attach the * right edge of <code>button1</code> to the left edge of <code>button2</code> * and then attach the left edge of <code>button2</code> to the right edge of * <code>button1</code>. This will over constrain the layout, causing undefined * behavior. The algorithm will terminate, but the results are undefined. * </p> * * @see FormData * @see FormAttachment * @see <a href="http://www.eclipse.org/swt/snippets/#formlayout">FormLayout * snippets</a> * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: * LayoutExample</a> * @see <a href="http://www.eclipse.org/swt/">Sample code and further * information</a> * * @since 2.0 */ public final class FormLayout extends Layout { /** * marginWidth specifies the number of pixels of horizontal margin that will * be placed along the left and right edges of the layout. * * The default value is 0. */ public int marginWidth = 0; /** * marginHeight specifies the number of pixels of vertical margin that will * be placed along the top and bottom edges of the layout. * * The default value is 0. */ public int marginHeight = 0; /** * marginLeft specifies the number of pixels of horizontal margin that will * be placed along the left edge of the layout. * * The default value is 0. * * @since 3.1 */ public int marginLeft = 0; /** * marginTop specifies the number of pixels of vertical margin that will be * placed along the top edge of the layout. * * The default value is 0. * * @since 3.1 */ public int marginTop = 0; /** * marginRight specifies the number of pixels of horizontal margin that will * be placed along the right edge of the layout. * * The default value is 0. * * @since 3.1 */ public int marginRight = 0; /** * marginBottom specifies the number of pixels of vertical margin that will * be placed along the bottom edge of the layout. * * The default value is 0. * * @since 3.1 */ public int marginBottom = 0; /** * spacing specifies the number of pixels between the edge of one control * and the edge of its neighbouring control. * * The default value is 0. * * @since 3.0 */ public int spacing = 0; /** * Constructs a new instance of this class. */ public FormLayout() { } /* * Computes the preferred height of the form with respect to the preferred * height of the control. * * Given that the equations for top (T) and bottom (B) of the control in * terms of the height of the form (X) are: T = AX + B B = CX + D * * The equation for the height of the control (H) is bottom (B) minus top * (T) or (H = B - T) or: * * H = (CX + D) - (AX + B) * * Solving for (X), the height of the form, we get: * * X = (H + B - D) / (C - A) * * When (A = C), (C - A = 0) and the equation has no solution for X. This is * a special case meaning that the control does not constrain the height of * the form. In this case, we need to arbitrarily define the height of the * form (X): * * Case 1: A = C, A = 0, C = 0 * * Let X = D, the distance from the top of the form to the bottom edge of * the control. In this case, the control was attached to the top of the * form and the form needs to be large enough to show the bottom edge of the * control. * * Case 2: A = C, A = 1, C = 1 * * Let X = -B, the distance from the bottom of the form to the top edge of * the control. In this case, the control was attached to the bottom of the * form and the only way that the control would be visible is if the offset * is negative. If the offset is positive, there is no possible height for * the form that will show the control as it will always be below the bottom * edge of the form. * * Case 3: A = C, A != 0, C != 0 and A != 1, C != 0 * * Let X = D / (1 - C), the distance from the top of the form to the bottom * edge of the control. In this case, since C is not 0 or 1, it must be a * fraction, U / V. The offset D is the distance from CX to the bottom edge * of the control. This represents a fraction of the form (1 - C)X. Since * the height of a fraction of the form is known, the height of the entire * form can be found by setting (1 - C)X = D. We solve this equation for X * in terms of U and V, giving us X = (U * D) / (U - V). Similarly, if the * offset D is negative, the control is positioned above CX. The offset -B * is the distance from the top edge of the control to CX. We can find the * height of the entire form by setting CX = -B. Solving in terms of U and V * gives us X = (-B * V) / U. */ int computeHeight(Control control, FormData data, boolean flushCache) { FormAttachment top = data .getTopAttachment(control, spacing, flushCache); FormAttachment bottom = data.getBottomAttachment(control, spacing, flushCache); FormAttachment height = bottom.minus(top); if (height.numerator == 0) { if (bottom.numerator == 0) return bottom.offset; if (bottom.numerator == bottom.denominator) return -top.offset; if (bottom.offset <= 0) { return -top.offset * top.denominator / bottom.numerator; } int divider = bottom.denominator - bottom.numerator; return bottom.denominator * bottom.offset / divider; } return height.solveY(data.getHeight(control, flushCache)); } protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { Point size = layout(composite, false, 0, 0, wHint, hHint, flushCache); if (wHint != SWT.DEFAULT) size.x = wHint; if (hHint != SWT.DEFAULT) size.y = hHint; return size; } protected boolean flushCache(Control control) { Object data = control.getLayoutData(); if (data != null) ((FormData) data).flushCache(); return true; } String getName() { String string = getClass().getName(); int index = string.lastIndexOf('.'); if (index == -1) return string; return string.substring(index + 1, string.length()); } /* * Computes the preferred height of the form with respect to the preferred * height of the control. */ int computeWidth(Control control, FormData data, boolean flushCache) { FormAttachment left = data.getLeftAttachment(control, spacing, flushCache); FormAttachment right = data.getRightAttachment(control, spacing, flushCache); FormAttachment width = right.minus(left); if (width.numerator == 0) { if (right.numerator == 0) return right.offset; if (right.numerator == right.denominator) return -left.offset; if (right.offset <= 0) { return -left.offset * left.denominator / left.numerator; } int divider = right.denominator - right.numerator; return right.denominator * right.offset / divider; } return width.solveY(data.getWidth(control, flushCache)); } protected void layout(Composite composite, boolean flushCache) { Rectangle rect = composite.getClientArea(); int x = rect.x + marginLeft + marginWidth; int y = rect.y + marginTop + marginHeight; int width = Math.max(0, rect.width - marginLeft - 2 * marginWidth - marginRight); int height = Math.max(0, rect.height - marginTop - 2 * marginHeight - marginBottom); layout(composite, true, x, y, width, height, flushCache); } Point layout(Composite composite, boolean move, int x, int y, int width, int height, boolean flushCache) { Control[] children = composite.getChildren(); for (int i = 0; i < children.length; i++) { Control child = children[i]; FormData data = (FormData) child.getLayoutData(); if (data == null) child.setLayoutData(data = new FormData()); if (flushCache) data.flushCache(); data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null; } boolean[] flush = null; Rectangle[] bounds = null; int w = 0, h = 0; for (int i = 0; i < children.length; i++) { Control child = children[i]; FormData data = (FormData) child.getLayoutData(); if (width != SWT.DEFAULT) { data.needed = false; FormAttachment left = data.getLeftAttachment(child, spacing, flushCache); FormAttachment right = data.getRightAttachment(child, spacing, flushCache); int x1 = left.solveX(width), x2 = right.solveX(width); if (data.height == SWT.DEFAULT && !data.needed) { int trim = 0; // TEMPORARY CODE if (child instanceof Scrollable) { Rectangle rect = ((Scrollable) child).computeTrim(0, 0, 0, 0); trim = rect.width; } else { trim = child.getBorderWidth() * 2; } data.cacheWidth = data.cacheHeight = -1; int currentWidth = Math.max(0, x2 - x1 - trim); data.computeSize(child, currentWidth, data.height, flushCache); if (flush == null) flush = new boolean[children.length]; flush[i] = true; } w = Math.max(x2, w); if (move) { if (bounds == null) bounds = new Rectangle[children.length]; bounds[i] = new Rectangle(0, 0, 0, 0); bounds[i].x = x + x1; bounds[i].width = x2 - x1; } } else { w = Math.max(computeWidth(child, data, flushCache), w); } } for (int i = 0; i < children.length; i++) { Control child = children[i]; FormData data = (FormData) child.getLayoutData(); if (height != SWT.DEFAULT) { int y1 = data.getTopAttachment(child, spacing, flushCache) .solveX(height); int y2 = data.getBottomAttachment(child, spacing, flushCache) .solveX(height); h = Math.max(y2, h); if (move) { bounds[i].y = y + y1; bounds[i].height = y2 - y1; } } else { h = Math.max(computeHeight(child, data, flushCache), h); } } for (int i = 0; i < children.length; i++) { Control child = children[i]; FormData data = (FormData) child.getLayoutData(); if (flush != null && flush[i]) data.cacheWidth = data.cacheHeight = -1; data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null; } if (move) { for (int i = 0; i < children.length; i++) { children[i].setBounds(bounds[i]); } } w += marginLeft + marginWidth * 2 + marginRight; h += marginTop + marginHeight * 2 + marginBottom; return new Point(w, h); } /** * Returns a string containing a concise, human-readable description of the * receiver. * * @return a string representation of the layout */ public String toString() { String string = getName() + " {"; if (marginWidth != 0) string += "marginWidth=" + marginWidth + " "; if (marginHeight != 0) string += "marginHeight=" + marginHeight + " "; if (marginLeft != 0) string += "marginLeft=" + marginLeft + " "; if (marginRight != 0) string += "marginRight=" + marginRight + " "; if (marginTop != 0) string += "marginTop=" + marginTop + " "; if (marginBottom != 0) string += "marginBottom=" + marginBottom + " "; if (spacing != 0) string += "spacing=" + spacing + " "; string = string.trim(); string += "}"; return string; } }