| /******************************************************************************* |
| * Copyright (c) 2000, 2006 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.swt.layout; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.*; |
| |
| /** |
| * 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 |
| * @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 attatched 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). |
| * Similarily, 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; |
| } |
| } |