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 }