001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.swt.layout;
011:
012: import org.eclipse.swt.*;
013: import org.eclipse.swt.graphics.*;
014: import org.eclipse.swt.widgets.*;
015:
016: /**
017: * Instances of this class control the position and size of the
018: * children of a composite control by using <code>FormAttachments</code>
019: * to optionally configure the left, top, right and bottom edges of
020: * each child.
021: * <p>
022: * The following example code creates a <code>FormLayout</code> and then sets
023: * it into a <code>Shell</code>:
024: * <pre>
025: * Display display = new Display ();
026: * Shell shell = new Shell(display);
027: * FormLayout layout = new FormLayout();
028: * layout.marginWidth = 3;
029: * layout.marginHeight = 3;
030: * shell.setLayout(layout);
031: * </pre>
032: * </p>
033: * <p>
034: * To use a <code>FormLayout</code>, create a <code>FormData</code> with
035: * <code>FormAttachment</code> for each child of <code>Composite</code>.
036: * The following example code attaches <code>button1</code> to the top
037: * and left edge of the composite and <code>button2</code> to the right
038: * edge of <code>button1</code> and the top and right edges of the
039: * composite:
040: * <pre>
041: * FormData data1 = new FormData();
042: * data1.left = new FormAttachment(0, 0);
043: * data1.top = new FormAttachment(0, 0);
044: * button1.setLayoutData(data1);
045: * FormData data2 = new FormData();
046: * data2.left = new FormAttachment(button1);
047: * data2.top = new FormAttachment(0, 0);
048: * data2.right = new FormAttachment(100, 0);
049: * button2.setLayoutData(data2);
050: * </pre>
051: * </p>
052: * <p>
053: * Each side of a child control can be attached to a position in the parent
054: * composite, or to other controls within the <code>Composite</code> by
055: * creating instances of <code>FormAttachment</code> and setting them into
056: * the top, bottom, left, and right fields of the child's <code>FormData</code>.
057: * </p>
058: * <p>
059: * If a side is not given an attachment, it is defined as not being attached
060: * to anything, causing the child to remain at its preferred size. If a child
061: * is given no attachment on either the left or the right or top or bottom, it is
062: * automatically attached to the left and top of the composite respectively.
063: * The following code positions <code>button1</code> and <code>button2</code>
064: * but relies on default attachments:
065: * <pre>
066: * FormData data2 = new FormData();
067: * data2.left = new FormAttachment(button1);
068: * data2.right = new FormAttachment(100, 0);
069: * button2.setLayoutData(data2);
070: * </pre>
071: * </p>
072: * <p>
073: * IMPORTANT: Do not define circular attachments. For example, do not attach
074: * the right edge of <code>button1</code> to the left edge of <code>button2</code>
075: * and then attach the left edge of <code>button2</code> to the right edge of
076: * <code>button1</code>. This will over constrain the layout, causing undefined
077: * behavior. The algorithm will terminate, but the results are undefined.
078: * </p>
079: *
080: * @see FormData
081: * @see FormAttachment
082: *
083: * @since 2.0
084: *
085: */
086: public final class FormLayout extends Layout {
087:
088: /**
089: * marginWidth specifies the number of pixels of horizontal margin
090: * that will be placed along the left and right edges of the layout.
091: *
092: * The default value is 0.
093: */
094: public int marginWidth = 0;
095:
096: /**
097: * marginHeight specifies the number of pixels of vertical margin
098: * that will be placed along the top and bottom edges of the layout.
099: *
100: * The default value is 0.
101: */
102: public int marginHeight = 0;
103:
104: /**
105: * marginLeft specifies the number of pixels of horizontal margin
106: * that will be placed along the left edge of the layout.
107: *
108: * The default value is 0.
109: *
110: * @since 3.1
111: */
112: public int marginLeft = 0;
113:
114: /**
115: * marginTop specifies the number of pixels of vertical margin
116: * that will be placed along the top edge of the layout.
117: *
118: * The default value is 0.
119: *
120: * @since 3.1
121: */
122: public int marginTop = 0;
123:
124: /**
125: * marginRight specifies the number of pixels of horizontal margin
126: * that will be placed along the right edge of the layout.
127: *
128: * The default value is 0.
129: *
130: * @since 3.1
131: */
132: public int marginRight = 0;
133:
134: /**
135: * marginBottom specifies the number of pixels of vertical margin
136: * that will be placed along the bottom edge of the layout.
137: *
138: * The default value is 0.
139: *
140: * @since 3.1
141: */
142: public int marginBottom = 0;
143:
144: /**
145: * spacing specifies the number of pixels between the edge of one control
146: * and the edge of its neighbouring control.
147: *
148: * The default value is 0.
149: *
150: * @since 3.0
151: */
152: public int spacing = 0;
153:
154: /**
155: * Constructs a new instance of this class.
156: */
157: public FormLayout() {
158: }
159:
160: /*
161: * Computes the preferred height of the form with
162: * respect to the preferred height of the control.
163: *
164: * Given that the equations for top (T) and bottom (B)
165: * of the control in terms of the height of the form (X)
166: * are:
167: * T = AX + B
168: * B = CX + D
169: *
170: * The equation for the height of the control (H)
171: * is bottom (B) minus top (T) or (H = B - T) or:
172: *
173: * H = (CX + D) - (AX + B)
174: *
175: * Solving for (X), the height of the form, we get:
176: *
177: * X = (H + B - D) / (C - A)
178: *
179: * When (A = C), (C - A = 0) and the equation has no
180: * solution for X. This is a special case meaning that
181: * the control does not constrain the height of the
182: * form. In this case, we need to arbitrarily define
183: * the height of the form (X):
184: *
185: * Case 1: A = C, A = 0, C = 0
186: *
187: * Let X = D, the distance from the top of the form
188: * to the bottom edge of the control. In this case,
189: * the control was attached to the top of the form
190: * and the form needs to be large enough to show the
191: * bottom edge of the control.
192: *
193: * Case 2: A = C, A = 1, C = 1
194: *
195: * Let X = -B, the distance from the bottom of the
196: * form to the top edge of the control. In this case,
197: * the control was attached to the bottom of the form
198: * and the only way that the control would be visible
199: * is if the offset is negative. If the offset is
200: * positive, there is no possible height for the form
201: * that will show the control as it will always be
202: * below the bottom edge of the form.
203: *
204: * Case 3: A = C, A != 0, C != 0 and A != 1, C != 0
205: *
206: * Let X = D / (1 - C), the distance from the top of the
207: * form to the bottom edge of the control. In this case,
208: * since C is not 0 or 1, it must be a fraction, U / V.
209: * The offset D is the distance from CX to the bottom edge
210: * of the control. This represents a fraction of the form
211: * (1 - C)X. Since the height of a fraction of the form is
212: * known, the height of the entire form can be found by setting
213: * (1 - C)X = D. We solve this equation for X in terms of U
214: * and V, giving us X = (U * D) / (U - V). Similarly, if the
215: * offset D is negative, the control is positioned above CX.
216: * The offset -B is the distance from the top edge of the control
217: * to CX. We can find the height of the entire form by setting
218: * CX = -B. Solving in terms of U and V gives us X = (-B * V) / U.
219: */
220: int computeHeight(Control control, FormData data, boolean flushCache) {
221: FormAttachment top = data.getTopAttachment(control, spacing,
222: flushCache);
223: FormAttachment bottom = data.getBottomAttachment(control,
224: spacing, flushCache);
225: FormAttachment height = bottom.minus(top);
226: if (height.numerator == 0) {
227: if (bottom.numerator == 0)
228: return bottom.offset;
229: if (bottom.numerator == bottom.denominator)
230: return -top.offset;
231: if (bottom.offset <= 0) {
232: return -top.offset * top.denominator / bottom.numerator;
233: }
234: int divider = bottom.denominator - bottom.numerator;
235: return bottom.denominator * bottom.offset / divider;
236: }
237: return height.solveY(data.getHeight(control, flushCache));
238: }
239:
240: protected Point computeSize(Composite composite, int wHint,
241: int hHint, boolean flushCache) {
242: Point size = layout(composite, false, 0, 0, wHint, hHint,
243: flushCache);
244: if (wHint != SWT.DEFAULT)
245: size.x = wHint;
246: if (hHint != SWT.DEFAULT)
247: size.y = hHint;
248: return size;
249: }
250:
251: protected boolean flushCache(Control control) {
252: Object data = control.getLayoutData();
253: if (data != null)
254: ((FormData) data).flushCache();
255: return true;
256: }
257:
258: String getName() {
259: String string = getClass().getName();
260: int index = string.lastIndexOf('.');
261: if (index == -1)
262: return string;
263: return string.substring(index + 1, string.length());
264: }
265:
266: /*
267: * Computes the preferred height of the form with
268: * respect to the preferred height of the control.
269: */
270: int computeWidth(Control control, FormData data, boolean flushCache) {
271: FormAttachment left = data.getLeftAttachment(control, spacing,
272: flushCache);
273: FormAttachment right = data.getRightAttachment(control,
274: spacing, flushCache);
275: FormAttachment width = right.minus(left);
276: if (width.numerator == 0) {
277: if (right.numerator == 0)
278: return right.offset;
279: if (right.numerator == right.denominator)
280: return -left.offset;
281: if (right.offset <= 0) {
282: return -left.offset * left.denominator / left.numerator;
283: }
284: int divider = right.denominator - right.numerator;
285: return right.denominator * right.offset / divider;
286: }
287: return width.solveY(data.getWidth(control, flushCache));
288: }
289:
290: protected void layout(Composite composite, boolean flushCache) {
291: Rectangle rect = composite.getClientArea();
292: int x = rect.x + marginLeft + marginWidth;
293: int y = rect.y + marginTop + marginHeight;
294: int width = Math.max(0, rect.width - marginLeft - 2
295: * marginWidth - marginRight);
296: int height = Math.max(0, rect.height - marginTop - 2
297: * marginHeight - marginBottom);
298: layout(composite, true, x, y, width, height, flushCache);
299: }
300:
301: Point layout(Composite composite, boolean move, int x, int y,
302: int width, int height, boolean flushCache) {
303: Control[] children = composite.getChildren();
304: for (int i = 0; i < children.length; i++) {
305: Control child = children[i];
306: FormData data = (FormData) child.getLayoutData();
307: if (data == null)
308: child.setLayoutData(data = new FormData());
309: if (flushCache)
310: data.flushCache();
311: data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null;
312: }
313: boolean[] flush = null;
314: Rectangle[] bounds = null;
315: int w = 0, h = 0;
316: for (int i = 0; i < children.length; i++) {
317: Control child = children[i];
318: FormData data = (FormData) child.getLayoutData();
319: if (width != SWT.DEFAULT) {
320: data.needed = false;
321: FormAttachment left = data.getLeftAttachment(child,
322: spacing, flushCache);
323: FormAttachment right = data.getRightAttachment(child,
324: spacing, flushCache);
325: int x1 = left.solveX(width), x2 = right.solveX(width);
326: if (data.height == SWT.DEFAULT && !data.needed) {
327: int trim = 0;
328: //TEMPORARY CODE
329: if (child instanceof Scrollable) {
330: Rectangle rect = ((Scrollable) child)
331: .computeTrim(0, 0, 0, 0);
332: trim = rect.width;
333: } else {
334: trim = child.getBorderWidth() * 2;
335: }
336: data.cacheWidth = data.cacheHeight = -1;
337: int currentWidth = Math.max(0, x2 - x1 - trim);
338: data.computeSize(child, currentWidth, data.height,
339: flushCache);
340: if (flush == null)
341: flush = new boolean[children.length];
342: flush[i] = true;
343: }
344: w = Math.max(x2, w);
345: if (move) {
346: if (bounds == null)
347: bounds = new Rectangle[children.length];
348: bounds[i] = new Rectangle(0, 0, 0, 0);
349: bounds[i].x = x + x1;
350: bounds[i].width = x2 - x1;
351: }
352: } else {
353: w = Math.max(computeWidth(child, data, flushCache), w);
354: }
355: }
356: for (int i = 0; i < children.length; i++) {
357: Control child = children[i];
358: FormData data = (FormData) child.getLayoutData();
359: if (height != SWT.DEFAULT) {
360: int y1 = data.getTopAttachment(child, spacing,
361: flushCache).solveX(height);
362: int y2 = data.getBottomAttachment(child, spacing,
363: flushCache).solveX(height);
364: h = Math.max(y2, h);
365: if (move) {
366: bounds[i].y = y + y1;
367: bounds[i].height = y2 - y1;
368: }
369: } else {
370: h = Math.max(computeHeight(child, data, flushCache), h);
371: }
372: }
373: for (int i = 0; i < children.length; i++) {
374: Control child = children[i];
375: FormData data = (FormData) child.getLayoutData();
376: if (flush != null && flush[i])
377: data.cacheWidth = data.cacheHeight = -1;
378: data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null;
379: }
380: if (move) {
381: for (int i = 0; i < children.length; i++) {
382: children[i].setBounds(bounds[i]);
383: }
384: }
385: w += marginLeft + marginWidth * 2 + marginRight;
386: h += marginTop + marginHeight * 2 + marginBottom;
387: return new Point(w, h);
388: }
389:
390: /**
391: * Returns a string containing a concise, human-readable
392: * description of the receiver.
393: *
394: * @return a string representation of the layout
395: */
396: public String toString() {
397: String string = getName() + " {";
398: if (marginWidth != 0)
399: string += "marginWidth=" + marginWidth + " ";
400: if (marginHeight != 0)
401: string += "marginHeight=" + marginHeight + " ";
402: if (marginLeft != 0)
403: string += "marginLeft=" + marginLeft + " ";
404: if (marginRight != 0)
405: string += "marginRight=" + marginRight + " ";
406: if (marginTop != 0)
407: string += "marginTop=" + marginTop + " ";
408: if (marginBottom != 0)
409: string += "marginBottom=" + marginBottom + " ";
410: if (spacing != 0)
411: string += "spacing=" + spacing + " ";
412: string = string.trim();
413: string += "}";
414: return string;
415: }
416: }
|