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.custom.SashForm;
14 
15 
16 
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.SWTException;
19 import org.eclipse.swt.graphics.Color;
20 import org.eclipse.swt.graphics.Rectangle;
21 import org.eclipse.swt.widgets.Composite;
22 import org.eclipse.swt.widgets.Control;
23 import org.eclipse.swt.widgets.Event;
24 import org.eclipse.swt.widgets.Layout;
25 import org.eclipse.swt.widgets.Listener;
26 import org.eclipse.swt.widgets.Sash;
27 import org.eclipse.swt.custom.SashFormLayout;
28 import org.eclipse.swt.custom.SashFormData;
29 import java.lang.all;
30 
31 /**
32  * The SashForm is a composite control that lays out its children in a
33  * row or column arrangement (as specified by the orientation) and places
34  * a Sash between each child. One child may be maximized to occupy the
35  * entire size of the SashForm.  The relative sizes of the children may
36  * be specified using weights.
37  * <p>
38  * <dl>
39  * <dt><b>Styles:</b></dt>
40  * <dd>HORIZONTAL, VERTICAL, SMOOTH</dd>
41  * </dl>
42  * </p>
43  *
44  * @see <a href="http://www.eclipse.org/swt/snippets/#sashform">SashForm snippets</a>
45  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a>
46  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
47  */
48 public class SashForm : Composite {
49 
50     /**
51     * The width of all sashes in the form.
52     */
53     public int SASH_WIDTH = 3;
54 
55     int sashStyle;
56     Sash[] sashes;
57     // Remember background and foreground
58     // colors to determine whether to set
59     // sashes to the default color (null) or
60     // a specific color
61     Color background = null;
62     Color foreground = null;
63     Control[] controls;
64     Control maxControl = null;
65     Listener sashListener;
66     static const int DRAG_MINIMUM = 20;
67 
68 /**
69  * Constructs a new instance of this class given its parent
70  * and a style value describing its behavior and appearance.
71  * <p>
72  * The style value is either one of the style constants defined in
73  * class <code>SWT</code> which is applicable to instances of this
74  * class, or must be built by <em>bitwise OR</em>'ing together
75  * (that is, using the <code>int</code> "|" operator) two or more
76  * of those <code>SWT</code> style constants. The class description
77  * lists the style constants that are applicable to the class.
78  * Style bits are also inherited from superclasses.
79  * </p>
80  *
81  * @param parent a widget which will be the parent of the new instance (cannot be null)
82  * @param style the style of widget to construct
83  *
84  * @exception IllegalArgumentException <ul>
85  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
86  * </ul>
87  * @exception SWTException <ul>
88  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
89  * </ul>
90  *
91  * @see SWT#HORIZONTAL
92  * @see SWT#VERTICAL
93  * @see #getStyle()
94  */
95 public this(Composite parent, int style) {
96     super(parent, checkStyle(style));
97     super.setLayout(new SashFormLayout());
98     sashStyle = ((style & SWT.VERTICAL) !is 0) ? SWT.HORIZONTAL : SWT.VERTICAL;
99     if ((style & SWT.BORDER) !is 0) sashStyle |= SWT.BORDER;
100     if ((style & SWT.SMOOTH) !is 0) sashStyle |= SWT.SMOOTH;
101     sashListener = new class() Listener {
102         public void handleEvent(Event e) {
103             onDragSash(e);
104         }
105     };
106 }
107 static int checkStyle (int style) {
108     int mask = SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
109     return style & mask;
110 }
111 /**
112  * Returns SWT.HORIZONTAL if the controls in the SashForm are laid out side by side
113  * or SWT.VERTICAL   if the controls in the SashForm are laid out top to bottom.
114  *
115  * @return SWT.HORIZONTAL or SWT.VERTICAL
116  */
117 public int getOrientation() {
118     //checkWidget();
119     return (sashStyle & SWT.VERTICAL) !is 0 ? SWT.HORIZONTAL : SWT.VERTICAL;
120 }
121 /**
122  * Returns the width of the sashes when the controls in the SashForm are 
123  * laid out.
124  * 
125  * @return the width of the sashes
126  * 
127  * @exception SWTException <ul>
128  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
129  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
130  * </ul>
131  * 
132  * @since 3.4
133  */
134 public int getSashWidth() {
135     checkWidget();
136     return SASH_WIDTH;
137 }
138 public override int getStyle() {
139     int style = super.getStyle();
140     style |= getOrientation() is SWT.VERTICAL ? SWT.VERTICAL : SWT.HORIZONTAL;
141     if ((sashStyle & SWT.SMOOTH) !is 0) style |= SWT.SMOOTH;
142     return style;
143 }
144 /**
145  * Answer the control that currently is maximized in the SashForm.
146  * This value may be null.
147  *
148  * @return the control that currently is maximized or null
149  */
150 public Control getMaximizedControl(){
151     //checkWidget();
152     return this.maxControl;
153 }
154 /**
155  * Answer the relative weight of each child in the SashForm.  The weight represents the
156  * percent of the total width (if SashForm has Horizontal orientation) or
157  * total height (if SashForm has Vertical orientation) each control occupies.
158  * The weights are returned in order of the creation of the widgets (weight[0]
159  * corresponds to the weight of the first child created).
160  *
161  * @return the relative weight of each child
162  *
163  * @exception SWTException <ul>
164  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
165  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
166  * </ul>
167  */
168 
169 public int[] getWeights() {
170     checkWidget();
171     Control[] cArray = getControls(false);
172     int[] ratios = new int[cArray.length];
173     for (int i = 0; i < cArray.length; i++) {
174         Object data = cArray[i].getLayoutData();
175         if ( auto sfd = cast(SashFormData)data ) {
176             ratios[i] = cast(int)(sfd.weight * 1000 >> 16);
177         } else {
178             ratios[i] = 200;
179         }
180     }
181     return ratios;
182 }
183 Control[] getControls(bool onlyVisible) {
184     Control[] children = getChildren();
185     Control[] result = new Control[0];
186     for (int i = 0; i < children.length; i++) {
187         if ( null !is cast(Sash)children[i]) continue;
188         if (onlyVisible && !children[i].getVisible()) continue;
189 
190         Control[] newResult = new Control[result.length + 1];
191         System.arraycopy(result, 0, newResult, 0, result.length);
192         newResult[result.length] = children[i];
193         result = newResult;
194     }
195     return result;
196 }
197 void onDragSash(Event event) {
198     Sash sash = cast(Sash)event.widget;
199     int sashIndex = -1;
200     for (int i= 0; i < sashes.length; i++) {
201         if (sashes[i] is sash) {
202             sashIndex = i;
203             break;
204         }
205     }
206     if (sashIndex is -1) return;
207 
208     Control c1 = controls[sashIndex];
209     Control c2 = controls[sashIndex + 1];
210     Rectangle b1 = c1.getBounds();
211     Rectangle b2 = c2.getBounds();
212 
213     Rectangle sashBounds = sash.getBounds();
214     Rectangle area = getClientArea();
215     bool correction = false;
216     if (getOrientation() is SWT.HORIZONTAL) {
217         correction = b1.width < DRAG_MINIMUM || b2.width < DRAG_MINIMUM;
218         int totalWidth = b2.x + b2.width - b1.x;
219         int shift = event.x - sashBounds.x;
220         b1.width += shift;
221         b2.x += shift;
222         b2.width -= shift;
223         if (b1.width < DRAG_MINIMUM) {
224             b1.width = DRAG_MINIMUM;
225             b2.x = b1.x + b1.width + sashBounds.width;
226             b2.width = totalWidth - b2.x;
227             event.x = b1.x + b1.width;
228             event.doit = false;
229         }
230         if (b2.width < DRAG_MINIMUM) {
231             b1.width = totalWidth - DRAG_MINIMUM - sashBounds.width;
232             b2.x = b1.x + b1.width + sashBounds.width;
233             b2.width = DRAG_MINIMUM;
234             event.x = b1.x + b1.width;
235             event.doit = false;
236         }
237         Object data1 = c1.getLayoutData();
238         if (data1 is null || !( null !is cast(SashFormData)data1 )) {
239             data1 = new SashFormData();
240             c1.setLayoutData(data1);
241         }
242         Object data2 = c2.getLayoutData();
243         if (data2 is null || !( null !is cast(SashFormData)data2 )) {
244             data2 = new SashFormData();
245             c2.setLayoutData(data2);
246         }
247         (cast(SashFormData)data1).weight = ((cast(long)b1.width << 16) + area.width - 1) / area.width;
248         (cast(SashFormData)data2).weight = ((cast(long)b2.width << 16) + area.width - 1) / area.width;
249     } else {
250         correction = b1.height < DRAG_MINIMUM || b2.height < DRAG_MINIMUM;
251         int totalHeight = b2.y + b2.height - b1.y;
252         int shift = event.y - sashBounds.y;
253         b1.height += shift;
254         b2.y += shift;
255         b2.height -= shift;
256         if (b1.height < DRAG_MINIMUM) {
257             b1.height = DRAG_MINIMUM;
258             b2.y = b1.y + b1.height + sashBounds.height;
259             b2.height = totalHeight - b2.y;
260             event.y = b1.y + b1.height;
261             event.doit = false;
262         }
263         if (b2.height < DRAG_MINIMUM) {
264             b1.height = totalHeight - DRAG_MINIMUM - sashBounds.height;
265             b2.y = b1.y + b1.height + sashBounds.height;
266             b2.height = DRAG_MINIMUM;
267             event.y = b1.y + b1.height;
268             event.doit = false;
269         }
270         Object data1 = c1.getLayoutData();
271         if (data1 is null || !( null !is cast(SashFormData)data1 )) {
272             data1 = new SashFormData();
273             c1.setLayoutData(data1);
274         }
275         Object data2 = c2.getLayoutData();
276         if (data2 is null || !(null !is cast(SashFormData)data2 )) {
277             data2 = new SashFormData();
278             c2.setLayoutData(data2);
279         }
280         (cast(SashFormData)data1).weight = ((cast(long)b1.height << 16) + area.height - 1) / area.height;
281         (cast(SashFormData)data2).weight = ((cast(long)b2.height << 16) + area.height - 1) / area.height;
282     }
283     if (correction || (event.doit && event.detail !is SWT.DRAG)) {
284         c1.setBounds(b1);
285         sash.setBounds(event.x, event.y, event.width, event.height);
286         c2.setBounds(b2);
287     }
288 }
289 /**
290  * If orientation is SWT.HORIZONTAL, lay the controls in the SashForm
291  * out side by side.  If orientation is SWT.VERTICAL, lay the
292  * controls in the SashForm out top to bottom.
293  *
294  * @param orientation SWT.HORIZONTAL or SWT.VERTICAL
295  *
296  * @exception SWTException <ul>
297  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
298  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
299  *    <li>ERROR_INVALID_ARGUMENT - if the value of orientation is not SWT.HORIZONTAL or SWT.VERTICAL
300  * </ul>
301  */
302 public void setOrientation(int orientation) {
303     checkWidget();
304     if (getOrientation() is orientation) return;
305     if (orientation !is SWT.HORIZONTAL && orientation !is SWT.VERTICAL) {
306         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
307     }
308     sashStyle &= ~(SWT.HORIZONTAL | SWT.VERTICAL);
309     sashStyle |= orientation is SWT.VERTICAL ? SWT.HORIZONTAL : SWT.VERTICAL;
310     for (int i = 0; i < sashes.length; i++) {
311         sashes[i].dispose();
312         sashes[i] = new Sash(this, sashStyle);
313         sashes[i].setBackground(background);
314         sashes[i].setForeground(foreground);
315         sashes[i].addListener(SWT.Selection, sashListener);
316     }
317     layout(false);
318 }
319 public override void setBackground (Color color) {
320     super.setBackground(color);
321     background = color;
322     for (int i = 0; i < sashes.length; i++) {
323         sashes[i].setBackground(background);
324     }
325 }
326 public override void setForeground (Color color) {
327     super.setForeground(color);
328     foreground = color;
329     for (int i = 0; i < sashes.length; i++) {
330         sashes[i].setForeground(foreground);
331     }
332 }
333 /**
334  * Sets the layout which is associated with the receiver to be
335  * the argument which may be null.
336  * <p>
337  * Note: No Layout can be set on this Control because it already
338  * manages the size and position of its children.
339  * </p>
340  *
341  * @param layout the receiver's new layout or null
342  *
343  * @exception SWTException <ul>
344  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
345  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
346  * </ul>
347  */
348 public override void setLayout (Layout layout) {
349     checkWidget();
350     return;
351 }
352 /**
353  * Specify the control that should take up the entire client area of the SashForm.
354  * If one control has been maximized, and this method is called with a different control,
355  * the previous control will be minimized and the new control will be maximized.
356  * If the value of control is null, the SashForm will minimize all controls and return to
357  * the default layout where all controls are laid out separated by sashes.
358  *
359  * @param control the control to be maximized or null
360  *
361  * @exception SWTException <ul>
362  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
363  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
364  * </ul>
365  */
366 public void setMaximizedControl(Control control){
367     checkWidget();
368     if (control is null) {
369         if (maxControl !is null) {
370             this.maxControl = null;
371             layout(false);
372             for (int i= 0; i < sashes.length; i++){
373                 sashes[i].setVisible(true);
374             }
375         }
376         return;
377     }
378 
379     for (int i= 0; i < sashes.length; i++){
380         sashes[i].setVisible(false);
381     }
382     maxControl = control;
383     layout(false);
384 }
385 
386 /**
387  * Specify the width of the sashes when the controls in the SashForm are 
388  * laid out.
389  * 
390  * @param width the width of the sashes
391  * 
392  * @exception SWTException <ul>
393  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
394  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
395  * </ul>
396  * 
397  * @since 3.4
398  */
399 public void setSashWidth(int width) {
400     checkWidget();
401     if (SASH_WIDTH is width) return;
402     SASH_WIDTH = width;
403     layout(false);
404 }
405 /**
406  * Specify the relative weight of each child in the SashForm.  This will determine
407  * what percent of the total width (if SashForm has Horizontal orientation) or
408  * total height (if SashForm has Vertical orientation) each control will occupy.
409  * The weights must be positive values and there must be an entry for each
410  * non-sash child of the SashForm.
411  *
412  * @param weights the relative weight of each child
413  *
414  * @exception SWTException <ul>
415  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
416  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
417  *    <li>ERROR_INVALID_ARGUMENT - if the weights value is null or of incorrect length (must match the number of children)</li>
418  * </ul>
419  */
420 public void setWeights(int[] weights) {
421     checkWidget();
422     Control[] cArray = getControls(false);
423     if (weights is null || weights.length !is cArray.length) {
424         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
425     }
426 
427     int total = 0;
428     for (int i = 0; i < weights.length; i++) {
429         if (weights[i] < 0) {
430             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
431         }
432         total += weights[i];
433     }
434     if (total is 0) {
435         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
436     }
437     for (int i = 0; i < cArray.length; i++) {
438         Object data = cArray[i].getLayoutData();
439         if (data is null || !( null !is cast(SashFormData)data )) {
440             data = new SashFormData();
441             cArray[i].setLayoutData(data);
442         }
443         (cast(SashFormData)data).weight = ((cast(long)weights[i] << 16) + total - 1) / total;
444     }
445 
446     layout(false);
447 }
448 }