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.widgets.ExpandBar;
14 
15 
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.SWTException;
18 import org.eclipse.swt.events.ExpandAdapter;
19 import org.eclipse.swt.events.ExpandEvent;
20 import org.eclipse.swt.events.ExpandListener;
21 import org.eclipse.swt.graphics.FontMetrics;
22 import org.eclipse.swt.graphics.GC;
23 import org.eclipse.swt.graphics.GCData;
24 import org.eclipse.swt.graphics.Point;
25 import org.eclipse.swt.internal.gtk.OS;
26 import org.eclipse.swt.widgets.ExpandItem;
27 import org.eclipse.swt.widgets.Composite;
28 import org.eclipse.swt.widgets.ScrollBar;
29 import org.eclipse.swt.widgets.TypedListener;
30 import org.eclipse.swt.widgets.Event;
31 import org.eclipse.swt.widgets.Control;
32 import java.lang.all;
33 
34 /**
35  * Instances of this class support the layout of selectable
36  * expand bar items.
37  * <p>
38  * The item children that may be added to instances of this class
39  * must be of type <code>ExpandItem</code>.
40  * </p><p>
41  * <dl>
42  * <dt><b>Styles:</b></dt>
43  * <dd>V_SCROLL</dd>
44  * <dt><b>Events:</b></dt>
45  * <dd>Expand, Collapse</dd>
46  * </dl>
47  * </p><p>
48  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
49  * </p>
50  *
51  * @see ExpandItem
52  * @see ExpandEvent
53  * @see ExpandListener
54  * @see ExpandAdapter
55  * @see <a href="http://www.eclipse.org/swt/snippets/#expandbar">ExpandBar snippets</a>
56  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
57  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
58  *
59  * @since 3.2
60  */
61 public class ExpandBar : Composite {
62 
63     alias Composite.computeSize computeSize;
64     alias Composite.createHandle createHandle;
65     alias Composite.forceFocus forceFocus;
66     alias Composite.setBounds setBounds;
67     alias Composite.setForegroundColor setForegroundColor;
68 
69     ExpandItem [] items;
70     ExpandItem lastFocus;
71     int itemCount;
72     int spacing;
73     int yCurrentScroll;
74 
75 /**
76  * Constructs a new instance of this class given its parent
77  * and a style value describing its behavior and appearance.
78  * <p>
79  * The style value is either one of the style constants defined in
80  * class <code>SWT</code> which is applicable to instances of this
81  * class, or must be built by <em>bitwise OR</em>'ing together
82  * (that is, using the <code>int</code> "|" operator) two or more
83  * of those <code>SWT</code> style constants. The class description
84  * lists the style constants that are applicable to the class.
85  * Style bits are also inherited from superclasses.
86  * </p>
87  *
88  * @param parent a composite control which will be the parent of the new instance (cannot be null)
89  * @param style the style of control to construct
90  *
91  * @exception IllegalArgumentException <ul>
92  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
93  * </ul>
94  * @exception SWTException <ul>
95  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
96  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
97  * </ul>
98  *
99  * @see Widget#checkSubclass
100  * @see Widget#getStyle
101  */
102 public this (Composite parent, int style) {
103     super (parent, style);
104 }
105 
106 /**
107  * Adds the listener to the collection of listeners who will
108  * be notified when an item in the receiver is expanded or collapsed
109  * by sending it one of the messages defined in the <code>ExpandListener</code>
110  * interface.
111  *
112  * @param listener the listener which should be notified
113  *
114  * @exception IllegalArgumentException <ul>
115  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
116  * </ul>
117  * @exception SWTException <ul>
118  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
119  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
120  * </ul>
121  *
122  * @see ExpandListener
123  * @see #removeExpandListener
124  */
125 public void addExpandListener (ExpandListener listener) {
126     checkWidget ();
127     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
128     TypedListener typedListener = new TypedListener (listener);
129     addListener (SWT.Expand, typedListener);
130     addListener (SWT.Collapse, typedListener);
131 }
132 
133 protected override void checkSubclass () {
134     if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
135 }
136 
137 public override Point computeSize (int wHint, int hHint, bool changed) {
138     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
139         if (wHint !is SWT.DEFAULT && wHint < 0) wHint = 0;
140         if (hHint !is SWT.DEFAULT && hHint < 0) hHint = 0;
141         Point size = computeNativeSize (handle, wHint, hHint, changed);
142         int border = OS.gtk_container_get_border_width (handle);
143         size.x += 2 * border;
144         size.y += 2 * border;
145         return size;
146     } else {
147         int height = 0, width = 0;
148         if (wHint is SWT.DEFAULT || hHint is SWT.DEFAULT) {
149             if (itemCount > 0) {
150                 height += spacing;
151                 GC gc = new GC (this);
152                 for (int i = 0; i < itemCount; i++) {
153                     ExpandItem item = items [i];
154                     height += item.getHeaderHeight ();
155                     if (item.expanded) height += item.height;
156                     height += spacing;
157                     width = Math.max (width, item.getPreferredWidth (gc));
158                 }
159                 gc.dispose ();
160             }
161         }
162         if (width is 0) width = DEFAULT_WIDTH;
163         if (height is 0) height = DEFAULT_HEIGHT;
164         if (wHint !is SWT.DEFAULT) width = wHint;
165         if (hHint !is SWT.DEFAULT) height = hHint;
166         return new Point (width, height);
167     }
168 }
169 
170 override void createHandle (int index) {
171     state |= HANDLE;
172     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
173         fixedHandle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
174         if (fixedHandle is null) error (SWT.ERROR_NO_HANDLES);
175         OS.gtk_fixed_set_has_window (fixedHandle, true);
176         handle = cast(GtkWidget*)OS.gtk_vbox_new (false, 0);
177         if (handle is null) error (SWT.ERROR_NO_HANDLES);
178         if ((style & SWT.V_SCROLL) !is 0) {
179             scrolledHandle = cast(GtkWidget*)OS.gtk_scrolled_window_new (null, null);
180             if (scrolledHandle is null) error (SWT.ERROR_NO_HANDLES);
181             int vsp = (style & SWT.V_SCROLL) !is 0 ? OS.GTK_POLICY_AUTOMATIC : OS.GTK_POLICY_NEVER;
182             OS.gtk_scrolled_window_set_policy (scrolledHandle, OS.GTK_POLICY_NEVER, vsp);
183             OS.gtk_container_add (fixedHandle, scrolledHandle);
184             OS.gtk_scrolled_window_add_with_viewport (scrolledHandle, handle);
185         } else {
186             OS.gtk_container_add (fixedHandle, handle);
187         }
188         OS.gtk_container_set_border_width (handle, 0);
189     } else {
190         auto topHandle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
191         if (topHandle is null) error (SWT.ERROR_NO_HANDLES);
192         OS.gtk_fixed_set_has_window (topHandle, true);
193         if ((style & SWT.V_SCROLL) !is 0) {
194             fixedHandle = topHandle;
195             scrolledHandle = cast(GtkWidget*)OS.gtk_scrolled_window_new (null, null);
196             if (scrolledHandle is null) error (SWT.ERROR_NO_HANDLES);
197             handle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
198             if (handle is null) error (SWT.ERROR_NO_HANDLES);
199             OS.gtk_fixed_set_has_window (handle, true);
200             OS.gtk_container_add (fixedHandle, scrolledHandle);
201 
202             /*
203             * Force the scrolledWindow to have a single child that is
204             * not scrolled automatically.  Calling gtk_container_add()
205             * seems to add the child correctly but cause a warning.
206             */
207             bool warnings = display.getWarnings ();
208             display.setWarnings (false);
209             OS.gtk_container_add (scrolledHandle, handle);
210             display.setWarnings (warnings);
211         } else {
212             handle = topHandle;
213         }
214         OS.GTK_WIDGET_SET_FLAGS (handle, OS.GTK_CAN_FOCUS);
215     }
216 }
217 
218 void createItem (ExpandItem item, int style, int index) {
219     if (!(0 <= index && index <= itemCount)) error (SWT.ERROR_INVALID_RANGE);
220     if (itemCount is items.length) {
221         ExpandItem [] newItems = new ExpandItem [itemCount + 4];
222         System.arraycopy (items, 0, newItems, 0, items.length);
223         items = newItems;
224     }
225     System.arraycopy (items, index, items, index + 1, itemCount - index);
226     items [index] = item;
227     itemCount++;
228     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
229         if (lastFocus is null) lastFocus = item;
230     }
231     item.width = Math.max (0, getClientArea ().width - spacing * 2);
232     layoutItems (index, true);
233 }
234 
235 override void createWidget (int index) {
236     super.createWidget (index);
237     items = new ExpandItem [4];
238 }
239 
240 void destroyItem (ExpandItem item) {
241     int index = 0;
242     while (index < itemCount) {
243         if (items [index] is item) break;
244         index++;
245     }
246     if (index is itemCount) return;
247     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
248         if (item is lastFocus) {
249             int focusIndex = index > 0 ? index - 1 : 1;
250             if (focusIndex < itemCount) {
251                 lastFocus = items [focusIndex];
252                 lastFocus.redraw ();
253             } else {
254                 lastFocus = null;
255             }
256         }
257     }
258     System.arraycopy (items, index + 1, items, index, --itemCount - index);
259     items [itemCount] = null;
260     item.redraw ();
261     layoutItems (index, true);
262 }
263 
264 override GtkWidget* eventHandle () {
265     return OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0) ? fixedHandle : handle;
266 }
267 
268 override bool forceFocus (GtkWidget* focusHandle) {
269     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
270         if (lastFocus !is null && lastFocus.setFocus ()) return true;
271         for (int i = 0; i < itemCount; i++) {
272             ExpandItem item = items [i];
273             if (item.setFocus ()) return true;
274         }
275     }
276     return super.forceFocus (focusHandle);
277 }
278 
279 override bool hasFocus () {
280     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
281         for (int i=0; i<itemCount; i++) {
282             ExpandItem item = items [i];
283             if (item.hasFocus ()) return true;
284         }
285     }
286     return super.hasFocus();
287 }
288 
289 int getBandHeight () {
290     if (font is null) return ExpandItem.CHEVRON_SIZE;
291     GC gc = new GC (this);
292     FontMetrics metrics = gc.getFontMetrics ();
293     gc.dispose ();
294     return Math.max (ExpandItem.CHEVRON_SIZE, metrics.getHeight ());
295 }
296 
297 override GdkColor* getForegroundColor () {
298     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
299         if ((state & FOREGROUND) is 0) {
300             return display.getSystemColor (SWT.COLOR_TITLE_FOREGROUND).handle;
301         }
302     }
303     return super.getForegroundColor ();
304 }
305 
306 /**
307  * Returns the item at the given, zero-relative index in the
308  * receiver. Throws an exception if the index is out of range.
309  *
310  * @param index the index of the item to return
311  * @return the item at the given index
312  *
313  * @exception IllegalArgumentException <ul>
314  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
315  * </ul>
316  * @exception SWTException <ul>
317  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
318  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
319  * </ul>
320  */
321 public ExpandItem getItem (int index) {
322     checkWidget();
323     if (!(0 <= index && index < itemCount)) error (SWT.ERROR_INVALID_RANGE);
324     return items [index];
325 }
326 
327 /**
328  * Returns the number of items contained in the receiver.
329  *
330  * @return the number of items
331  *
332  * @exception SWTException <ul>
333  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
334  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
335  * </ul>
336  */
337 public int getItemCount () {
338     checkWidget();
339     return itemCount;
340 }
341 
342 /**
343  * Returns an array of <code>ExpandItem</code>s which are the items
344  * in the receiver.
345  * <p>
346  * Note: This is not the actual structure used by the receiver
347  * to maintain its list of items, so modifying the array will
348  * not affect the receiver.
349  * </p>
350  *
351  * @return the items in the receiver
352  *
353  * @exception SWTException <ul>
354  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
355  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
356  * </ul>
357  */
358 public ExpandItem [] getItems () {
359     checkWidget ();
360     ExpandItem [] result = new ExpandItem [itemCount];
361     System.arraycopy (items, 0, result, 0, itemCount);
362     return result;
363 }
364 
365 /**
366  * Returns the receiver's spacing.
367  *
368  * @return the spacing
369  *
370  * @exception SWTException <ul>
371  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
372  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
373  * </ul>
374  */
375 public int getSpacing () {
376     checkWidget ();
377     return spacing;
378 }
379 
380 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
381     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
382         int x = cast(int)gdkEvent.x;
383         int y = cast(int)gdkEvent.y;
384         for (int i = 0; i < itemCount; i++) {
385             ExpandItem item = items[i];
386             bool hover = item.x <= x && x < (item.x + item.width) && item.y <= y && y < (item.y + getBandHeight ());
387             if (hover && item !is lastFocus) {
388                 lastFocus.redraw ();
389                 lastFocus = item;
390                 lastFocus.redraw ();
391                 forceFocus ();
392                 break;
393             }
394         }
395     }
396     return super.gtk_button_press_event (widget, gdkEvent);
397 }
398 
399 override int gtk_button_release_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
400     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
401         if (lastFocus !is null) {
402             int x = cast(int)gdkEvent.x;
403             int y = cast(int)gdkEvent.y;
404             bool hover = lastFocus.x <= x && x < (lastFocus.x + lastFocus.width) && lastFocus.y <= y && y < (lastFocus.y + getBandHeight ());
405             if (hover) {
406                 Event ev = new Event ();
407                 ev.item = lastFocus;
408                 notifyListeners (lastFocus.expanded ? SWT.Collapse : SWT.Expand, ev);
409                 lastFocus.expanded = !lastFocus.expanded;
410                 showItem (lastFocus);
411             }
412         }
413     }
414     return super.gtk_button_release_event (widget, gdkEvent);
415 }
416 
417 override int gtk_expose_event (GtkWidget* widget, GdkEventExpose* gdkEvent) {
418     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
419         GCData data = new GCData ();
420         data.damageRgn = gdkEvent.region;
421         GC gc = GC.gtk_new (this, data);
422         OS.gdk_gc_set_clip_region (gc.handle, gdkEvent.region);
423         bool hasFocus = isFocusControl ();
424         for (int i = 0; i < itemCount; i++) {
425             ExpandItem item = items [i];
426             item.drawItem (gc, hasFocus && item is lastFocus);
427         }
428         gc.dispose ();
429     }
430     return super.gtk_expose_event (widget, gdkEvent);
431 }
432 
433 override int gtk_focus_in_event (GtkWidget* widget, GdkEventFocus* event) {
434     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
435         if (lastFocus !is null) lastFocus.redraw ();
436     }
437     return super.gtk_focus_in_event(widget, event);
438 }
439 
440 override int gtk_focus_out_event (GtkWidget* widget, GdkEventFocus* event) {
441     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
442         if (lastFocus !is null) lastFocus.redraw ();
443     }
444     return super.gtk_focus_out_event (widget, event);
445 }
446 
447 override int gtk_key_press_event (GtkWidget* widget, GdkEventKey* gdkEvent) {
448     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
449         if (!hasFocus ()) return 0;
450         auto result = super.gtk_key_press_event (widget, gdkEvent);
451         if (result !is 0) return result;
452         int index = 0;
453         while (index < itemCount) {
454             if (items [index].hasFocus ()) break;
455             index++;
456         }
457         bool next = false;
458         switch (gdkEvent.keyval) {
459             case OS.GDK_Up:
460             case OS.GDK_Left: next = false; break;
461             case OS.GDK_Down:
462             case OS.GDK_Right: next = true; break;
463             default: return result;
464         }
465         int start = index, offset = next ? 1 : -1;
466         while ((index = (index + offset + itemCount) % itemCount) !is start) {
467             ExpandItem item = items [index];
468             if (item.setFocus ()) return result;
469         }
470         return result;
471     } else {
472         if (lastFocus !is null) {
473             switch (gdkEvent.keyval) {
474                 case OS.GDK_Return:
475                 case OS.GDK_space:
476                     Event ev = new Event ();
477                     ev.item = lastFocus;
478                     sendEvent (lastFocus.expanded ? SWT.Collapse :SWT.Expand, ev);
479                     lastFocus.expanded = !lastFocus.expanded;
480                     showItem (lastFocus);
481                     break;
482                 case OS.GDK_Up:
483                 case OS.GDK_KP_Up: {
484                     int focusIndex = indexOf (lastFocus);
485                     if (focusIndex > 0) {
486                         lastFocus.redraw ();
487                         lastFocus = items [focusIndex - 1];
488                         lastFocus.redraw ();
489                     }
490                     break;
491                 }
492                 case OS.GDK_Down:
493                 case OS.GDK_KP_Down: {
494                     int focusIndex = indexOf (lastFocus);
495                     if (focusIndex < itemCount - 1) {
496                         lastFocus.redraw ();
497                         lastFocus = items [focusIndex + 1];
498                         lastFocus.redraw ();
499                     }
500                     break;
501                 }
502                 default:
503             }
504         }
505     }
506     return super.gtk_key_press_event (widget, gdkEvent);
507 }
508 
509 /**
510  * Searches the receiver's list starting at the first item
511  * (index 0) until an item is found that is equal to the
512  * argument, and returns the index of that item. If no item
513  * is found, returns -1.
514  *
515  * @param item the search item
516  * @return the index of the item
517  *
518  * @exception IllegalArgumentException <ul>
519  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
520  *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
521  * </ul>
522  * @exception SWTException <ul>
523  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
524  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
525  * </ul>
526  */
527 public int indexOf (ExpandItem item) {
528     checkWidget();
529     if (item is null) error (SWT.ERROR_NULL_ARGUMENT);
530     for (int i = 0; i < itemCount; i++) {
531         if (items [i] is item) return i;
532     }
533     return -1;
534 }
535 
536 void layoutItems (int index, bool setScrollbar_) {
537     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
538         for (int i = 0; i < itemCount; i++) {
539             ExpandItem item = items [i];
540             if (item !is null) item.resizeControl (yCurrentScroll);
541         }
542     } else {
543         if (index < itemCount) {
544             int y = spacing - yCurrentScroll;
545             for (int i = 0; i < index; i++) {
546                 ExpandItem item = items [i];
547                 if (item.expanded) y += item.height;
548                 y += item.getHeaderHeight() + spacing;
549             }
550             for (int i = index; i < itemCount; i++) {
551                 ExpandItem item = items [i];
552                 item.setBounds (spacing, y, 0, 0, true, false);
553                 if (item.expanded) y += item.height;
554                 y += item.getHeaderHeight() + spacing;
555             }
556         }
557         if (setScrollbar_) setScrollbar ();
558     }
559 }
560 
561 override GtkWidget* parentingHandle () {
562     return OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0) ? fixedHandle : handle;
563 }
564 
565 override void releaseChildren (bool destroy) {
566     for (int i = 0; i < itemCount; i++) {
567         ExpandItem item = items [i];
568         if (item !is null && !item.isDisposed ()) {
569             item.release (false);
570         }
571     }
572     super.releaseChildren (destroy);
573 }
574 
575 /**
576  * Removes the listener from the collection of listeners who will
577  * be notified when items in the receiver are expanded or collapsed.
578  *
579  * @param listener the listener which should no longer be notified
580  *
581  * @exception IllegalArgumentException <ul>
582  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
583  * </ul>
584  * @exception SWTException <ul>
585  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
586  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
587  * </ul>
588  *
589  * @see ExpandListener
590  * @see #addExpandListener
591  */
592 public void removeExpandListener (ExpandListener listener) {
593     checkWidget ();
594     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
595     if (eventTable is null) return;
596     eventTable.unhook (SWT.Expand, listener);
597     eventTable.unhook (SWT.Collapse, listener);
598 }
599 
600 override int setBounds (int x, int y, int width, int height, bool move, bool resize) {
601     int result = super.setBounds (x, y, width, height, move, resize);
602     if (OS.GTK_VERSION < OS.buildVERSION (2, 4, 0)) {
603         if (resize) {
604             if ((style & SWT.V_SCROLL) !is 0) {
605                 setScrollbar ();
606             } else {
607                 for (int i = 0; i < itemCount; i++) {
608                     ExpandItem item = items [i];
609                     int newWidth = Math.max (0, getClientArea ().width - spacing * 2);
610                     if (item.width !is newWidth) {
611                         item.setBounds (0, 0, newWidth, item.height, false, true);
612                     }
613                 }
614             }
615         }
616     }
617     return result;
618 }
619 
620 override void setFontDescription (PangoFontDescription* font) {
621     super.setFontDescription (font);
622     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
623         for (int i = 0; i < itemCount; i++) {
624             items[i].setFontDescription (font);
625         }
626         layoutItems (0, true);
627     }
628 }
629 
630 override void setForegroundColor (GdkColor* color) {
631     super.setForegroundColor (color);
632     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
633         for (int i = 0; i < itemCount; i++) {
634             items[i].setForegroundColor (color);
635         }
636     }
637 }
638 
639 void setScrollbar () {
640     if (itemCount is 0) return;
641     if ((style & SWT.V_SCROLL) is 0) return;
642     int height = getClientArea ().height;
643     ExpandItem item = items [itemCount - 1];
644     int maxHeight = item.y + getBandHeight () + spacing;
645     if (item.expanded) maxHeight += item.height;
646     auto adjustmentHandle = OS.gtk_scrolled_window_get_vadjustment (scrolledHandle);
647     yCurrentScroll = cast(int)adjustmentHandle.value;
648 
649     //claim bottom free space
650     if (yCurrentScroll > 0 && height > maxHeight) {
651         yCurrentScroll = Math.max (0, yCurrentScroll + maxHeight - height);
652         layoutItems (0, false);
653     }
654     maxHeight += yCurrentScroll;
655     adjustmentHandle.value = Math.min (yCurrentScroll, maxHeight);
656     adjustmentHandle.upper = maxHeight;
657     adjustmentHandle.page_size = height;
658     OS.gtk_adjustment_changed (adjustmentHandle);
659     int policy = maxHeight > height ? OS.GTK_POLICY_ALWAYS : OS.GTK_POLICY_NEVER;
660     OS.gtk_scrolled_window_set_policy (scrolledHandle, OS.GTK_POLICY_NEVER, policy);
661     int width = OS.GTK_WIDGET_WIDTH (fixedHandle) - spacing * 2;
662     if (policy is OS.GTK_POLICY_ALWAYS) {
663         auto vHandle = OS.GTK_SCROLLED_WINDOW_VSCROLLBAR (scrolledHandle);
664         GtkRequisition requisition;
665         OS.gtk_widget_size_request (vHandle, &requisition);
666         width -= requisition.width;
667     }
668     width = Math.max (0,  width);
669     for (int i = 0; i < itemCount; i++) {
670         ExpandItem item2 = items[i];
671         item2.setBounds (0, 0, width, item2.height, false, true);
672     }
673 }
674 
675 /**
676  * Sets the receiver's spacing. Spacing specifies the number of pixels allocated around
677  * each item.
678  * 
679  * @param spacing the spacing around each item
680  *
681  * @exception SWTException <ul>
682  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
683  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
684  * </ul>
685  */
686 public void setSpacing (int spacing) {
687     checkWidget ();
688     if (spacing < 0) return;
689     if (spacing is this.spacing) return;
690     this.spacing = spacing;
691     if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
692         OS.gtk_box_set_spacing (handle, spacing);
693         OS.gtk_container_set_border_width (handle, spacing);
694     } else {
695         if ((style & SWT.V_SCROLL) is 0) {
696             int width = Math.max (0, getClientArea ().width - spacing * 2);
697             for (int i = 0; i < itemCount; i++) {
698                 ExpandItem item = items [i];
699                 if (item.width !is width) item.setBounds (0, 0, width, item.height, false, true);
700             }
701         }
702         layoutItems (0, true);
703         redraw ();
704     }
705 }
706 
707 void showItem (ExpandItem item) {
708     Control control = item.control;
709     if (control !is null && !control.isDisposed ()) {
710         control.setVisible (item.expanded);
711     }
712     item.redraw ();
713     int index = indexOf (item);
714     layoutItems (index + 1, true);
715 }
716 
717 override void updateScrollBarValue (ScrollBar bar) {
718     yCurrentScroll = bar.getSelection();
719     layoutItems (0, false);
720 }
721 }