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.CTabFolder;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.SWTException;
17 import org.eclipse.swt.accessibility.ACC;
18 import org.eclipse.swt.accessibility.Accessible;
19 import org.eclipse.swt.accessibility.AccessibleAdapter;
20 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
21 import org.eclipse.swt.accessibility.AccessibleControlEvent;
22 import org.eclipse.swt.accessibility.AccessibleEvent;
23 import org.eclipse.swt.events.SelectionAdapter;
24 import org.eclipse.swt.events.SelectionEvent;
25 import org.eclipse.swt.events.SelectionListener;
26 import org.eclipse.swt.graphics.Color;
27 import org.eclipse.swt.graphics.Font;
28 import org.eclipse.swt.graphics.FontData;
29 import org.eclipse.swt.graphics.GC;
30 import org.eclipse.swt.graphics.Image;
31 import org.eclipse.swt.graphics.Point;
32 import org.eclipse.swt.graphics.RGB;
33 import org.eclipse.swt.graphics.Rectangle;
34 import org.eclipse.swt.graphics.Region;
35 import org.eclipse.swt.widgets.Composite;
36 import org.eclipse.swt.widgets.Control;
37 import org.eclipse.swt.widgets.Display;
38 import org.eclipse.swt.widgets.Event;
39 import org.eclipse.swt.widgets.Layout;
40 import org.eclipse.swt.widgets.Listener;
41 import org.eclipse.swt.widgets.Menu;
42 import org.eclipse.swt.widgets.MenuItem;
43 import org.eclipse.swt.widgets.TypedListener;
44 import org.eclipse.swt.custom.CTabItem;
45 import org.eclipse.swt.custom.CTabFolder2Listener;
46 import org.eclipse.swt.custom.CTabFolderListener;
47 import org.eclipse.swt.custom.CTabFolderLayout;
48 import org.eclipse.swt.custom.CTabFolderEvent;
49 
50 import java.lang.all;
51 import java.nonstandard.UnsafeUtf;
52 
53 /**
54  *
55  * Instances of this class implement the notebook user interface
56  * metaphor.  It allows the user to select a notebook page from
57  * set of pages.
58  * <p>
59  * The item children that may be added to instances of this class
60  * must be of type <code>CTabItem</code>.
61  * <code>Control</code> children are created and then set into a
62  * tab item using <code>CTabItem#setControl</code>.
63  * </p><p>
64  * Note that although this class is a subclass of <code>Composite</code>,
65  * it does not make sense to set a layout on it.
66  * </p><p>
67  * <dl>
68  * <dt><b>Styles:</b></dt>
69  * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd>
70  * <dt><b>Events:</b></dt>
71  * <dd>Selection</dd>
72  * <dd>"CTabFolder2"</dd>
73  * </dl>
74  * <p>
75  * Note: Only one of the styles TOP and BOTTOM
76  * may be specified.
77  * </p><p>
78  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
79  * </p>
80  *
81  * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a>
82  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a>
83  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
84  */
85 
86 public class CTabFolder : Composite {
87 
88     /**
89      * marginWidth specifies the number of pixels of horizontal margin
90      * that will be placed along the left and right edges of the form.
91      *
92      * The default value is 0.
93      */
94     public int marginWidth = 0;
95     /**
96      * marginHeight specifies the number of pixels of vertical margin
97      * that will be placed along the top and bottom edges of the form.
98      *
99      * The default value is 0.
100      */
101     public int marginHeight = 0;
102 
103     /**
104      * A multiple of the tab height that specifies the minimum width to which a tab
105      * will be compressed before scrolling arrows are used to navigate the tabs.
106      *
107      * NOTE This field is badly named and can not be fixed for backwards compatibility.
108      * It should not be capitalized.
109      *
110      * @deprecated This field is no longer used.  See setMinimumCharacters(int)
111      */
112     public int MIN_TAB_WIDTH = 4;
113 
114     /**
115      * Color of innermost line of drop shadow border.
116      *
117      * NOTE This field is badly named and can not be fixed for backwards compatibility.
118      * It should be capitalized.
119      *
120      * @deprecated drop shadow border is no longer drawn in 3.0
121      */
122     public static RGB borderInsideRGB;
123     /**
124      * Color of middle line of drop shadow border.
125      *
126      * NOTE This field is badly named and can not be fixed for backwards compatibility.
127      * It should be capitalized.
128      *
129      * @deprecated drop shadow border is no longer drawn in 3.0
130      */
131     public static RGB borderMiddleRGB;
132     /**
133      * Color of outermost line of drop shadow border.
134      *
135      * NOTE This field is badly named and can not be fixed for backwards compatibility.
136      * It should be capitalized.
137      *
138      * @deprecated drop shadow border is no longer drawn in 3.0
139      */
140     public static RGB borderOutsideRGB;
141 
142     /* sizing, positioning */
143     int xClient, yClient;
144     bool onBottom = false;
145     bool single = false;
146     bool simple = true;
147     int fixedTabHeight = SWT.DEFAULT;
148     int tabHeight;
149     int minChars = 20;
150 
151     /* item management */
152     CTabItem[] items;
153     int firstIndex = -1; // index of the left most visible tab.
154     int selectedIndex = -1;
155     int[] priority;
156     bool mru = false;
157     Listener listener;
158 
159     /* External Listener management */
160     CTabFolder2Listener[] folderListeners;
161     // support for deprecated listener mechanism
162     CTabFolderListener[] tabListeners;
163 
164     /* Selected item appearance */
165     Image selectionBgImage;
166     Color[] selectionGradientColors;
167     int[] selectionGradientPercents;
168     bool selectionGradientVertical;
169     Color selectionForeground;
170     Color selectionBackground;  //selection fade end
171     Color selectionFadeStart;
172 
173     Color selectionHighlightGradientBegin = null;  //null is no highlight
174     //Although we are given new colours all the time to show different states (active, etc),
175     //some of which may have a highlight and some not, we'd like to retain the highlight colours
176     //as a cache so that we can reuse them if we're again told to show the highlight.
177     //We are relying on the fact that only one tab state usually gets a highlight, so only
178     //a single cache is required. If that happens to not be true, cache simply becomes less effective,
179     //but we don't leak colours.
180     Color[] selectionHighlightGradientColorsCache = null;  //null is a legal value, check on access
181 
182     /* Unselected item appearance */
183     Image bgImage;
184     Color[] gradientColors;
185     int[] gradientPercents;
186     bool gradientVertical;
187     bool showUnselectedImage = true;
188 
189     static Color borderColor;
190 
191     // close, min/max and chevron buttons
192     bool showClose = false;
193     bool showUnselectedClose = true;
194 
195     Rectangle chevronRect;
196     int chevronImageState = NORMAL;
197     bool showChevron = false;
198     Menu showMenu;
199 
200     bool showMin = false;
201     Rectangle minRect;
202     bool minimized = false;
203     int minImageState = NORMAL;
204 
205     bool showMax = false;
206     Rectangle maxRect;
207     bool maximized = false;
208     int maxImageState = NORMAL;
209 
210     Control topRight;
211     Rectangle topRightRect;
212     int topRightAlignment = SWT.RIGHT;
213 
214     // borders and shapes
215     int borderLeft = 0;
216     int borderRight = 0;
217     int borderTop = 0;
218     int borderBottom = 0;
219 
220     int highlight_margin = 0;
221     int highlight_header = 0;
222 
223     int[] curve;
224     int[] topCurveHighlightStart;
225     int[] topCurveHighlightEnd;
226     int curveWidth = 0;
227     int curveIndent = 0;
228 
229     // when disposing CTabFolder, don't try to layout the items or
230     // change the selection as each child is destroyed.
231     bool inDispose = false;
232 
233     // keep track of size changes in order to redraw only affected area
234     // on Resize
235     Point oldSize;
236     Font oldFont;
237 
238     // internal constants
239     static const int DEFAULT_WIDTH = 64;
240     static const int DEFAULT_HEIGHT = 64;
241     static const int BUTTON_SIZE = 18;
242 
243     static const int[] TOP_LEFT_CORNER = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
244 
245     //TOP_LEFT_CORNER_HILITE is laid out in reverse (ie. top to bottom)
246     //so can fade in same direction as right swoop curve
247     static const int[] TOP_LEFT_CORNER_HILITE = [5,2, 4,2, 3,3, 2,4, 2,5, 1,6];
248 
249     static const int[] TOP_RIGHT_CORNER = [-6,0, -5,1, -4,1, -1,4, -1,5, 0,6];
250     static const int[] BOTTOM_LEFT_CORNER = [0,-6, 1,-5, 1,-4, 4,-1, 5,-1, 6,0];
251     static const int[] BOTTOM_RIGHT_CORNER = [-6,0, -5,-1, -4,-1, -1,-4, -1,-5, 0,-6];
252 
253     static const int[] SIMPLE_TOP_LEFT_CORNER = [0,2, 1,1, 2,0];
254     static const int[] SIMPLE_TOP_RIGHT_CORNER = [-2,0, -1,1, 0,2];
255     static const int[] SIMPLE_BOTTOM_LEFT_CORNER = [0,-2, 1,-1, 2,0];
256     static const int[] SIMPLE_BOTTOM_RIGHT_CORNER = [-2,0, -1,-1, 0,-2];
257     static const int[] SIMPLE_UNSELECTED_INNER_CORNER = [0,0];
258 
259     static const int[] TOP_LEFT_CORNER_BORDERLESS = [0,6, 1,5, 1,4, 4,1, 5,1, 6,0];
260     static const int[] TOP_RIGHT_CORNER_BORDERLESS = [-7,0, -6,1, -5,1, -2,4, -2,5, -1,6];
261     static const int[] BOTTOM_LEFT_CORNER_BORDERLESS = [0,-6, 1,-6, 1,-5, 2,-4, 4,-2, 5,-1, 6,-1, 6,0];
262     static const int[] BOTTOM_RIGHT_CORNER_BORDERLESS = [-7,0, -7,-1, -6,-1, -5,-2, -3,-4, -2,-5, -2,-6, -1,-6];
263 
264     static const int[] SIMPLE_TOP_LEFT_CORNER_BORDERLESS = [0,2, 1,1, 2,0];
265     static const int[] SIMPLE_TOP_RIGHT_CORNER_BORDERLESS= [-3,0, -2,1, -1,2];
266     static const int[] SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS = [0,-3, 1,-2, 2,-1, 3,0];
267     static const int[] SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS = [-4,0, -3,-1, -2,-2, -1,-3];
268 
269     static const int SELECTION_FOREGROUND = SWT.COLOR_LIST_FOREGROUND;
270     static const int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND;
271     static const int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW;
272     static const int FOREGROUND = SWT.COLOR_WIDGET_FOREGROUND;
273     static const int BACKGROUND = SWT.COLOR_WIDGET_BACKGROUND;
274     static const int BUTTON_BORDER = SWT.COLOR_WIDGET_DARK_SHADOW;
275     static const int BUTTON_FILL = SWT.COLOR_LIST_BACKGROUND;
276 
277     static const int NONE = 0;
278     static const int NORMAL = 1;
279     static const int HOT = 2;
280     static const int SELECTED = 3;
281     static const RGB CLOSE_FILL;
282 
283     static const int CHEVRON_CHILD_ID = 0;
284     static const int MINIMIZE_CHILD_ID = 1;
285     static const int MAXIMIZE_CHILD_ID = 2;
286     static const int EXTRA_CHILD_ID_COUNT = 3;
287 
288 static this(){
289     borderInsideRGB  = new RGB (132, 130, 132);
290     borderMiddleRGB  = new RGB (143, 141, 138);
291     borderOutsideRGB = new RGB (171, 168, 165);
292     CLOSE_FILL = new RGB(252, 160, 160);
293 }
294 
295 /**
296  * Constructs a new instance of this class given its parent
297  * and a style value describing its behavior and appearance.
298  * <p>
299  * The style value is either one of the style constants defined in
300  * class <code>SWT</code> which is applicable to instances of this
301  * class, or must be built by <em>bitwise OR</em>'ing together
302  * (that is, using the <code>int</code> "|" operator) two or more
303  * of those <code>SWT</code> style constants. The class description
304  * lists the style constants that are applicable to the class.
305  * Style bits are also inherited from superclasses.
306  * </p>
307  *
308  * @param parent a widget which will be the parent of the new instance (cannot be null)
309  * @param style the style of widget to construct
310  *
311  * @exception IllegalArgumentException <ul>
312  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
313  * </ul>
314  * @exception SWTException <ul>
315  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
316  * </ul>
317  *
318  * @see SWT#TOP
319  * @see SWT#BOTTOM
320  * @see SWT#FLAT
321  * @see SWT#BORDER
322  * @see SWT#SINGLE
323  * @see SWT#MULTI
324  * @see #getStyle()
325  */
326 public this(Composite parent, int style) {
327     chevronRect = new Rectangle(0, 0, 0, 0);
328     minRect = new Rectangle(0, 0, 0, 0);
329     maxRect = new Rectangle(0, 0, 0, 0);
330     topRightRect = new Rectangle(0, 0, 0, 0);
331     super(parent, checkStyle (parent, style));
332     super.setLayout(new CTabFolderLayout());
333     int style2 = super.getStyle();
334     oldFont = getFont();
335     onBottom = (style2 & SWT.BOTTOM) !is 0;
336     showClose = (style2 & SWT.CLOSE) !is 0;
337 //  showMin = (style2 & SWT.MIN) !is 0; - conflicts with SWT.TOP
338 //  showMax = (style2 & SWT.MAX) !is 0; - conflicts with SWT.BOTTOM
339     single = (style2 & SWT.SINGLE) !is 0;
340     borderLeft = borderRight = (style & SWT.BORDER) !is 0 ? 1 : 0;
341     borderTop = onBottom ? borderLeft : 0;
342     borderBottom = onBottom ? 0 : borderLeft;
343     highlight_header = (style & SWT.FLAT) !is 0 ? 1 : 3;
344     highlight_margin = (style & SWT.FLAT) !is 0 ? 0 : 2;
345     //set up default colors
346     Display display = getDisplay();
347     selectionForeground = display.getSystemColor(SELECTION_FOREGROUND);
348     selectionBackground = display.getSystemColor(SELECTION_BACKGROUND);
349     borderColor = display.getSystemColor(BORDER1_COLOR);
350     updateTabHeight(false);
351 
352     initAccessible();
353 
354     // Add all listeners
355     listener = new class() Listener {
356         public void handleEvent(Event event) {
357             switch (event.type) {
358                 case SWT.Dispose:          onDispose(event); break;
359                 case SWT.DragDetect:       onDragDetect(event); break;
360                 case SWT.FocusIn:          onFocus(event);  break;
361                 case SWT.FocusOut:         onFocus(event);  break;
362                 case SWT.KeyDown:          onKeyDown(event); break;
363                 case SWT.MouseDoubleClick: onMouseDoubleClick(event); break;
364                 case SWT.MouseDown:        onMouse(event);  break;
365                 case SWT.MouseEnter:       onMouse(event);  break;
366                 case SWT.MouseExit:        onMouse(event);  break;
367                 case SWT.MouseMove:        onMouse(event); break;
368                 case SWT.MouseUp:          onMouse(event); break;
369                 case SWT.Paint:            onPaint(event);  break;
370                 case SWT.Resize:           onResize();  break;
371                 case SWT.Traverse:         onTraverse(event); break;
372                 default:
373             }
374         }
375     };
376 
377     int[] folderEvents = [
378         SWT.Dispose,
379         SWT.DragDetect,
380         SWT.FocusIn,
381         SWT.FocusOut,
382         SWT.KeyDown,
383         SWT.MouseDoubleClick,
384         SWT.MouseDown,
385         SWT.MouseEnter,
386         SWT.MouseExit,
387         SWT.MouseMove,
388         SWT.MouseUp,
389         SWT.Paint,
390         SWT.Resize,
391         SWT.Traverse,
392     ];
393     for (int i = 0; i < folderEvents.length; i++) {
394         addListener(folderEvents[i], listener);
395     }
396 }
397 static int checkStyle (Composite parent, int style) {
398     int mask = SWT.CLOSE | SWT.TOP | SWT.BOTTOM | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.SINGLE | SWT.MULTI;
399     style = style & mask;
400     // TOP and BOTTOM are mutually exclusive.
401     // TOP is the default
402     if ((style & SWT.TOP) !is 0) style = style & ~SWT.BOTTOM;
403     // SINGLE and MULTI are mutually exclusive.
404     // MULTI is the default
405     if ((style & SWT.MULTI) !is 0) style = style & ~SWT.SINGLE;
406     // reduce the flash by not redrawing the entire area on a Resize event
407     style |= SWT.NO_REDRAW_RESIZE;
408     //TEMPORARY CODE
409     /*
410      * The default background on carbon and some GTK themes is not a solid color
411      * but a texture.  To show the correct default background, we must allow
412      * the operating system to draw it and therefore, we can not use the
413      * NO_BACKGROUND style.  The NO_BACKGROUND style is not required on platforms
414      * that use double buffering which is true in both of these cases.
415      */
416     String platform = SWT.getPlatform();
417     if ("carbon"==platform || "gtk"==platform) return style; //$NON-NLS-1$ //$NON-NLS-2$
418 
419     //TEMPORARY CODE
420     /*
421      * In Right To Left orientation on Windows, all GC calls that use a brush are drawing
422      * offset by one pixel.  This results in some parts of the CTabFolder not drawing correctly.
423      * To alleviate some of the appearance problems, allow the OS to draw the background.
424      * This does not draw correctly but the result is less obviously wrong.
425      */
426     if ((style & SWT.RIGHT_TO_LEFT) !is 0) return style;
427     if ((parent.getStyle() & SWT.MIRRORED) !is 0 && (style & SWT.LEFT_TO_RIGHT) is 0) return style;
428 
429     return style | SWT.NO_BACKGROUND;
430 }
431 static void fillRegion(GC gc, Region region) {
432     // NOTE: region passed in to this function will be modified
433     Region clipping = new Region();
434     gc.getClipping(clipping);
435     region.intersect(clipping);
436     gc.setClipping(region);
437     gc.fillRectangle(region.getBounds());
438     gc.setClipping(clipping);
439     clipping.dispose();
440 }
441 /**
442  *
443  * Adds the listener to the collection of listeners who will
444  * be notified when a tab item is closed, minimized, maximized,
445  * restored, or to show the list of items that are not
446  * currently visible.
447  *
448  * @param listener the listener which should be notified
449  *
450  * @exception IllegalArgumentException <ul>
451  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
452  * </ul>
453  *
454  * @exception SWTException <ul>
455  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
456  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
457  * </ul>
458  *
459  * @see CTabFolder2Listener
460  * @see #removeCTabFolder2Listener(CTabFolder2Listener)
461  *
462  * @since 3.0
463  */
464 public void addCTabFolder2Listener(CTabFolder2Listener listener) {
465     checkWidget();
466     if (listener is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
467     // add to array
468     CTabFolder2Listener[] newListeners = new CTabFolder2Listener[folderListeners.length + 1];
469     SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, 0, newListeners, 0, cast(int)/*64bit*/folderListeners.length);
470     folderListeners = newListeners;
471     folderListeners[folderListeners.length - 1] = listener;
472 }
473 /**
474  * Adds the listener to the collection of listeners who will
475  * be notified when a tab item is closed.
476  *
477  * @param listener the listener which should be notified
478  *
479  * @exception IllegalArgumentException <ul>
480  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
481  * </ul>
482  * @exception SWTException <ul>
483  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
484  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
485  * </ul>
486  *
487  * @see CTabFolderListener
488  * @see #removeCTabFolderListener(CTabFolderListener)
489  *
490  * @deprecated use addCTabFolder2Listener(CTabFolder2Listener)
491  */
492 public void addCTabFolderListener(CTabFolderListener listener) {
493     checkWidget();
494     if (listener is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
495     // add to array
496     CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length + 1];
497     SimpleType!(CTabFolderListener).arraycopy(tabListeners, 0, newTabListeners, 0, cast(int)/*64bit*/tabListeners.length);
498     tabListeners = newTabListeners;
499     tabListeners[tabListeners.length - 1] = listener;
500     // display close button to be backwards compatible
501     if (!showClose) {
502         showClose = true;
503         updateItems();
504         redraw();
505     }
506 }
507 /**
508  * Adds the listener to the collection of listeners who will
509  * be notified when the user changes the receiver's selection, by sending
510  * it one of the messages defined in the <code>SelectionListener</code>
511  * interface.
512  * <p>
513  * <code>widgetSelected</code> is called when the user changes the selected tab.
514  * <code>widgetDefaultSelected</code> is not called.
515  * </p>
516  *
517  * @param listener the listener which should be notified when the user changes the receiver's selection
518  *
519  * @exception IllegalArgumentException <ul>
520  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</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  * @see SelectionListener
528  * @see #removeSelectionListener
529  * @see SelectionEvent
530  */
531 public void addSelectionListener(SelectionListener listener) {
532     checkWidget();
533     if (listener is null) {
534         SWT.error(SWT.ERROR_NULL_ARGUMENT);
535     }
536     TypedListener typedListener = new TypedListener(listener);
537     addListener(SWT.Selection, typedListener);
538     addListener(SWT.DefaultSelection, typedListener);
539 }
540 void antialias (int[] shape, RGB lineRGB, RGB innerRGB, RGB outerRGB, GC gc){
541     // Don't perform anti-aliasing on Mac and WPF because the platform
542     // already does it.  The simple style also does not require anti-aliasing.
543     if (simple || "carbon".equals(SWT.getPlatform()) || "wpf".equals(SWT.getPlatform())) return; //$NON-NLS-1$
544     // Don't perform anti-aliasing on low resolution displays
545     if (getDisplay().getDepth() < 15) return;
546     if (outerRGB !is null) {
547         int index = 0;
548         bool left = true;
549         int oldY = onBottom ? 0 : getSize().y;
550         int[] outer = new int[shape.length];
551         for (int i = 0; i < shape.length/2; i++) {
552             if (left && (index + 3 < shape.length)) {
553                 left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
554                 oldY = shape[index+1];
555             }
556             outer[index] = shape[index] + (left ? -1 : +1);
557             index++;
558             outer[index] = shape[index];
559             index++;
560         }
561         RGB from = lineRGB;
562         RGB to = outerRGB;
563         int red = from.red + 2*(to.red - from.red)/3;
564         int green = from.green + 2*(to.green - from.green)/3;
565         int blue = from.blue + 2*(to.blue - from.blue)/3;
566         Color color = new Color(getDisplay(), red, green, blue);
567         gc.setForeground(color);
568         gc.drawPolyline(outer);
569         color.dispose();
570     }
571     if (innerRGB !is null) {
572         int[] inner = new int[shape.length];
573         int index = 0;
574         bool left = true;
575         int oldY = onBottom ? 0 : getSize().y;
576         for (int i = 0; i < shape.length/2; i++) {
577             if (left && (index + 3 < shape.length)) {
578                 left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3];
579                 oldY = shape[index+1];
580             }
581             inner[index] = shape[index] + (left ? +1 : -1);
582             index++;
583             inner[index] = shape[index];
584             index++;
585         }
586         RGB from = lineRGB;
587         RGB to = innerRGB;
588         int red = from.red + 2*(to.red - from.red)/3;
589         int green = from.green + 2*(to.green - from.green)/3;
590         int blue = from.blue + 2*(to.blue - from.blue)/3;
591         Color color = new Color(getDisplay(), red, green, blue);
592         gc.setForeground(color);
593         gc.drawPolyline(inner);
594         color.dispose();
595     }
596 }
597 public override Rectangle computeTrim (int x, int y, int width, int height) {
598     checkWidget();
599     int trimX = x - marginWidth - highlight_margin - borderLeft;
600     int trimWidth = width + borderLeft + borderRight + 2*marginWidth + 2*highlight_margin;
601     if (minimized) {
602         int trimY = onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop;
603         int trimHeight = borderTop + borderBottom + tabHeight + highlight_header;
604         return new Rectangle (trimX, trimY, trimWidth, trimHeight);
605     } else {
606         int trimY = onBottom ? y - marginHeight - highlight_margin - borderTop: y - marginHeight - highlight_header - tabHeight - borderTop;
607         int trimHeight = height + borderTop + borderBottom + 2*marginHeight + tabHeight + highlight_header + highlight_margin;
608         return new Rectangle (trimX, trimY, trimWidth, trimHeight);
609     }
610 }
611 void createItem (CTabItem item, int index) {
612     if (0 > index || index > getItemCount ())SWT.error (SWT.ERROR_INVALID_RANGE);
613     item.parent = this;
614     CTabItem[] newItems = new CTabItem [items.length + 1];
615     System.arraycopy(items, 0, newItems, 0, index);
616     newItems[index] = item;
617     System.arraycopy(items, index, newItems, index + 1, items.length - index);
618     items = newItems;
619     if (selectedIndex >= index) selectedIndex ++;
620     int[] newPriority = new int[priority.length + 1];
621     int next = 0,  priorityIndex = cast(int)/*64bit*/priority.length;
622     for (int i = 0; i < priority.length; i++) {
623         if (!mru && priority[i] is index) {
624             priorityIndex = next++;
625         }
626         newPriority[next++] = priority[i] >= index ? priority[i] + 1 : priority[i];
627     }
628     newPriority[priorityIndex] = index;
629     priority = newPriority;
630 
631     if (items.length is 1) {
632         if (!updateTabHeight(false)) updateItems();
633         redraw();
634     } else {
635         updateItems();
636         redrawTabs();
637     }
638 }
639 void destroyItem (CTabItem item) {
640     if (inDispose) return;
641     int index = indexOf(item);
642     if (index is -1) return;
643 
644     if (items.length is 1) {
645         items = new CTabItem[0];
646         priority = new int[0];
647         firstIndex = -1;
648         selectedIndex = -1;
649 
650         Control control = item.getControl();
651         if (control !is null && !control.isDisposed()) {
652             control.setVisible(false);
653         }
654         setToolTipText(null);
655         setButtonBounds();
656         redraw();
657         return;
658     }
659 
660     CTabItem[] newItems = new CTabItem [items.length - 1];
661     System.arraycopy(items, 0, newItems, 0, index);
662     System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
663     items = newItems;
664 
665     int[] newPriority = new int[priority.length - 1];
666     int next = 0;
667     for (int i = 0; i < priority.length; i++) {
668         if (priority [i] is index) continue;
669         newPriority[next++] = priority[i] > index ? priority[i] - 1 : priority [i];
670     }
671     priority = newPriority;
672 
673     // move the selection if this item is selected
674     if (selectedIndex is index) {
675         Control control = item.getControl();
676         selectedIndex = -1;
677         int nextSelection = mru ? priority[0] : Math.max(0, index - 1);
678         setSelection(nextSelection, true);
679         if (control !is null && !control.isDisposed()) {
680             control.setVisible(false);
681         }
682     } else if (selectedIndex > index) {
683         selectedIndex --;
684     }
685 
686     updateItems();
687     redrawTabs();
688 }
689 void drawBackground(GC gc, int[] shape, bool selected) {
690     Color defaultBackground = selected ? selectionBackground : getBackground();
691     Image image = selected ? selectionBgImage : bgImage;
692     Color[] colors = selected ? selectionGradientColors : gradientColors;
693     int[] percents = selected ? selectionGradientPercents : gradientPercents;
694     bool vertical = selected ? selectionGradientVertical : gradientVertical;
695     Point size = getSize();
696     int width = size.x;
697     int height = tabHeight + highlight_header;
698     int x = 0;
699     if (borderLeft > 0) {
700         x += 1; width -= 2;
701     }
702     int y = onBottom ? size.y - borderBottom - height : borderTop;
703     drawBackground(gc, shape, x, y, width, height, defaultBackground, image, colors, percents, vertical);
704 }
705 void drawBackground(GC gc, int[] shape, int x, int y, int width, int height, Color defaultBackground, Image image, Color[] colors, int[] percents, bool vertical) {
706     Region clipping = new Region();
707     gc.getClipping(clipping);
708     Region region = new Region();
709     region.add(shape);
710     region.intersect(clipping);
711     gc.setClipping(region);
712 
713     if (image !is null) {
714         // draw the background image in shape
715         gc.setBackground(defaultBackground);
716         gc.fillRectangle(x, y, width, height);
717         Rectangle imageRect = image.getBounds();
718         gc.drawImage(image, imageRect.x, imageRect.y, imageRect.width, imageRect.height, x, y, width, height);
719     } else if (colors !is null) {
720         // draw gradient
721         if (colors.length is 1) {
722             Color background = colors[0] !is null ? colors[0] : defaultBackground;
723             gc.setBackground(background);
724             gc.fillRectangle(x, y, width, height);
725         } else {
726             if (vertical) {
727                 if (onBottom) {
728                     int pos = 0;
729                     if (percents[percents.length - 1] < 100) {
730                         pos = percents[percents.length - 1] * height / 100;
731                         gc.setBackground(defaultBackground);
732                         gc.fillRectangle(x, y, width, pos);
733                     }
734                     Color lastColor = colors[colors.length-1];
735                     if (lastColor is null) lastColor = defaultBackground;
736                     for (ptrdiff_t i = cast(ptrdiff_t) (percents.length)-1; i >= 0; i--) {
737                         gc.setForeground(lastColor);
738                         lastColor = colors[i];
739                         if (lastColor is null) lastColor = defaultBackground;
740                         gc.setBackground(lastColor);
741                         int gradientHeight = percents[i] * height / 100;
742                         gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
743                         pos += gradientHeight;
744                     }
745                 } else {
746                     Color lastColor = colors[0];
747                     if (lastColor is null) lastColor = defaultBackground;
748                     int pos = 0;
749                     for (int i = 0; i < percents.length; i++) {
750                         gc.setForeground(lastColor);
751                         lastColor = colors[i + 1];
752                         if (lastColor is null) lastColor = defaultBackground;
753                         gc.setBackground(lastColor);
754                         int gradientHeight = percents[i] * height / 100;
755                         gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true);
756                         pos += gradientHeight;
757                     }
758                     if (pos < height) {
759                         gc.setBackground(defaultBackground);
760                         gc.fillRectangle(x, pos, width, height-pos+1);
761                     }
762                 }
763             } else { //horizontal gradient
764                 y = 0;
765                 height = getSize().y;
766                 Color lastColor = colors[0];
767                 if (lastColor is null) lastColor = defaultBackground;
768                 int pos = 0;
769                 for (int i = 0; i < percents.length; ++i) {
770                     gc.setForeground(lastColor);
771                     lastColor = colors[i + 1];
772                     if (lastColor is null) lastColor = defaultBackground;
773                     gc.setBackground(lastColor);
774                     int gradientWidth = (percents[i] * width / 100) - pos;
775                     gc.fillGradientRectangle(x+pos, y, gradientWidth, height, false);
776                     pos += gradientWidth;
777                 }
778                 if (pos < width) {
779                     gc.setBackground(defaultBackground);
780                     gc.fillRectangle(x+pos, y, width-pos, height);
781                 }
782             }
783         }
784     } else {
785         // draw a solid background using default background in shape
786         if ((getStyle() & SWT.NO_BACKGROUND) !is 0 || defaultBackground!=getBackground()) {
787             gc.setBackground(defaultBackground);
788             gc.fillRectangle(x, y, width, height);
789         }
790     }
791     gc.setClipping(clipping);
792     clipping.dispose();
793     region.dispose();
794 }
795 void drawBody(Event event) {
796     GC gc = event.gc;
797     Point size = getSize();
798 
799     // fill in body
800     if (!minimized){
801         int width = size.x  - borderLeft - borderRight - 2*highlight_margin;
802         int height = size.y - borderTop - borderBottom - tabHeight - highlight_header - highlight_margin;
803         // Draw highlight margin
804         if (highlight_margin > 0) {
805             int[] shape = null;
806             if (onBottom) {
807                 int x1 = borderLeft;
808                 int y1 = borderTop;
809                 int x2 = size.x - borderRight;
810                 int y2 = size.y - borderBottom - tabHeight - highlight_header;
811                 shape = [x1,y1, x2,y1, x2,y2, x2-highlight_margin,y2,
812                                    x2-highlight_margin, y1+highlight_margin, x1+highlight_margin,y1+highlight_margin,
813                                    x1+highlight_margin,y2, x1,y2];
814             } else {
815                 int x1 = borderLeft;
816                 int y1 = borderTop + tabHeight + highlight_header;
817                 int x2 = size.x - borderRight;
818                 int y2 = size.y - borderBottom;
819                 shape = [x1,y1, x1+highlight_margin,y1, x1+highlight_margin,y2-highlight_margin,
820                                    x2-highlight_margin,y2-highlight_margin, x2-highlight_margin,y1,
821                                    x2,y1, x2,y2, x1,y2];
822             }
823             // If horizontal gradient, show gradient across the whole area
824             if (selectedIndex !is -1 && selectionGradientColors !is null && selectionGradientColors.length > 1 && !selectionGradientVertical) {
825                 drawBackground(gc, shape, true);
826             } else if (selectedIndex is -1 && gradientColors !is null && gradientColors.length > 1 && !gradientVertical) {
827                 drawBackground(gc, shape, false);
828             } else {
829                 gc.setBackground(selectedIndex is -1 ? getBackground() : selectionBackground);
830                 gc.fillPolygon(shape);
831             }
832         }
833         //Draw client area
834         if ((getStyle() & SWT.NO_BACKGROUND) !is 0) {
835             gc.setBackground(getBackground());
836             gc.fillRectangle(xClient - marginWidth, yClient - marginHeight, width, height);
837         }
838     } else {
839         if ((getStyle() & SWT.NO_BACKGROUND) !is 0) {
840             int height = borderTop + tabHeight + highlight_header + borderBottom;
841             if (size.y > height) {
842                 gc.setBackground(getParent().getBackground());
843                 gc.fillRectangle(0, height, size.x, size.y - height);
844             }
845         }
846     }
847 
848     //draw 1 pixel border around outside
849     if (borderLeft > 0) {
850         gc.setForeground(borderColor);
851         int x1 = borderLeft - 1;
852         int x2 = size.x - borderRight;
853         int y1 = onBottom ? borderTop - 1 : borderTop + tabHeight;
854         int y2 = onBottom ? size.y - tabHeight - borderBottom - 1 : size.y - borderBottom;
855         gc.drawLine(x1, y1, x1, y2); // left
856         gc.drawLine(x2, y1, x2, y2); // right
857         if (onBottom) {
858             gc.drawLine(x1, y1, x2, y1); // top
859         } else {
860             gc.drawLine(x1, y2, x2, y2); // bottom
861         }
862     }
863 }
864 
865 void drawChevron(GC gc) {
866     if (chevronRect.width is 0 || chevronRect.height is 0) return;
867     // draw chevron (10x7)
868     Display display = getDisplay();
869     Point dpi = display.getDPI();
870     int fontHeight = 72 * 10 / dpi.y;
871     FontData fd = getFont().getFontData()[0];
872     fd.setHeight(fontHeight);
873     Font f = new Font(display, fd);
874     int fHeight = f.getFontData()[0].getHeight() * dpi.y / 72;
875     int indent = Math.max(2, (chevronRect.height - fHeight - 4) /2);
876     int x = chevronRect.x + 2;
877     int y = chevronRect.y + indent;
878     int count;
879     if (single) {
880         count = cast(int)/*64bit*/(selectedIndex is -1 ? items.length : items.length - 1);
881     } else {
882         int showCount = 0;
883         while (showCount < priority.length && items[priority[showCount]].showing) {
884             showCount++;
885         }
886         count = cast(int)/*64bit*/items.length - showCount;
887     }
888     String chevronString = count > 99 ? "99+" : String_valueOf(count); //$NON-NLS-1$
889     switch (chevronImageState) {
890         case NORMAL: {
891             Color chevronBorder = single ? getSelectionForeground() : getForeground();
892             gc.setForeground(chevronBorder);
893             gc.setFont(f);
894             gc.drawLine(x,y,     x+2,y+2);
895             gc.drawLine(x+2,y+2, x,y+4);
896             gc.drawLine(x+1,y,   x+3,y+2);
897             gc.drawLine(x+3,y+2, x+1,y+4);
898             gc.drawLine(x+4,y,   x+6,y+2);
899             gc.drawLine(x+6,y+2, x+5,y+4);
900             gc.drawLine(x+5,y,   x+7,y+2);
901             gc.drawLine(x+7,y+2, x+4,y+4);
902             gc.drawString(chevronString, x+7, y+3, true);
903             break;
904         }
905         case HOT: {
906             gc.setForeground(display.getSystemColor(BUTTON_BORDER));
907             gc.setBackground(display.getSystemColor(BUTTON_FILL));
908             gc.setFont(f);
909             gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6);
910             gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6);
911             gc.drawLine(x,y,     x+2,y+2);
912             gc.drawLine(x+2,y+2, x,y+4);
913             gc.drawLine(x+1,y,   x+3,y+2);
914             gc.drawLine(x+3,y+2, x+1,y+4);
915             gc.drawLine(x+4,y,   x+6,y+2);
916             gc.drawLine(x+6,y+2, x+5,y+4);
917             gc.drawLine(x+5,y,   x+7,y+2);
918             gc.drawLine(x+7,y+2, x+4,y+4);
919             gc.drawString(chevronString, x+7, y+3, true);
920             break;
921         }
922         case SELECTED: {
923             gc.setForeground(display.getSystemColor(BUTTON_BORDER));
924             gc.setBackground(display.getSystemColor(BUTTON_FILL));
925             gc.setFont(f);
926             gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6);
927             gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6);
928             gc.drawLine(x+1,y+1, x+3,y+3);
929             gc.drawLine(x+3,y+3, x+1,y+5);
930             gc.drawLine(x+2,y+1, x+4,y+3);
931             gc.drawLine(x+4,y+3, x+2,y+5);
932             gc.drawLine(x+5,y+1, x+7,y+3);
933             gc.drawLine(x+7,y+3, x+6,y+5);
934             gc.drawLine(x+6,y+1, x+8,y+3);
935             gc.drawLine(x+8,y+3, x+5,y+5);
936             gc.drawString(chevronString, x+8, y+4, true);
937             break;
938         }
939         default:
940     }
941     f.dispose();
942 }
943 void drawMaximize(GC gc) {
944     if (maxRect.width is 0 || maxRect.height is 0) return;
945     Display display = getDisplay();
946     // 5x4 or 7x9
947     int x = maxRect.x + (CTabFolder.BUTTON_SIZE - 10)/2;
948     int y = maxRect.y + 3;
949 
950     gc.setForeground(display.getSystemColor(BUTTON_BORDER));
951     gc.setBackground(display.getSystemColor(BUTTON_FILL));
952 
953     switch (maxImageState) {
954         case NORMAL: {
955             if (!maximized) {
956                 gc.fillRectangle(x, y, 9, 9);
957                 gc.drawRectangle(x, y, 9, 9);
958                 gc.drawLine(x+1, y+2, x+8, y+2);
959             } else {
960                 gc.fillRectangle(x, y+3, 5, 4);
961                 gc.fillRectangle(x+2, y, 5, 4);
962                 gc.drawRectangle(x, y+3, 5, 4);
963                 gc.drawRectangle(x+2, y, 5, 4);
964                 gc.drawLine(x+3, y+1, x+6, y+1);
965                 gc.drawLine(x+1, y+4, x+4, y+4);
966             }
967             break;
968         }
969         case HOT: {
970             gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6);
971             gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6);
972             if (!maximized) {
973                 gc.fillRectangle(x, y, 9, 9);
974                 gc.drawRectangle(x, y, 9, 9);
975                 gc.drawLine(x+1, y+2, x+8, y+2);
976             } else {
977                 gc.fillRectangle(x, y+3, 5, 4);
978                 gc.fillRectangle(x+2, y, 5, 4);
979                 gc.drawRectangle(x, y+3, 5, 4);
980                 gc.drawRectangle(x+2, y, 5, 4);
981                 gc.drawLine(x+3, y+1, x+6, y+1);
982                 gc.drawLine(x+1, y+4, x+4, y+4);
983             }
984             break;
985         }
986         case SELECTED: {
987             gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6);
988             gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6);
989             if (!maximized) {
990                 gc.fillRectangle(x+1, y+1, 9, 9);
991                 gc.drawRectangle(x+1, y+1, 9, 9);
992                 gc.drawLine(x+2, y+3, x+9, y+3);
993             } else {
994                 gc.fillRectangle(x+1, y+4, 5, 4);
995                 gc.fillRectangle(x+3, y+1, 5, 4);
996                 gc.drawRectangle(x+1, y+4, 5, 4);
997                 gc.drawRectangle(x+3, y+1, 5, 4);
998                 gc.drawLine(x+4, y+2, x+7, y+2);
999                 gc.drawLine(x+2, y+5, x+5, y+5);
1000             }
1001             break;
1002         }
1003         default:
1004     }
1005 }
1006 void drawMinimize(GC gc) {
1007     if (minRect.width is 0 || minRect.height is 0) return;
1008     Display display = getDisplay();
1009     // 5x4 or 9x3
1010     int x = minRect.x + (BUTTON_SIZE - 10)/2;
1011     int y = minRect.y + 3;
1012 
1013     gc.setForeground(display.getSystemColor(BUTTON_BORDER));
1014     gc.setBackground(display.getSystemColor(BUTTON_FILL));
1015 
1016     switch (minImageState) {
1017         case NORMAL: {
1018             if (!minimized) {
1019                 gc.fillRectangle(x, y, 9, 3);
1020                 gc.drawRectangle(x, y, 9, 3);
1021             } else {
1022                 gc.fillRectangle(x, y+3, 5, 4);
1023                 gc.fillRectangle(x+2, y, 5, 4);
1024                 gc.drawRectangle(x, y+3, 5, 4);
1025                 gc.drawRectangle(x+2, y, 5, 4);
1026                 gc.drawLine(x+3, y+1, x+6, y+1);
1027                 gc.drawLine(x+1, y+4, x+4, y+4);
1028             }
1029             break;
1030         }
1031         case HOT: {
1032             gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6);
1033             gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6);
1034             if (!minimized) {
1035                 gc.fillRectangle(x, y, 9, 3);
1036                 gc.drawRectangle(x, y, 9, 3);
1037             } else {
1038                 gc.fillRectangle(x, y+3, 5, 4);
1039                 gc.fillRectangle(x+2, y, 5, 4);
1040                 gc.drawRectangle(x, y+3, 5, 4);
1041                 gc.drawRectangle(x+2, y, 5, 4);
1042                 gc.drawLine(x+3, y+1, x+6, y+1);
1043                 gc.drawLine(x+1, y+4, x+4, y+4);
1044             }
1045             break;
1046         }
1047         case SELECTED: {
1048             gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6);
1049             gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6);
1050             if (!minimized) {
1051                 gc.fillRectangle(x+1, y+1, 9, 3);
1052                 gc.drawRectangle(x+1, y+1, 9, 3);
1053             } else {
1054                 gc.fillRectangle(x+1, y+4, 5, 4);
1055                 gc.fillRectangle(x+3, y+1, 5, 4);
1056                 gc.drawRectangle(x+1, y+4, 5, 4);
1057                 gc.drawRectangle(x+3, y+1, 5, 4);
1058                 gc.drawLine(x+4, y+2, x+7, y+2);
1059                 gc.drawLine(x+2, y+5, x+5, y+5);
1060             }
1061             break;
1062         }
1063         default:
1064     }
1065 }
1066 void drawTabArea(Event event) {
1067     GC gc = event.gc;
1068     Point size = getSize();
1069     int[] shape = null;
1070 
1071     if (tabHeight is 0) {
1072         int style = getStyle();
1073         if ((style & SWT.FLAT) !is 0 && (style & SWT.BORDER) is 0) return;
1074         int x1 = borderLeft - 1;
1075         int x2 = size.x - borderRight;
1076         int y1 = onBottom ? size.y - borderBottom - highlight_header - 1 : borderTop + highlight_header;
1077         int y2 = onBottom ? size.y - borderBottom : borderTop;
1078         if (borderLeft > 0 && onBottom) y2 -= 1;
1079 
1080         shape = [x1, y1, x1,y2, x2,y2, x2,y1];
1081 
1082         // If horizontal gradient, show gradient across the whole area
1083         if (selectedIndex !is -1 && selectionGradientColors !is null && selectionGradientColors.length > 1 && !selectionGradientVertical) {
1084             drawBackground(gc, shape, true);
1085         } else if (selectedIndex is -1 && gradientColors !is null && gradientColors.length > 1 && !gradientVertical) {
1086             drawBackground(gc, shape, false);
1087         } else {
1088             gc.setBackground(selectedIndex is -1 ? getBackground() : selectionBackground);
1089             gc.fillPolygon(shape);
1090         }
1091 
1092         //draw 1 pixel border
1093         if (borderLeft > 0) {
1094             gc.setForeground(borderColor);
1095             gc.drawPolyline(shape);
1096         }
1097         return;
1098     }
1099 
1100     int x = Math.max(0, borderLeft - 1);
1101     int y = onBottom ? size.y - borderBottom - tabHeight : borderTop;
1102     int width = size.x - borderLeft - borderRight + 1;
1103     int height = tabHeight - 1;
1104 
1105     // Draw Tab Header
1106     if (onBottom) {
1107         TryConst!(int)[] left, right;
1108         if ((getStyle() & SWT.BORDER) !is 0) {
1109             left = simple ? SIMPLE_BOTTOM_LEFT_CORNER : BOTTOM_LEFT_CORNER;
1110             right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER : BOTTOM_RIGHT_CORNER;
1111         } else {
1112             left = simple ? SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS : BOTTOM_LEFT_CORNER_BORDERLESS;
1113             right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS : BOTTOM_RIGHT_CORNER_BORDERLESS;
1114         }
1115         shape = new int[left.length + right.length + 4];
1116         int index = 0;
1117         shape[index++] = x;
1118         shape[index++] = y-highlight_header;
1119         for (int i = 0; i < left.length/2; i++) {
1120             shape[index++] = x+left[2*i];
1121             shape[index++] = y+height+left[2*i+1];
1122             if (borderLeft is 0) shape[index-1] += 1;
1123         }
1124         for (int i = 0; i < right.length/2; i++) {
1125             shape[index++] = x+width+right[2*i];
1126             shape[index++] = y+height+right[2*i+1];
1127             if (borderLeft is 0) shape[index-1] += 1;
1128         }
1129         shape[index++] = x+width;
1130         shape[index++] = y-highlight_header;
1131     } else {
1132         TryConst!(int)[] left, right;
1133         if ((getStyle() & SWT.BORDER) !is 0) {
1134             left = simple ? SIMPLE_TOP_LEFT_CORNER : TOP_LEFT_CORNER;
1135             right = simple ? SIMPLE_TOP_RIGHT_CORNER : TOP_RIGHT_CORNER;
1136         } else {
1137             left = simple ? SIMPLE_TOP_LEFT_CORNER_BORDERLESS : TOP_LEFT_CORNER_BORDERLESS;
1138             right = simple ? SIMPLE_TOP_RIGHT_CORNER_BORDERLESS : TOP_RIGHT_CORNER_BORDERLESS;
1139         }
1140         shape = new int[left.length + right.length + 4];
1141         int index = 0;
1142         shape[index++] = x;
1143         shape[index++] = y+height+highlight_header + 1;
1144         for (int i = 0; i < left.length/2; i++) {
1145             shape[index++] = x+left[2*i];
1146             shape[index++] = y+left[2*i+1];
1147         }
1148         for (int i = 0; i < right.length/2; i++) {
1149             shape[index++] = x+width+right[2*i];
1150             shape[index++] = y+right[2*i+1];
1151         }
1152         shape[index++] = x+width;
1153         shape[index++] = y+height+highlight_header + 1;
1154     }
1155     // Fill in background
1156     bool bkSelected = single && selectedIndex !is -1;
1157     drawBackground(gc, shape, bkSelected);
1158     // Fill in parent background for non-rectangular shape
1159     Region r = new Region();
1160     r.add(new Rectangle(x, y, width + 1, height + 1));
1161     r.subtract(shape);
1162     gc.setBackground(getParent().getBackground());
1163     fillRegion(gc, r);
1164     r.dispose();
1165 
1166     // Draw the unselected tabs.
1167     if (!single) {
1168         for (int i=0; i < items.length; i++) {
1169             if (i !is selectedIndex && event.getBounds().intersects(items[i].getBounds())) {
1170                 items[i].onPaint(gc, false);
1171             }
1172         }
1173     }
1174 
1175     // Draw selected tab
1176     if (selectedIndex !is -1) {
1177         CTabItem item = items[selectedIndex];
1178         item.onPaint(gc, true);
1179     } else {
1180         // if no selected tab - draw line across bottom of all tabs
1181         int x1 = borderLeft;
1182         int y1 = (onBottom) ? size.y - borderBottom - tabHeight - 1 : borderTop + tabHeight;
1183         int x2 = size.x - borderRight;
1184         gc.setForeground(borderColor);
1185         gc.drawLine(x1, y1, x2, y1);
1186     }
1187 
1188     // Draw Buttons
1189     drawChevron(gc);
1190     drawMinimize(gc);
1191     drawMaximize(gc);
1192 
1193     // Draw border line
1194     if (borderLeft > 0) {
1195         RGB outside = getParent().getBackground().getRGB();
1196         antialias(shape, borderColor.getRGB(), null, outside, gc);
1197         gc.setForeground(borderColor);
1198         gc.drawPolyline(shape);
1199     }
1200 }
1201 /**
1202  * Returns <code>true</code> if the receiver's border is visible.
1203  *
1204  * @return the receiver's border visibility state
1205  *
1206  * @exception SWTException <ul>
1207  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1208  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1209  * </ul>
1210  *
1211  * @since 3.0
1212  */
1213 public bool getBorderVisible() {
1214     checkWidget();
1215     return borderLeft is 1;
1216 }
1217 public override Rectangle getClientArea() {
1218     checkWidget();
1219     if (minimized) return new Rectangle(xClient, yClient, 0, 0);
1220     Point size = getSize();
1221     int width = size.x  - borderLeft - borderRight - 2*marginWidth - 2*highlight_margin;
1222     int height = size.y - borderTop - borderBottom - 2*marginHeight - highlight_margin - highlight_header;
1223     height -= tabHeight;
1224     return new Rectangle(xClient, yClient, width, height);
1225 }
1226 /**
1227  * Return the tab that is located at the specified index.
1228  *
1229  * @param index the index of the tab item
1230  * @return the item at the specified index
1231  *
1232  * @exception IllegalArgumentException <ul>
1233  *    <li>ERROR_INVALID_RANGE - if the index is out of range</li>
1234  * </ul>
1235  * @exception SWTException <ul>
1236  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1237  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1238  * </ul>
1239  */
1240 public CTabItem getItem (int index) {
1241     //checkWidget();
1242     if (index  < 0 || index >= items.length)
1243         SWT.error(SWT.ERROR_INVALID_RANGE);
1244     return items [index];
1245 }
1246 /**
1247  * Gets the item at a point in the widget.
1248  *
1249  * @param pt the point in coordinates relative to the CTabFolder
1250  * @return the item at a point or null
1251  *
1252  * @exception SWTException <ul>
1253  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1254  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1255  *  </ul>
1256  */
1257 public CTabItem getItem (Point pt) {
1258     //checkWidget();
1259     if (items.length is 0) return null;
1260     Point size = getSize();
1261     if (size.x <= borderLeft + borderRight) return null;
1262     if (showChevron && chevronRect.contains(pt)) return null;
1263     for (int i = 0; i < priority.length; i++) {
1264         CTabItem item = items[priority[i]];
1265         Rectangle rect = item.getBounds();
1266         if (rect.contains(pt)) return item;
1267     }
1268     return null;
1269 }
1270 /**
1271  * Return the number of tabs in the folder.
1272  *
1273  * @return the number of tabs in the folder
1274  *
1275  * @exception SWTException <ul>
1276  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1277  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1278  *  </ul>
1279  */
1280 public int getItemCount(){
1281     //checkWidget();
1282     return cast(int)/*64bit*/items.length;
1283 }
1284 /**
1285  * Return the tab items.
1286  *
1287  * @return the tab items
1288  *
1289  * @exception SWTException <ul>
1290  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1291  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1292  *  </ul>
1293  */
1294 public CTabItem [] getItems() {
1295     //checkWidget();
1296     CTabItem[] tabItems = new CTabItem [items.length];
1297     System.arraycopy(items, 0, tabItems, 0, items.length);
1298     return tabItems;
1299 }
1300 /*
1301  * Return the lowercase of the first non-'&' character following
1302  * an '&' character in the given string. If there are no '&'
1303  * characters in the given string, return '\0'.
1304  */
1305 dchar _findMnemonic (String string) {
1306     if (string is null) return '\0';
1307     int index = 0;
1308     int length_ = cast(int)/*64bit*/string.length;
1309     do {
1310         while (index < length_ && string[index] !is '&') index++;
1311         if (++index >= length_) return '\0';
1312         if (string[index] !is '&') return Character.toLowerCase (string.dcharAt (index));
1313         index++;
1314     } while (index < length_);
1315     return '\0';
1316 }
1317 String stripMnemonic (String string) {
1318     int index = 0;
1319     int length_ = cast(int)/*64bit*/string.length;
1320     do {
1321         while ((index < length_) && (string[index] !is '&')) index++;
1322         if (++index >= length_) return string;
1323         if (string[index] !is '&') {
1324             return string.substring(0, index-1) ~ string.substring(index, length_);
1325         }
1326         index++;
1327     } while (index < length_);
1328     return string;
1329 }
1330 /**
1331  * Returns <code>true</code> if the receiver is minimized.
1332  *
1333  * @return the receiver's minimized state
1334  *
1335  * @exception SWTException <ul>
1336  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1337  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1338  * </ul>
1339  *
1340  * @since 3.0
1341  */
1342 public bool getMinimized() {
1343     checkWidget();
1344     return minimized;
1345 }
1346 /**
1347  * Returns <code>true</code> if the minimize button
1348  * is visible.
1349  *
1350  * @return the visibility of the minimized button
1351  *
1352  * @exception SWTException <ul>
1353  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1354  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1355  * </ul>
1356  *
1357  * @since 3.0
1358  */
1359 public bool getMinimizeVisible() {
1360     checkWidget();
1361     return showMin;
1362 }
1363 /**
1364  * Returns the number of characters that will
1365  * appear in a fully compressed tab.
1366  *
1367  * @return number of characters that will appear in a fully compressed tab
1368  *
1369  * @since 3.0
1370  */
1371 public int getMinimumCharacters() {
1372     checkWidget();
1373     return minChars;
1374 }
1375 /**
1376  * Returns <code>true</code> if the receiver is maximized.
1377  * <p>
1378  *
1379  * @return the receiver's maximized state
1380  *
1381  * @exception SWTException <ul>
1382  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1383  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1384  * </ul>
1385  *
1386  * @since 3.0
1387  */
1388 public bool getMaximized() {
1389     checkWidget();
1390     return maximized;
1391 }
1392 /**
1393  * Returns <code>true</code> if the maximize button
1394  * is visible.
1395  *
1396  * @return the visibility of the maximized button
1397  *
1398  * @exception SWTException <ul>
1399  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1400  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1401  * </ul>
1402  *
1403  * @since 3.0
1404  */
1405 public bool getMaximizeVisible() {
1406     checkWidget();
1407     return showMax;
1408 }
1409 /**
1410  * Returns <code>true</code> if the receiver displays most
1411  * recently used tabs and <code>false</code> otherwise.
1412  * <p>
1413  * When there is not enough horizontal space to show all the tabs,
1414  * by default, tabs are shown sequentially from left to right in
1415  * order of their index.  When the MRU visibility is turned on,
1416  * the tabs that are visible will be the tabs most recently selected.
1417  * Tabs will still maintain their left to right order based on index
1418  * but only the most recently selected tabs are visible.
1419  * <p>
1420  * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
1421  * "Tab 3" and "Tab 4" (in order by index).  The user selects
1422  * "Tab 1" and then "Tab 3".  If the CTabFolder is now
1423  * compressed so that only two tabs are visible, by default,
1424  * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
1425  * selected and "Tab 2" because it is the previous item in index order).
1426  * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
1427  * and "Tab 3" (in that order from left to right).</p>
1428  *
1429  * @return the receiver's header's visibility state
1430  *
1431  * @exception SWTException <ul>
1432  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1433  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1434  * </ul>
1435  *
1436  * @since 3.1
1437  */
1438 public bool getMRUVisible() {
1439     checkWidget();
1440     return mru;
1441 }
1442 int getRightItemEdge (){
1443     int x = getSize().x - borderRight - 3;
1444     if (showMin) x -= BUTTON_SIZE;
1445     if (showMax) x -= BUTTON_SIZE;
1446     if (showChevron) x -= 3*BUTTON_SIZE/2;
1447     if (topRight !is null && topRightAlignment !is SWT.FILL) {
1448         Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
1449         x -= rightSize.x + 3;
1450     }
1451     return Math.max(0, x);
1452 }
1453 /**
1454  * Return the selected tab item, or null if there is no selection.
1455  *
1456  * @return the selected tab item, or null if none has been selected
1457  *
1458  * @exception SWTException <ul>
1459  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1460  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1461  *  </ul>
1462  */
1463 public CTabItem getSelection() {
1464     //checkWidget();
1465     if (selectedIndex is -1) return null;
1466     return items[selectedIndex];
1467 }
1468 /**
1469  * Returns the receiver's selection background color.
1470  *
1471  * @return the selection background color of the receiver
1472  *
1473  * @exception SWTException <ul>
1474  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1475  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1476  * </ul>
1477  *
1478  * @since 3.0
1479  */
1480 public Color getSelectionBackground() {
1481     checkWidget();
1482     return selectionBackground;
1483 }
1484 /**
1485  * Returns the receiver's selection foreground color.
1486  *
1487  * @return the selection foreground color of the receiver
1488  *
1489  * @exception SWTException <ul>
1490  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1491  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1492  * </ul>
1493  *
1494  * @since 3.0
1495  */
1496 public Color getSelectionForeground() {
1497     checkWidget();
1498     return selectionForeground;
1499 }
1500 /**
1501  * Return the index of the selected tab item, or -1 if there
1502  * is no selection.
1503  *
1504  * @return the index of the selected tab item or -1
1505  *
1506  * @exception SWTException <ul>
1507  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1508  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1509  *  </ul>
1510  */
1511 public int getSelectionIndex() {
1512     //checkWidget();
1513     return selectedIndex;
1514 }
1515 /**
1516  * Returns <code>true</code> if the CTabFolder is rendered
1517  * with a simple, traditional shape.
1518  *
1519  * @return <code>true</code> if the CTabFolder is rendered with a simple shape
1520  *
1521  * @since 3.0
1522  */
1523 public bool getSimple() {
1524     checkWidget();
1525     return simple;
1526 }
1527 /**
1528  * Returns <code>true</code> if the CTabFolder only displays the selected tab
1529  * and <code>false</code> if the CTabFolder displays multiple tabs.
1530  *
1531  * @return <code>true</code> if the CTabFolder only displays the selected tab and <code>false</code> if the CTabFolder displays multiple tabs
1532  *
1533  * @since 3.0
1534  */
1535 public bool getSingle() {
1536     checkWidget();
1537     return single;
1538 }
1539 
1540 public override int getStyle() {
1541     int style = super.getStyle();
1542     style &= ~(SWT.TOP | SWT.BOTTOM);
1543     style |= onBottom ? SWT.BOTTOM : SWT.TOP;
1544     style &= ~(SWT.SINGLE | SWT.MULTI);
1545     style |= single ? SWT.SINGLE : SWT.MULTI;
1546     if (borderLeft !is 0) style |= SWT.BORDER;
1547     style &= ~SWT.CLOSE;
1548     if (showClose) style |= SWT.CLOSE;
1549     return style;
1550 }
1551 /**
1552  * Returns the height of the tab
1553  *
1554  * @return the height of the tab
1555  *
1556  * @exception SWTException <ul>
1557  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1558  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1559  *  </ul>
1560  */
1561 public int getTabHeight(){
1562     checkWidget();
1563     if (fixedTabHeight !is SWT.DEFAULT) return fixedTabHeight;
1564     return tabHeight - 1; // -1 for line drawn across top of tab
1565 }
1566 /**
1567  * Returns the position of the tab.  Possible values are SWT.TOP or SWT.BOTTOM.
1568  *
1569  * @return the position of the tab
1570  *
1571  * @exception SWTException <ul>
1572  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1573  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1574  *  </ul>
1575  */
1576 public int getTabPosition(){
1577     checkWidget();
1578     return onBottom ? SWT.BOTTOM : SWT.TOP;
1579 }
1580 /**
1581  * Returns the control in the top right corner of the tab folder.
1582  * Typically this is a close button or a composite with a menu and close button.
1583  *
1584  * @return the control in the top right corner of the tab folder or null
1585  *
1586  * @exception  SWTException <ul>
1587  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1588  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1589  *  </ul>
1590  *
1591  * @since 2.1
1592  */
1593 public Control getTopRight() {
1594     checkWidget();
1595     return topRight;
1596 }
1597 /**
1598  * Returns <code>true</code> if the close button appears
1599  * when the user hovers over an unselected tabs.
1600  *
1601  * @return <code>true</code> if the close button appears on unselected tabs
1602  *
1603  * @since 3.0
1604  */
1605 public bool getUnselectedCloseVisible() {
1606     checkWidget();
1607     return showUnselectedClose;
1608 }
1609 /**
1610  * Returns <code>true</code> if an image appears
1611  * in unselected tabs.
1612  *
1613  * @return <code>true</code> if an image appears in unselected tabs
1614  *
1615  * @since 3.0
1616  */
1617 public bool getUnselectedImageVisible() {
1618     checkWidget();
1619     return showUnselectedImage;
1620 }
1621 /**
1622  * Return the index of the specified tab or -1 if the tab is not
1623  * in the receiver.
1624  *
1625  * @param item the tab item for which the index is required
1626  *
1627  * @return the index of the specified tab item or -1
1628  *
1629  * @exception IllegalArgumentException <ul>
1630  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1631  * </ul>
1632  *
1633  * @exception SWTException <ul>
1634  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
1635  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
1636  * </ul>
1637  */
1638 public int indexOf(CTabItem item) {
1639     checkWidget();
1640     if (item is null) {
1641         SWT.error(SWT.ERROR_NULL_ARGUMENT);
1642     }
1643     for (int i = 0; i < items.length; i++) {
1644         if (items[i] is item) return i;
1645     }
1646     return -1;
1647 }
1648 void initAccessible() {
1649     Accessible accessible = getAccessible();
1650     accessible.addAccessibleListener(new class() AccessibleAdapter {
1651         override
1652         public void getName(AccessibleEvent e) {
1653             String name = null;
1654             int childID = e.childID;
1655             if (childID >= 0 && childID < items.length) {
1656                 name = stripMnemonic(items[childID].getText());
1657             } else if (childID is items.length + CHEVRON_CHILD_ID) {
1658                 name = SWT.getMessage("SWT_ShowList"); //$NON-NLS-1$
1659             } else if (childID is items.length + MINIMIZE_CHILD_ID) {
1660                 name = minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$
1661             } else if (childID is items.length + MAXIMIZE_CHILD_ID) {
1662                 name = maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$
1663             }
1664             e.result = name;
1665         }
1666 
1667         override
1668         public void getHelp(AccessibleEvent e) {
1669             String help = null;
1670             int childID = e.childID;
1671             if (childID is ACC.CHILDID_SELF) {
1672                 help = getToolTipText();
1673             } else if (childID >= 0 && childID < items.length) {
1674                 help = items[childID].getToolTipText();
1675             }
1676             e.result = help;
1677         }
1678 
1679         override
1680         public void getKeyboardShortcut(AccessibleEvent e) {
1681             String shortcut = null;
1682             int childID = e.childID;
1683             if (childID >= 0 && childID < items.length) {
1684                 String text = items[childID].getText();
1685                 if (text !is null) {
1686                     dchar mnemonic = _findMnemonic(text);
1687                     if (mnemonic !is '\0') {
1688                         shortcut = "Alt+"~dcharToString(mnemonic); //$NON-NLS-1$
1689                     }
1690                 }
1691             }
1692             e.result = shortcut;
1693         }
1694     });
1695 
1696     accessible.addAccessibleControlListener(new class() AccessibleControlAdapter {
1697         override
1698         public void getChildAtPoint(AccessibleControlEvent e) {
1699             Point testPoint = toControl(e.x, e.y);
1700             int childID = ACC.CHILDID_NONE;
1701             for (int i = 0; i < items.length; i++) {
1702                 if (items[i].getBounds().contains(testPoint)) {
1703                     childID = i;
1704                     break;
1705                 }
1706             }
1707             if (childID is ACC.CHILDID_NONE) {
1708                 if (showChevron && chevronRect.contains(testPoint)) {
1709                     childID = cast(int)/*64bit*/items.length + CHEVRON_CHILD_ID;
1710                 } else if (showMin && minRect.contains(testPoint)) {
1711                     childID = cast(int)/*64bit*/items.length + MINIMIZE_CHILD_ID;
1712                 } else if (showMax && maxRect.contains(testPoint)) {
1713                     childID = cast(int)/*64bit*/items.length + MAXIMIZE_CHILD_ID;
1714                 } else {
1715                     Rectangle location = getBounds();
1716                     location.height = location.height - getClientArea().height;
1717                     if (location.contains(testPoint)) {
1718                         childID = ACC.CHILDID_SELF;
1719                     }
1720                 }
1721             }
1722             e.childID = childID;
1723         }
1724 
1725         override
1726         public void getLocation(AccessibleControlEvent e) {
1727             Rectangle location = null;
1728             Point pt = null;
1729             int childID = e.childID;
1730             if (childID is ACC.CHILDID_SELF) {
1731                 location = getBounds();
1732                 pt = getParent().toDisplay(location.x, location.y);
1733             } else {
1734                 if (childID >= 0 && childID < items.length && items[childID].isShowing()) {
1735                     location = items[childID].getBounds();
1736                 } else if (showChevron && childID is items.length + CHEVRON_CHILD_ID) {
1737                     location = chevronRect;
1738                 } else if (showMin && childID is items.length + MINIMIZE_CHILD_ID) {
1739                     location = minRect;
1740                 } else if (showMax && childID is items.length + MAXIMIZE_CHILD_ID) {
1741                     location = maxRect;
1742                 }
1743                 if (location !is null) {
1744                     pt = toDisplay(location.x, location.y);
1745                 }
1746             }
1747             if (location !is null && pt !is null) {
1748                 e.x = pt.x;
1749                 e.y = pt.y;
1750                 e.width = location.width;
1751                 e.height = location.height;
1752             }
1753         }
1754 
1755         override
1756         public void getChildCount(AccessibleControlEvent e) {
1757             e.detail = cast(int)/*64bit*/items.length + EXTRA_CHILD_ID_COUNT;
1758         }
1759 
1760         override
1761         public void getDefaultAction(AccessibleControlEvent e) {
1762             String action = null;
1763             int childID = e.childID;
1764             if (childID >= 0 && childID < items.length) {
1765                 action = SWT.getMessage ("SWT_Switch"); //$NON-NLS-1$
1766             }
1767             if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) {
1768                 action = SWT.getMessage ("SWT_Press"); //$NON-NLS-1$
1769             }
1770             e.result = action;
1771         }
1772 
1773         override
1774         public void getFocus(AccessibleControlEvent e) {
1775             int childID = ACC.CHILDID_NONE;
1776             if (isFocusControl()) {
1777                 if (selectedIndex is -1) {
1778                     childID = ACC.CHILDID_SELF;
1779                 } else {
1780                     childID = selectedIndex;
1781                 }
1782             }
1783             e.childID = childID;
1784         }
1785 
1786         override
1787         public void getRole(AccessibleControlEvent e) {
1788             int role = 0;
1789             int childID = e.childID;
1790             if (childID is ACC.CHILDID_SELF) {
1791                 role = ACC.ROLE_TABFOLDER;
1792             } else if (childID >= 0 && childID < items.length) {
1793                 role = ACC.ROLE_TABITEM;
1794             } else if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) {
1795                 role = ACC.ROLE_PUSHBUTTON;
1796             }
1797             e.detail = role;
1798         }
1799 
1800         override
1801         public void getSelection(AccessibleControlEvent e) {
1802             e.childID = (selectedIndex is -1) ? ACC.CHILDID_NONE : selectedIndex;
1803         }
1804 
1805         override
1806         public void getState(AccessibleControlEvent e) {
1807             int state = 0;
1808             int childID = e.childID;
1809             if (childID is ACC.CHILDID_SELF) {
1810                 state = ACC.STATE_NORMAL;
1811             } else if (childID >= 0 && childID < items.length) {
1812                 state = ACC.STATE_SELECTABLE;
1813                 if (isFocusControl()) {
1814                     state |= ACC.STATE_FOCUSABLE;
1815                 }
1816                 if (selectedIndex is childID) {
1817                     state |= ACC.STATE_SELECTED;
1818                     if (isFocusControl()) {
1819                         state |= ACC.STATE_FOCUSED;
1820                     }
1821                 }
1822             } else if (childID is items.length + CHEVRON_CHILD_ID) {
1823                 state = showChevron ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
1824             } else if (childID is items.length + MINIMIZE_CHILD_ID) {
1825                 state = showMin ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
1826             } else if (childID is items.length + MAXIMIZE_CHILD_ID) {
1827                 state = showMax ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE;
1828             }
1829             e.detail = state;
1830         }
1831 
1832         override
1833         public void getChildren(AccessibleControlEvent e) {
1834             int childIdCount = cast(int)/*64bit*/items.length + EXTRA_CHILD_ID_COUNT;
1835             Object[] children = new Object[childIdCount];
1836             for (int i = 0; i < childIdCount; i++) {
1837                 children[i] = new Integer(i);
1838             }
1839             e.children = children;
1840         }
1841     });
1842 
1843     addListener(SWT.Selection, new class(accessible) Listener {
1844         Accessible acc;
1845         this( Accessible a ){
1846             this.acc = a;
1847         }
1848         public void handleEvent(Event event) {
1849             if (isFocusControl()) {
1850                 if (selectedIndex is -1) {
1851                     acc.setFocus(ACC.CHILDID_SELF);
1852                 } else {
1853                     acc.setFocus(selectedIndex);
1854                 }
1855             }
1856         }
1857     });
1858 
1859     addListener(SWT.FocusIn, new class(accessible) Listener {
1860         Accessible acc;
1861         this( Accessible a ){ this.acc = a; }
1862         public void handleEvent(Event event) {
1863             if (selectedIndex is -1) {
1864                 acc.setFocus(ACC.CHILDID_SELF);
1865             } else {
1866                 acc.setFocus(selectedIndex);
1867             }
1868         }
1869     });
1870 }
1871 
1872 void onKeyDown (Event event) {
1873     switch (event.keyCode) {
1874         case SWT.ARROW_LEFT:
1875         case SWT.ARROW_RIGHT:
1876             auto count = items.length;
1877             if (count is 0) return;
1878             if (selectedIndex  is -1) return;
1879             int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) !is 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
1880             int offset =  event.keyCode is leadKey ? -1 : 1;
1881             int index;
1882             if (!mru) {
1883                 index = selectedIndex + offset;
1884             } else {
1885                 int[] visible = new int[items.length];
1886                 int idx = 0;
1887                 int current = -1;
1888                 for (int i = 0; i < items.length; i++) {
1889                     if (items[i].showing) {
1890                         if (i is selectedIndex) current = idx;
1891                         visible [idx++] = i;
1892                     }
1893                 }
1894                 if (current + offset >= 0 && current + offset < idx){
1895                     index = visible [current + offset];
1896                 } else {
1897                     if (showChevron) {
1898                         CTabFolderEvent e = new CTabFolderEvent(this);
1899                         e.widget = this;
1900                         e.time = event.time;
1901                         e.x = chevronRect.x;
1902                         e.y = chevronRect.y;
1903                         e.width = chevronRect.width;
1904                         e.height = chevronRect.height;
1905                         e.doit = true;
1906                         for (int i = 0; i < folderListeners.length; i++) {
1907                             folderListeners[i].showList(e);
1908                         }
1909                         if (e.doit && !isDisposed()) {
1910                             showList(chevronRect);
1911                         }
1912                     }
1913                     return;
1914                 }
1915             }
1916             if (index < 0 || index >= count) return;
1917             setSelection (index, true);
1918             forceFocus();
1919             break;
1920         default:
1921             break;
1922     }
1923 }
1924 void onDispose(Event event) {
1925     removeListener(SWT.Dispose, listener);
1926     notifyListeners(SWT.Dispose, event);
1927     event.type = SWT.None;
1928     /*
1929      * Usually when an item is disposed, destroyItem will change the size of the items array,
1930      * reset the bounds of all the tabs and manage the widget associated with the tab.
1931      * Since the whole folder is being disposed, this is not necessary.  For speed
1932      * the inDispose flag is used to skip over this part of the item dispose.
1933      */
1934     inDispose = true;
1935 
1936     if (showMenu !is null && !showMenu.isDisposed()) {
1937         showMenu.dispose();
1938         showMenu = null;
1939     }
1940     int length = cast(int)/*64bit*/items.length;
1941     for (int i = 0; i < length; i++) {
1942         if (items[i] !is null) {
1943             items[i].dispose();
1944         }
1945     }
1946 
1947     selectionGradientColors = null;
1948     selectionGradientPercents = null;
1949     selectionBgImage = null;
1950 
1951     selectionBackground = null;
1952     selectionForeground = null;
1953     disposeSelectionHighlightGradientColors();
1954 }
1955 void onDragDetect(Event event) {
1956     bool consume = false;
1957     if (chevronRect.contains(event.x, event.y) ||
1958         minRect.contains(event.x, event.y) ||
1959         maxRect.contains(event.x, event.y)){
1960         consume = true;
1961     } else {
1962         for (int i = 0; i < items.length; i++) {
1963             if (items[i].closeRect.contains(event.x, event.y)) {
1964                     consume = true;
1965                     break;
1966             }
1967         }
1968     }
1969     if (consume) {
1970         event.type = SWT.None;
1971     }
1972 }
1973 void onFocus(Event event) {
1974     checkWidget();
1975     if (selectedIndex >= 0) {
1976         redraw();
1977     } else {
1978         setSelection(0, true);
1979     }
1980 }
1981 bool onMnemonic (Event event) {
1982     auto key = event.character;
1983     for (int i = 0; i < items.length; i++) {
1984         if (items[i] !is null) {
1985             auto mnemonic = _findMnemonic (items[i].getText ());
1986             if (mnemonic !is '\0') {
1987                 if ( CharacterToLower(key) is mnemonic) {
1988                     setSelection(i, true);
1989                     return true;
1990                 }
1991             }
1992         }
1993     }
1994     return false;
1995 }
1996 void onMouseDoubleClick(Event event) {
1997     if (event.button !is 1 ||
1998         (event.stateMask & SWT.BUTTON2) !is 0 ||
1999         (event.stateMask & SWT.BUTTON3) !is 0) return;
2000     Event e = new Event();
2001     e.item = getItem(new Point(event.x, event.y));
2002     if (e.item !is null) {
2003         notifyListeners(SWT.DefaultSelection, e);
2004     }
2005 }
2006 void onMouse(Event event) {
2007     int x = event.x, y = event.y;
2008     CTabItem item = null;
2009     switch (event.type) {
2010         case SWT.MouseEnter: {
2011             setToolTipText(null);
2012             break;
2013         }
2014         case SWT.MouseExit: {
2015             if (minImageState !is NORMAL) {
2016                 minImageState = NORMAL;
2017                 redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
2018             }
2019             if (maxImageState !is NORMAL) {
2020                 maxImageState = NORMAL;
2021                 redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
2022             }
2023             if (chevronImageState !is NORMAL) {
2024                 chevronImageState = NORMAL;
2025                 redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
2026             }
2027             for (int i=0; i<items.length; i++) {
2028                 item = items[i];
2029                 if (i !is selectedIndex && item.closeImageState !is NONE) {
2030                     item.closeImageState = NONE;
2031                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2032                 }
2033                 if (i is selectedIndex && item.closeImageState !is NORMAL) {
2034                     item.closeImageState = NORMAL;
2035                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2036                 }
2037             }
2038             break;
2039         }
2040         case SWT.MouseDown: {
2041             if (minRect.contains(x, y)) {
2042                 if (event.button !is 1) return;
2043                 minImageState = SELECTED;
2044                 redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
2045                 update();
2046                 return;
2047             }
2048             if (maxRect.contains(x, y)) {
2049                 if (event.button !is 1) return;
2050                 maxImageState = SELECTED;
2051                 redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
2052                 update();
2053                 return;
2054             }
2055             if (chevronRect.contains(x, y)) {
2056                 if (event.button !is 1) return;
2057                 if (chevronImageState !is HOT) {
2058                     chevronImageState = HOT;
2059                 } else {
2060                     chevronImageState = SELECTED;
2061                 }
2062                 redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
2063                 update();
2064                 return;
2065             }
2066             item = null;
2067             if (single) {
2068                 if (selectedIndex !is -1) {
2069                     Rectangle bounds = items[selectedIndex].getBounds();
2070                     if (bounds.contains(x, y)){
2071                         item = items[selectedIndex];
2072                     }
2073                 }
2074             } else {
2075                 for (int i=0; i<items.length; i++) {
2076                     Rectangle bounds = items[i].getBounds();
2077                     if (bounds.contains(x, y)){
2078                         item = items[i];
2079                     }
2080                 }
2081             }
2082             if (item !is null) {
2083                 if (item.closeRect.contains(x,y)){
2084                     if (event.button !is 1) return;
2085                     item.closeImageState = SELECTED;
2086                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2087                     update();
2088                     return;
2089                 }
2090                 int index = indexOf(item);
2091                 if (item.showing){
2092                     setSelection(index, true);
2093                 }
2094                 return;
2095             }
2096             break;
2097         }
2098         case SWT.MouseMove: {
2099             _setToolTipText(event.x, event.y);
2100             bool close = false, minimize = false, maximize = false, chevron = false;
2101             if (minRect.contains(x, y)) {
2102                 minimize = true;
2103                 if (minImageState !is SELECTED && minImageState !is HOT) {
2104                     minImageState = HOT;
2105                     redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
2106                 }
2107             }
2108             if (maxRect.contains(x, y)) {
2109                 maximize = true;
2110                 if (maxImageState !is SELECTED && maxImageState !is HOT) {
2111                     maxImageState = HOT;
2112                     redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
2113                 }
2114             }
2115             if (chevronRect.contains(x, y)) {
2116                 chevron = true;
2117                 if (chevronImageState !is SELECTED && chevronImageState !is HOT) {
2118                     chevronImageState = HOT;
2119                     redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
2120                 }
2121             }
2122             if (minImageState !is NORMAL && !minimize) {
2123                 minImageState = NORMAL;
2124                 redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
2125             }
2126             if (maxImageState !is NORMAL && !maximize) {
2127                 maxImageState = NORMAL;
2128                 redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
2129             }
2130             if (chevronImageState !is NORMAL && !chevron) {
2131                 chevronImageState = NORMAL;
2132                 redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false);
2133             }
2134             for (int i=0; i<items.length; i++) {
2135                 item = items[i];
2136                 close = false;
2137                 if (item.getBounds().contains(x, y)) {
2138                     close = true;
2139                     if (item.closeRect.contains(x, y)) {
2140                         if (item.closeImageState !is SELECTED && item.closeImageState !is HOT) {
2141                             item.closeImageState = HOT;
2142                             redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2143                         }
2144                     } else {
2145                         if (item.closeImageState !is NORMAL) {
2146                             item.closeImageState = NORMAL;
2147                             redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2148                         }
2149                     }
2150                 }
2151                 if (i !is selectedIndex && item.closeImageState !is NONE && !close) {
2152                     item.closeImageState = NONE;
2153                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2154                 }
2155                 if (i is selectedIndex && item.closeImageState !is NORMAL && !close) {
2156                     item.closeImageState = NORMAL;
2157                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2158                 }
2159             }
2160             break;
2161         }
2162         case SWT.MouseUp: {
2163             if (event.button !is 1) return;
2164             if (chevronRect.contains(x, y)) {
2165                 bool selected = chevronImageState is SELECTED;
2166                 if (!selected) return;
2167                 CTabFolderEvent e = new CTabFolderEvent(this);
2168                 e.widget = this;
2169                 e.time = event.time;
2170                 e.x = chevronRect.x;
2171                 e.y = chevronRect.y;
2172                 e.width = chevronRect.width;
2173                 e.height = chevronRect.height;
2174                 e.doit = true;
2175                 for (int i = 0; i < folderListeners.length; i++) {
2176                     folderListeners[i].showList(e);
2177                 }
2178                 if (e.doit && !isDisposed()) {
2179                     showList(chevronRect);
2180                 }
2181                 return;
2182             }
2183             if (minRect.contains(x, y)) {
2184                 bool selected = minImageState is SELECTED;
2185                 minImageState = HOT;
2186                 redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
2187                 if (!selected) return;
2188                 CTabFolderEvent e = new CTabFolderEvent(this);
2189                 e.widget = this;
2190                 e.time = event.time;
2191                 for (int i = 0; i < folderListeners.length; i++) {
2192                     if (minimized) {
2193                         folderListeners[i].restore(e);
2194                     } else {
2195                         folderListeners[i].minimize(e);
2196                     }
2197                 }
2198                 return;
2199             }
2200             if (maxRect.contains(x, y)) {
2201                 bool selected = maxImageState is SELECTED;
2202                 maxImageState = HOT;
2203                 redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
2204                 if (!selected) return;
2205                 CTabFolderEvent e = new CTabFolderEvent(this);
2206                 e.widget = this;
2207                 e.time = event.time;
2208                 for (int i = 0; i < folderListeners.length; i++) {
2209                     if (maximized) {
2210                         folderListeners[i].restore(e);
2211                     } else {
2212                         folderListeners[i].maximize(e);
2213                     }
2214                 }
2215                 return;
2216             }
2217             item = null;
2218             if (single) {
2219                 if (selectedIndex !is -1) {
2220                     Rectangle bounds = items[selectedIndex].getBounds();
2221                     if (bounds.contains(x, y)){
2222                         item = items[selectedIndex];
2223                     }
2224                 }
2225             } else {
2226                 for (int i=0; i<items.length; i++) {
2227                     Rectangle bounds = items[i].getBounds();
2228                     if (bounds.contains(x, y)){
2229                         item = items[i];
2230                     }
2231                 }
2232             }
2233             if (item !is null) {
2234                 if (item.closeRect.contains(x,y)) {
2235                     bool selected = item.closeImageState is SELECTED;
2236                     item.closeImageState = HOT;
2237                     redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
2238                     if (!selected) return;
2239                     CTabFolderEvent e = new CTabFolderEvent(this);
2240                     e.widget = this;
2241                     e.time = event.time;
2242                     e.item = item;
2243                     e.doit = true;
2244                     for (int j = 0; j < folderListeners.length; j++) {
2245                         CTabFolder2Listener listener = folderListeners[j];
2246                         listener.close(e);
2247                     }
2248                     for (int j = 0; j < tabListeners.length; j++) {
2249                         CTabFolderListener listener = tabListeners[j];
2250                         listener.itemClosed(e);
2251                     }
2252                     if (e.doit) item.dispose();
2253                     if (!isDisposed() && item.isDisposed()) {
2254                         Display display = getDisplay();
2255                         Point pt = display.getCursorLocation();
2256                         pt = display.map(null, this, pt.x, pt.y);
2257                         CTabItem nextItem = getItem(pt);
2258                         if (nextItem !is null) {
2259                             if (nextItem.closeRect.contains(pt)) {
2260                                 if (nextItem.closeImageState !is SELECTED && nextItem.closeImageState !is HOT) {
2261                                     nextItem.closeImageState = HOT;
2262                                     redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false);
2263                                 }
2264                             } else {
2265                                 if (nextItem.closeImageState !is NORMAL) {
2266                                     nextItem.closeImageState = NORMAL;
2267                                     redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false);
2268                                 }
2269                             }
2270                         }
2271                     }
2272                     return;
2273                 }
2274             }
2275         default:
2276         }
2277     }
2278 }
2279 bool onPageTraversal(Event event) {
2280     auto count = items.length;
2281     if (count is 0) return false;
2282     size_t index = selectedIndex;
2283     if (index  is -1) {
2284         index = 0;
2285     } else {
2286         int offset = (event.detail is SWT.TRAVERSE_PAGE_NEXT) ? 1 : -1;
2287         if (!mru) {
2288             index = (selectedIndex + offset + count) % count;
2289         } else {
2290             int[] visible = new int[items.length];
2291             int idx = 0;
2292             int current = -1;
2293             for (int i = 0; i < items.length; i++) {
2294                 if (items[i].showing) {
2295                     if (i is selectedIndex) current = idx;
2296                     visible [idx++] = i;
2297                 }
2298             }
2299             if (current + offset >= 0 && current + offset < idx){
2300                 index = visible [current + offset];
2301             } else {
2302                 if (showChevron) {
2303                     CTabFolderEvent e = new CTabFolderEvent(this);
2304                     e.widget = this;
2305                     e.time = event.time;
2306                     e.x = chevronRect.x;
2307                     e.y = chevronRect.y;
2308                     e.width = chevronRect.width;
2309                     e.height = chevronRect.height;
2310                     e.doit = true;
2311                     for (int i = 0; i < folderListeners.length; i++) {
2312                         folderListeners[i].showList(e);
2313                     }
2314                     if (e.doit && !isDisposed()) {
2315                         showList(chevronRect);
2316                     }
2317                 }
2318                 return true;
2319             }
2320         }
2321     }
2322     setSelection (cast(int)/*64bit*/index, true);
2323     return true;
2324 }
2325 void onPaint(Event event) {
2326     if (inDispose) return;
2327     Font font = getFont();
2328     if (oldFont is null || oldFont!=font) {
2329         // handle case where  default font changes
2330         oldFont = font;
2331         if (!updateTabHeight(false)) {
2332             updateItems();
2333             redraw();
2334             return;
2335         }
2336     }
2337 
2338     GC gc = event.gc;
2339     Font gcFont = gc.getFont();
2340     Color gcBackground = gc.getBackground();
2341     Color gcForeground = gc.getForeground();
2342 
2343 // Useful for debugging paint problems
2344 //{
2345 //Point size = getSize();
2346 //gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN));
2347 //gc.fillRectangle(-10, -10, size.x + 20, size.y+20);
2348 //}
2349 
2350     drawBody(event);
2351 
2352     gc.setFont(gcFont);
2353     gc.setForeground(gcForeground);
2354     gc.setBackground(gcBackground);
2355 
2356     drawTabArea(event);
2357 
2358     gc.setFont(gcFont);
2359     gc.setForeground(gcForeground);
2360     gc.setBackground(gcBackground);
2361 }
2362 
2363 void onResize() {
2364     if (updateItems()) redrawTabs();
2365 
2366     Point size = getSize();
2367     if (oldSize is null) {
2368         redraw();
2369     } else {
2370         if (onBottom && size.y !is oldSize.y) {
2371             redraw();
2372         } else {
2373             int x1 = Math.min(size.x, oldSize.x);
2374             if (size.x !is oldSize.x) x1 -= borderRight + highlight_margin + 2;
2375             if (!simple) x1 -= 5; // rounded top right corner
2376             int y1 = Math.min(size.y, oldSize.y);
2377             if (size.y !is oldSize.y) y1 -= borderBottom + highlight_margin;
2378             int x2 = Math.max(size.x, oldSize.x);
2379             int y2 = Math.max(size.y, oldSize.y);
2380             redraw(0, y1, x2, y2 - y1, false);
2381             redraw(x1, 0, x2 - x1, y2, false);
2382         }
2383     }
2384     oldSize = size;
2385 }
2386 void onTraverse (Event event) {
2387     switch (event.detail) {
2388         case SWT.TRAVERSE_ESCAPE:
2389         case SWT.TRAVERSE_RETURN:
2390         case SWT.TRAVERSE_TAB_NEXT:
2391         case SWT.TRAVERSE_TAB_PREVIOUS:
2392             Control focusControl = getDisplay().getFocusControl();
2393             if (focusControl is this) event.doit = true;
2394             break;
2395         case SWT.TRAVERSE_MNEMONIC:
2396             event.doit = onMnemonic(event);
2397             if (event.doit) event.detail = SWT.TRAVERSE_NONE;
2398             break;
2399         case SWT.TRAVERSE_PAGE_NEXT:
2400         case SWT.TRAVERSE_PAGE_PREVIOUS:
2401             event.doit = onPageTraversal(event);
2402             event.detail = SWT.TRAVERSE_NONE;
2403             break;
2404         default:
2405     }
2406 }
2407 void redrawTabs() {
2408     Point size = getSize();
2409     if (onBottom) {
2410         redraw(0, size.y - borderBottom - tabHeight - highlight_header - 1, size.x, borderBottom + tabHeight + highlight_header + 1, false);
2411     } else {
2412         redraw(0, 0, size.x, borderTop + tabHeight + highlight_header + 1, false);
2413     }
2414 }
2415 /**
2416  * Removes the listener.
2417  *
2418  * @param listener the listener which should no longer be notified
2419  *
2420  * @exception IllegalArgumentException <ul>
2421  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
2422  * </ul>
2423  *
2424  * @exception SWTException <ul>
2425  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
2426  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
2427  * </ul>
2428  *
2429  * @see #addCTabFolder2Listener(CTabFolder2Listener)
2430  *
2431  * @since 3.0
2432  */
2433 public void removeCTabFolder2Listener(CTabFolder2Listener listener) {
2434     checkWidget();
2435     if (listener is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
2436     if (folderListeners.length is 0) return;
2437     int index = -1;
2438     for (int i = 0; i < folderListeners.length; i++) {
2439         if (listener is folderListeners[i]){
2440             index = i;
2441             break;
2442         }
2443     }
2444     if (index is -1) return;
2445     if (folderListeners.length is 1) {
2446         folderListeners = new CTabFolder2Listener[0];
2447         return;
2448     }
2449     CTabFolder2Listener[] newTabListeners = new CTabFolder2Listener[folderListeners.length - 1];
2450     SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, 0, newTabListeners, 0, index);
2451     SimpleType!(CTabFolder2Listener).arraycopy(folderListeners, index + 1, newTabListeners, index, folderListeners.length - index - 1);
2452     folderListeners = newTabListeners;
2453 }
2454 /**
2455  * Removes the listener.
2456  *
2457  * @param listener the listener which should no longer be notified
2458  *
2459  * @exception IllegalArgumentException <ul>
2460  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
2461  * </ul>
2462  *
2463  * @exception SWTException <ul>
2464  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
2465  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
2466  * </ul>
2467  *
2468  * @deprecated see removeCTabFolderCloseListener(CTabFolderListener)
2469  */
2470 public void removeCTabFolderListener(CTabFolderListener listener) {
2471     checkWidget();
2472     if (listener is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
2473     if (tabListeners.length is 0) return;
2474     int index = -1;
2475     for (int i = 0; i < tabListeners.length; i++) {
2476         if (listener is tabListeners[i]){
2477             index = i;
2478             break;
2479         }
2480     }
2481     if (index is -1) return;
2482     if (tabListeners.length is 1) {
2483         tabListeners = new CTabFolderListener[0];
2484         return;
2485     }
2486     CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length - 1];
2487     SimpleType!(CTabFolderListener).arraycopy(tabListeners, 0, newTabListeners, 0, index);
2488     SimpleType!(CTabFolderListener).arraycopy(tabListeners, index + 1, newTabListeners, index, tabListeners.length - index - 1);
2489     tabListeners = newTabListeners;
2490 }
2491 /**
2492  * Removes the listener from the collection of listeners who will
2493  * be notified when the user changes the receiver's selection.
2494  *
2495  * @param listener the listener which should no longer be notified
2496  *
2497  * @exception IllegalArgumentException <ul>
2498  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
2499  * </ul>
2500  * @exception SWTException <ul>
2501  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2502  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2503  * </ul>
2504  *
2505  * @see SelectionListener
2506  * @see #addSelectionListener
2507  */
2508 public void removeSelectionListener(SelectionListener listener) {
2509     checkWidget();
2510     if (listener is null) {
2511         SWT.error(SWT.ERROR_NULL_ARGUMENT);
2512     }
2513     removeListener(SWT.Selection, listener);
2514     removeListener(SWT.DefaultSelection, listener);
2515 }
2516 public override void setBackground (Color color) {
2517     super.setBackground(color);
2518     redraw();
2519 }
2520 /**
2521  * Specify a gradient of colours to be drawn in the background of the unselected tabs.
2522  * For example to draw a gradient that varies from dark blue to blue and then to
2523  * white, use the following call to setBackground:
2524  * <pre>
2525  *  cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
2526  *                                 display.getSystemColor(SWT.COLOR_BLUE),
2527  *                                 display.getSystemColor(SWT.COLOR_WHITE),
2528  *                                 display.getSystemColor(SWT.COLOR_WHITE)},
2529  *                     new int[] {25, 50, 100});
2530  * </pre>
2531  *
2532  * @param colors an array of Color that specifies the colors to appear in the gradient
2533  *               in order of appearance left to right.  The value <code>null</code> clears the
2534  *               background gradient. The value <code>null</code> can be used inside the array of
2535  *               Color to specify the background color.
2536  * @param percents an array of integers between 0 and 100 specifying the percent of the width
2537  *                 of the widget at which the color should change.  The size of the percents array must be one
2538  *                 less than the size of the colors array.
2539  *
2540  * @exception SWTException <ul>
2541  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
2542  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
2543  *  </ul>
2544  *
2545  * @since 3.0
2546  */
2547 void setBackground(Color[] colors, int[] percents) {
2548     setBackground(colors, percents, false);
2549 }
2550 /**
2551  * Specify a gradient of colours to be drawn in the background of the unselected tab.
2552  * For example to draw a vertical gradient that varies from dark blue to blue and then to
2553  * white, use the following call to setBackground:
2554  * <pre>
2555  *  cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
2556  *                                 display.getSystemColor(SWT.COLOR_BLUE),
2557  *                                 display.getSystemColor(SWT.COLOR_WHITE),
2558  *                                 display.getSystemColor(SWT.COLOR_WHITE)},
2559  *                        new int[] {25, 50, 100}, true);
2560  * </pre>
2561  *
2562  * @param colors an array of Color that specifies the colors to appear in the gradient
2563  *               in order of appearance left to right.  The value <code>null</code> clears the
2564  *               background gradient. The value <code>null</code> can be used inside the array of
2565  *               Color to specify the background color.
2566  * @param percents an array of integers between 0 and 100 specifying the percent of the width
2567  *                 of the widget at which the color should change.  The size of the percents array must be one
2568  *                 less than the size of the colors array.
2569  *
2570  * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
2571  *
2572  * @exception SWTException <ul>
2573  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
2574  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
2575  *  </ul>
2576  *
2577  * @since 3.0
2578  */
2579 void setBackground(Color[] colors, int[] percents, bool vertical) {
2580     checkWidget();
2581     if (colors !is null) {
2582         if (percents is null || percents.length !is colors.length - 1) {
2583             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2584         }
2585         for (int i = 0; i < percents.length; i++) {
2586             if (percents[i] < 0 || percents[i] > 100) {
2587                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2588             }
2589             if (i > 0 && percents[i] < percents[i-1]) {
2590                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2591             }
2592         }
2593         if (getDisplay().getDepth() < 15) {
2594             // Don't use gradients on low color displays
2595             colors = [colors[colors.length - 1]];
2596             percents = null;
2597         }
2598     }
2599 
2600     // Are these settings the same as before?
2601     if (bgImage is null) {
2602         if ((gradientColors !is null) && (colors !is null) &&
2603             (gradientColors.length is colors.length)) {
2604             bool same = false;
2605             for (int i = 0; i < gradientColors.length; i++) {
2606                 if (gradientColors[i] is null) {
2607                     same = colors[i] is null;
2608                 } else {
2609                     same = cast(bool)(gradientColors[i]==colors[i]);
2610                 }
2611                 if (!same) break;
2612             }
2613             if (same) {
2614                 for (int i = 0; i < gradientPercents.length; i++) {
2615                     same = gradientPercents[i] is percents[i];
2616                     if (!same) break;
2617                 }
2618             }
2619             if (same && this.gradientVertical is vertical) return;
2620         }
2621     } else {
2622         bgImage = null;
2623     }
2624     // Store the new settings
2625     if (colors is null) {
2626         gradientColors = null;
2627         gradientPercents = null;
2628         gradientVertical = false;
2629         setBackground(cast(Color)null);
2630     } else {
2631         gradientColors = new Color[colors.length];
2632         for (int i = 0; i < colors.length; ++i) {
2633             gradientColors[i] = colors[i];
2634         }
2635         gradientPercents = new int[percents.length];
2636         for (int i = 0; i < percents.length; ++i) {
2637             gradientPercents[i] = percents[i];
2638         }
2639         gradientVertical = vertical;
2640         setBackground(gradientColors[gradientColors.length-1]);
2641     }
2642 
2643     // Refresh with the new settings
2644     redraw();
2645 }
2646 
2647 /**
2648  * Set the image to be drawn in the background of the unselected tab.  Image
2649  * is stretched or compressed to cover entire unselected tab area.
2650  *
2651  * @param image the image to be drawn in the background
2652  *
2653  * @exception SWTException <ul>
2654  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2655  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2656  * </ul>
2657  *
2658  * @since 3.0
2659  */
2660 void setBackground(Image image) {
2661     checkWidget();
2662     if (image is bgImage) return;
2663     if (image !is null) {
2664         gradientColors = null;
2665         gradientPercents = null;
2666     }
2667     bgImage = image;
2668     redraw();
2669 }
2670 /**
2671  * Toggle the visibility of the border
2672  *
2673  * @param show true if the border should be displayed
2674  *
2675  * @exception SWTException <ul>
2676  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2677  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2678  * </ul>
2679  */
2680 public void setBorderVisible(bool show) {
2681     checkWidget();
2682     if ((borderLeft is 1) is show) return;
2683     borderLeft = borderRight = show ? 1 : 0;
2684     borderTop = onBottom ? borderLeft : 0;
2685     borderBottom = onBottom ? 0 : borderLeft;
2686     Rectangle rectBefore = getClientArea();
2687     updateItems();
2688     Rectangle rectAfter = getClientArea();
2689     if (rectBefore!=rectAfter) {
2690         notifyListeners(SWT.Resize, new Event());
2691     }
2692     redraw();
2693 }
2694 void setButtonBounds() {
2695     Point size = getSize();
2696     int oldX, oldY, oldWidth, oldHeight;
2697     // max button
2698     oldX = maxRect.x;
2699     oldY = maxRect.y;
2700     oldWidth = maxRect.width;
2701     oldHeight = maxRect.height;
2702     maxRect.x = maxRect.y = maxRect.width = maxRect.height = 0;
2703     if (showMax) {
2704         maxRect.x = size.x - borderRight - BUTTON_SIZE - 3;
2705         if (borderRight > 0) maxRect.x += 1;
2706         maxRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
2707         maxRect.width = BUTTON_SIZE;
2708         maxRect.height = BUTTON_SIZE;
2709     }
2710     if (oldX !is maxRect.x || oldWidth !is maxRect.width ||
2711         oldY !is maxRect.y || oldHeight !is maxRect.height) {
2712         int left = Math.min(oldX, maxRect.x);
2713         int right = Math.max(oldX + oldWidth, maxRect.x + maxRect.width);
2714         int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
2715         redraw(left, top, right - left, tabHeight, false);
2716     }
2717 
2718     // min button
2719     oldX = minRect.x;
2720     oldY = minRect.y;
2721     oldWidth = minRect.width;
2722     oldHeight = minRect.height;
2723     minRect.x = minRect.y = minRect.width = minRect.height = 0;
2724     if (showMin) {
2725         minRect.x = size.x - borderRight - maxRect.width - BUTTON_SIZE - 3;
2726         if (borderRight > 0) minRect.x += 1;
2727         minRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
2728         minRect.width = BUTTON_SIZE;
2729         minRect.height = BUTTON_SIZE;
2730     }
2731     if (oldX !is minRect.x || oldWidth !is minRect.width ||
2732         oldY !is minRect.y || oldHeight !is minRect.height) {
2733         int left = Math.min(oldX, minRect.x);
2734         int right = Math.max(oldX + oldWidth, minRect.x + minRect.width);
2735         int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
2736         redraw(left, top, right - left, tabHeight, false);
2737     }
2738 
2739     // top right control
2740     oldX = topRightRect.x;
2741     oldY = topRightRect.y;
2742     oldWidth = topRightRect.width;
2743     oldHeight = topRightRect.height;
2744     topRightRect.x = topRightRect.y = topRightRect.width = topRightRect.height = 0;
2745     if (topRight !is null) {
2746         CTabItem item = null;
2747         switch (topRightAlignment) {
2748             case SWT.FILL: {
2749                 int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
2750                 if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2;
2751                 if (single) {
2752                     if (items.length is 0 || selectedIndex is -1) {
2753                         topRightRect.x = borderLeft + 3;
2754                         topRightRect.width = rightEdge - topRightRect.x;
2755                     } else {
2756                         // fill size is 0 if item compressed
2757                         item = items[selectedIndex];
2758                         if (item.x + item.width + 7 + 3*BUTTON_SIZE/2 >= rightEdge) break;
2759                         topRightRect.x = item.x + item.width + 7 + 3*BUTTON_SIZE/2;
2760                         topRightRect.width = rightEdge - topRightRect.x;
2761                     }
2762                 } else {
2763                     // fill size is 0 if chevron showing
2764                     if (showChevron) break;
2765                     if (items.length is 0) {
2766                         topRightRect.x = borderLeft + 3;
2767                     } else {
2768                         item = items[items.length - 1];
2769                         topRightRect.x = item.x + item.width;
2770                         if (!simple && items.length - 1 is selectedIndex) topRightRect.x += curveWidth - curveIndent;
2771                     }
2772                     topRightRect.width = Math.max(0, rightEdge - topRightRect.x);
2773                 }
2774                 topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
2775                 topRightRect.height = tabHeight - 1;
2776                 break;
2777             }
2778             case SWT.RIGHT: {
2779                 Point topRightSize = topRight.computeSize(SWT.DEFAULT, tabHeight, false);
2780                 int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
2781                 if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2;
2782                 topRightRect.x = rightEdge - topRightSize.x;
2783                 topRightRect.width = topRightSize.x;
2784                 topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
2785                 topRightRect.height = tabHeight - 1;
2786                 break;
2787             }
2788             default:
2789                 break;
2790         }
2791         topRight.setBounds(topRightRect);
2792     }
2793     if (oldX !is topRightRect.x || oldWidth !is topRightRect.width ||
2794         oldY !is topRightRect.y || oldHeight !is topRightRect.height) {
2795         int left = Math.min(oldX, topRightRect.x);
2796         int right = Math.max(oldX + oldWidth, topRightRect.x + topRightRect.width);
2797         int top = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1;
2798         redraw(left, top, right - left, tabHeight, false);
2799     }
2800 
2801     // chevron button
2802     oldX = chevronRect.x;
2803     oldY = chevronRect.y;
2804     oldWidth = chevronRect.width;
2805     oldHeight = chevronRect.height;
2806     chevronRect.x = chevronRect.y = chevronRect.height = chevronRect.width = 0;
2807     if (single) {
2808         if (selectedIndex is -1 || items.length > 1) {
2809             chevronRect.width = 3*BUTTON_SIZE/2;
2810             chevronRect.height = BUTTON_SIZE;
2811             chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2;
2812             if (selectedIndex is -1) {
2813                 chevronRect.x = size.x - borderRight - 3 - minRect.width - maxRect.width - topRightRect.width - chevronRect.width;
2814             } else {
2815                 CTabItem item = items[selectedIndex];
2816                 int w = size.x - borderRight - 3 - minRect.width - maxRect.width - chevronRect.width;
2817                 if (topRightRect.width > 0) w -= topRightRect.width + 3;
2818                 chevronRect.x = Math.min(item.x + item.width + 3, w);
2819             }
2820             if (borderRight > 0) chevronRect.x += 1;
2821         }
2822     } else {
2823         if (showChevron) {
2824             chevronRect.width = 3*BUTTON_SIZE/2;
2825             chevronRect.height = BUTTON_SIZE;
2826             int i = 0, lastIndex = -1;
2827             while (i < priority.length && items[priority[i]].showing) {
2828                 lastIndex = Math.max(lastIndex, priority[i++]);
2829             }
2830             if (lastIndex is -1) lastIndex = firstIndex;
2831             CTabItem lastItem = items[lastIndex];
2832             int w = lastItem.x + lastItem.width + 3;
2833             if (!simple && lastIndex is selectedIndex) w += curveWidth - 2*curveIndent;
2834             chevronRect.x = Math.min(w, getRightItemEdge());
2835             chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2;
2836         }
2837     }
2838     if (oldX !is chevronRect.x || oldWidth !is chevronRect.width ||
2839         oldY !is chevronRect.y || oldHeight !is chevronRect.height) {
2840         int left = Math.min(oldX, chevronRect.x);
2841         int right = Math.max(oldX + oldWidth, chevronRect.x + chevronRect.width);
2842         int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
2843         redraw(left, top, right - left, tabHeight, false);
2844     }
2845 }
2846 public override void setFont(Font font) {
2847     checkWidget();
2848     if (font !is null && font==getFont()) return;
2849     super.setFont(font);
2850     oldFont = getFont();
2851     if (!updateTabHeight(false)) {
2852         updateItems();
2853         redraw();
2854     }
2855 }
2856 public override void setForeground (Color color) {
2857     super.setForeground(color);
2858     redraw();
2859 }
2860 /**
2861  * Display an insert marker before or after the specified tab item.
2862  *
2863  * A value of null will clear the mark.
2864  *
2865  * @param item the item with which the mark is associated or null
2866  *
2867  * @param after true if the mark should be displayed after the specified item
2868  *
2869  * @exception SWTException <ul>
2870  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2871  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2872  * </ul>
2873  */
2874 public void setInsertMark(CTabItem item, bool after) {
2875     checkWidget();
2876 }
2877 /**
2878  * Display an insert marker before or after the specified tab item.
2879  *
2880  * A value of -1 will clear the mark.
2881  *
2882  * @param index the index of the item with which the mark is associated or null
2883  *
2884  * @param after true if the mark should be displayed after the specified item
2885  *
2886  * @exception IllegalArgumentException<ul>
2887  * </ul>
2888  *
2889  * @exception SWTException <ul>
2890  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2891  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2892  * </ul>
2893  */
2894 public void setInsertMark(int index, bool after) {
2895     checkWidget();
2896     if (index < -1 || index >= getItemCount()) {
2897         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
2898     }
2899 }
2900 bool setItemLocation() {
2901     bool changed = false;
2902     if (items.length is 0) return false;
2903     Point size = getSize();
2904     int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop;
2905     if (single) {
2906         int defaultX = getDisplay().getBounds().width + 10; // off screen
2907         for (int i = 0; i < items.length; i++) {
2908             CTabItem item = items[i];
2909             if (i is selectedIndex) {
2910                 firstIndex = selectedIndex;
2911                 int oldX = item.x, oldY = item.y;
2912                 item.x = borderLeft;
2913                 item.y = y;
2914                 item.showing = true;
2915                 if (showClose || item.showClose) {
2916                     item.closeRect.x = borderLeft + CTabItem.LEFT_MARGIN;
2917                     item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
2918                 }
2919                 if (item.x !is oldX || item.y !is oldY) changed = true;
2920             } else {
2921                 item.x = defaultX;
2922                 item.showing = false;
2923             }
2924         }
2925     } else {
2926         int rightItemEdge = getRightItemEdge();
2927         int maxWidth = rightItemEdge - borderLeft;
2928         int width = 0;
2929         for (int i = 0; i < priority.length; i++) {
2930             CTabItem item = items[priority[i]];
2931             width += item.width;
2932             item.showing = i is 0 ? true : item.width > 0 && width <= maxWidth;
2933             if (!simple && priority[i] is selectedIndex) width += curveWidth - 2*curveIndent;
2934         }
2935         int x = 0;
2936         int defaultX = getDisplay().getBounds().width + 10; // off screen
2937         firstIndex = cast(int)/*64bit*/items.length - 1;
2938         for (int i = 0; i < items.length; i++) {
2939             CTabItem item = items[i];
2940             if (!item.showing) {
2941                 if (item.x !is defaultX) changed = true;
2942                 item.x = defaultX;
2943             } else {
2944                 firstIndex = Math.min(firstIndex, i);
2945                 if (item.x !is x || item.y !is y) changed = true;
2946                 item.x = x;
2947                 item.y = y;
2948                 if (i is selectedIndex) {
2949                     int edge = Math.min(item.x + item.width, rightItemEdge);
2950                     item.closeRect.x = edge - CTabItem.RIGHT_MARGIN - BUTTON_SIZE;
2951                 } else {
2952                     item.closeRect.x = item.x + item.width - CTabItem.RIGHT_MARGIN - BUTTON_SIZE;
2953                 }
2954                 item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2;
2955                 x = x + item.width;
2956                 if (!simple && i is selectedIndex) x += curveWidth - 2*curveIndent;
2957             }
2958         }
2959     }
2960     return changed;
2961 }
2962 bool setItemSize() {
2963     bool changed = false;
2964     if (isDisposed()) return changed;
2965     Point size = getSize();
2966     if (size.x <= 0 || size.y <= 0) return changed;
2967     xClient = borderLeft + marginWidth + highlight_margin;
2968     if (onBottom) {
2969         yClient = borderTop + highlight_margin + marginHeight;
2970     } else {
2971         yClient = borderTop + tabHeight + highlight_header + marginHeight;
2972     }
2973     showChevron = false;
2974     if (single) {
2975         showChevron = true;
2976         if (selectedIndex !is -1) {
2977             CTabItem tab = items[selectedIndex];
2978             GC gc = new GC(this);
2979             int width = tab.preferredWidth(gc, true, false);
2980             gc.dispose();
2981             width = Math.min(width, getRightItemEdge() - borderLeft);
2982             if (tab.height !is tabHeight || tab.width !is width) {
2983                 changed = true;
2984                 tab.shortenedText = null;
2985                 tab.shortenedTextWidth = 0;
2986                 tab.height = tabHeight;
2987                 tab.width = width;
2988                 tab.closeRect.width = tab.closeRect.height = 0;
2989                 if (showClose || tab.showClose) {
2990                     tab.closeRect.width = BUTTON_SIZE;
2991                     tab.closeRect.height = BUTTON_SIZE;
2992                 }
2993             }
2994         }
2995         return changed;
2996     }
2997 
2998     if (items.length is 0) return changed;
2999 
3000     int[] widths;
3001     GC gc = new GC(this);
3002     int tabAreaWidth = size.x - borderLeft - borderRight - 3;
3003     if (showMin) tabAreaWidth -= BUTTON_SIZE;
3004     if (showMax) tabAreaWidth -= BUTTON_SIZE;
3005     if (topRightAlignment is SWT.RIGHT && topRight !is null) {
3006         Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
3007         tabAreaWidth -= rightSize.x + 3;
3008     }
3009     if (!simple) tabAreaWidth -= curveWidth - 2*curveIndent;
3010     tabAreaWidth = Math.max(0, tabAreaWidth);
3011 
3012     // First, try the minimum tab size at full compression.
3013     int minWidth = 0;
3014     int[] minWidths = new int[items.length];
3015     for (int i = 0; i < priority.length; i++) {
3016         int index = priority[i];
3017         minWidths[index] = items[index].preferredWidth(gc, index is selectedIndex, true);
3018         minWidth += minWidths[index];
3019         if (minWidth > tabAreaWidth) break;
3020     }
3021     if (minWidth > tabAreaWidth) {
3022         // full compression required and a chevron
3023         showChevron = items.length > 1;
3024         if (showChevron) tabAreaWidth -= 3*BUTTON_SIZE/2;
3025         widths = minWidths;
3026         int index = selectedIndex !is -1 ? selectedIndex : 0;
3027         if (tabAreaWidth < widths[index]) {
3028             widths[index] = Math.max(0, tabAreaWidth);
3029         }
3030     } else {
3031         int maxWidth = 0;
3032         int[] maxWidths = new int[items.length];
3033         for (int i = 0; i < items.length; i++) {
3034             maxWidths[i] = items[i].preferredWidth(gc, i is selectedIndex, false);
3035             maxWidth += maxWidths[i];
3036         }
3037         if (maxWidth <= tabAreaWidth) {
3038             // no compression required
3039             widths = maxWidths;
3040         } else {
3041             // determine compression for each item
3042             auto extra = (tabAreaWidth - minWidth) / items.length;
3043             while (true) {
3044                 int large = 0, totalWidth = 0;
3045                 for (int i = 0 ; i < items.length; i++) {
3046                     if (maxWidths[i] > minWidths[i] + extra) {
3047                         totalWidth += minWidths[i] + extra;
3048                         large++;
3049                     } else {
3050                         totalWidth += maxWidths[i];
3051                     }
3052                 }
3053                 if (totalWidth >= tabAreaWidth) {
3054                     extra--;
3055                     break;
3056                 }
3057                 if (large is 0 || tabAreaWidth - totalWidth < large) break;
3058                 extra++;
3059             }
3060             widths = new int[items.length];
3061             for (int i = 0; i < items.length; i++) {
3062                 widths[i] = cast(int)/*64bit*/Math.min
3063                                    (maxWidths[i], minWidths[i] + extra);
3064             }
3065         }
3066     }
3067     gc.dispose();
3068 
3069     for (int i = 0; i < items.length; i++) {
3070         CTabItem tab = items[i];
3071         int width = widths[i];
3072         if (tab.height !is tabHeight || tab.width !is width) {
3073             changed = true;
3074             tab.shortenedText = null;
3075             tab.shortenedTextWidth = 0;
3076             tab.height = tabHeight;
3077             tab.width = width;
3078             tab.closeRect.width = tab.closeRect.height = 0;
3079             if (showClose || tab.showClose) {
3080                 if (i is selectedIndex || showUnselectedClose) {
3081                     tab.closeRect.width = BUTTON_SIZE;
3082                     tab.closeRect.height = BUTTON_SIZE;
3083                 }
3084             }
3085         }
3086     }
3087     return changed;
3088 }
3089 /**
3090  * Marks the receiver's maximize button as visible if the argument is <code>true</code>,
3091  * and marks it invisible otherwise.
3092  *
3093  * @param visible the new visibility state
3094  *
3095  * @exception SWTException <ul>
3096  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3097  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3098  * </ul>
3099  *
3100  * @since 3.0
3101  */
3102 public void setMaximizeVisible(bool visible) {
3103     checkWidget();
3104     if (showMax is visible) return;
3105     // display maximize button
3106     showMax = visible;
3107     updateItems();
3108     redraw();
3109 }
3110 /**
3111  * Sets the layout which is associated with the receiver to be
3112  * the argument which may be null.
3113  * <p>
3114  * Note: No Layout can be set on this Control because it already
3115  * manages the size and position of its children.
3116  * </p>
3117  *
3118  * @param layout the receiver's new layout or null
3119  *
3120  * @exception SWTException <ul>
3121  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3122  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3123  * </ul>
3124  */
3125 public override void setLayout (Layout layout) {
3126     checkWidget();
3127     return;
3128 }
3129 /**
3130  * Sets the maximized state of the receiver.
3131  *
3132  * @param maximize the new maximized state
3133  *
3134  * @exception SWTException <ul>
3135  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3136  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3137  * </ul>
3138  *
3139  * @since 3.0
3140  */
3141 public void setMaximized(bool maximize) {
3142     checkWidget ();
3143     if (this.maximized is maximize) return;
3144     if (maximize && this.minimized) setMinimized(false);
3145     this.maximized = maximize;
3146     redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false);
3147 }
3148 /**
3149  * Marks the receiver's minimize button as visible if the argument is <code>true</code>,
3150  * and marks it invisible otherwise.
3151  *
3152  * @param visible the new visibility state
3153  *
3154  * @exception SWTException <ul>
3155  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3156  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3157  * </ul>
3158  *
3159  * @since 3.0
3160  */
3161 public void setMinimizeVisible(bool visible) {
3162     checkWidget();
3163     if (showMin is visible) return;
3164     // display minimize button
3165     showMin = visible;
3166     updateItems();
3167     redraw();
3168 }
3169 /**
3170  * Sets the minimized state of the receiver.
3171  *
3172  * @param minimize the new minimized state
3173  *
3174  * @exception SWTException <ul>
3175  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3176  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3177  * </ul>
3178  *
3179  * @since 3.0
3180  */
3181 public void setMinimized(bool minimize) {
3182     checkWidget ();
3183     if (this.minimized is minimize) return;
3184     if (minimize && this.maximized) setMaximized(false);
3185     this.minimized = minimize;
3186     redraw(minRect.x, minRect.y, minRect.width, minRect.height, false);
3187 }
3188 
3189 /**
3190  * Sets the minimum number of characters that will
3191  * be displayed in a fully compressed tab.
3192  *
3193  * @param count the minimum number of characters that will be displayed in a fully compressed tab
3194  *
3195  * @exception SWTException <ul>
3196  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3197  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3198  *    <li>ERROR_INVALID_RANGE - if the count is less than zero</li>
3199  * </ul>
3200  *
3201  * @since 3.0
3202  */
3203 public void setMinimumCharacters(int count) {
3204     checkWidget ();
3205     if (count < 0) SWT.error(SWT.ERROR_INVALID_RANGE);
3206     if (minChars is count) return;
3207     minChars = count;
3208     if (updateItems()) redrawTabs();
3209 }
3210 
3211 /**
3212  * When there is not enough horizontal space to show all the tabs,
3213  * by default, tabs are shown sequentially from left to right in
3214  * order of their index.  When the MRU visibility is turned on,
3215  * the tabs that are visible will be the tabs most recently selected.
3216  * Tabs will still maintain their left to right order based on index
3217  * but only the most recently selected tabs are visible.
3218  * <p>
3219  * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
3220  * "Tab 3" and "Tab 4" (in order by index).  The user selects
3221  * "Tab 1" and then "Tab 3".  If the CTabFolder is now
3222  * compressed so that only two tabs are visible, by default,
3223  * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
3224  * selected and "Tab 2" because it is the previous item in index order).
3225  * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
3226  * and "Tab 3" (in that order from left to right).</p>
3227  *
3228  * @param show the new visibility state
3229  *
3230  * @exception SWTException <ul>
3231  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3232  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3233  * </ul>
3234  *
3235  * @since 3.1
3236  */
3237 public void setMRUVisible(bool show) {
3238     checkWidget();
3239     if (mru is show) return;
3240     mru = show;
3241     if (!mru) {
3242         int idx = firstIndex;
3243         int next = 0;
3244         for (int i = firstIndex; i < items.length; i++) {
3245             priority[next++] = i;
3246         }
3247         for (int i = 0; i < idx; i++) {
3248             priority[next++] = i;
3249         }
3250         if (updateItems()) redrawTabs();
3251     }
3252 }
3253 /**
3254  * Set the selection to the tab at the specified item.
3255  *
3256  * @param item the tab item to be selected
3257  *
3258  * @exception IllegalArgumentException <ul>
3259  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
3260  * </ul>
3261  *
3262  * @exception SWTException <ul>
3263  *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
3264  *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
3265  * </ul>
3266  */
3267 public void setSelection(CTabItem item) {
3268     checkWidget();
3269     if (item is null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
3270     int index = indexOf(item);
3271     setSelection(index);
3272 }
3273 /**
3274  * Set the selection to the tab at the specified index.
3275  *
3276  * @param index the index of the tab item to be selected
3277  *
3278  * @exception SWTException <ul>
3279  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3280  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3281  * </ul>
3282  */
3283 public void setSelection(int index) {
3284     checkWidget();
3285     if (index < 0 || index >= items.length) return;
3286     CTabItem selection = items[index];
3287     if (selectedIndex is index) {
3288         showItem(selection);
3289         return;
3290     }
3291 
3292     int oldIndex = selectedIndex;
3293     selectedIndex = index;
3294     if (oldIndex !is -1) {
3295         items[oldIndex].closeImageState = NONE;
3296     }
3297     selection.closeImageState = NORMAL;
3298     selection.showing = false;
3299 
3300     Control newControl = selection.control;
3301     Control oldControl = null;
3302     if (oldIndex !is -1) {
3303         oldControl = items[oldIndex].control;
3304     }
3305 
3306     if (newControl !is oldControl) {
3307         if (newControl !is null && !newControl.isDisposed()) {
3308             newControl.setBounds(getClientArea());
3309             newControl.setVisible(true);
3310         }
3311         if (oldControl !is null && !oldControl.isDisposed()) {
3312             oldControl.setVisible(false);
3313         }
3314     }
3315     showItem(selection);
3316     redraw();
3317 }
3318 void setSelection(int index, bool notify) {
3319     int oldSelectedIndex = selectedIndex;
3320     setSelection(index);
3321     if (notify && selectedIndex !is oldSelectedIndex && selectedIndex !is -1) {
3322         Event event = new Event();
3323         event.item = getItem(selectedIndex);
3324         notifyListeners(SWT.Selection, event);
3325     }
3326 }
3327 /**
3328  * Sets the receiver's selection background color to the color specified
3329  * by the argument, or to the default system color for the control
3330  * if the argument is null.
3331  *
3332  * @param color the new color (or null)
3333  *
3334  * @exception IllegalArgumentException <ul>
3335  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
3336  * </ul>
3337  * @exception SWTException <ul>
3338  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3339  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3340  * </ul>
3341  *
3342  * @since 3.0
3343  */
3344 public void setSelectionBackground (Color color) {
3345     checkWidget();
3346     setSelectionHighlightGradientColor(null);
3347     if (selectionBackground is color) return;
3348     if (color is null) color = getDisplay().getSystemColor(SELECTION_BACKGROUND);
3349     selectionBackground = color;
3350     if (selectedIndex > -1) redraw();
3351 }
3352 /**
3353  * Specify a gradient of colours to be draw in the background of the selected tab.
3354  * For example to draw a gradient that varies from dark blue to blue and then to
3355  * white, use the following call to setBackground:
3356  * <pre>
3357  *  cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
3358  *                                 display.getSystemColor(SWT.COLOR_BLUE),
3359  *                                 display.getSystemColor(SWT.COLOR_WHITE),
3360  *                                 display.getSystemColor(SWT.COLOR_WHITE)},
3361  *                     new int[] {25, 50, 100});
3362  * </pre>
3363  *
3364  * @param colors an array of Color that specifies the colors to appear in the gradient
3365  *               in order of appearance left to right.  The value <code>null</code> clears the
3366  *               background gradient. The value <code>null</code> can be used inside the array of
3367  *               Color to specify the background color.
3368  * @param percents an array of integers between 0 and 100 specifying the percent of the width
3369  *                 of the widget at which the color should change.  The size of the percents array must be one
3370  *                 less than the size of the colors array.
3371  *
3372  * @exception SWTException <ul>
3373  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
3374  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
3375  *  </ul>
3376  */
3377 public void setSelectionBackground(Color[] colors, int[] percents) {
3378     setSelectionBackground(colors, percents, false);
3379 }
3380 /**
3381  * Specify a gradient of colours to be draw in the background of the selected tab.
3382  * For example to draw a vertical gradient that varies from dark blue to blue and then to
3383  * white, use the following call to setBackground:
3384  * <pre>
3385  *  cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
3386  *                                 display.getSystemColor(SWT.COLOR_BLUE),
3387  *                                 display.getSystemColor(SWT.COLOR_WHITE),
3388  *                                 display.getSystemColor(SWT.COLOR_WHITE)},
3389  *                        new int[] {25, 50, 100}, true);
3390  * </pre>
3391  *
3392  * @param colors an array of Color that specifies the colors to appear in the gradient
3393  *               in order of appearance left to right.  The value <code>null</code> clears the
3394  *               background gradient. The value <code>null</code> can be used inside the array of
3395  *               Color to specify the background color.
3396  * @param percents an array of integers between 0 and 100 specifying the percent of the width
3397  *                 of the widget at which the color should change.  The size of the percents array must be one
3398  *                 less than the size of the colors array.
3399  *
3400  * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
3401  *
3402  * @exception SWTException <ul>
3403  *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
3404  *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
3405  *  </ul>
3406  *
3407  * @since 3.0
3408  */
3409 public void setSelectionBackground(Color[] colors, int[] percents, bool vertical) {
3410     checkWidget();
3411     int colorsLength;
3412     Color highlightBeginColor = null;  //null is no highlight
3413 
3414     if (colors !is null) {
3415         //The colors array can optionally have an extra entry which describes the highlight top color
3416         //Thus its either one or two larger than the percents array
3417         if (percents is null ||
3418                 ! ((percents.length is colors.length - 1) || (percents.length is colors.length - 2))){
3419             SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3420         }
3421         for (int i = 0; i < percents.length; i++) {
3422             if (percents[i] < 0 || percents[i] > 100) {
3423                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3424             }
3425             if (i > 0 && percents[i] < percents[i-1]) {
3426                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3427             }
3428         }
3429         //If the colors is exactly two more than percents then last is highlight
3430         //Keep track of *real* colorsLength (minus the highlight)
3431         if(percents.length is colors.length - 2) {
3432             highlightBeginColor = colors[colors.length - 1];
3433             colorsLength = cast(int)/*64bit*/colors.length - 1;
3434         } else {
3435             colorsLength = cast(int)/*64bit*/colors.length;
3436         }
3437         if (getDisplay().getDepth() < 15) {
3438             // Don't use gradients on low color displays
3439             colors = [colors[colorsLength - 1]];
3440             colorsLength = cast(int)/*64bit*/colors.length;
3441             percents = null;
3442         }
3443     } else {
3444         colorsLength = 0;
3445     }
3446 
3447     // Are these settings the same as before?
3448     if (selectionBgImage is null) {
3449         if ((selectionGradientColors !is null) && (colors !is null) &&
3450             (selectionGradientColors.length is colorsLength)) {
3451             bool same = false;
3452             for (int i = 0; i < selectionGradientColors.length; i++) {
3453                 if (selectionGradientColors[i] is null) {
3454                     same = colors[i] is null;
3455                 } else {
3456                     same = cast(bool)(selectionGradientColors[i]==colors[i]);
3457                 }
3458                 if (!same) break;
3459             }
3460             if (same) {
3461                 for (int i = 0; i < selectionGradientPercents.length; i++) {
3462                     same = selectionGradientPercents[i] is percents[i];
3463                     if (!same) break;
3464                 }
3465             }
3466             if (same && this.selectionGradientVertical is vertical) return;
3467         }
3468     } else {
3469         selectionBgImage = null;
3470     }
3471     // Store the new settings
3472     if (colors is null) {
3473         selectionGradientColors = null;
3474         selectionGradientPercents = null;
3475         selectionGradientVertical = false;
3476         setSelectionBackground(cast(Color)null);
3477         setSelectionHighlightGradientColor(null);
3478     } else {
3479         selectionGradientColors = new Color[colorsLength];
3480         for (int i = 0; i < colorsLength; ++i) {
3481             selectionGradientColors[i] = colors[i];
3482         }
3483         selectionGradientPercents = new int[percents.length];
3484         for (int i = 0; i < percents.length; ++i) {
3485             selectionGradientPercents[i] = percents[i];
3486         }
3487         selectionGradientVertical = vertical;
3488         setSelectionBackground(selectionGradientColors[selectionGradientColors.length-1]);
3489         setSelectionHighlightGradientColor(highlightBeginColor);
3490     }
3491 
3492     // Refresh with the new settings
3493     if (selectedIndex > -1) redraw();
3494 }
3495 
3496 /*
3497  * Set the color for the highlight start for selected tabs.
3498  * Update the cache of highlight gradient colors if required.
3499  */
3500 
3501 void setSelectionHighlightGradientColor(Color start) {
3502     //Set to null to match all the early return cases.
3503     //For early returns, don't realloc the cache, we may get a cache hit next time we're given the highlight
3504     selectionHighlightGradientBegin = null;
3505 
3506     if(start is null)
3507         return;
3508 
3509     //don't bother on low colour
3510     if (getDisplay().getDepth() < 15)
3511         return;
3512 
3513     //don't bother if we don't have a background gradient
3514     if(selectionGradientColors.length < 2)
3515         return;
3516 
3517     //OK we know its a valid gradient now
3518     selectionHighlightGradientBegin = start;
3519 
3520     if(! isSelectionHighlightColorsCacheHit(start))
3521         createSelectionHighlightGradientColors(start);  //if no cache hit then compute new ones
3522 }
3523 
3524 /*
3525  * Return true if given start color, the cache of highlight colors we have
3526  * would match the highlight colors we'd compute.
3527  */
3528 bool isSelectionHighlightColorsCacheHit(Color start) {
3529 
3530     if(selectionHighlightGradientColorsCache is null)
3531         return false;
3532 
3533     //this case should never happen but check to be safe before accessing array indexes
3534     if(selectionHighlightGradientColorsCache.length < 2)
3535         return false;
3536 
3537     Color highlightBegin = selectionHighlightGradientColorsCache[0];
3538     Color highlightEnd = selectionHighlightGradientColorsCache[selectionHighlightGradientColorsCache.length - 1];
3539 
3540     if( highlightBegin!=start)
3541         return false;
3542 
3543     //Compare number of colours we have vs. we'd compute
3544     if(selectionHighlightGradientColorsCache.length !is tabHeight)
3545         return false;
3546 
3547     //Compare existing highlight end to what it would be (selectionBackground)
3548     if( highlightEnd!=selectionBackground)
3549         return false;
3550 
3551     return true;
3552 }
3553 
3554 /**
3555  * Set the image to be drawn in the background of the selected tab.  Image
3556  * is stretched or compressed to cover entire selection tab area.
3557  *
3558  * @param image the image to be drawn in the background
3559  *
3560  * @exception SWTException <ul>
3561  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3562  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3563  * </ul>
3564  */
3565 public void setSelectionBackground(Image image) {
3566     checkWidget();
3567     setSelectionHighlightGradientColor(null);
3568     if (image is selectionBgImage) return;
3569     if (image !is null) {
3570         selectionGradientColors = null;
3571         selectionGradientPercents = null;
3572         disposeSelectionHighlightGradientColors();
3573     }
3574     selectionBgImage = image;
3575     if (selectedIndex > -1) redraw();
3576 }
3577 /**
3578  * Set the foreground color of the selected tab.
3579  *
3580  * @param color the color of the text displayed in the selected tab
3581  *
3582  * @exception SWTException <ul>
3583  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3584  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3585  * </ul>
3586  */
3587 public void setSelectionForeground (Color color) {
3588     checkWidget();
3589     if (selectionForeground is color) return;
3590     if (color is null) color = getDisplay().getSystemColor(SELECTION_FOREGROUND);
3591     selectionForeground = color;
3592     if (selectedIndex > -1) redraw();
3593 }
3594 
3595 /*
3596  * Allocate colors for the highlight line.
3597  * Colours will be a gradual blend ranging from to.
3598  * Blend length will be tab height.
3599  * Recompute this if tab height changes.
3600  * Could remain null if there'd be no gradient (start=end or low colour display)
3601  */
3602 void createSelectionHighlightGradientColors(Color start) {
3603     disposeSelectionHighlightGradientColors(); //dispose if existing
3604 
3605     if(start is null)  //shouldn't happen but just to be safe
3606         return;
3607 
3608     //alloc colours for entire height to ensure it matches wherever we stop drawing
3609     int fadeGradientSize = tabHeight;
3610 
3611     RGB from = start.getRGB();
3612     RGB to = selectionBackground.getRGB();
3613 
3614     selectionHighlightGradientColorsCache = new Color[fadeGradientSize];
3615     int denom = fadeGradientSize - 1;
3616 
3617     for (int i = 0; i < fadeGradientSize; i++) {
3618         int propFrom = denom - i;
3619         int propTo = i;
3620         int red = (to.red * propTo + from.red * propFrom) / denom;
3621         int green = (to.green * propTo  + from.green * propFrom) / denom;
3622         int blue = (to.blue * propTo  + from.blue * propFrom) / denom;
3623         selectionHighlightGradientColorsCache[i] = new Color(getDisplay(), red, green, blue);
3624     }
3625 }
3626 
3627 void disposeSelectionHighlightGradientColors() {
3628     if(selectionHighlightGradientColorsCache is null)
3629         return;
3630     for (int i = 0; i < selectionHighlightGradientColorsCache.length; i++) {
3631         selectionHighlightGradientColorsCache[i].dispose();
3632     }
3633     selectionHighlightGradientColorsCache = null;
3634 }
3635 
3636 /*
3637  * Return the gradient start color for selected tabs, which is the start of the tab fade
3638  * (end is selectionBackground).
3639  */
3640 Color getSelectionBackgroundGradientBegin() {
3641     if (selectionGradientColors is null)
3642         return getSelectionBackground();
3643     if (selectionGradientColors.length is 0)
3644         return getSelectionBackground();
3645     return selectionGradientColors[0];
3646 }
3647 
3648 /**
3649  * Sets the shape that the CTabFolder will use to render itself.
3650  *
3651  * @param simple <code>true</code> if the CTabFolder should render itself in a simple, traditional style
3652  *
3653  * @exception SWTException <ul>
3654  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3655  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3656  * </ul>
3657  *
3658  * @since 3.0
3659  */
3660 public void setSimple(bool simple) {
3661     checkWidget();
3662     if (this.simple !is simple) {
3663         this.simple = simple;
3664         Rectangle rectBefore = getClientArea();
3665         updateItems();
3666         Rectangle rectAfter = getClientArea();
3667         if (rectBefore!=rectAfter) {
3668             notifyListeners(SWT.Resize, new Event());
3669         }
3670         redraw();
3671     }
3672 }
3673 /**
3674  * Sets the number of tabs that the CTabFolder should display
3675  *
3676  * @param single <code>true</code> if only the selected tab should be displayed otherwise, multiple tabs will be shown.
3677  *
3678  * @exception SWTException <ul>
3679  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3680  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3681  * </ul>
3682  *
3683  * @since 3.0
3684  */
3685 public void setSingle(bool single) {
3686     checkWidget();
3687     if (this.single !is single) {
3688         this.single = single;
3689         if (!single) {
3690             for (int i = 0; i < items.length; i++) {
3691                 if (i !is selectedIndex && items[i].closeImageState is NORMAL) {
3692                     items[i].closeImageState = NONE;
3693                 }
3694             }
3695         }
3696         Rectangle rectBefore = getClientArea();
3697         updateItems();
3698         Rectangle rectAfter = getClientArea();
3699         if (rectBefore!=rectAfter) {
3700             notifyListeners(SWT.Resize, new Event());
3701         }
3702         redraw();
3703     }
3704 }
3705 /**
3706  * Specify a fixed height for the tab items.  If no height is specified,
3707  * the default height is the height of the text or the image, whichever
3708  * is greater. Specifying a height of -1 will revert to the default height.
3709  *
3710  * @param height the pixel value of the height or -1
3711  *
3712  * @exception SWTException <ul>
3713  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3714  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3715  *    <li>ERROR_INVALID_ARGUMENT - if called with a height of less than 0</li>
3716  * </ul>
3717  */
3718 public void setTabHeight(int height) {
3719     checkWidget();
3720     if (height < -1) {
3721         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3722     }
3723     fixedTabHeight = height;
3724     updateTabHeight(false);
3725 }
3726 /**
3727  * Specify whether the tabs should appear along the top of the folder
3728  * or along the bottom of the folder.
3729  *
3730  * @param position <code>SWT.TOP</code> for tabs along the top or <code>SWT.BOTTOM</code> for tabs along the bottom
3731  *
3732  * @exception SWTException <ul>
3733  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3734  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3735  *    <li>ERROR_INVALID_ARGUMENT - if the position value is not either SWT.TOP or SWT.BOTTOM</li>
3736  * </ul>
3737  *
3738  * @since 3.0
3739  */
3740 public void setTabPosition(int position) {
3741     checkWidget();
3742     if (position !is SWT.TOP && position !is SWT.BOTTOM) {
3743         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3744     }
3745     if (onBottom !is (position is SWT.BOTTOM)) {
3746         onBottom = position is SWT.BOTTOM;
3747         borderTop = onBottom ? borderLeft : 0;
3748         borderBottom = onBottom ? 0 : borderRight;
3749         updateTabHeight(true);
3750         Rectangle rectBefore = getClientArea();
3751         updateItems();
3752         Rectangle rectAfter = getClientArea();
3753         if (rectBefore!=rectAfter) {
3754             notifyListeners(SWT.Resize, new Event());
3755         }
3756         redraw();
3757     }
3758 }
3759 /**
3760  * Set the control that appears in the top right corner of the tab folder.
3761  * Typically this is a close button or a composite with a Menu and close button.
3762  * The topRight control is optional.  Setting the top right control to null will
3763  * remove it from the tab folder.
3764  *
3765  * @param control the control to be displayed in the top right corner or null
3766  *
3767  * @exception SWTException <ul>
3768  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3769  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3770  *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
3771  * </ul>
3772  *
3773  * @since 2.1
3774  */
3775 public void setTopRight(Control control) {
3776     setTopRight(control, SWT.RIGHT);
3777 }
3778 /**
3779  * Set the control that appears in the top right corner of the tab folder.
3780  * Typically this is a close button or a composite with a Menu and close button.
3781  * The topRight control is optional.  Setting the top right control to null
3782  * will remove it from the tab folder.
3783  * <p>
3784  * The alignment parameter sets the layout of the control in the tab area.
3785  * <code>SWT.RIGHT</code> will cause the control to be positioned on the far
3786  * right of the folder and it will have its default size.  <code>SWT.FILL</code>
3787  * will size the control to fill all the available space to the right of the
3788  * last tab.  If there is no available space, the control will not be visible.
3789  * </p>
3790  *
3791  * @param control the control to be displayed in the top right corner or null
3792  * @param alignment <code>SWT.RIGHT</code> or <code>SWT.FILL</code>
3793  *
3794  * @exception SWTException <ul>
3795  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3796  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3797  *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
3798  * </ul>
3799  *
3800  * @since 3.0
3801  */
3802 public void setTopRight(Control control, int alignment) {
3803     checkWidget();
3804     if (alignment !is SWT.RIGHT && alignment !is SWT.FILL) {
3805         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3806     }
3807     if (control !is null && control.getParent() !is this) {
3808         SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3809     }
3810     topRight = control;
3811     topRightAlignment = alignment;
3812     if (updateItems()) redraw();
3813 }
3814 /**
3815  * Specify whether the close button appears
3816  * when the user hovers over an unselected tabs.
3817  *
3818  * @param visible <code>true</code> makes the close button appear
3819  *
3820  * @exception SWTException <ul>
3821  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3822  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3823  * </ul>
3824  *
3825  * @since 3.0
3826  */
3827 public void setUnselectedCloseVisible(bool visible) {
3828     checkWidget();
3829     if (showUnselectedClose is visible) return;
3830     // display close button when mouse hovers
3831     showUnselectedClose = visible;
3832     updateItems();
3833     redraw();
3834 }
3835 /**
3836  * Specify whether the image appears on unselected tabs.
3837  *
3838  * @param visible <code>true</code> makes the image appear
3839  *
3840  * @exception SWTException <ul>
3841  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3842  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3843  * </ul>
3844  *
3845  * @since 3.0
3846  */
3847 public void setUnselectedImageVisible(bool visible) {
3848     checkWidget();
3849     if (showUnselectedImage is visible) return;
3850     // display image on unselected items
3851     showUnselectedImage = visible;
3852     updateItems();
3853     redraw();
3854 }
3855 /**
3856  * Shows the item.  If the item is already showing in the receiver,
3857  * this method simply returns.  Otherwise, the items are scrolled until
3858  * the item is visible.
3859  *
3860  * @param item the item to be shown
3861  *
3862  * @exception IllegalArgumentException <ul>
3863  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
3864  *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
3865  * </ul>
3866  * @exception SWTException <ul>
3867  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3868  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3869  * </ul>
3870  *
3871  * @see CTabFolder#showSelection()
3872  *
3873  * @since 2.0
3874  */
3875 public void showItem (CTabItem item) {
3876     checkWidget();
3877     if (item is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
3878     if (item.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3879     int index = indexOf(item);
3880     if (index is -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3881     int idx = -1;
3882     for (int i = 0; i < priority.length; i++) {
3883         if (priority[i] is index) {
3884             idx = i;
3885             break;
3886         }
3887     }
3888     if (mru) {
3889         // move to front of mru order
3890         int[] newPriority = new int[priority.length];
3891         System.arraycopy(priority, 0, newPriority, 1, idx);
3892         System.arraycopy(priority, idx+1, newPriority, idx+1, priority.length - idx - 1);
3893         newPriority[0] = index;
3894         priority = newPriority;
3895     }
3896     if (item.isShowing()) return;
3897     updateItems(index);
3898     redrawTabs();
3899 }
3900 void showList (Rectangle rect) {
3901     if (items.length is 0 || !showChevron) return;
3902     if (showMenu is null || showMenu.isDisposed()) {
3903         showMenu = new Menu(this);
3904     } else {
3905         MenuItem[] items = showMenu.getItems();
3906         for (int i = 0; i < items.length; i++) {
3907             items[i].dispose();
3908         }
3909     }
3910     static const String id = "CTabFolder_showList_Index"; //$NON-NLS-1$
3911     for (int i = 0; i < items.length; i++) {
3912         CTabItem tab = items[i];
3913         if (tab.showing) continue;
3914         MenuItem item = new MenuItem(showMenu, SWT.NONE);
3915         item.setText(tab.getText());
3916         item.setImage(tab.getImage());
3917         item.setData(id, tab);
3918         item.addSelectionListener(new class() SelectionAdapter {
3919             override
3920             public void widgetSelected(SelectionEvent e) {
3921                 MenuItem menuItem = cast(MenuItem)e.widget;
3922                 int index = indexOf(cast(CTabItem)menuItem.getData(id));
3923                 this.outer.setSelection(index, true);
3924             }
3925         });
3926     }
3927     int x = rect.x;
3928     int y = rect.y + rect.height;
3929     Point location = getDisplay().map(this, null, x, y);
3930     showMenu.setLocation(location.x, location.y);
3931     showMenu.setVisible(true);
3932 }
3933 /**
3934  * Shows the selection.  If the selection is already showing in the receiver,
3935  * this method simply returns.  Otherwise, the items are scrolled until
3936  * the selection is visible.
3937  *
3938  * @exception SWTException <ul>
3939  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3940  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3941  * </ul>
3942  *
3943  * @see CTabFolder#showItem(CTabItem)
3944  *
3945  * @since 2.0
3946  */
3947 public void showSelection () {
3948     checkWidget ();
3949     if (selectedIndex !is -1) {
3950         showItem(getSelection());
3951     }
3952 }
3953 
3954 void _setToolTipText (int x, int y) {
3955     String oldTip = getToolTipText();
3956     String newTip = _getToolTip(x, y);
3957     if (newTip is null || newTip!=oldTip) {
3958         setToolTipText(newTip);
3959     }
3960 }
3961 
3962 bool updateItems() {
3963     return updateItems(selectedIndex);
3964 }
3965 
3966 bool updateItems(int showIndex) {
3967     if (!single && !mru && showIndex !is -1) {
3968         // make sure selected item will be showing
3969         int firstIndex = showIndex;
3970         if (priority[0] < showIndex) {
3971             int maxWidth = getRightItemEdge() - borderLeft;
3972             if (!simple) maxWidth -= curveWidth - 2*curveIndent;
3973             int width = 0;
3974             int[] widths = new int[items.length];
3975             GC gc = new GC(this);
3976             for (int i = priority[0]; i <= showIndex; i++) {
3977                 widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
3978                 width += widths[i];
3979                 if (width > maxWidth) break;
3980             }
3981             if (width > maxWidth) {
3982                 width = 0;
3983                 for (int i = showIndex; i >= 0; i--) {
3984                     if (widths[i] is 0) widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
3985                     width += widths[i];
3986                     if (width > maxWidth) break;
3987                     firstIndex = i;
3988                 }
3989             } else {
3990                 firstIndex = priority[0];
3991                 for (int i = showIndex + 1; i < items.length; i++) {
3992                     widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
3993                     width += widths[i];
3994                     if (width >= maxWidth) break;
3995                 }
3996                 if (width < maxWidth) {
3997                     for (int i = priority[0] - 1; i >= 0; i--) {
3998                         if (widths[i] is 0) widths[i] = items[i].preferredWidth(gc, i is selectedIndex, true);
3999                         width += widths[i];
4000                         if (width > maxWidth) break;
4001                         firstIndex = i;
4002                     }
4003                 }
4004             }
4005             gc.dispose();
4006         }
4007         if (firstIndex !is priority[0]) {
4008             int index = 0;
4009             for (int i = firstIndex; i < items.length; i++) {
4010                 priority[index++] = i;
4011             }
4012             for (int i = 0; i < firstIndex; i++) {
4013                 priority[index++] = i;
4014             }
4015         }
4016     }
4017 
4018     bool oldShowChevron = showChevron;
4019     bool changed = setItemSize();
4020     changed |= setItemLocation();
4021     setButtonBounds();
4022     changed |= showChevron !is oldShowChevron;
4023     if (changed && getToolTipText() !is null) {
4024         Point pt = getDisplay().getCursorLocation();
4025         pt = toControl(pt);
4026         _setToolTipText(pt.x, pt.y);
4027     }
4028     return changed;
4029 }
4030 bool updateTabHeight(bool force){
4031     int style = getStyle();
4032     if (fixedTabHeight is 0 && (style & SWT.FLAT) !is 0 && (style & SWT.BORDER) is 0) highlight_header = 0;
4033     int oldHeight = tabHeight;
4034     if (fixedTabHeight !is SWT.DEFAULT) {
4035         tabHeight = fixedTabHeight is 0 ? 0 : fixedTabHeight + 1; // +1 for line drawn across top of tab
4036     } else {
4037         int tempHeight = 0;
4038         GC gc = new GC(this);
4039         if (items.length is 0) {
4040             tempHeight = gc.textExtent("Default", CTabItem.FLAGS).y + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN; //$NON-NLS-1$
4041         } else {
4042             for (int i=0; i < items.length; i++) {
4043                 tempHeight = Math.max(tempHeight, items[i].preferredHeight(gc));
4044             }
4045         }
4046         gc.dispose();
4047         tabHeight =  tempHeight;
4048     }
4049     if (!force && tabHeight is oldHeight) return false;
4050 
4051     oldSize = null;
4052     if (onBottom) {
4053         int d = tabHeight - 12;
4054         curve = [0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d,
4055                           11,7+d, 11+d,7,
4056                           12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1];
4057         curveWidth = 26+d;
4058         curveIndent = curveWidth/3;
4059     } else {
4060         int d = tabHeight - 12;
4061         curve = [0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5,
4062                           11,6, 11+d,6+d,
4063                           12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d];
4064         curveWidth = 26+d;
4065         curveIndent = curveWidth/3;
4066 
4067         //this could be static but since values depend on curve, better to keep in one place
4068         topCurveHighlightStart = [
4069                 0, 2,  1, 2,  2, 2,
4070                 3, 3,  4, 3,  5, 3,
4071                 6, 4,  7, 4,
4072                 8, 5,
4073                 9, 6, 10, 6];
4074 
4075         //also, by adding in 'd' here we save some math cost when drawing the curve
4076         topCurveHighlightEnd = [
4077                 10+d, 6+d,
4078                 11+d, 7+d,
4079                 12+d, 8+d,  13+d, 8+d,
4080                 14+d, 9+d,
4081                 15+d, 10+d,  16+d, 10+d,
4082                 17+d, 11+d,  18+d, 11+d,  19+d, 11+d,
4083                 20+d, 12+d,  21+d, 12+d,  22+d,  12+d ];
4084     }
4085     notifyListeners(SWT.Resize, new Event());
4086     return true;
4087 }
4088 String _getToolTip(int x, int y) {
4089     if (showMin && minRect.contains(x, y)) return minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$
4090     if (showMax && maxRect.contains(x, y)) return maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$
4091     if (showChevron && chevronRect.contains(x, y)) return SWT.getMessage("SWT_ShowList"); //$NON-NLS-1$
4092     CTabItem item = getItem(new Point (x, y));
4093     if (item is null) return null;
4094     if (!item.showing) return null;
4095     if ((showClose || item.showClose) && item.closeRect.contains(x, y)) {
4096         return SWT.getMessage("SWT_Close"); //$NON-NLS-1$
4097     }
4098     return item.getToolTipText();
4099 }
4100 }