1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.layout.FormLayout;
14 
15 import org.eclipse.swt.layout.FormAttachment;
16 import org.eclipse.swt.layout.FormData;
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.graphics.Point;
19 import org.eclipse.swt.graphics.Rectangle;
20 import org.eclipse.swt.widgets.Control;
21 import org.eclipse.swt.widgets.Layout;
22 import org.eclipse.swt.widgets.Composite;
23 import org.eclipse.swt.widgets.Scrollable;
24 
25 import java.lang.all;
26 
27 /**
28  * Instances of this class control the position and size of the
29  * children of a composite control by using <code>FormAttachments</code>
30  * to optionally configure the left, top, right and bottom edges of
31  * each child.
32  * <p>
33  * The following example code creates a <code>FormLayout</code> and then sets
34  * it into a <code>Shell</code>:
35  * <pre>
36  *      Display display = new Display ();
37  *      Shell shell = new Shell(display);
38  *      FormLayout layout = new FormLayout();
39  *      layout.marginWidth = 3;
40  *      layout.marginHeight = 3;
41  *      shell.setLayout(layout);
42  * </pre>
43  * </p>
44  * <p>
45  * To use a <code>FormLayout</code>, create a <code>FormData</code> with
46  * <code>FormAttachment</code> for each child of <code>Composite</code>.
47  * The following example code attaches <code>button1</code> to the top
48  * and left edge of the composite and <code>button2</code> to the right
49  * edge of <code>button1</code> and the top and right edges of the
50  * composite:
51  * <pre>
52  *      FormData data1 = new FormData();
53  *      data1.left = new FormAttachment(0, 0);
54  *      data1.top = new FormAttachment(0, 0);
55  *      button1.setLayoutData(data1);
56  *      FormData data2 = new FormData();
57  *      data2.left = new FormAttachment(button1);
58  *      data2.top = new FormAttachment(0, 0);
59  *      data2.right = new FormAttachment(100, 0);
60  *      button2.setLayoutData(data2);
61  * </pre>
62  * </p>
63  * <p>
64  * Each side of a child control can be attached to a position in the parent
65  * composite, or to other controls within the <code>Composite</code> by
66  * creating instances of <code>FormAttachment</code> and setting them into
67  * the top, bottom, left, and right fields of the child's <code>FormData</code>.
68  * </p>
69  * <p>
70  * If a side is not given an attachment, it is defined as not being attached
71  * to anything, causing the child to remain at its preferred size.  If a child
72  * is given no attachment on either the left or the right or top or bottom, it is
73  * automatically attached to the left and top of the composite respectively.
74  * The following code positions <code>button1</code> and <code>button2</code>
75  * but relies on default attachments:
76  * <pre>
77  *      FormData data2 = new FormData();
78  *      data2.left = new FormAttachment(button1);
79  *      data2.right = new FormAttachment(100, 0);
80  *      button2.setLayoutData(data2);
81  * </pre>
82  * </p>
83  * <p>
84  * IMPORTANT: Do not define circular attachments.  For example, do not attach
85  * the right edge of <code>button1</code> to the left edge of <code>button2</code>
86  * and then attach the left edge of <code>button2</code> to the right edge of
87  * <code>button1</code>.  This will over constrain the layout, causing undefined
88  * behavior.  The algorithm will terminate, but the results are undefined.
89  * </p>
90  *
91  * @see FormData
92  * @see FormAttachment
93  * @see <a href="http://www.eclipse.org/swt/snippets/#formlayout">FormLayout snippets</a>
94  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: LayoutExample</a>
95  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> 
96  *
97  * @since 2.0
98  */
99 public final class FormLayout : Layout {
100 
101     /**
102      * marginWidth specifies the number of pixels of horizontal margin
103      * that will be placed along the left and right edges of the layout.
104      *
105      * The default value is 0.
106      */
107     public int marginWidth = 0;
108 
109     /**
110      * marginHeight specifies the number of pixels of vertical margin
111      * that will be placed along the top and bottom edges of the layout.
112      *
113      * The default value is 0.
114      */
115     public int marginHeight = 0;
116 
117 
118     /**
119      * marginLeft specifies the number of pixels of horizontal margin
120      * that will be placed along the left edge of the layout.
121      *
122      * The default value is 0.
123      *
124      * @since 3.1
125      */
126     public int marginLeft = 0;
127 
128     /**
129      * marginTop specifies the number of pixels of vertical margin
130      * that will be placed along the top edge of the layout.
131      *
132      * The default value is 0.
133      *
134      * @since 3.1
135      */
136     public int marginTop = 0;
137 
138     /**
139      * marginRight specifies the number of pixels of horizontal margin
140      * that will be placed along the right edge of the layout.
141      *
142      * The default value is 0.
143      *
144      * @since 3.1
145      */
146     public int marginRight = 0;
147 
148     /**
149      * marginBottom specifies the number of pixels of vertical margin
150      * that will be placed along the bottom edge of the layout.
151      *
152      * The default value is 0.
153      *
154      * @since 3.1
155      */
156     public int marginBottom = 0;
157 
158     /**
159      * spacing specifies the number of pixels between the edge of one control
160      * and the edge of its neighbouring control.
161      *
162      * The default value is 0.
163      *
164      * @since 3.0
165      */
166     public int spacing = 0;
167 
168 /**
169  * Constructs a new instance of this class.
170  */
171 public this () {
172 }
173 
174 /*
175  * Computes the preferred height of the form with
176  * respect to the preferred height of the control.
177  *
178  * Given that the equations for top (T) and bottom (B)
179  * of the control in terms of the height of the form (X)
180  * are:
181  *      T = AX + B
182  *      B = CX + D
183  *
184  * The equation for the height of the control (H)
185  * is bottom (B) minus top (T) or (H = B - T) or:
186  *
187  *      H = (CX + D) - (AX + B)
188  *
189  * Solving for (X), the height of the form, we get:
190  *
191  *      X = (H + B - D) / (C - A)
192  *
193  * When (A = C), (C - A = 0) and the equation has no
194  * solution for X.  This is a special case meaning that
195  * the control does not constrain the height of the
196  * form.  In this case, we need to arbitrarily define
197  * the height of the form (X):
198  *
199  * Case 1: A = C, A = 0, C = 0
200  *
201  *      Let X = D, the distance from the top of the form
202  *      to the bottom edge of the control.  In this case,
203  *      the control was attached to the top of the form
204  *      and the form needs to be large enough to show the
205  *      bottom edge of the control.
206  *
207  * Case 2: A = C, A = 1, C = 1
208  *
209  *      Let X = -B, the distance from the bottom of the
210  *      form to the top edge of the control.  In this case,
211  *      the control was attached to the bottom of the form
212  *      and the only way that the control would be visible
213  *      is if the offset is negative.  If the offset is
214  *      positive, there is no possible height for the form
215  *      that will show the control as it will always be
216  *      below the bottom edge of the form.
217  *
218  * Case 3: A = C, A !is 0, C !is 0 and A !is 1, C !is 0
219  *
220  *      Let X = D / (1 - C), the distance from the top of the
221  *      form to the bottom edge of the control.  In this case,
222  *      since C is not 0 or 1, it must be a fraction, U / V.
223  *      The offset D is the distance from CX to the bottom edge
224  *      of the control.  This represents a fraction of the form
225  *      (1 - C)X. Since the height of a fraction of the form is
226  *      known, the height of the entire form can be found by setting
227  *      (1 - C)X = D.  We solve this equation for X in terms of U
228  *      and V, giving us X = (U * D) / (U - V). Similarly, if the
229  *      offset D is negative, the control is positioned above CX.
230  *      The offset -B is the distance from the top edge of the control
231  *      to CX. We can find the height of the entire form by setting
232  *      CX = -B. Solving in terms of U and V gives us X = (-B * V) / U.
233  */
234 int computeHeight (Control control, FormData data, bool flushCache) {
235     FormAttachment top = data.getTopAttachment (control, spacing, flushCache);
236     FormAttachment bottom = data.getBottomAttachment (control, spacing, flushCache);
237     FormAttachment height = bottom.minus (top);
238     if (height.numerator is 0) {
239         if (bottom.numerator is 0) return bottom.offset;
240         if (bottom.numerator is bottom.denominator) return -top.offset;
241         if (bottom.offset <= 0) {
242             return -top.offset * top.denominator / bottom.numerator;
243         }
244         int divider = bottom.denominator - bottom.numerator;
245         return bottom.denominator * bottom.offset / divider;
246     }
247     return height.solveY (data.getHeight (control, flushCache));
248 }
249 
250 override protected Point computeSize (Composite composite, int wHint, int hHint, bool flushCache) {
251     Point size = layout (composite, false, 0, 0, wHint, hHint, flushCache);
252     if (wHint !is SWT.DEFAULT) size.x = wHint;
253     if (hHint !is SWT.DEFAULT) size.y = hHint;
254     return size;
255 }
256 
257 override protected bool flushCache (Control control) {
258     Object data = control.getLayoutData ();
259     if (data !is null) (cast(FormData) data).flushCache ();
260     return true;
261 }
262 
263 String getName () {
264     String string = this.classinfo.name;
265     int index = string.lastIndexOf('.');
266     if (index is -1 ) return string;
267     return string[ index + 1 .. string.length ];
268 }
269 
270 /*
271  * Computes the preferred height of the form with
272  * respect to the preferred height of the control.
273  */
274 int computeWidth (Control control, FormData data, bool flushCache) {
275     FormAttachment left = data.getLeftAttachment (control, spacing, flushCache);
276     FormAttachment right = data.getRightAttachment (control, spacing, flushCache);
277     FormAttachment width = right.minus (left);
278     if (width.numerator is 0) {
279         if (right.numerator is 0) return right.offset;
280         if (right.numerator is right.denominator) 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 override protected void layout (Composite composite, bool 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 * marginWidth - marginRight);
295     int height = Math.max (0, rect.height - marginTop - 2 * marginHeight - marginBottom);
296     layout (composite, true, x, y, width, height, flushCache);
297 }
298 
299 Point layout (Composite composite, bool move, int x, int y, int width, int height, bool flushCache) {
300     Control [] children = composite.getChildren ();
301     for (int i=0; i<children.length; i++) {
302         Control child = children [i];
303         FormData data = cast(FormData) child.getLayoutData ();
304         if (data is null) child.setLayoutData (data = new FormData ());
305         if (flushCache) data.flushCache ();
306         data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null;
307     }
308     bool [] flush = null;
309     Rectangle [] bounds = null;
310     int w = 0, h = 0;
311     for (int i=0; i<children.length; i++) {
312         Control child = children [i];
313         FormData data = cast(FormData) child.getLayoutData ();
314         if (width !is SWT.DEFAULT) {
315             data.needed = false;
316             FormAttachment left = data.getLeftAttachment (child, spacing, flushCache);
317             FormAttachment right = data.getRightAttachment (child, spacing, flushCache);
318             int x1 = left.solveX (width), x2 = right.solveX (width);
319             if (data.height is SWT.DEFAULT && !data.needed) {
320                 int trim = 0;
321                 //TEMPORARY CODE
322                 if ( auto sa = cast(Scrollable)child) {
323                     Rectangle rect = sa.computeTrim (0, 0, 0, 0);
324                     trim = rect.width;
325                 } else {
326                     trim = child.getBorderWidth () * 2;
327                 }
328                 data.cacheWidth = data.cacheHeight = -1;
329                 int currentWidth = Math.max (0, x2 - x1 - trim);
330                 data.computeSize (child, currentWidth, data.height, flushCache);
331                 if (flush is null) flush = new bool [children.length];
332                 flush [i] = true;
333             }
334             w = Math.max (x2, w);
335             if (move) {
336                 if (bounds is null) bounds = new Rectangle [children.length];
337                 bounds [i] = new Rectangle (0, 0, 0, 0);
338                 bounds [i].x = x + x1;
339                 bounds [i].width = x2 - x1;
340             }
341         } else {
342             w = Math.max (computeWidth (child, data, flushCache), w);
343         }
344     }
345     for (int i=0; i<children.length; i++) {
346         Control child = children [i];
347         FormData data = cast(FormData) child.getLayoutData ();
348         if (height !is SWT.DEFAULT) {
349             int y1 = data.getTopAttachment (child, spacing, flushCache).solveX (height);
350             int y2 = data.getBottomAttachment (child, spacing, flushCache).solveX (height);
351             h = Math.max (y2, h);
352             if (move) {
353                 bounds [i].y = y + y1;
354                 bounds [i].height = y2 - y1;
355             }
356         } else {
357             h = Math.max (computeHeight (child, data, flushCache), h);
358         }
359     }
360     for (int i=0; i<children.length; i++) {
361         Control child = children [i];
362         FormData data = cast(FormData) child.getLayoutData ();
363         if (flush !is null && flush [i]) data.cacheWidth = data.cacheHeight = -1;
364         data.cacheLeft = data.cacheRight = data.cacheTop = data.cacheBottom = null;
365     }
366     if (move) {
367         for (int i=0; i<children.length; i++) {
368             children [i].setBounds (bounds [i]);
369         }
370     }
371     w += marginLeft + marginWidth * 2 + marginRight;
372     h += marginTop + marginHeight * 2 + marginBottom;
373     return new Point (w, h);
374 }
375 
376 /**
377  * Returns a string containing a concise, human-readable
378  * description of the receiver.
379  *
380  * @return a string representation of the layout
381  */
382 override public String toString () {
383     String string =  getName ()~" {";
384     if (marginWidth !is 0) string ~= "marginWidth="~String_valueOf(marginWidth)~" ";
385     if (marginHeight !is 0) string ~= "marginHeight="~String_valueOf(marginHeight)~" ";
386     if (marginLeft !is 0) string ~= "marginLeft="~String_valueOf(marginLeft)~" ";
387     if (marginRight !is 0) string ~= "marginRight="~String_valueOf(marginRight)~" ";
388     if (marginTop !is 0) string ~= "marginTop="~String_valueOf(marginTop)~" ";
389     if (marginBottom !is 0) string ~= "marginBottom="~String_valueOf(marginBottom)~" ";
390     if (spacing !is 0) string ~= "spacing="~String_valueOf(spacing)~" ";
391     string = string.trim();
392     string ~= "}";
393     return string;
394 }
395 }