org.eclipse.swt.custom.CTabFolder.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.custom.CTabFolder.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.custom;

import org.eclipse.swt.*;
import org.eclipse.swt.accessibility.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.DPIUtil.*;
import org.eclipse.swt.widgets.*;

/**
 *
 * Instances of this class implement the notebook user interface
 * metaphor.  It allows the user to select a notebook page from
 * set of pages.
 * <p>
 * The item children that may be added to instances of this class
 * must be of type <code>CTabItem</code>.
 * <code>Control</code> children are created and then set into a
 * tab item using <code>CTabItem#setControl</code>.
 * </p><p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection</dd>
 * <dd>"CTabFolder2"</dd>
 * </dl>
 * Note: Only one of the styles TOP and BOTTOM
 * may be specified.
 * <p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 * @noextend This class is not intended to be subclassed by clients.
 */

public class CTabFolder extends Composite {

    /**
     * marginWidth specifies the number of points of horizontal margin
     * that will be placed along the left and right edges of the form.
     *
     * The default value is 0.
     */
    public int marginWidth = 0;
    /**
     * marginHeight specifies the number of points of vertical margin
     * that will be placed along the top and bottom edges of the form.
     *
     * The default value is 0.
     */
    public int marginHeight = 0;

    /**
     * A multiple of the tab height that specifies the minimum width to which a tab
     * will be compressed before scrolling arrows are used to navigate the tabs.
     *
     * NOTE This field is badly named and can not be fixed for backwards compatibility.
     * It should not be capitalized.
     *
     * @deprecated This field is no longer used.  See setMinimumCharacters(int)
     */
    @Deprecated
    public int MIN_TAB_WIDTH = 4;

    /**
     * Color of innermost line of drop shadow border.
     *
     * NOTE This field is badly named and can not be fixed for backwards compatibility.
     * It should be capitalized.
     *
     * @deprecated drop shadow border is no longer drawn in 3.0
     */
    @Deprecated
    public static RGB borderInsideRGB = new RGB(132, 130, 132);
    /**
     * Color of middle line of drop shadow border.
     *
     * NOTE This field is badly named and can not be fixed for backwards compatibility.
     * It should be capitalized.
     *
     * @deprecated drop shadow border is no longer drawn in 3.0
     */
    @Deprecated
    public static RGB borderMiddleRGB = new RGB(143, 141, 138);
    /**
     * Color of outermost line of drop shadow border.
     *
     * NOTE This field is badly named and can not be fixed for backwards compatibility.
     * It should be capitalized.
     *
     * @deprecated drop shadow border is no longer drawn in 3.0
     */
    @Deprecated
    public static RGB borderOutsideRGB = new RGB(171, 168, 165);

    /* sizing, positioning */
    boolean onBottom = false;
    boolean single = false;
    boolean simple = true;
    int fixedTabHeight = SWT.DEFAULT;
    int tabHeight;
    int minChars = 20;
    boolean borderVisible = false;

    /* item management */
    CTabFolderRenderer renderer;
    CTabItem items[] = new CTabItem[0];
    /** index of the left most visible tab. */
    int firstIndex = -1;
    int selectedIndex = -1;

    /**
     * Indices of the elements in the {@link #items} array, used to manage tab
     * visibility and candidates to be hidden/shown next.
     * <p>
     * If there is not enough place for all tabs, tabs starting from the end of
     * the {@link #priority} array will be hidden first (independently from the
     * {@link #mru} flag!) => the right most elements have the highest priority
     * to be hidden.
     * <p>
     * If there is more place to show previously hidden tabs, tabs starting from
     * the beginning of the {@link #priority} array will be made visible first
     * (independently from the {@link #mru} flag!) => the left most elements
     * have the highest priority to be shown.
     * <p>
     * The update strategy of the {@link #priority} array however depends on the
     * {@link #mru} flag.
     * <p>
     * If {@link #mru} flag is set, the first index is always the index of the
     * currently selected tab, next one is the tab selected before current
     * etc...
     * <p>
     * Example: [4,2,5,1,3,0], just representing the last selection order.
     * <p>
     * If {@link #mru} flag is not set, the first index is always the index of
     * the left most visible tab ({@link #firstIndex} field), next indices are
     * incremented by one up to <code>priority.length-1</code>, and the rest
     * filled with indices starting with <code>firstIndex-1</code> and
     * decremented by one until 0 index is reached.
     * <p>
     * The tabs between first index and the index of the currently selected tab
     * are always visible.
     * <p>
     * Example: 6 tabs, 2 and 3 are indices of currently shown tabs:
     * [2,3,4,5,1,0]. The array consists of two blocks: sorted ascending from
     * first visible (2) to last available (5), and the rest sorted descending
     * (1,0). 4 and 5 are the hidden tabs on the right side, 0 and 1 are the
     * hidden tabs on the left side from the visible tabs 2 and 3.
     *
     * @see #updateItems(int)
     * @see #setItemLocation(GC)
     */
    int[] priority = new int[0];
    boolean mru = false;
    Listener listener;
    boolean ignoreTraverse;
    boolean useDefaultRenderer;

    /* External Listener management */
    CTabFolder2Listener[] folderListeners = new CTabFolder2Listener[0];
    // support for deprecated listener mechanism
    CTabFolderListener[] tabListeners = new CTabFolderListener[0];

    /* Selected item appearance */
    Image selectionBgImage;
    Color[] selectionGradientColors;
    int[] selectionGradientPercents;
    boolean selectionGradientVertical;
    Color selectionForeground;
    Color selectionBackground;

    /* Unselected item appearance */
    Color[] gradientColors;
    int[] gradientPercents;
    boolean gradientVertical;
    boolean showUnselectedImage = true;

    // close, min/max and chevron buttons
    boolean showClose = false;
    boolean showUnselectedClose = true;

    boolean showMin = false;
    boolean minimized = false;
    boolean showMax = false;
    boolean maximized = false;
    ToolBar minMaxTb;
    ToolItem maxItem;
    ToolItem minItem;
    Image maxImage;
    Image minImage;
    boolean hoverTb;
    Rectangle hoverRect = new Rectangle(0, 0, 0, 0);
    boolean hovering;
    boolean hoverTimerRunning;
    boolean highlight;
    boolean highlightEnabled = true;

    boolean showChevron = false;
    Menu showMenu;
    ToolBar chevronTb;
    ToolItem chevronItem;
    int chevronCount;
    boolean chevronVisible = true;

    Image chevronImage;
    Control topRight;
    int topRightAlignment = SWT.RIGHT;
    boolean ignoreResize;
    Control[] controls;
    int[] controlAlignments;
    Rectangle[] controlRects;
    Image[] controlBkImages;

    int updateFlags;
    final static int REDRAW = 1 << 1;
    final static int REDRAW_TABS = 1 << 2;
    final static int UPDATE_TAB_HEIGHT = 1 << 3;
    Runnable updateRun;

    // when disposing CTabFolder, don't try to layout the items or
    // change the selection as each child is destroyed.
    boolean inDispose = false;

    // keep track of size changes in order to redraw only affected area
    // on Resize
    Point oldSize;
    Font oldFont;

    // internal constants
    static final int DEFAULT_WIDTH = 64;
    static final int DEFAULT_HEIGHT = 64;

    static final int SELECTION_FOREGROUND = SWT.COLOR_LIST_FOREGROUND;
    static final int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND;

    static final int FOREGROUND = SWT.COLOR_WIDGET_FOREGROUND;
    static final int BACKGROUND = SWT.COLOR_WIDGET_BACKGROUND;

    //TODO: add setter for spacing?
    static final int SPACING = 3;

    /**
     * Constructs a new instance of this class given its parent
     * and a style value describing its behavior and appearance.
     * <p>
     * The style value is either one of the style constants defined in
     * class <code>SWT</code> which is applicable to instances of this
     * class, or must be built by <em>bitwise OR</em>'ing together
     * (that is, using the <code>int</code> "|" operator) two or more
     * of those <code>SWT</code> style constants. The class description
     * lists the style constants that are applicable to the class.
     * Style bits are also inherited from superclasses.
     * </p>
     *
     * @param parent a widget which will be the parent of the new instance (cannot be null)
     * @param style the style of widget to construct
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
     * </ul>
     *
     * @see SWT#TOP
     * @see SWT#BOTTOM
     * @see SWT#FLAT
     * @see SWT#BORDER
     * @see SWT#SINGLE
     * @see SWT#MULTI
     * @see #getStyle()
     */
    public CTabFolder(Composite parent, int style) {
        super(parent, checkStyle(parent, style));
        init(style);
    }

    void init(int style) {
        super.setLayout(new CTabFolderLayout());
        int style2 = super.getStyle();
        oldFont = getFont();
        onBottom = (style2 & SWT.BOTTOM) != 0;
        showClose = (style2 & SWT.CLOSE) != 0;
        //   showMin = (style2 & SWT.MIN) != 0; - conflicts with SWT.TOP
        //   showMax = (style2 & SWT.MAX) != 0; - conflicts with SWT.BOTTOM
        single = (style2 & SWT.SINGLE) != 0;
        borderVisible = (style & SWT.BORDER) != 0;
        //set up default colors
        Display display = getDisplay();
        selectionForeground = display.getSystemColor(SELECTION_FOREGROUND);
        selectionBackground = display.getSystemColor(SELECTION_BACKGROUND);
        renderer = new CTabFolderRenderer(this);
        useDefaultRenderer = true;
        controls = new Control[0];
        controlAlignments = new int[0];
        controlRects = new Rectangle[0];
        controlBkImages = new Image[0];
        updateTabHeight(false);

        // Add all listeners
        listener = event -> {
            switch (event.type) {
            case SWT.Dispose:
                onDispose(event);
                break;
            case SWT.DragDetect:
                onDragDetect(event);
                break;
            case SWT.FocusIn:
                onFocus(event);
                break;
            case SWT.FocusOut:
                onFocus(event);
                break;
            case SWT.KeyDown:
                onKeyDown(event);
                break;
            case SWT.MenuDetect:
                onMenuDetect(event);
                break;
            case SWT.MouseDoubleClick:
                onMouseDoubleClick(event);
                break;
            case SWT.MouseDown:
                onMouse(event);
                break;
            case SWT.MouseEnter:
                onMouse(event);
                break;
            case SWT.MouseExit:
                onMouse(event);
                break;
            case SWT.MouseHover:
                onMouse(event);
                break;
            case SWT.MouseMove:
                onMouse(event);
                break;
            case SWT.MouseUp:
                onMouse(event);
                break;
            case SWT.Paint:
                onPaint(event);
                break;
            case SWT.Resize:
                onResize(event);
                break;
            case SWT.Traverse:
                onTraverse(event);
                break;
            case SWT.Selection:
                onSelection(event);
                break;
            case SWT.Activate:
                onActivate(event);
                break;
            case SWT.Deactivate:
                onDeactivate(event);
                break;
            }
        };

        int[] folderEvents = new int[] { SWT.Dispose, SWT.DragDetect, SWT.FocusIn, SWT.FocusOut, SWT.KeyDown,
                SWT.MenuDetect, SWT.MouseDoubleClick, SWT.MouseDown, SWT.MouseEnter, SWT.MouseExit, SWT.MouseHover,
                SWT.MouseMove, SWT.MouseUp, SWT.Paint, SWT.Resize, SWT.Traverse, SWT.Activate, SWT.Deactivate };
        for (int i = 0; i < folderEvents.length; i++) {
            addListener(folderEvents[i], listener);
        }

        initAccessible();
    }

    void onDeactivate(Event event) {
        if (!highlightEnabled) {
            return;
        }
        this.highlight = false;
        redraw();
    }

    void onActivate(Event event) {
        if (!highlightEnabled) {
            return;
        }
        this.highlight = true;
        redraw();
    }

    static int checkStyle(Composite parent, int style) {
        int mask = SWT.CLOSE | SWT.TOP | SWT.BOTTOM | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.SINGLE
                | SWT.MULTI;
        style = style & mask;
        // TOP and BOTTOM are mutually exclusive.
        // TOP is the default
        if ((style & SWT.TOP) != 0)
            style = style & ~SWT.BOTTOM;
        // SINGLE and MULTI are mutually exclusive.
        // MULTI is the default
        if ((style & SWT.MULTI) != 0)
            style = style & ~SWT.SINGLE;
        // reduce the flash by not redrawing the entire area on a Resize event
        style |= SWT.NO_REDRAW_RESIZE;

        //TEMPORARY CODE
        /*
         * In Right To Left orientation on Windows, all GC calls that use a brush are drawing
         * offset by one pixel.  This results in some parts of the CTabFolder not drawing correctly.
         * To alleviate some of the appearance problems, allow the OS to draw the background.
         * This does not draw correctly but the result is less obviously wrong.
         */
        if ((style & SWT.RIGHT_TO_LEFT) != 0)
            return style;
        if ((parent.getStyle() & SWT.MIRRORED) != 0 && (style & SWT.LEFT_TO_RIGHT) == 0)
            return style;

        return style | SWT.DOUBLE_BUFFERED;
    }

    /**
     *
     * Adds the listener to the collection of listeners who will
     * be notified when a tab item is closed, minimized, maximized,
     * restored, or to show the list of items that are not
     * currently visible.
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     *
     * @see CTabFolder2Listener
     * @see #removeCTabFolder2Listener(CTabFolder2Listener)
     *
     * @since 3.0
     */
    public void addCTabFolder2Listener(CTabFolder2Listener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        // add to array
        CTabFolder2Listener[] newListeners = new CTabFolder2Listener[folderListeners.length + 1];
        System.arraycopy(folderListeners, 0, newListeners, 0, folderListeners.length);
        folderListeners = newListeners;
        folderListeners[folderListeners.length - 1] = listener;
    }

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when a tab item is closed.
     *
     * @param listener the listener which should be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     *
     * @see CTabFolderListener
     * @see #removeCTabFolderListener(CTabFolderListener)
     *
     * @deprecated use addCTabFolder2Listener(CTabFolder2Listener)
     */
    @Deprecated
    public void addCTabFolderListener(CTabFolderListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        // add to array
        CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length + 1];
        System.arraycopy(tabListeners, 0, newTabListeners, 0, tabListeners.length);
        tabListeners = newTabListeners;
        tabListeners[tabListeners.length - 1] = listener;
        // display close button to be backwards compatible
        if (!showClose) {
            showClose = true;
            updateFolder(REDRAW);
        }
    }

    /**
     * Adds the listener to the collection of listeners who will
     * be notified when the user changes the receiver's selection, by sending
     * it one of the messages defined in the <code>SelectionListener</code>
     * interface.
     * <p>
     * <code>widgetSelected</code> is called when the user changes the selected tab.
     * <code>widgetDefaultSelected</code> is not called.
     * </p>
     *
     * @param listener the listener which should be notified when the user changes the receiver's selection
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #removeSelectionListener
     * @see SelectionEvent
     */
    public void addSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        TypedListener typedListener = new TypedListener(listener);
        addListener(SWT.Selection, typedListener);
        addListener(SWT.DefaultSelection, typedListener);
    }

    Rectangle[] computeControlBounds(Point size, boolean[][] position) {
        if (controls == null || controls.length == 0)
            return new Rectangle[0];
        Rectangle[] rects = new Rectangle[controls.length];
        for (int i = 0; i < rects.length; i++) {
            rects[i] = new Rectangle(0, 0, 0, 0);
        }
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BORDER, SWT.NONE, 0, 0, 0, 0);
        int borderRight = trim.width + trim.x;
        int borderLeft = -trim.x;
        int borderBottom = trim.height + trim.y;
        int borderTop = -trim.y;

        Point[] tabControlSize = new Point[controls.length];
        boolean[] overflow = new boolean[controls.length];
        //Left Control
        int leftWidth = 0;
        int x = borderLeft + SPACING;
        int rightWidth = 0;
        int allWidth = 0;
        for (int i = 0; i < controls.length; i++) {
            Point ctrlSize = tabControlSize[i] = !controls[i].isDisposed() && controls[i].getVisible()
                    ? controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT)
                    : new Point(0, 0);
            int alignment = controlAlignments[i];
            if ((alignment & SWT.LEAD) != 0) {
                rects[i].width = ctrlSize.x;
                rects[i].height = getControlHeight(ctrlSize);
                rects[i].x = x;
                rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
                x += ctrlSize.x;
                leftWidth += ctrlSize.x;
            } else {
                if ((alignment & (SWT.FILL | SWT.WRAP)) == 0) {
                    rightWidth += ctrlSize.x;
                }
                allWidth += ctrlSize.x;
            }
        }
        if (leftWidth > 0)
            leftWidth += SPACING * 2;

        int itemWidth = 0;
        for (int i = 0; i < items.length; i++) {
            if (items[i].showing)
                itemWidth += items[i].width;
        }

        int maxWidth = size.x - borderLeft - leftWidth - borderRight;
        int availableWidth = Math.max(0, maxWidth - itemWidth - rightWidth);
        if (rightWidth > 0)
            availableWidth -= SPACING * 2;
        x = size.x - borderRight - SPACING;
        if (itemWidth + allWidth <= maxWidth) {
            //Everything fits
            for (int i = 0; i < controls.length; i++) {
                int alignment = controlAlignments[i];
                if ((alignment & SWT.TRAIL) != 0) {
                    Point ctrlSize = tabControlSize[i];
                    x -= ctrlSize.x;
                    rects[i].width = ctrlSize.x;
                    rects[i].height = getControlHeight(ctrlSize);
                    rects[i].x = x;
                    rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
                    if ((alignment & (SWT.FILL | SWT.WRAP)) != 0)
                        availableWidth -= ctrlSize.x;
                }
                if (tabControlSize[i].y >= tabHeight && fixedTabHeight == SWT.DEFAULT) {
                    overflow[i] = true;
                }
            }
        } else {
            for (int i = 0; i < controls.length; i++) {
                int alignment = controlAlignments[i];
                Point ctrlSize = tabControlSize[i];
                if ((alignment & SWT.TRAIL) != 0) {
                    if ((alignment & (SWT.FILL | SWT.WRAP)) == 0) {
                        x -= ctrlSize.x;
                        rects[i].width = ctrlSize.x;
                        rects[i].height = getControlHeight(ctrlSize);
                        rects[i].x = x;
                        rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
                    } else if (((alignment & (SWT.WRAP)) != 0 && ctrlSize.x < availableWidth)) {
                        x -= ctrlSize.x;
                        rects[i].width = ctrlSize.x;
                        rects[i].height = getControlHeight(ctrlSize);
                        rects[i].x = x;
                        rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
                        availableWidth -= ctrlSize.x;
                    } else if ((alignment & (SWT.FILL)) != 0 && (alignment & (SWT.WRAP)) == 0) {
                        rects[i].width = 0;
                        rects[i].height = getControlHeight(ctrlSize);
                        rects[i].x = x;
                        rects[i].y = getControlY(size, rects, borderBottom, borderTop, i);
                    } else {
                        if ((alignment & (SWT.WRAP)) != 0) {
                            overflow[i] = true;
                        }
                    }
                }
            }
        }

        //Any space, distribute amongst FILL
        if (availableWidth > 0) {
            int fillCount = 0;
            for (int i = 0; i < controls.length; i++) {
                int alignment = controlAlignments[i];
                if ((alignment & SWT.TRAIL) != 0 && (alignment & SWT.FILL) != 0 && !overflow[i]) {
                    fillCount++;
                }
            }
            if (fillCount != 0) {
                int extraSpace = availableWidth / fillCount;
                int addedSpace = 0;
                for (int i = 0; i < controls.length; i++) {
                    int alignment = controlAlignments[i];
                    if ((alignment & SWT.TRAIL) != 0) {
                        if ((alignment & SWT.FILL) != 0 && !overflow[i]) {
                            rects[i].width += extraSpace;
                            addedSpace += extraSpace;
                        }
                        if (!overflow[i]) {
                            rects[i].x -= addedSpace;
                        }
                    }
                }
            }
        }

        //Go through overflow laying out all wrapped controls
        Rectangle bodyTrim = renderer.computeTrim(CTabFolderRenderer.PART_BODY, SWT.NONE, 0, 0, 0, 0);
        int bodyRight = bodyTrim.width + bodyTrim.x;
        int bodyLeft = -bodyTrim.x;
        int bodyWidth = size.x - bodyLeft - bodyRight;
        x = size.x - bodyRight;
        int y = onBottom ? this.getSize().y - getTabHeight() + 2 * bodyTrim.y : -bodyTrim.y;
        availableWidth = bodyWidth;
        int maxHeight = 0;
        for (int i = 0; i < controls.length; i++) {
            Point ctrlSize = tabControlSize[i];
            if (overflow[i]) {
                if (availableWidth > ctrlSize.x) {
                    x -= ctrlSize.x;
                    rects[i].width = ctrlSize.x;
                    rects[i].y = onBottom ? y - ctrlSize.y : y;
                    rects[i].height = ctrlSize.y;
                    rects[i].x = x;
                    availableWidth -= ctrlSize.x;
                    maxHeight = Math.max(maxHeight, ctrlSize.y);
                } else {
                    x = size.x - bodyRight;
                    y += maxHeight;
                    maxHeight = 0;
                    availableWidth = bodyWidth;
                    if (availableWidth > ctrlSize.x) {
                        //Relayout this control in the next line
                        i--;
                    } else {
                        ctrlSize = controls[i].isDisposed() ? new Point(0, 0)
                                : controls[i].computeSize(bodyWidth, SWT.DEFAULT);
                        rects[i].width = bodyWidth;
                        rects[i].y = onBottom ? y - ctrlSize.y : y;
                        rects[i].height = ctrlSize.y;
                        rects[i].x = size.x - ctrlSize.x - bodyRight;
                        y += ctrlSize.y;
                    }
                }
            }
        }

        if (showChevron) {
            int i = 0, lastIndex = -1;
            while (i < priority.length && items[priority[i]].showing) {
                lastIndex = Math.max(lastIndex, priority[i++]);
            }
            if (lastIndex == -1)
                lastIndex = selectedIndex;
            if (lastIndex != -1) {
                CTabItem lastItem = items[lastIndex];
                int w = lastItem.x + lastItem.width + SPACING;
                if (!simple && lastIndex == selectedIndex)
                    w -= (renderer.curveIndent - 7);
                rects[controls.length - 1].x = w;
            }
        }

        if (position != null)
            position[0] = overflow;
        return rects;
    }

    int getControlHeight(Point ctrlSize) {
        return fixedTabHeight == SWT.DEFAULT ? Math.max(tabHeight - 1, ctrlSize.y) : ctrlSize.y;
    }

    /*
    * This class was not intended to be subclassed but this restriction
    * cannot be enforced without breaking backward compatibility.
    */
    //protected void checkSubclass () {
    //   String name = getClass ().getName ();
    //   int index = name.lastIndexOf ('.');
    //   if (!name.substring (0, index + 1).equals ("org.eclipse.swt.custom.")) {
    //      SWT.error (SWT.ERROR_INVALID_SUBCLASS);
    //   }
    //}
    @Override
    public Rectangle computeTrim(int x, int y, int width, int height) {
        checkWidget();
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BODY, SWT.NONE, x, y, width, height);
        Point size = new Point(width, height);
        int wrapHeight = getWrappedHeight(size);
        if (onBottom) {
            trim.height += wrapHeight;
        } else {
            trim.y -= wrapHeight;
            trim.height += wrapHeight;
        }
        return trim;
    }

    Image createButtonImage(Display display, int button) {
        GC tempGC = new GC(this);
        Point size = renderer.computeSize(button, SWT.NONE, tempGC, SWT.DEFAULT, SWT.DEFAULT);
        tempGC.dispose();

        Rectangle trim = renderer.computeTrim(button, SWT.NONE, 0, 0, 0, 0);
        Image image = new Image(display, size.x - trim.width, size.y - trim.height);
        GC gc = new GC(image);
        Color transColor = renderer.parent.getBackground();
        gc.setBackground(transColor);
        gc.fillRectangle(image.getBounds());
        renderer.draw(button, SWT.NONE, new Rectangle(trim.x, trim.y, size.x, size.y), gc);
        gc.dispose();

        final ImageData imageData = image.getImageData(DPIUtil.getDeviceZoom());
        imageData.transparentPixel = imageData.palette.getPixel(transColor.getRGB());
        image.dispose();
        image = new Image(display, new AutoScaleImageDataProvider(display, imageData, DPIUtil.getDeviceZoom()));
        return image;
    }

    void createItem(CTabItem item, int index) {
        if (0 > index || index > getItemCount())
            SWT.error(SWT.ERROR_INVALID_RANGE);
        item.parent = this;
        CTabItem[] newItems = new CTabItem[items.length + 1];
        System.arraycopy(items, 0, newItems, 0, index);
        newItems[index] = item;
        System.arraycopy(items, index, newItems, index + 1, items.length - index);
        items = newItems;
        if (selectedIndex >= index)
            selectedIndex++;
        int[] newPriority = new int[priority.length + 1];
        int next = 0, priorityIndex = priority.length;
        for (int i = 0; i < priority.length; i++) {
            if (!mru && priority[i] == index) {
                priorityIndex = next++;
            }
            newPriority[next++] = priority[i] >= index ? priority[i] + 1 : priority[i];
        }
        newPriority[priorityIndex] = index;
        priority = newPriority;

        if (items.length == 1) {
            updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
        } else {
            updateFolder(REDRAW_TABS);
        }
    }

    void destroyItem(CTabItem item) {
        if (inDispose)
            return;
        int index = indexOf(item);
        if (index == -1)
            return;

        if (items.length == 1) {
            items = new CTabItem[0];
            priority = new int[0];
            firstIndex = -1;
            selectedIndex = -1;

            Control control = item.control;
            if (control != null && !control.isDisposed()) {
                control.setVisible(false);
            }
            setToolTipText(null);
            GC gc = new GC(this);
            setButtonBounds(gc);
            gc.dispose();
            redraw();
            return;
        }

        CTabItem[] newItems = new CTabItem[items.length - 1];
        System.arraycopy(items, 0, newItems, 0, index);
        System.arraycopy(items, index + 1, newItems, index, items.length - index - 1);
        items = newItems;

        int[] newPriority = new int[priority.length - 1];
        int next = 0;
        for (int i = 0; i < priority.length; i++) {
            if (priority[i] == index)
                continue;
            newPriority[next++] = priority[i] > index ? priority[i] - 1 : priority[i];
        }
        priority = newPriority;

        // move the selection if this item is selected
        if (selectedIndex == index) {
            Control control = item.getControl();
            selectedIndex = -1;
            int nextSelection = mru ? priority[0] : Math.max(0, index - 1);
            setSelection(nextSelection, true);
            if (control != null && !control.isDisposed()) {
                control.setVisible(false);
            }
        } else if (selectedIndex > index) {
            selectedIndex--;
        }

        requestLayout();
        updateFolder(UPDATE_TAB_HEIGHT | REDRAW_TABS);
    }

    /**
     * Returns <code>true</code> if the receiver's border is visible.
     *
     * @return the receiver's border visibility state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public boolean getBorderVisible() {
        checkWidget();
        return borderVisible;
    }

    ToolBar getChevron() {
        if (chevronTb == null) {
            chevronTb = new ToolBar(this, SWT.FLAT);
            initAccessibleChevronTb();
            addTabControl(chevronTb, SWT.TRAIL, -1, false);
        }
        if (chevronItem == null) {
            chevronItem = new ToolItem(chevronTb, SWT.PUSH);
            chevronItem.setToolTipText(SWT.getMessage("SWT_ShowList"));
            chevronItem.addListener(SWT.Selection, listener);
        }
        return chevronTb;
    }

    /**
     * Returns <code>true</code> if the chevron button
     * is visible when necessary.
     *
     * @return the visibility of the chevron button
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     */
    /*public*/ boolean getChevronVisible() {
        checkWidget();
        return chevronVisible;
    }

    @Override
    public Rectangle getClientArea() {
        checkWidget();
        //TODO: HACK - find a better way to get padding
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BODY, SWT.FILL, 0, 0, 0, 0);
        Point size = getSize();
        int wrapHeight = getWrappedHeight(size);
        if (onBottom) {
            trim.height += wrapHeight;
        } else {
            trim.y -= wrapHeight;
            trim.height += wrapHeight;
        }
        if (minimized)
            return new Rectangle(-trim.x, -trim.y, 0, 0);
        int width = size.x - trim.width;
        int height = size.y - trim.height;
        return new Rectangle(-trim.x, -trim.y, width, height);
    }

    /**
     * Return the tab that is located at the specified index.
     *
     * @param index the index of the tab item
     * @return the item at the specified index
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_RANGE - if the index is out of range</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     */
    public CTabItem getItem(int index) {
        /*
         * This call is intentionally commented out, to allow this getter method to be
         * called from a thread which is different from one that created the widget.
         */
        //checkWidget();
        if (index < 0 || index >= items.length)
            SWT.error(SWT.ERROR_INVALID_RANGE);
        return items[index];
    }

    /**
     * Gets the item at a point in the widget.
     *
     * @param pt the point in coordinates relative to the CTabFolder
     * @return the item at a point or null
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public CTabItem getItem(Point pt) {
        /*
         * This call is intentionally commented out, to allow this getter method to be
         * called from a thread which is different from one that created the widget.
         */
        //checkWidget();
        if (items.length == 0)
            return null;
        runUpdate();
        Point size = getSize();
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BORDER, SWT.NONE, 0, 0, 0, 0);
        if (size.x <= trim.width)
            return null;
        for (int i = 0; i < priority.length; i++) {
            CTabItem item = items[priority[i]];
            Rectangle rect = item.getBounds();
            if (rect.contains(pt))
                return item;
        }
        return null;
    }

    /**
     * Return the number of tabs in the folder.
     *
     * @return the number of tabs in the folder
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public int getItemCount() {
        //checkWidget();
        return items.length;
    }

    /**
     * Return the tab items.
     *
     * @return the tab items
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public CTabItem[] getItems() {
        /*
         * This call is intentionally commented out, to allow this getter method to be
         * called from a thread which is different from one that created the widget.
         */
        //checkWidget();
        CTabItem[] tabItems = new CTabItem[items.length];
        System.arraycopy(items, 0, tabItems, 0, items.length);
        return tabItems;
    }

    int getLeftItemEdge(GC gc, int part) {
        Rectangle trim = renderer.computeTrim(part, SWT.NONE, 0, 0, 0, 0);
        int x = -trim.x;
        int width = 0;
        for (int i = 0; i < controls.length; i++) {
            if ((controlAlignments[i] & SWT.LEAD) != 0 && !controls[i].isDisposed() && controls[i].getVisible()) {
                width += controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
            }
        }
        if (width != 0)
            width += SPACING * 2;
        x += width;
        return Math.max(0, x);
    }

    /*
     * Return the lowercase of the first non-'&' character following
     * an '&' character in the given string. If there are no '&'
     * characters in the given string, return '\0'.
     */
    char _findMnemonic(String string) {
        if (string == null)
            return '\0';
        int index = 0;
        int length = string.length();
        do {
            while (index < length && string.charAt(index) != '&')
                index++;
            if (++index >= length)
                return '\0';
            if (string.charAt(index) != '&')
                return Character.toLowerCase(string.charAt(index));
            index++;
        } while (index < length);
        return '\0';
    }

    String stripMnemonic(String string) {
        int index = 0;
        int length = string.length();
        do {
            while ((index < length) && (string.charAt(index) != '&'))
                index++;
            if (++index >= length)
                return string;
            if (string.charAt(index) != '&') {
                return string.substring(0, index - 1) + string.substring(index, length);
            }
            index++;
        } while (index < length);
        return string;
    }

    /**
     * Returns <code>true</code> if the receiver is minimized.
     *
     * @return the receiver's minimized state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public boolean getMinimized() {
        checkWidget();
        return minimized;
    }

    /**
     * Returns <code>true</code> if the minimize button
     * is visible.
     *
     * @return the visibility of the minimized button
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public boolean getMinimizeVisible() {
        checkWidget();
        return showMin;
    }

    /**
     * Returns the number of characters that will
     * appear in a fully compressed tab.
     *
     * @return number of characters that will appear in a fully compressed tab
     *
     * @since 3.0
     */
    public int getMinimumCharacters() {
        checkWidget();
        return minChars;
    }

    /**
     * Returns <code>true</code> if the receiver is maximized.
     * <p>
     *
     * @return the receiver's maximized state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public boolean getMaximized() {
        checkWidget();
        return maximized;
    }

    /**
     * Returns <code>true</code> if the maximize button
     * is visible.
     *
     * @return the visibility of the maximized button
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public boolean getMaximizeVisible() {
        checkWidget();
        return showMax;
    }

    /**
     * Returns <code>true</code> if the receiver displays most
     * recently used tabs and <code>false</code> otherwise.
     * <p>
     * When there is not enough horizontal space to show all the tabs,
     * by default, tabs are shown sequentially from left to right in
     * order of their index.  When the MRU visibility is turned on,
     * the tabs that are visible will be the tabs most recently selected.
     * Tabs will still maintain their left to right order based on index
     * but only the most recently selected tabs are visible.
     * <p>
     * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
     * "Tab 3" and "Tab 4" (in order by index).  The user selects
     * "Tab 1" and then "Tab 3".  If the CTabFolder is now
     * compressed so that only two tabs are visible, by default,
     * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
     * selected and "Tab 2" because it is the previous item in index order).
     * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
     * and "Tab 3" (in that order from left to right).</p>
     *
     * @return the receiver's header's visibility state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.1
     */
    public boolean getMRUVisible() {
        checkWidget();
        return mru;
    }

    /**
     * Returns the receiver's renderer.
     *
     * @return the receiver's renderer
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @see #setRenderer(CTabFolderRenderer)
     * @see CTabFolderRenderer
     *
     * @since 3.6
     */
    public CTabFolderRenderer getRenderer() {
        checkWidget();
        return renderer;
    }

    int getRightItemEdge(GC gc) {
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BORDER, SWT.NONE, 0, 0, 0, 0);
        int x = getSize().x - (trim.width + trim.x);
        int width = 0;
        for (int i = 0; i < controls.length; i++) {
            int align = controlAlignments[i];
            if ((align & SWT.WRAP) == 0 && (align & SWT.LEAD) == 0 && !controls[i].isDisposed()
                    && controls[i].getVisible()) {
                Point rightSize = controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT);
                width += rightSize.x;
            }
        }
        if (width != 0)
            width += SPACING * 2;
        x -= width;
        return Math.max(0, x);
    }

    /**
     * Return the selected tab item, or null if there is no selection.
     *
     * @return the selected tab item, or null if none has been selected
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public CTabItem getSelection() {
        /*
         * This call is intentionally commented out, to allow this getter method to be
         * called from a thread which is different from one that created the widget.
         */
        //checkWidget();
        if (selectedIndex == -1)
            return null;
        return items[selectedIndex];
    }

    /**
     * Returns the receiver's selection background color.
     *
     * @return the selection background color of the receiver
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public Color getSelectionBackground() {
        checkWidget();
        return selectionBackground;
    }

    /**
     * Returns the receiver's selection foreground color.
     *
     * @return the selection foreground color of the receiver
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public Color getSelectionForeground() {
        checkWidget();
        return selectionForeground;
    }

    /**
     * Return the index of the selected tab item, or -1 if there
     * is no selection.
     *
     * @return the index of the selected tab item or -1
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public int getSelectionIndex() {
        /*
         * This call is intentionally commented out, to allow this getter method to be
         * called from a thread which is different from one that created the widget.
         */
        //checkWidget();
        return selectedIndex;
    }

    /**
     * Returns <code>true</code> if the CTabFolder is rendered
     * with a simple, traditional shape.
     *
     * @return <code>true</code> if the CTabFolder is rendered with a simple shape
     *
     * @since 3.0
     */
    public boolean getSimple() {
        checkWidget();
        return simple;
    }

    /**
     * Returns <code>true</code> if the CTabFolder only displays the selected tab
     * and <code>false</code> if the CTabFolder displays multiple tabs.
     *
     * @return <code>true</code> if the CTabFolder only displays the selected tab and <code>false</code> if the CTabFolder displays multiple tabs
     *
     * @since 3.0
     */
    public boolean getSingle() {
        checkWidget();
        return single;
    }

    @Override
    public int getStyle() {
        int style = super.getStyle();
        style &= ~(SWT.TOP | SWT.BOTTOM);
        style |= onBottom ? SWT.BOTTOM : SWT.TOP;
        style &= ~(SWT.SINGLE | SWT.MULTI);
        style |= single ? SWT.SINGLE : SWT.MULTI;
        if (borderVisible)
            style |= SWT.BORDER;
        style &= ~SWT.CLOSE;
        if (showClose)
            style |= SWT.CLOSE;
        return style;
    }

    /**
     * Returns the height of the tab
     *
     * @return the height of the tab
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public int getTabHeight() {
        checkWidget();
        if (fixedTabHeight != SWT.DEFAULT)
            return fixedTabHeight;
        return tabHeight - 1; // -1 for line drawn across top of tab //TODO: replace w/ computeTrim of tab area?
    }

    /**
     * Returns the position of the tab.  Possible values are SWT.TOP or SWT.BOTTOM.
     *
     * @return the position of the tab
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public int getTabPosition() {
        checkWidget();
        return onBottom ? SWT.BOTTOM : SWT.TOP;
    }

    /**
     * Returns the control in the top right corner of the tab folder.
     * Typically this is a close button or a composite with a menu and close button.
     *
     * @return the control in the top right corner of the tab folder or null
     *
     * @exception  SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @since 2.1
     */
    public Control getTopRight() {
        checkWidget();
        return topRight;
    }

    /**
     * Returns the alignment of the top right control.
     *
     * @return the alignment of the top right control which is either
     * <code>SWT.RIGHT</code> or <code>SWT.FILL</code>
     *
     * @exception  SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @since 3.6
     */
    public int getTopRightAlignment() {
        checkWidget();
        return topRightAlignment;
    }

    /**
     * Returns <code>true</code> if the close button appears
     * when the user hovers over an unselected tabs.
     *
     * @return <code>true</code> if the close button appears on unselected tabs
     *
     * @since 3.0
     */
    public boolean getUnselectedCloseVisible() {
        checkWidget();
        return showUnselectedClose;
    }

    /**
     * Returns <code>true</code> if an image appears
     * in unselected tabs.
     *
     * @return <code>true</code> if an image appears in unselected tabs
     *
     * @since 3.0
     */
    public boolean getUnselectedImageVisible() {
        checkWidget();
        return showUnselectedImage;
    }

    /**
     * Return the index of the specified tab or -1 if the tab is not
     * in the receiver.
     *
     * @param item the tab item for which the index is required
     *
     * @return the index of the specified tab item or -1
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     */
    public int indexOf(CTabItem item) {
        checkWidget();
        if (item == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        for (int i = 0; i < items.length; i++) {
            if (items[i] == item)
                return i;
        }
        return -1;
    }

    void initAccessible() {
        final Accessible accessible = getAccessible();
        accessible.addAccessibleListener(new AccessibleAdapter() {
            @Override
            public void getName(AccessibleEvent e) {
                CTabItem item = null;
                int childID = e.childID;
                if (childID == ACC.CHILDID_SELF) {
                    if (selectedIndex != -1) {
                        item = items[selectedIndex];
                    }
                } else if (childID >= 0 && childID < items.length) {
                    item = items[childID];
                }
                e.result = item == null ? null : stripMnemonic(item.getText());
            }

            @Override
            public void getHelp(AccessibleEvent e) {
                String help = null;
                int childID = e.childID;
                if (childID == ACC.CHILDID_SELF) {
                    help = getToolTipText();
                } else if (childID >= 0 && childID < items.length) {
                    help = items[childID].getToolTipText();
                }
                e.result = help;
            }

            @Override
            public void getKeyboardShortcut(AccessibleEvent e) {
                String shortcut = null;
                int childID = e.childID;
                if (childID >= 0 && childID < items.length) {
                    String text = items[childID].getText();
                    if (text != null) {
                        char mnemonic = _findMnemonic(text);
                        if (mnemonic != '\0') {
                            shortcut = SWT.getMessage("SWT_Page_Mnemonic", //$NON-NLS-1$
                                    new Object[] { Character.valueOf(mnemonic) });
                        }
                    }
                }
                if (childID == ACC.CHILDID_SELF) {
                    shortcut = SWT.getMessage("SWT_SwitchPage_Shortcut"); //$NON-NLS-1$
                }
                e.result = shortcut;
            }
        });

        accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
            @Override
            public void getChildAtPoint(AccessibleControlEvent e) {
                Point testPoint = toControl(e.x, e.y);
                int childID = ACC.CHILDID_NONE;
                for (int i = 0; i < items.length; i++) {
                    if (items[i].getBounds().contains(testPoint)) {
                        childID = i;
                        break;
                    }
                }
                if (childID == ACC.CHILDID_NONE) {
                    Rectangle location = getBounds();
                    location.x = location.y = 0;
                    location.height = location.height - getClientArea().height;
                    if (location.contains(testPoint)) {
                        childID = ACC.CHILDID_SELF;
                    }
                }
                e.childID = childID;
            }

            @Override
            public void getLocation(AccessibleControlEvent e) {
                Rectangle location = null;
                Point pt = null;
                int childID = e.childID;
                if (childID == ACC.CHILDID_SELF) {
                    location = getBounds();
                    pt = getParent().toDisplay(location.x, location.y);
                } else {
                    if (childID >= 0 && childID < items.length && items[childID].showing) {
                        location = items[childID].getBounds();
                    }
                    if (location != null) {
                        pt = toDisplay(location.x, location.y);
                    }
                }
                if (location != null && pt != null) {
                    e.x = pt.x;
                    e.y = pt.y;
                    e.width = location.width;
                    e.height = location.height;
                }
            }

            @Override
            public void getChildCount(AccessibleControlEvent e) {
                e.detail = items.length;
            }

            @Override
            public void getDefaultAction(AccessibleControlEvent e) {
                String action = null;
                int childID = e.childID;
                if (childID >= 0 && childID < items.length) {
                    action = SWT.getMessage("SWT_Switch"); //$NON-NLS-1$
                }
                e.result = action;
            }

            @Override
            public void getFocus(AccessibleControlEvent e) {
                int childID = ACC.CHILDID_NONE;
                if (isFocusControl()) {
                    if (selectedIndex == -1) {
                        childID = ACC.CHILDID_SELF;
                    } else {
                        childID = selectedIndex;
                    }
                }
                e.childID = childID;
            }

            @Override
            public void getRole(AccessibleControlEvent e) {
                int role = 0;
                int childID = e.childID;
                if (childID == ACC.CHILDID_SELF) {
                    role = ACC.ROLE_TABFOLDER;
                } else if (childID >= 0 && childID < items.length) {
                    role = ACC.ROLE_TABITEM;
                }
                e.detail = role;
            }

            @Override
            public void getSelection(AccessibleControlEvent e) {
                e.childID = (selectedIndex == -1) ? ACC.CHILDID_NONE : selectedIndex;
            }

            @Override
            public void getState(AccessibleControlEvent e) {
                int state = 0;
                int childID = e.childID;
                if (childID == ACC.CHILDID_SELF) {
                    state = ACC.STATE_NORMAL;
                } else if (childID >= 0 && childID < items.length) {
                    state = ACC.STATE_SELECTABLE;
                    if (isFocusControl()) {
                        state |= ACC.STATE_FOCUSABLE;
                    }
                    if (selectedIndex == childID) {
                        state |= ACC.STATE_SELECTED;
                        if (isFocusControl()) {
                            state |= ACC.STATE_FOCUSED;
                        }
                    }
                }
                e.detail = state;
            }

            @Override
            public void getChildren(AccessibleControlEvent e) {
                int childIdCount = items.length;
                Object[] children = new Object[childIdCount];
                for (int i = 0; i < childIdCount; i++) {
                    children[i] = Integer.valueOf(i);
                }
                e.children = children;
            }
        });

        addListener(SWT.Selection, event -> {
            if (isFocusControl()) {
                if (selectedIndex == -1) {
                    accessible.setFocus(ACC.CHILDID_SELF);
                } else {
                    accessible.setFocus(selectedIndex);
                }
            }
        });

        addListener(SWT.FocusIn, event -> {
            if (selectedIndex == -1) {
                accessible.setFocus(ACC.CHILDID_SELF);
            } else {
                accessible.setFocus(selectedIndex);
            }
        });
    }

    void initAccessibleMinMaxTb() {
        minMaxTb.getAccessible().addAccessibleListener(new AccessibleAdapter() {
            @Override
            public void getName(AccessibleEvent e) {
                if (e.childID != ACC.CHILDID_SELF) {
                    if (minItem != null && e.childID == minMaxTb.indexOf(minItem)) {
                        e.result = minItem.getToolTipText();
                    } else if (maxItem != null && e.childID == minMaxTb.indexOf(maxItem)) {
                        e.result = maxItem.getToolTipText();
                    }
                }
            }
        });
    }

    void initAccessibleChevronTb() {
        chevronTb.getAccessible().addAccessibleListener(new AccessibleAdapter() {
            @Override
            public void getName(AccessibleEvent e) {
                if (e.childID != ACC.CHILDID_SELF) {
                    if (chevronItem != null && e.childID == chevronTb.indexOf(chevronItem)) {
                        e.result = chevronItem.getToolTipText();
                    }
                }
            }
        });
    }

    void onKeyDown(Event event) {
        runUpdate();
        switch (event.keyCode) {
        case SWT.ARROW_LEFT:
        case SWT.ARROW_RIGHT:
            int count = items.length;
            if (count == 0)
                return;
            if (selectedIndex == -1)
                return;
            int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
            int offset = event.keyCode == leadKey ? -1 : 1;
            int index;
            if (!mru) {
                index = selectedIndex + offset;
            } else {
                int[] visible = new int[items.length];
                int idx = 0;
                int current = -1;
                for (int i = 0; i < items.length; i++) {
                    if (items[i].showing) {
                        if (i == selectedIndex)
                            current = idx;
                        visible[idx++] = i;
                    }
                }
                if (current + offset >= 0 && current + offset < idx) {
                    index = visible[current + offset];
                } else {
                    if (showChevron) {
                        Rectangle chevronRect = chevronItem.getBounds();
                        chevronRect = event.display.map(chevronTb, this, chevronRect);
                        CTabFolderEvent e = new CTabFolderEvent(this);
                        e.widget = this;
                        e.time = event.time;
                        e.x = chevronRect.x;
                        e.y = chevronRect.y;
                        e.width = chevronRect.width;
                        e.height = chevronRect.height;
                        e.doit = true;
                        for (int i = 0; i < folderListeners.length; i++) {
                            folderListeners[i].showList(e);
                        }
                        if (e.doit && !isDisposed()) {
                            showList(chevronRect);
                        }
                    }
                    return;
                }
            }
            if (index < 0 || index >= count)
                return;
            setSelection(index, true);
            forceFocus();
        }
    }

    void onDispose(Event event) {
        removeListener(SWT.Dispose, listener);
        notifyListeners(SWT.Dispose, event);
        event.type = SWT.None;
        /*
         * Usually when an item is disposed, destroyItem will change the size of the items array,
         * reset the bounds of all the tabs and manage the widget associated with the tab.
         * Since the whole folder is being disposed, this is not necessary.  For speed
         * the inDispose flag is used to skip over this part of the item dispose.
         */
        inDispose = true;

        if (showMenu != null && !showMenu.isDisposed()) {
            showMenu.dispose();
            showMenu = null;
        }
        int length = items.length;
        for (int i = 0; i < length; i++) {
            if (items[i] != null) {
                items[i].dispose();
            }
        }

        gradientColors = null;

        selectionGradientColors = null;
        selectionGradientPercents = null;
        selectionBgImage = null;

        selectionBackground = null;
        selectionForeground = null;

        if (controlBkImages != null) {
            for (int i = 0; i < controlBkImages.length; i++) {
                if (controlBkImages[i] != null) {
                    controlBkImages[i].dispose();
                    controlBkImages[i] = null;
                }
            }
            controlBkImages = null;
        }
        controls = null;
        controlAlignments = null;
        controlRects = null;

        if (maxImage != null)
            maxImage.dispose();
        maxImage = null;

        if (minImage != null)
            minImage.dispose();
        minImage = null;

        if (chevronImage != null)
            chevronImage.dispose();
        chevronImage = null;

        if (renderer != null)
            renderer.dispose();
        renderer = null;

        minItem = null;
        maxItem = null;
        minMaxTb = null;

        chevronItem = null;
        chevronTb = null;

        if (folderListeners.length != 0)
            folderListeners = new CTabFolder2Listener[0];
        if (tabListeners.length != 0)
            tabListeners = new CTabFolderListener[0];
    }

    void onDragDetect(Event event) {
        boolean consume = false;
        for (int i = 0; i < items.length; i++) {
            if (items[i].closeRect.contains(event.x, event.y)) {
                consume = true;
                break;
            }
        }
        if (consume) {
            event.type = SWT.None;
        }
    }

    void onFocus(Event event) {
        checkWidget();
        if (selectedIndex >= 0) {
            redraw();
        } else {
            setSelection(0, true);
        }
    }

    boolean onMnemonic(Event event, boolean doit) {
        char key = event.character;
        for (int i = 0; i < items.length; i++) {
            if (items[i] != null) {
                char mnemonic = _findMnemonic(items[i].getText());
                if (mnemonic != '\0') {
                    if (Character.toLowerCase(key) == mnemonic) {
                        if (doit) {
                            setSelection(i, true);
                            forceFocus();
                        }
                        return true;
                    }
                }
            }
        }
        return false;
    }

    void onMenuDetect(Event event) {
        if (event.detail == SWT.MENU_KEYBOARD) {
            if (selectedIndex != -1) {
                CTabItem item = items[selectedIndex];
                Rectangle rect = getDisplay().map(this, null, item.getBounds());
                if (!rect.contains(event.x, event.y)) {
                    /* If the mouse is not in the currently-selected tab,
                     * then pop up the menu near the top-right corner of the current tab.
                     */
                    Rectangle itemTrim = renderer.computeTrim(selectedIndex, SWT.NONE, 0, 0, 0, 0);
                    Rectangle closeTrim = renderer.computeTrim(CTabFolderRenderer.PART_CLOSE_BUTTON, SWT.NONE, 0, 0,
                            0, 0);
                    event.x = rect.x + rect.width - item.closeRect.width + itemTrim.x - closeTrim.width;
                    event.y = rect.y - itemTrim.y - closeTrim.y;
                }
            }
        }
    }

    void onMouseDoubleClick(Event event) {
        if (event.button != 1 || (event.stateMask & SWT.BUTTON2) != 0 || (event.stateMask & SWT.BUTTON3) != 0)
            return;
        Event e = new Event();
        e.item = getItem(new Point(event.x, event.y));
        if (e.item != null) {
            notifyListeners(SWT.DefaultSelection, e);
        }
    }

    void onMouse(Event event) {
        if (isDisposed()) {
            return;
        }
        int x = event.x, y = event.y;
        switch (event.type) {
        case SWT.MouseEnter: {
            setToolTipText(null);
            break;
        }
        case SWT.MouseExit: {
            for (int i = 0; i < items.length; i++) {
                CTabItem item = items[i];
                if (i != selectedIndex && item.closeImageState != SWT.BACKGROUND) {
                    item.closeImageState = SWT.BACKGROUND;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                }
                if ((item.state & SWT.HOT) != 0) {
                    item.state &= ~SWT.HOT;
                    redraw(item.x, item.y, item.width, item.height, false);
                }
                if (i == selectedIndex && item.closeImageState != SWT.NONE) {
                    item.closeImageState = SWT.NONE;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                }
            }
            break;
        }
        case SWT.MouseHover:
        case SWT.MouseDown: {
            if (hoverTb && hoverRect.contains(x, y) && !hovering) {
                hovering = true;
                updateItems();
                hoverTimerRunning = true;
                event.display.timerExec(2000, new Runnable() {
                    @Override
                    public void run() {
                        if (isDisposed())
                            return;
                        if (hovering) {
                            Display display = getDisplay();
                            Control c = display.getCursorControl();
                            boolean reschedule = false;
                            if (c != null) {
                                for (int i = 0; i < controls.length; i++) {
                                    Control temp = c;
                                    do {
                                        if (temp.equals(controls[i])) {
                                            reschedule = true;
                                        } else {
                                            temp = temp.getParent();
                                            if (temp == null || temp.equals(CTabFolder.this))
                                                break;
                                        }
                                    } while (!reschedule);
                                    if (reschedule)
                                        break;
                                }
                            }
                            if (reschedule && hoverTimerRunning) {
                                display.timerExec(2000, this);
                            } else {
                                hovering = false;
                                updateItems();
                            }
                        }
                    }
                });
                return;
            }
            if (event.button != 1)
                return;
            CTabItem item = null;
            if (single) {
                if (selectedIndex != -1) {
                    Rectangle bounds = items[selectedIndex].getBounds();
                    if (bounds.contains(x, y)) {
                        item = items[selectedIndex];
                    }
                }
            } else {
                for (int i = 0; i < items.length; i++) {
                    Rectangle bounds = items[i].getBounds();
                    if (bounds.contains(x, y)) {
                        item = items[i];
                    }
                }
            }
            if (item != null) {
                if (item.closeRect.contains(x, y)) {
                    item.closeImageState = SWT.SELECTED;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                    update();
                    return;
                }
                int index = indexOf(item);
                if (item.showing) {
                    int oldSelectedIndex = selectedIndex;
                    setSelection(index, true);
                    if (oldSelectedIndex == selectedIndex) {
                        /* If the click is on the selected tabitem, then set focus to the tabfolder */
                        forceFocus();
                    }
                }
                return;
            }
            break;
        }
        case SWT.MouseMove: {
            _setToolTipText(event.x, event.y);
            boolean close = false;
            for (int i = 0; i < items.length; i++) {
                CTabItem item = items[i];
                close = false;
                if (item.getBounds().contains(x, y)) {
                    close = true;
                    if (item.closeRect.contains(x, y)) {
                        if (item.closeImageState != SWT.SELECTED && item.closeImageState != SWT.HOT) {
                            item.closeImageState = SWT.HOT;
                            redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height,
                                    false);
                        }
                    } else {
                        if (item.closeImageState != SWT.NONE) {
                            item.closeImageState = SWT.NONE;
                            redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height,
                                    false);
                        }
                    }
                    if ((item.state & SWT.HOT) == 0) {
                        item.state |= SWT.HOT;
                        redraw(item.x, item.y, item.width, item.height, false);
                    }
                }
                if (i != selectedIndex && item.closeImageState != SWT.BACKGROUND && !close) {
                    item.closeImageState = SWT.BACKGROUND;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                }
                if ((item.state & SWT.HOT) != 0 && !close) {
                    item.state &= ~SWT.HOT;
                    redraw(item.x, item.y, item.width, item.height, false);
                }
                if (i == selectedIndex && item.closeImageState != SWT.NONE && !close) {
                    item.closeImageState = SWT.NONE;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                }
            }
            break;
        }
        case SWT.MouseUp: {
            if (event.button != 1)
                return;
            CTabItem item = null;
            if (single) {
                if (selectedIndex != -1) {
                    Rectangle bounds = items[selectedIndex].getBounds();
                    if (bounds.contains(x, y)) {
                        item = items[selectedIndex];
                    }
                }
            } else {
                for (int i = 0; i < items.length; i++) {
                    Rectangle bounds = items[i].getBounds();
                    if (bounds.contains(x, y)) {
                        item = items[i];
                    }
                }
            }
            if (item != null) {
                if (item.closeRect.contains(x, y)) {
                    boolean selected = item.closeImageState == SWT.SELECTED;
                    item.closeImageState = SWT.HOT;
                    redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false);
                    if (!selected)
                        return;
                    CTabFolderEvent e = new CTabFolderEvent(this);
                    e.widget = this;
                    e.time = event.time;
                    e.item = item;
                    e.doit = true;
                    for (int j = 0; j < folderListeners.length; j++) {
                        CTabFolder2Listener listener = folderListeners[j];
                        listener.close(e);
                    }
                    for (int j = 0; j < tabListeners.length; j++) {
                        CTabFolderListener listener = tabListeners[j];
                        listener.itemClosed(e);
                    }
                    if (e.doit)
                        item.dispose();
                    if (!isDisposed() && item.isDisposed()) {
                        Display display = getDisplay();
                        Point pt = display.getCursorLocation();
                        pt = display.map(null, this, pt.x, pt.y);
                        CTabItem nextItem = getItem(pt);
                        if (nextItem != null) {
                            if (nextItem.closeRect.contains(pt)) {
                                if (nextItem.closeImageState != SWT.SELECTED
                                        && nextItem.closeImageState != SWT.HOT) {
                                    nextItem.closeImageState = SWT.HOT;
                                    redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width,
                                            nextItem.closeRect.height, false);
                                }
                            } else {
                                if (nextItem.closeImageState != SWT.NONE) {
                                    nextItem.closeImageState = SWT.NONE;
                                    redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width,
                                            nextItem.closeRect.height, false);
                                }
                            }
                        }
                    }
                    return;
                }
            }
        }
        }
    }

    void onPageTraversal(Event event) {
        int count = items.length;
        if (count == 0)
            return;
        int index = selectedIndex;
        if (index == -1) {
            index = 0;
        } else {
            int offset = (event.detail == SWT.TRAVERSE_PAGE_NEXT) ? 1 : -1;
            if (!mru) {
                index = (selectedIndex + offset + count) % count;
            } else {
                int[] visible = new int[items.length];
                int idx = 0;
                int current = -1;
                for (int i = 0; i < items.length; i++) {
                    if (items[i].showing) {
                        if (i == selectedIndex)
                            current = idx;
                        visible[idx++] = i;
                    }
                }
                if (current + offset >= 0 && current + offset < idx) {
                    index = visible[current + offset];
                } else {
                    if (showChevron) {
                        Rectangle chevronRect = chevronItem.getBounds();
                        chevronRect = event.display.map(chevronTb, this, chevronRect);
                        CTabFolderEvent e = new CTabFolderEvent(this);
                        e.widget = this;
                        e.time = event.time;
                        e.x = chevronRect.x;
                        e.y = chevronRect.y;
                        e.width = chevronRect.width;
                        e.height = chevronRect.height;
                        e.doit = true;
                        for (int i = 0; i < folderListeners.length; i++) {
                            folderListeners[i].showList(e);
                        }
                        if (e.doit && !isDisposed()) {
                            showList(chevronRect);
                        }
                    }
                }
            }
        }
        setSelection(index, true);
    }

    void onPaint(Event event) {
        if (inDispose)
            return;
        Font font = getFont();
        if (oldFont == null || !oldFont.equals(font)) {
            // handle case where  default font changes
            oldFont = font;
            if (!updateTabHeight(false)) {
                updateItems();
                redraw();
                return;
            }
        }

        GC gc = event.gc;
        Font gcFont = gc.getFont();
        Color gcBackground = gc.getBackground();
        Color gcForeground = gc.getForeground();

        // Useful for debugging paint problems
        //{
        //Point size = getSize();
        //gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN));
        //gc.fillRectangle(-10, -10, size.x + 20, size.y+20);
        //}

        Point size = getSize();
        Rectangle bodyRect = new Rectangle(0, 0, size.x, size.y);
        renderer.draw(CTabFolderRenderer.PART_BODY, SWT.BACKGROUND | SWT.FOREGROUND, bodyRect, gc);

        gc.setFont(gcFont);
        gc.setForeground(gcForeground);
        gc.setBackground(gcBackground);

        renderer.draw(CTabFolderRenderer.PART_HEADER, SWT.BACKGROUND | SWT.FOREGROUND, bodyRect, gc);

        gc.setFont(gcFont);
        gc.setForeground(gcForeground);
        gc.setBackground(gcBackground);

        if (!single) {
            for (int i = 0; i < items.length; i++) {
                Rectangle itemBounds = items[i].getBounds();
                if (i != selectedIndex && event.getBounds().intersects(itemBounds)) {
                    renderer.draw(i, SWT.BACKGROUND | SWT.FOREGROUND | items[i].state, itemBounds, gc);
                }
            }
        }

        gc.setFont(gcFont);
        gc.setForeground(gcForeground);
        gc.setBackground(gcBackground);

        if (selectedIndex != -1) {
            renderer.draw(selectedIndex, items[selectedIndex].state | SWT.BACKGROUND | SWT.FOREGROUND,
                    items[selectedIndex].getBounds(), gc);
        }

        gc.setFont(gcFont);
        gc.setForeground(gcForeground);
        gc.setBackground(gcBackground);

        if (hoverTb) {
            Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BORDER, SWT.NONE, 0, 0, 0, 0);
            int x = getSize().x - (trim.width + trim.x);
            hoverRect = new Rectangle(x - 16 - SPACING, 2, 16, getTabHeight() - 2);
            gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW));
            x = hoverRect.x;
            int y = hoverRect.y;
            gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE));
            gc.fillRectangle(x + hoverRect.width - 6, y, 5, 5);
            gc.drawRectangle(x + hoverRect.width - 6, y, 5, 5);
            gc.drawLine(x + hoverRect.width - 6, y + 2, x + hoverRect.width - 6 + 5, y + 2);
            gc.fillRectangle(x, y, 5, 2);
            gc.drawRectangle(x, y, 5, 2);
        }
        gc.setFont(gcFont);
        gc.setForeground(gcForeground);
        gc.setBackground(gcBackground);
    }

    void onResize(Event event) {
        if (inDispose)
            return;
        if (ignoreResize)
            return;
        if (updateItems()) {
            redrawTabs();
        }
        Point size = getSize();
        if (oldSize == null) {
            redraw();
        } else {
            if (onBottom && size.y != oldSize.y) {
                redraw();
            } else {
                int x1 = Math.min(size.x, oldSize.x);
                Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BODY, SWT.NONE, 0, 0, 0, 0);
                if (size.x != oldSize.x)
                    x1 -= trim.width + trim.x - marginWidth + 2;
                if (!simple)
                    x1 -= 5; // rounded top right corner
                int y1 = Math.min(size.y, oldSize.y);
                if (size.y != oldSize.y)
                    y1 -= trim.height + trim.y - marginHeight;
                int x2 = Math.max(size.x, oldSize.x);
                int y2 = Math.max(size.y, oldSize.y);
                redraw(0, y1, x2, y2 - y1, false);
                redraw(x1, 0, x2 - x1, y2, false);
                if (hoverTb) {
                    redraw(hoverRect.x, hoverRect.y, hoverRect.width, hoverRect.height, false);
                }
            }
        }
        oldSize = size;
    }

    void onSelection(Event event) {
        if (hovering) {
            hovering = false;
            updateItems();
        }
        if (event.widget == maxItem) {
            CTabFolderEvent e = new CTabFolderEvent(this);
            e.widget = CTabFolder.this;
            e.time = event.time;
            for (int i = 0; i < folderListeners.length; i++) {
                if (maximized) {
                    folderListeners[i].restore(e);
                } else {
                    folderListeners[i].maximize(e);
                }
            }
        } else if (event.widget == minItem) {
            CTabFolderEvent e = new CTabFolderEvent(this);
            e.widget = CTabFolder.this;
            e.time = event.time;
            for (int i = 0; i < folderListeners.length; i++) {
                if (minimized) {
                    folderListeners[i].restore(e);
                } else {
                    folderListeners[i].minimize(e);
                }
            }
        } else if (event.widget == chevronItem) {
            Rectangle chevronRect = chevronItem.getBounds();
            chevronRect = event.display.map(chevronTb, this, chevronRect);
            CTabFolderEvent e = new CTabFolderEvent(this);
            e.widget = this;
            e.time = event.time;
            e.x = chevronRect.x;
            e.y = chevronRect.y;
            e.width = chevronRect.width;
            e.height = chevronRect.height;
            e.doit = true;
            for (int i = 0; i < folderListeners.length; i++) {
                folderListeners[i].showList(e);
            }
            if (e.doit && !isDisposed()) {
                showList(chevronRect);
            }
        }
    }

    void onTraverse(Event event) {
        if (ignoreTraverse)
            return;
        runUpdate();
        switch (event.detail) {
        case SWT.TRAVERSE_ESCAPE:
        case SWT.TRAVERSE_RETURN:
        case SWT.TRAVERSE_TAB_NEXT:
        case SWT.TRAVERSE_TAB_PREVIOUS:
            Control focusControl = getDisplay().getFocusControl();
            if (focusControl == this)
                event.doit = true;
            break;
        case SWT.TRAVERSE_MNEMONIC:
            event.doit = onMnemonic(event, false);
            break;
        case SWT.TRAVERSE_PAGE_NEXT:
        case SWT.TRAVERSE_PAGE_PREVIOUS:
            event.doit = items.length > 0;
            break;
        }
        ignoreTraverse = true;
        notifyListeners(SWT.Traverse, event);
        ignoreTraverse = false;
        event.type = SWT.None;
        if (isDisposed())
            return;
        if (!event.doit)
            return;
        switch (event.detail) {
        case SWT.TRAVERSE_MNEMONIC:
            onMnemonic(event, true);
            event.detail = SWT.TRAVERSE_NONE;
            break;
        case SWT.TRAVERSE_PAGE_NEXT:
        case SWT.TRAVERSE_PAGE_PREVIOUS:
            onPageTraversal(event);
            event.detail = SWT.TRAVERSE_NONE;
            break;
        }
    }

    void redrawTabs() {
        Point size = getSize();
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BODY, SWT.NONE, 0, 0, 0, 0);
        if (onBottom) {
            int h = trim.height + trim.y - marginHeight;
            redraw(0, size.y - h - 1, size.x, h + 1, false);
        } else {
            redraw(0, 0, size.x, -trim.y - marginHeight + 1, false);
        }
    }

    /**
     * Removes the listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     *
     * @see #addCTabFolder2Listener(CTabFolder2Listener)
     *
     * @since 3.0
     */
    public void removeCTabFolder2Listener(CTabFolder2Listener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (folderListeners.length == 0)
            return;
        int index = -1;
        for (int i = 0; i < folderListeners.length; i++) {
            if (listener == folderListeners[i]) {
                index = i;
                break;
            }
        }
        if (index == -1)
            return;
        if (folderListeners.length == 1) {
            folderListeners = new CTabFolder2Listener[0];
            return;
        }
        CTabFolder2Listener[] newTabListeners = new CTabFolder2Listener[folderListeners.length - 1];
        System.arraycopy(folderListeners, 0, newTabListeners, 0, index);
        System.arraycopy(folderListeners, index + 1, newTabListeners, index, folderListeners.length - index - 1);
        folderListeners = newTabListeners;
    }

    /**
     * Removes the listener.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     *
     * @deprecated see removeCTabFolderCloseListener(CTabFolderListener)
     */
    @Deprecated
    public void removeCTabFolderListener(CTabFolderListener listener) {
        checkWidget();
        if (listener == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (tabListeners.length == 0)
            return;
        int index = -1;
        for (int i = 0; i < tabListeners.length; i++) {
            if (listener == tabListeners[i]) {
                index = i;
                break;
            }
        }
        if (index == -1)
            return;
        if (tabListeners.length == 1) {
            tabListeners = new CTabFolderListener[0];
            return;
        }
        CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length - 1];
        System.arraycopy(tabListeners, 0, newTabListeners, 0, index);
        System.arraycopy(tabListeners, index + 1, newTabListeners, index, tabListeners.length - index - 1);
        tabListeners = newTabListeners;
    }

    /**
     * Removes the listener from the collection of listeners who will
     * be notified when the user changes the receiver's selection.
     *
     * @param listener the listener which should no longer be notified
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see SelectionListener
     * @see #addSelectionListener
     */
    public void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(SWT.Selection, listener);
        removeListener(SWT.DefaultSelection, listener);
    }

    @Override
    public void reskin(int flags) {
        super.reskin(flags);
        for (int i = 0; i < items.length; i++) {
            items[i].reskin(flags);
        }
    }

    @Override
    public void setBackground(Color color) {
        super.setBackground(color);
        renderer.createAntialiasColors(); //TODO: need better caching strategy
        updateBkImages();
        redraw();
    }

    /**
     * Specify a gradient of colors to be drawn in the background of the unselected tabs.
     * For example to draw a gradient that varies from dark blue to blue and then to
     * white, use the following call to setBackground:
     * <pre>
     *   cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE)},
     *                     new int[] {25, 50, 100});
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient
     *               in order of appearance left to right.  The value <code>null</code> clears the
     *               background gradient. The value <code>null</code> can be used inside the array of
     *               Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width
     *                 of the widget at which the color should change.  The size of the <code>percents</code>
     *                 array must be one less than the size of the <code>colors</code> array.
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @since 3.6
     */
    public void setBackground(Color[] colors, int[] percents) {
        setBackground(colors, percents, false);
    }

    /**
     * Specify a gradient of colors to be drawn in the background of the unselected tab.
     * For example to draw a vertical gradient that varies from dark blue to blue and then to
     * white, use the following call to setBackground:
     * <pre>
     *   cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE)},
     *                        new int[] {25, 50, 100}, true);
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient
     *               in order of appearance left to right.  The value <code>null</code> clears the
     *               background gradient. The value <code>null</code> can be used inside the array of
     *               Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width
     *                 of the widget at which the color should change.  The size of the <code>percents</code>
     *                 array must be one less than the size of the <code>colors</code> array.
     *
     * @param vertical indicate the direction of the gradient. <code>True</code> is vertical and <code>false</code> is horizontal.
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @since 3.6
     */
    public void setBackground(Color[] colors, int[] percents, boolean vertical) {
        checkWidget();
        if (colors != null) {
            if (percents == null || percents.length != colors.length - 1) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
            for (int i = 0; i < percents.length; i++) {
                if (percents[i] < 0 || percents[i] > 100) {
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
                if (i > 0 && percents[i] < percents[i - 1]) {
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
            }
            if (getDisplay().getDepth() < 15) {
                // Don't use gradients on low color displays
                colors = new Color[] { colors[colors.length - 1] };
                percents = new int[] {};
            }
        }

        // Are these settings the same as before?
        if ((gradientColors != null) && (colors != null) && (gradientColors.length == colors.length)) {
            boolean same = false;
            for (int i = 0; i < gradientColors.length; i++) {
                if (gradientColors[i] == null) {
                    same = colors[i] == null;
                } else {
                    same = gradientColors[i].equals(colors[i]);
                }
                if (!same)
                    break;
            }
            if (same) {
                for (int i = 0; i < gradientPercents.length; i++) {
                    same = gradientPercents[i] == percents[i];
                    if (!same)
                        break;
                }
            }
            if (same && this.gradientVertical == vertical)
                return;
        }
        // Store the new settings
        if (colors == null) {
            gradientColors = null;
            gradientPercents = null;
            gradientVertical = false;
            setBackground((Color) null);
        } else {
            gradientColors = new Color[colors.length];
            for (int i = 0; i < colors.length; ++i) {
                gradientColors[i] = colors[i];
            }
            gradientPercents = new int[percents.length];
            for (int i = 0; i < percents.length; ++i) {
                gradientPercents[i] = percents[i];
            }
            gradientVertical = vertical;
            setBackground(gradientColors[gradientColors.length - 1]);
        }

        // Refresh with the new settings
        redraw();
    }

    @Override
    public void setBackgroundImage(Image image) {
        super.setBackgroundImage(image);
        renderer.createAntialiasColors(); //TODO: need better caching strategy
        redraw();
    }

    /**
     * Toggle the visibility of the border
     *
     * @param show true if the border should be displayed
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setBorderVisible(boolean show) {
        checkWidget();
        if (borderVisible == show)
            return;
        this.borderVisible = show;
        updateFolder(REDRAW);
    }

    void setButtonBounds(GC gc) {
        Point size = getSize();
        // max button
        Display display = getDisplay();
        if (showMax) {
            if (minMaxTb == null) {
                minMaxTb = new ToolBar(this, SWT.FLAT);
                initAccessibleMinMaxTb();
                addTabControl(minMaxTb, SWT.TRAIL, 0, false);
            }
            if (maxItem == null) {
                maxItem = new ToolItem(minMaxTb, SWT.PUSH);
                if (maxImage == null) {
                    maxImage = createButtonImage(display, CTabFolderRenderer.PART_MAX_BUTTON);
                }
                maxItem.setImage(maxImage);
                maxItem.setToolTipText(maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize")); //$NON-NLS-1$ //$NON-NLS-2$
                maxItem.addListener(SWT.Selection, listener);
            }
        } else {
            //might need to remove it if already there
            if (maxItem != null) {
                maxItem.dispose();
                maxItem = null;
            }
        }
        // min button
        if (showMin) {
            if (minMaxTb == null) {
                minMaxTb = new ToolBar(this, SWT.FLAT);
                initAccessibleMinMaxTb();
                addTabControl(minMaxTb, SWT.TRAIL, 0, false);
            }
            if (minItem == null) {
                minItem = new ToolItem(minMaxTb, SWT.PUSH, 0);
                if (minImage == null) {
                    minImage = createButtonImage(display, CTabFolderRenderer.PART_MIN_BUTTON);
                }
                minItem.setImage(minImage);
                minItem.setToolTipText(minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize")); //$NON-NLS-1$ //$NON-NLS-2$
                minItem.addListener(SWT.Selection, listener);
            }
        } else {
            //might need to remove it if already there
            if (minItem != null) {
                minItem.dispose();
                minItem = null;
            }
        }
        if (minMaxTb != null && minMaxTb.getItemCount() == 0) {
            removeTabControl(minMaxTb, false);
            minMaxTb.dispose();
            minMaxTb = null;
        }
        if (showChevron) {
            int itemCount = items.length;
            int count;
            if (single) {
                count = selectedIndex == -1 ? itemCount : itemCount - 1;
            } else {
                int showCount = 0;
                while (showCount < priority.length && items[priority[showCount]].showing) {
                    showCount++;
                }
                count = itemCount - showCount;
            }
            if (count != chevronCount) {
                chevronCount = count;
                if (chevronImage != null)
                    chevronImage.dispose();
                chevronImage = createButtonImage(display, CTabFolderRenderer.PART_CHEVRON_BUTTON);
                chevronItem.setImage(chevronImage);
            }
        }

        boolean[][] overflow = new boolean[1][0];
        Rectangle[] rects = computeControlBounds(size, overflow);
        if (fixedTabHeight != SWT.DEFAULT) {
            int height = fixedTabHeight;
            if (!hovering) {
                hoverTb = false;
                Rectangle tabBounds = this.getBounds();
                for (int i = 0; i < rects.length; i++) {
                    if (!(overflow[0][i])) {
                        if (rects[i].height > height) {
                            hoverTb = true;
                            break;
                        }
                    }
                }
                if (hoverTb) {
                    for (int i = 0; i < rects.length; i++) {
                        if (!(overflow[0][i])) {
                            if (rects[i].height > height) {
                                rects[i].x = tabBounds.width + 20;
                            }
                        }
                    }
                }
            }
        }
        int headerHeight = 0;
        for (int i = 0; i < rects.length; i++) {
            if (!overflow[0][i])
                headerHeight = Math.max(rects[i].height, headerHeight);
        }
        boolean changed = false;
        ignoreResize = true;
        for (int i = 0; i < controls.length; i++) {
            if (!controls[i].isDisposed()) {
                if (overflow[0][i]) {
                    controls[i].setBounds(rects[i]);
                } else {
                    controls[i].moveAbove(null);
                    controls[i].setBounds(rects[i].x, rects[i].y, rects[i].width, headerHeight);
                }
            }
            if (!changed && !rects[i].equals(controlRects[i]))
                changed = true;
        }
        ignoreResize = false;
        controlRects = rects;
        if (changed || hovering)
            updateBkImages();
    }

    @Override
    public boolean setFocus() {
        checkWidget();

        /*
        * Feature in SWT.  When a new tab item is selected
        * and the previous tab item had focus, removing focus
        * from the previous tab item causes fixFocus() to give
        * focus to the first child, which is usually one of the
        * toolbars. This is unexpected.
        * The fix is to try to set focus on the first tab item
        * if fixFocus() is called.
        */
        Control focusControl = getDisplay().getFocusControl();
        boolean fixFocus = isAncestor(focusControl);
        if (fixFocus) {
            CTabItem item = getSelection();
            if (item != null) {
                if (item.setFocus())
                    return true;
            }
        }
        return super.setFocus();
    }

    /* Copy of isFocusAncestor from Control. */
    boolean isAncestor(Control control) {
        while (control != null && control != this && !(control instanceof Shell)) {
            control = control.getParent();
        }
        return control == this;
    }

    @Override
    public void setFont(Font font) {
        checkWidget();
        if (font != null && font.equals(getFont()))
            return;
        super.setFont(font);
        oldFont = getFont();
        updateFolder(REDRAW);
    }

    @Override
    public void setForeground(Color color) {
        super.setForeground(color);
        redraw();
    }

    /**
     * Display an insert marker before or after the specified tab item.
     *
     * A value of null will clear the mark.
     *
     * @param item the item with which the mark is associated or null
     *
     * @param after true if the mark should be displayed after the specified item
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setInsertMark(CTabItem item, boolean after) {
        checkWidget();
    }

    /**
     * Display an insert marker before or after the specified tab item.
     *
     * A value of -1 will clear the mark.
     *
     * @param index the index of the item with which the mark is associated or -1
     *
     * @param after true if the mark should be displayed after the specified item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setInsertMark(int index, boolean after) {
        checkWidget();
        if (index < -1 || index >= getItemCount()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
    }

    boolean setItemLocation(GC gc) {
        boolean changed = false;
        if (items.length == 0)
            return false;
        Rectangle trim = renderer.computeTrim(CTabFolderRenderer.PART_BORDER, SWT.NONE, 0, 0, 0, 0);
        int borderBottom = trim.height + trim.y;
        int borderTop = -trim.y;
        Point size = getSize();
        int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop;
        Point closeButtonSize = renderer.computeSize(CTabFolderRenderer.PART_CLOSE_BUTTON, 0, gc, SWT.DEFAULT,
                SWT.DEFAULT);
        int leftItemEdge = getLeftItemEdge(gc, CTabFolderRenderer.PART_BORDER);
        if (single) {
            int defaultX = getDisplay().getBounds().width + 10; // off screen
            for (int i = 0; i < items.length; i++) {
                CTabItem item = items[i];
                if (i == selectedIndex) {
                    firstIndex = selectedIndex;
                    int oldX = item.x, oldY = item.y;
                    item.x = leftItemEdge;
                    item.y = y;
                    item.showing = true;
                    if (showClose || item.showClose) {
                        item.closeRect.x = leftItemEdge - renderer.computeTrim(i, SWT.NONE, 0, 0, 0, 0).x;
                        item.closeRect.y = onBottom
                                ? size.y - borderBottom - tabHeight + (tabHeight - closeButtonSize.y) / 2
                                : borderTop + (tabHeight - closeButtonSize.y) / 2;
                    }
                    if (item.x != oldX || item.y != oldY)
                        changed = true;
                } else {
                    item.x = defaultX;
                    item.showing = false;
                }
            }
        } else {
            int rightItemEdge = getRightItemEdge(gc);
            int maxWidth = rightItemEdge - leftItemEdge;
            int width = 0;
            for (int i = 0; i < priority.length; i++) {
                CTabItem item = items[priority[i]];
                width += item.width;
                item.showing = i == 0 ? true : item.width > 0 && width <= maxWidth;
            }
            int x = getLeftItemEdge(gc, CTabFolderRenderer.PART_HEADER);
            int defaultX = getDisplay().getBounds().width + 10; // off screen
            firstIndex = items.length - 1;
            for (int i = 0; i < items.length; i++) {
                CTabItem item = items[i];
                if (!item.showing) {
                    if (item.x != defaultX)
                        changed = true;
                    item.x = defaultX;
                } else {
                    firstIndex = Math.min(firstIndex, i);
                    if (item.x != x || item.y != y)
                        changed = true;
                    item.x = x;
                    item.y = y;
                    int state = SWT.NONE;
                    if (i == selectedIndex)
                        state |= SWT.SELECTED;
                    Rectangle edgeTrim = renderer.computeTrim(i, state, 0, 0, 0, 0);
                    item.closeRect.x = item.x + item.width - (edgeTrim.width + edgeTrim.x) - closeButtonSize.x;
                    item.closeRect.y = onBottom
                            ? size.y - borderBottom - tabHeight + (tabHeight - closeButtonSize.y) / 2
                            : borderTop + (tabHeight - closeButtonSize.y) / 2;
                    x = x + item.width;
                    if (!simple && i == selectedIndex)
                        x -= renderer.curveIndent; //TODO: fix next item position
                }
            }
        }
        return changed;
    }

    /**
     * Reorder the items of the receiver.
     * @param indices an array containing the new indices for all items
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the indices array is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the indices array is not the same length as the number of items,
     *    if there are duplicate indices or an index is out of range.</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    /*public*/ void setItemOrder(int[] indices) {
        checkWidget();
        if (indices == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (indices.length != items.length)
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        int newSelectedIndex = -1;
        boolean[] seen = new boolean[items.length];
        CTabItem[] temp = new CTabItem[items.length];
        for (int i = 0; i < indices.length; i++) {
            int index = indices[i];
            if (!(0 <= index && index < items.length))
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            if (seen[index])
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            seen[index] = true;
            if (index == selectedIndex)
                newSelectedIndex = i;
            temp[i] = items[index];
        }
        items = temp;
        selectedIndex = newSelectedIndex;
        updateFolder(REDRAW);
    }

    boolean setItemSize(GC gc) {
        boolean changed = false;
        if (isDisposed())
            return changed;
        Point size = getSize();
        if (size.x <= 0 || size.y <= 0)
            return changed;
        ToolBar chevron = getChevron();
        if (chevron != null)
            chevron.setVisible(false);
        showChevron = false;
        if (single) {
            showChevron = chevronVisible && items.length > 1;
            if (showChevron) {
                chevron.setVisible(true);
            }
            if (selectedIndex != -1) {
                CTabItem tab = items[selectedIndex];
                int width = renderer.computeSize(selectedIndex, SWT.SELECTED, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                width = Math.min(width, getRightItemEdge(gc) - getLeftItemEdge(gc, CTabFolderRenderer.PART_BORDER));
                if (tab.height != tabHeight || tab.width != width) {
                    changed = true;
                    tab.shortenedText = null;
                    tab.shortenedTextWidth = 0;
                    tab.height = tabHeight;
                    tab.width = width;
                    tab.closeRect.width = tab.closeRect.height = 0;
                    if (showClose || tab.showClose) {
                        Point closeSize = renderer.computeSize(CTabFolderRenderer.PART_CLOSE_BUTTON, SWT.SELECTED,
                                gc, SWT.DEFAULT, SWT.DEFAULT);
                        tab.closeRect.width = closeSize.x;
                        tab.closeRect.height = closeSize.y;
                    }
                }
            }
            return changed;
        }

        if (items.length == 0)
            return changed;
        int[] widths;
        int tabAreaWidth = Math.max(0, getRightItemEdge(gc) - getLeftItemEdge(gc, CTabFolderRenderer.PART_BORDER));
        // First, try the minimum tab size at full compression.
        int minWidth = 0;
        int[] minWidths = new int[items.length];
        for (int i = 0; i < priority.length; i++) {
            int index = priority[i];
            int state = CTabFolderRenderer.MINIMUM_SIZE;
            if (index == selectedIndex)
                state |= SWT.SELECTED;
            minWidths[index] = renderer.computeSize(index, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
            minWidth += minWidths[index];
            if (minWidth > tabAreaWidth)
                break;
        }
        if (minWidth > tabAreaWidth) {
            // full compression required and a chevron
            showChevron = chevronVisible && items.length > 1;
            if (showChevron) {
                tabAreaWidth -= chevron.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
                chevron.setVisible(true);
            }
            widths = minWidths;
            int index = selectedIndex != -1 ? selectedIndex : 0;
            if (tabAreaWidth < widths[index]) {
                widths[index] = Math.max(0, tabAreaWidth);
            }
        } else {
            int maxWidth = 0;
            int[] maxWidths = new int[items.length];
            for (int i = 0; i < items.length; i++) {
                int state = 0;
                if (i == selectedIndex)
                    state |= SWT.SELECTED;
                maxWidths[i] = renderer.computeSize(i, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                maxWidth += maxWidths[i];
            }
            if (maxWidth <= tabAreaWidth) {
                // no compression required
                widths = maxWidths;
            } else {
                // determine compression for each item
                int extra = (tabAreaWidth - minWidth) / items.length;
                while (true) {
                    int large = 0, totalWidth = 0;
                    for (int i = 0; i < items.length; i++) {
                        if (maxWidths[i] > minWidths[i] + extra) {
                            totalWidth += minWidths[i] + extra;
                            large++;
                        } else {
                            totalWidth += maxWidths[i];
                        }
                    }
                    if (totalWidth >= tabAreaWidth) {
                        extra--;
                        break;
                    }
                    if (large == 0 || tabAreaWidth - totalWidth < large)
                        break;
                    extra++;
                }
                widths = new int[items.length];
                for (int i = 0; i < items.length; i++) {
                    widths[i] = Math.min(maxWidths[i], minWidths[i] + extra);
                }
            }
        }

        for (int i = 0; i < items.length; i++) {
            CTabItem tab = items[i];
            int width = widths[i];
            if (tab.height != tabHeight || tab.width != width) {
                changed = true;
                tab.shortenedText = null;
                tab.shortenedTextWidth = 0;
                tab.height = tabHeight;
                tab.width = width;
                tab.closeRect.width = tab.closeRect.height = 0;
                if (showClose || tab.showClose) {
                    if (i == selectedIndex || showUnselectedClose) {
                        Point closeSize = renderer.computeSize(CTabFolderRenderer.PART_CLOSE_BUTTON, SWT.NONE, gc,
                                SWT.DEFAULT, SWT.DEFAULT);
                        tab.closeRect.width = closeSize.x;
                        tab.closeRect.height = closeSize.y;
                    }
                }
            }
        }
        return changed;
    }

    /**
     * Marks the receiver's maximize button as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     *
     * @param visible the new visibility state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setMaximizeVisible(boolean visible) {
        checkWidget();
        if (showMax == visible)
            return;
        // display maximize button
        showMax = visible;
        updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
    }

    /**
     * Sets the layout which is associated with the receiver to be
     * the argument which may be null.
     * <p>
     * Note: No Layout can be set on this Control because it already
     * manages the size and position of its children.
     * </p>
     *
     * @param layout the receiver's new layout or null
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    @Override
    public void setLayout(Layout layout) {
        checkWidget();
        return;
    }

    /**
     * Sets the maximized state of the receiver.
     *
     * @param maximize the new maximized state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setMaximized(boolean maximize) {
        checkWidget();
        if (this.maximized == maximize)
            return;
        if (maximize && this.minimized)
            setMinimized(false);
        this.maximized = maximize;
        if (minMaxTb != null && maxItem != null) {
            if (maxImage != null)
                maxImage.dispose();
            maxImage = createButtonImage(getDisplay(), CTabFolderRenderer.PART_MAX_BUTTON);
            maxItem.setImage(maxImage);
            maxItem.setToolTipText(maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize")); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    /**
     * Marks the receiver's minimize button as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     *
     * @param visible the new visibility state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setMinimizeVisible(boolean visible) {
        checkWidget();
        if (showMin == visible)
            return;
        // display minimize button
        showMin = visible;
        updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
    }

    /**
     * Sets the minimized state of the receiver.
     *
     * @param minimize the new minimized state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setMinimized(boolean minimize) {
        checkWidget();
        if (this.minimized == minimize)
            return;
        if (minimize && this.maximized)
            setMaximized(false);
        this.minimized = minimize;
        if (minMaxTb != null && minItem != null) {
            if (minImage != null)
                minImage.dispose();
            minImage = createButtonImage(getDisplay(), CTabFolderRenderer.PART_MIN_BUTTON);
            minItem.setImage(minImage);
            minItem.setToolTipText(minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize")); //$NON-NLS-1$ //$NON-NLS-2$
        }
    }

    /**
     * Sets the minimum number of characters that will
     * be displayed in a fully compressed tab.
     *
     * @param count the minimum number of characters that will be displayed in a fully compressed tab
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_RANGE - if the count is less than zero</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setMinimumCharacters(int count) {
        checkWidget();
        if (count < 0)
            SWT.error(SWT.ERROR_INVALID_RANGE);
        if (minChars == count)
            return;
        minChars = count;
        updateFolder(REDRAW_TABS);
    }

    /**
     * When there is not enough horizontal space to show all the tabs,
     * by default, tabs are shown sequentially from left to right in
     * order of their index.  When the MRU visibility is turned on,
     * the tabs that are visible will be the tabs most recently selected.
     * Tabs will still maintain their left to right order based on index
     * but only the most recently selected tabs are visible.
     * <p>
     * For example, consider a CTabFolder that contains "Tab 1", "Tab 2",
     * "Tab 3" and "Tab 4" (in order by index).  The user selects
     * "Tab 1" and then "Tab 3".  If the CTabFolder is now
     * compressed so that only two tabs are visible, by default,
     * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently
     * selected and "Tab 2" because it is the previous item in index order).
     * If MRU visibility is enabled, the two visible tabs will be "Tab 1"
     * and "Tab 3" (in that order from left to right).</p>
     *
     * @param show the new visibility state
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.1
     */
    public void setMRUVisible(boolean show) {
        checkWidget();
        if (mru == show)
            return;
        mru = show;
        if (!mru) {
            if (firstIndex == -1)
                return;
            int idx = firstIndex;
            int next = 0;
            for (int i = firstIndex; i < items.length; i++) {
                priority[next++] = i;
            }
            for (int i = 0; i < idx; i++) {
                priority[next++] = i;
            }
            updateFolder(REDRAW_TABS);
        }
    }

    /**
     * Sets the renderer which is associated with the receiver to be
     * the argument which may be null. In the case of null, the default
     * renderer is used.
     *
     * @param renderer a new renderer
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @see CTabFolderRenderer
     *
     * @since 3.6
     */
    public void setRenderer(CTabFolderRenderer renderer) {
        checkWidget();
        if (this.renderer == renderer || (useDefaultRenderer && renderer == null))
            return;
        if (this.renderer != null)
            this.renderer.dispose();
        useDefaultRenderer = renderer == null;
        if (useDefaultRenderer)
            renderer = new CTabFolderRenderer(this);
        this.renderer = renderer;
        updateFolder(REDRAW);
    }

    /**
     * Set the selection to the tab at the specified item.
     *
     * @param item the tab item to be selected
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     * </ul>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     */
    public void setSelection(CTabItem item) {
        checkWidget();
        if (item == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        int index = indexOf(item);
        setSelection(index);
    }

    /**
     * Set the selection to the tab at the specified index.
     *
     * @param index the index of the tab item to be selected
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelection(int index) {
        checkWidget();
        if (index < 0 || index >= items.length)
            return;
        CTabItem selection = items[index];
        if (selectedIndex == index) {
            showItem(selection);
            return;
        }

        int oldIndex = selectedIndex;
        selectedIndex = index;
        if (oldIndex != -1) {
            items[oldIndex].closeImageState = SWT.BACKGROUND;
            items[oldIndex].state &= ~SWT.SELECTED;
        }
        selection.closeImageState = SWT.NONE;
        selection.showing = false;
        selection.state |= SWT.SELECTED;

        Control newControl = selection.control;
        Control oldControl = null;
        if (oldIndex != -1) {
            oldControl = items[oldIndex].control;
        }

        if (newControl != oldControl) {
            if (newControl != null && !newControl.isDisposed()) {
                newControl.setBounds(getClientArea());
                newControl.setVisible(true);
            }
            if (oldControl != null && !oldControl.isDisposed()) {
                oldControl.setVisible(false);
            }
        }
        showItem(selection);
        redraw();
    }

    void setSelection(int index, boolean notify) {
        int oldSelectedIndex = selectedIndex;
        setSelection(index);
        if (notify && selectedIndex != oldSelectedIndex && selectedIndex != -1) {
            Event event = new Event();
            event.item = getItem(selectedIndex);
            notifyListeners(SWT.Selection, event);
        }
    }

    /**
     * Sets the receiver's selection background color to the color specified
     * by the argument, or to the default system color for the control
     * if the argument is null.
     *
     * @param color the new color (or null)
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setSelectionBackground(Color color) {
        if (inDispose)
            return;
        checkWidget();
        setSelectionHighlightGradientColor(null);
        if (selectionBackground == color)
            return;
        if (color == null)
            color = getDisplay().getSystemColor(SELECTION_BACKGROUND);
        selectionBackground = color;
        renderer.createAntialiasColors(); //TODO:  need better caching strategy
        if (selectedIndex > -1)
            redraw();
    }

    /**
     * Specify a gradient of colours to be draw in the background of the selected tab.
     * For example to draw a gradient that varies from dark blue to blue and then to
     * white, use the following call to setBackground:
     * <pre>
     *   cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE)},
     *                     new int[] {25, 50, 100});
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient
     *               in order of appearance left to right.  The value <code>null</code> clears the
     *               background gradient. The value <code>null</code> can be used inside the array of
     *               Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width
     *                 of the widget at which the color should change.  The size of the percents array must be one
     *                 less than the size of the colors array.
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     */
    public void setSelectionBackground(Color[] colors, int[] percents) {
        setSelectionBackground(colors, percents, false);
    }

    /**
     * Specify a gradient of colours to be draw in the background of the selected tab.
     * For example to draw a vertical gradient that varies from dark blue to blue and then to
     * white, use the following call to setBackground:
     * <pre>
     *   cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_BLUE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE),
     *                                 display.getSystemColor(SWT.COLOR_WHITE)},
     *                        new int[] {25, 50, 100}, true);
     * </pre>
     *
     * @param colors an array of Color that specifies the colors to appear in the gradient
     *               in order of appearance left to right.  The value <code>null</code> clears the
     *               background gradient. The value <code>null</code> can be used inside the array of
     *               Color to specify the background color.
     * @param percents an array of integers between 0 and 100 specifying the percent of the width
     *                 of the widget at which the color should change.  The size of the percents array must be one
     *                 less than the size of the colors array.
     *
     * @param vertical indicate the direction of the gradient.  True is vertical and false is horizontal.
     *
     * @exception SWTException <ul>
     *      <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *      <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     *   </ul>
     *
     * @since 3.0
     */
    public void setSelectionBackground(Color[] colors, int[] percents, boolean vertical) {
        checkWidget();
        int colorsLength;
        Color highlightBeginColor = null; //null == no highlight

        if (colors != null) {
            //The colors array can optionally have an extra entry which describes the highlight top color
            //Thus its either one or two larger than the percents array
            if (percents == null
                    || !((percents.length == colors.length - 1) || (percents.length == colors.length - 2))) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
            for (int i = 0; i < percents.length; i++) {
                if (percents[i] < 0 || percents[i] > 100) {
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
                if (i > 0 && percents[i] < percents[i - 1]) {
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
            }
            //If the colors is exactly two more than percents then last is highlight
            //Keep track of *real* colorsLength (minus the highlight)
            if (percents.length == colors.length - 2) {
                highlightBeginColor = colors[colors.length - 1];
                colorsLength = colors.length - 1;
            } else {
                colorsLength = colors.length;
            }
            if (getDisplay().getDepth() < 15) {
                // Don't use gradients on low color displays
                colors = new Color[] { colors[colorsLength - 1] };
                colorsLength = colors.length;
                percents = new int[] {};
            }
        } else {
            colorsLength = 0;
        }

        // Are these settings the same as before?
        if (selectionBgImage == null) {
            if ((selectionGradientColors != null) && (colors != null)
                    && (selectionGradientColors.length == colorsLength)) {
                boolean same = false;
                for (int i = 0; i < selectionGradientColors.length; i++) {
                    if (selectionGradientColors[i] == null) {
                        same = colors[i] == null;
                    } else {
                        same = selectionGradientColors[i].equals(colors[i]);
                    }
                    if (!same)
                        break;
                }
                if (same) {
                    for (int i = 0; i < selectionGradientPercents.length; i++) {
                        same = selectionGradientPercents[i] == percents[i];
                        if (!same)
                            break;
                    }
                }
                if (same && this.selectionGradientVertical == vertical)
                    return;
            }
        } else {
            selectionBgImage = null;
        }
        // Store the new settings
        if (colors == null) {
            selectionGradientColors = null;
            selectionGradientPercents = null;
            selectionGradientVertical = false;
            setSelectionBackground((Color) null);
            setSelectionHighlightGradientColor(null);
        } else {
            selectionGradientColors = new Color[colorsLength];
            for (int i = 0; i < colorsLength; ++i) {
                selectionGradientColors[i] = colors[i];
            }
            selectionGradientPercents = new int[percents.length];
            for (int i = 0; i < percents.length; ++i) {
                selectionGradientPercents[i] = percents[i];
            }
            selectionGradientVertical = vertical;
            setSelectionBackground(selectionGradientColors[selectionGradientColors.length - 1]);
            setSelectionHighlightGradientColor(highlightBeginColor);
        }

        // Refresh with the new settings
        if (selectedIndex > -1)
            redraw();
    }

    /*
     * Set the color for the highlight start for selected tabs.
     * Update the cache of highlight gradient colors if required.
     */
    void setSelectionHighlightGradientColor(Color start) {
        if (inDispose)
            return;
        renderer.setSelectionHighlightGradientColor(start); //TODO: need better caching strategy
    }

    /**
     * Set the image to be drawn in the background of the selected tab.  Image
     * is stretched or compressed to cover entire selection tab area.
     *
     * @param image the image to be drawn in the background
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelectionBackground(Image image) {
        checkWidget();
        setSelectionHighlightGradientColor(null);
        if (image == selectionBgImage)
            return;
        if (image != null) {
            selectionGradientColors = null;
            selectionGradientPercents = null;
            renderer.disposeSelectionHighlightGradientColors(); //TODO: need better caching strategy
        }
        selectionBgImage = image;
        renderer.createAntialiasColors(); //TODO:  need better caching strategy
        if (selectedIndex > -1)
            redraw();
    }

    /**
     * Set the foreground color of the selected tab.
     *
     * @param color the color of the text displayed in the selected tab
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelectionForeground(Color color) {
        checkWidget();
        if (selectionForeground == color)
            return;
        if (color == null)
            color = getDisplay().getSystemColor(SELECTION_FOREGROUND);
        selectionForeground = color;
        if (selectedIndex > -1)
            redraw();
    }

    /**
     * Sets the shape that the CTabFolder will use to render itself.
     *
     * @param simple <code>true</code> if the CTabFolder should render itself in a simple, traditional style
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setSimple(boolean simple) {
        checkWidget();
        if (this.simple != simple) {
            this.simple = simple;
            updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
        }
    }

    /**
     * Sets the number of tabs that the CTabFolder should display
     *
     * @param single <code>true</code> if only the selected tab should be displayed otherwise, multiple tabs will be shown.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setSingle(boolean single) {
        checkWidget();
        if (this.single != single) {
            this.single = single;
            if (!single) {
                for (int i = 0; i < items.length; i++) {
                    if (i != selectedIndex && items[i].closeImageState == SWT.NONE) {
                        items[i].closeImageState = SWT.BACKGROUND;
                    }
                }
            }
            updateFolder(REDRAW);
        }
    }

    int getControlY(Point size, Rectangle[] rects, int borderBottom, int borderTop, int i) {
        int center = fixedTabHeight != SWT.DEFAULT ? 0 : (tabHeight - rects[i].height) / 2;
        return onBottom ? size.y - borderBottom - tabHeight + center : 1 + borderTop + center;
    }

    /**
     * Specify a fixed height for the tab items.  If no height is specified,
     * the default height is the height of the text or the image, whichever
     * is greater. Specifying a height of -1 will revert to the default height.
     *
     * @param height the point value of the height or -1
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if called with a height of less than 0</li>
     * </ul>
     */
    public void setTabHeight(int height) {
        checkWidget();
        if (height < -1) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        fixedTabHeight = height;
        updateFolder(UPDATE_TAB_HEIGHT);
    }

    /**
     * Specify whether the tabs should appear along the top of the folder
     * or along the bottom of the folder.
     *
     * @param position <code>SWT.TOP</code> for tabs along the top or <code>SWT.BOTTOM</code> for tabs along the bottom
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the position value is not either SWT.TOP or SWT.BOTTOM</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setTabPosition(int position) {
        checkWidget();
        if (position != SWT.TOP && position != SWT.BOTTOM) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (onBottom != (position == SWT.BOTTOM)) {
            onBottom = position == SWT.BOTTOM;
            updateFolder(REDRAW);
        }
    }

    /**
     * Set the control that appears in the top right corner of the tab folder.
     * Typically this is a close button or a composite with a Menu and close button.
     * The topRight control is optional.  Setting the top right control to null will
     * remove it from the tab folder.
     *
     * @param control the control to be displayed in the top right corner or null
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the control is disposed, or not a child of this CTabFolder</li>
     * </ul>
     *
     * @since 2.1
     */
    public void setTopRight(Control control) {
        setTopRight(control, SWT.RIGHT);
    }

    /**
     * Set the control that appears in the top right corner of the tab folder.
     * Typically this is a close button or a composite with a Menu and close button.
     * The topRight control is optional.  Setting the top right control to null
     * will remove it from the tab folder.
     * <p>
     * The alignment parameter sets the layout of the control in the tab area.
     * <code>SWT.RIGHT</code> will cause the control to be positioned on the far
     * right of the folder and it will have its default size.  <code>SWT.FILL</code>
     * will size the control to fill all the available space to the right of the
     * last tab.  If there is no available space, the control will not be visible.
     * <code>SWT.RIGHT | SWT.WRAP</code> will allow the control to wrap below the
     * tabs if there is not enough available space to the right of the last tab.
     * </p>
     *
     * @param control the control to be displayed in the top right corner or null
     * @param alignment <code>SWT.RIGHT</code> or <code>SWT.FILL</code> or <code>SWT.RIGHT | SWT.WRAP</code>
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the control is disposed, or not a child of this CTabFolder</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setTopRight(Control control, int alignment) {
        checkWidget();
        if (alignment != SWT.RIGHT && alignment != SWT.FILL && alignment != (SWT.RIGHT | SWT.WRAP)) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (control != null && (control.isDisposed() || control.getParent() != this)) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (topRight == control && topRightAlignment == alignment)
            return;
        if (topRight != null && !topRight.isDisposed())
            removeTabControl(topRight, false);
        topRight = control;
        topRightAlignment = alignment;
        alignment &= ~SWT.RIGHT;
        if (control != null)
            addTabControl(control, SWT.TRAIL | alignment, -1, false);
        updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
    }

    /**
     * Specify whether the close button appears
     * when the user hovers over an unselected tabs.
     *
     * @param visible <code>true</code> makes the close button appear
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setUnselectedCloseVisible(boolean visible) {
        checkWidget();
        if (showUnselectedClose == visible)
            return;
        // display close button when mouse hovers
        showUnselectedClose = visible;
        updateFolder(REDRAW);
    }

    /**
     * Specify whether the image appears on unselected tabs.
     *
     * @param visible <code>true</code> makes the image appear
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.0
     */
    public void setUnselectedImageVisible(boolean visible) {
        checkWidget();
        if (showUnselectedImage == visible)
            return;
        // display image on unselected items
        showUnselectedImage = visible;
        updateFolder(REDRAW);
    }

    /**
     * Shows the item.  If the item is already showing in the receiver,
     * this method simply returns.  Otherwise, the items are scrolled until
     * the item is visible.
     *
     * @param item the item to be shown
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see CTabFolder#showSelection()
     *
     * @since 2.0
     */
    public void showItem(CTabItem item) {
        checkWidget();
        if (item == null)
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        if (item.isDisposed())
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        int index = indexOf(item);
        if (index == -1)
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        int idx = -1;
        for (int i = 0; i < priority.length; i++) {
            if (priority[i] == index) {
                idx = i;
                break;
            }
        }
        if (mru) {
            // move to front of mru order
            int[] newPriority = new int[priority.length];
            System.arraycopy(priority, 0, newPriority, 1, idx);
            System.arraycopy(priority, idx + 1, newPriority, idx + 1, priority.length - idx - 1);
            newPriority[0] = index;
            priority = newPriority;
        }
        if (item.showing)
            return;
        updateFolder(REDRAW_TABS);
    }

    void showList(Rectangle rect) {
        if (items.length == 0 || !showChevron)
            return;
        if (showMenu == null || showMenu.isDisposed()) {
            showMenu = new Menu(getShell(), getStyle() & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT));
        } else {
            MenuItem[] items = showMenu.getItems();
            for (int i = 0; i < items.length; i++) {
                items[i].dispose();
            }
        }
        final String id = "CTabFolder_showList_Index"; //$NON-NLS-1$
        for (int i = 0; i < items.length; i++) {
            CTabItem tab = items[i];
            if (tab.showing)
                continue;
            MenuItem item = new MenuItem(showMenu, SWT.NONE);
            // Bug 533124 In the case where you have multi line tab text, we force the drop-down menu to have single line entries to ensure consistent behavior across platforms.
            item.setText(tab.getText().replace("\n", " "));
            item.setImage(tab.getImage());
            item.setData(id, tab);
            item.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    MenuItem menuItem = (MenuItem) e.widget;
                    int index = indexOf((CTabItem) menuItem.getData(id));
                    CTabFolder.this.setSelection(index, true);
                }
            });
        }
        int x = rect.x;
        int y = rect.y + rect.height;
        Point location = getDisplay().map(this, null, x, y);
        showMenu.setLocation(location.x, location.y);
        showMenu.setVisible(true);
    }

    /**
     * Shows the selection.  If the selection is already showing in the receiver,
     * this method simply returns.  Otherwise, the items are scrolled until
     * the selection is visible.
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @see CTabFolder#showItem(CTabItem)
     *
     * @since 2.0
     */
    public void showSelection() {
        checkWidget();
        if (selectedIndex != -1) {
            showItem(getSelection());
        }
    }

    void _setToolTipText(int x, int y) {
        String oldTip = getToolTipText();
        String newTip = _getToolTip(x, y);
        if (newTip == null || !newTip.equals(oldTip)) {
            setToolTipText(newTip);
        }
    }

    boolean updateItems() {
        return updateItems(selectedIndex);
    }

    boolean updateItems(int showIndex) {
        GC gc = new GC(this);
        if (!single && !mru && showIndex != -1) {
            // make sure selected item will be showing
            int firstIndex = showIndex;
            if (priority[0] < showIndex) {
                int maxWidth = getRightItemEdge(gc) - getLeftItemEdge(gc, CTabFolderRenderer.PART_BORDER);
                int width = 0;
                int[] widths = new int[items.length];
                for (int i = priority[0]; i <= showIndex; i++) {
                    int state = CTabFolderRenderer.MINIMUM_SIZE;
                    if (i == selectedIndex)
                        state |= SWT.SELECTED;
                    widths[i] = renderer.computeSize(i, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                    width += widths[i];
                    if (width > maxWidth)
                        break;
                }
                if (width > maxWidth) {
                    width = 0;
                    for (int i = showIndex; i >= 0; i--) {
                        int state = CTabFolderRenderer.MINIMUM_SIZE;
                        if (i == selectedIndex)
                            state |= SWT.SELECTED;
                        if (widths[i] == 0)
                            widths[i] = renderer.computeSize(i, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                        width += widths[i];
                        if (width > maxWidth)
                            break;
                        firstIndex = i;
                    }
                } else {
                    firstIndex = priority[0];
                    for (int i = showIndex + 1; i < items.length; i++) {
                        int state = CTabFolderRenderer.MINIMUM_SIZE;
                        if (i == selectedIndex)
                            state |= SWT.SELECTED;
                        widths[i] = renderer.computeSize(i, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                        width += widths[i];
                        if (width >= maxWidth)
                            break;
                    }
                    if (width < maxWidth) {
                        for (int i = priority[0] - 1; i >= 0; i--) {
                            int state = CTabFolderRenderer.MINIMUM_SIZE;
                            if (i == selectedIndex)
                                state |= SWT.SELECTED;
                            if (widths[i] == 0)
                                widths[i] = renderer.computeSize(i, state, gc, SWT.DEFAULT, SWT.DEFAULT).x;
                            width += widths[i];
                            if (width > maxWidth)
                                break;
                            firstIndex = i;
                        }
                    }
                }

            }
            if (firstIndex != priority[0]) {
                int index = 0;
                // enumerate tabs from first visible to the last existing one (sorted ascending)
                for (int i = firstIndex; i < items.length; i++) {
                    priority[index++] = i;
                }
                // enumerate hidden tabs on the left hand from first visible one
                // in the inverse order (sorted descending) so that the originally
                // first opened tab is always at the end of the list
                for (int i = firstIndex - 1; i >= 0; i--) {
                    priority[index++] = i;
                }
            }
        }

        boolean oldShowChevron = showChevron;
        boolean changed = setItemSize(gc);
        changed |= setItemLocation(gc);
        setButtonBounds(gc);
        changed |= showChevron != oldShowChevron;
        if (changed && getToolTipText() != null) {
            Point pt = getDisplay().getCursorLocation();
            pt = toControl(pt);
            _setToolTipText(pt.x, pt.y);
        }
        gc.dispose();
        return changed;
    }

    boolean updateTabHeight(boolean force) {
        int oldHeight = tabHeight;
        GC gc = new GC(this);
        tabHeight = renderer.computeSize(CTabFolderRenderer.PART_HEADER, SWT.NONE, gc, SWT.DEFAULT, SWT.DEFAULT).y;
        gc.dispose();
        if (fixedTabHeight == SWT.DEFAULT && controls != null && controls.length > 0) {
            for (int i = 0; i < controls.length; i++) {
                if ((controlAlignments[i] & SWT.WRAP) == 0 && !controls[i].isDisposed()
                        && controls[i].getVisible()) {
                    int topHeight = controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
                    topHeight += renderer.computeTrim(CTabFolderRenderer.PART_HEADER, SWT.NONE, 0, 0, 0, 0).height
                            + 1;
                    tabHeight = Math.max(topHeight, tabHeight);
                }
            }
        }
        if (!force && tabHeight == oldHeight)
            return false;
        oldSize = null;
        return true;
    }

    void updateFolder(int flags) {
        updateFlags |= flags;
        if (updateRun != null)
            return;
        updateRun = () -> {
            updateRun = null;
            if (isDisposed())
                return;
            runUpdate();
        };
        getDisplay().asyncExec(updateRun);
    }

    void runUpdate() {
        if (updateFlags == 0)
            return;
        int flags = updateFlags;
        updateFlags = 0;
        Rectangle rectBefore = getClientArea();
        updateTabHeight(false);
        updateItems(selectedIndex);
        if ((flags & REDRAW) != 0) {
            redraw();
        } else if ((flags & REDRAW_TABS) != 0) {
            redrawTabs();
        }
        Rectangle rectAfter = getClientArea();
        if (!rectBefore.equals(rectAfter)) {
            notifyListeners(SWT.Resize, new Event());
            layout();
        }
    }

    void updateBkImages() {
        if (controls != null && controls.length > 0) {
            for (int i = 0; i < controls.length; i++) {
                Control control = controls[i];
                if (!control.isDisposed()) {
                    if (hovering) {
                        if (control instanceof Composite)
                            ((Composite) control).setBackgroundMode(SWT.INHERIT_NONE);
                        control.setBackgroundImage(null);
                        control.setBackground(getBackground());
                    } else {
                        if (control instanceof Composite)
                            ((Composite) control).setBackgroundMode(SWT.INHERIT_DEFAULT);
                        Rectangle bounds = control.getBounds();
                        int tabHeight = getTabHeight();
                        int height = this.getSize().y;
                        boolean wrapped = onBottom ? bounds.y + bounds.height < height - tabHeight
                                : bounds.y > tabHeight;
                        if (wrapped || gradientColors == null) {
                            control.setBackgroundImage(null);
                            control.setBackground(getBackground());
                        } else {
                            bounds.width = 10;
                            if (!onBottom) {
                                bounds.y = -bounds.y;
                                bounds.height -= 2 * bounds.y - 1;
                            } else {
                                bounds.height += height - (bounds.y + bounds.height);
                                bounds.y = -1;
                            }
                            bounds.x = 0;
                            if (controlBkImages[i] != null)
                                controlBkImages[i].dispose();
                            controlBkImages[i] = new Image(control.getDisplay(), bounds);
                            GC gc = new GC(controlBkImages[i]);
                            renderer.draw(CTabFolderRenderer.PART_BACKGROUND, 0, bounds, gc);
                            gc.dispose();
                            control.setBackground(null);
                            control.setBackgroundImage(controlBkImages[i]);
                        }
                    }
                }
            }

        }
    }

    String _getToolTip(int x, int y) {
        CTabItem item = getItem(new Point(x, y));
        if (item == null)
            return null;
        if (!item.showing)
            return null;
        if ((showClose || item.showClose) && item.closeRect.contains(x, y)) {
            return SWT.getMessage("SWT_Close"); //$NON-NLS-1$
        }
        return item.getToolTipText();
    }

    /**
    * Set a control that can appear to the left or to the right of the folder tabs.
    * This method can also be used instead of #setTopRight(Control). To remove a tab
    * control, see#removeTabControl(Control);
    * <p>
    * The flags parameter sets the layout of the control in the tab area.
    * <code>SWT.LEAD</code> will cause the control to be positioned on the left
    * of the tabs. <code>SWT.TRAIL</code> will cause the control to be positioned on
    * the far right of the folder and it will have its default size. <code>SWT.TRAIL</code>
    * can be combined with <code>SWT.FILL</code>to fill all the available space to the
    * right of the last tab. <code>SWT.WRAP</code> can also be added to <code>SWT.TRAIL</code>
    * only to cause a control to wrap if there is not enough space to display it in its
    * entirety.
    * </p>
    * @param control the control to be displayed in the top right corner or null
    *
    * @param flags valid combinations are:
    * <ul><li>SWT.LEAD
    * <li> SWT.TRAIL (| SWT.FILL | SWT.WRAP)
    * </ul>
    * @exception SWTException <ul>
    *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
    *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
    *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
    * </ul>
    */
    /*public*/ void addTabControl(Control control, int flags) {
        checkWidget();
        addTabControl(control, flags, -1, true);
    }

    void addTabControl(Control control, int flags, int index, boolean update) {
        switch (flags) {
        case SWT.TRAIL:
        case SWT.TRAIL | SWT.WRAP:
        case SWT.TRAIL | SWT.FILL:
        case SWT.TRAIL | SWT.FILL | SWT.WRAP:
        case SWT.LEAD:
            break;
        default:
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            break;
        }
        if (control != null && control.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        //check for duplicates
        for (int i = 0; i < controls.length; i++) {
            if (controls[i] == control) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
        }
        int length = controls.length;

        control.addListener(SWT.Resize, listener);

        //Grow all 4 arrays
        Control[] newControls = new Control[length + 1];
        System.arraycopy(controls, 0, newControls, 0, length);
        controls = newControls;
        int[] newAlignment = new int[length + 1];
        System.arraycopy(controlAlignments, 0, newAlignment, 0, length);
        controlAlignments = newAlignment;
        Rectangle[] newRect = new Rectangle[length + 1];
        System.arraycopy(controlRects, 0, newRect, 0, length);
        controlRects = newRect;
        Image[] newImage = new Image[length + 1];
        System.arraycopy(controlBkImages, 0, newImage, 0, length);
        controlBkImages = newImage;
        if (index == -1) {
            index = length;
            if (chevronTb != null && control != chevronTb)
                index--;
        }
        System.arraycopy(controls, index, controls, index + 1, length - index);
        System.arraycopy(controlAlignments, index, controlAlignments, index + 1, length - index);
        System.arraycopy(controlRects, index, controlRects, index + 1, length - index);
        System.arraycopy(controlBkImages, index, controlBkImages, index + 1, length - index);
        controls[index] = control;
        controlAlignments[index] = flags;
        controlRects[index] = new Rectangle(0, 0, 0, 0);
        if (update) {
            updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
        }
    }

    /**
    * Removes the control from the list of tab controls.
    *
    * @param control the control to be removed
    *
    * @exception SWTException <ul>
    *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
    *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
    *    <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li>
    * </ul>
    */
    /*public*/ void removeTabControl(Control control) {
        checkWidget();
        removeTabControl(control, true);
    }

    void removeTabControl(Control control, boolean update) {
        if (control != null && control.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        int index = -1;
        for (int i = 0; i < controls.length; i++) {
            if (controls[i] == control) {
                index = i;
                break;
            }
        }
        if (index == -1)
            return;

        if (!control.isDisposed()) {
            control.removeListener(SWT.Resize, listener);
            control.setBackground(null);
            control.setBackgroundImage(null);
            if (control instanceof Composite)
                ((Composite) control).setBackgroundMode(SWT.INHERIT_NONE);
        }

        if (controlBkImages[index] != null && !controlBkImages[index].isDisposed())
            controlBkImages[index].dispose();
        if (controls.length == 1) {
            controls = new Control[0];
            controlAlignments = new int[0];
            controlRects = new Rectangle[0];
            controlBkImages = new Image[0];
        } else {
            Control[] newControls = new Control[controls.length - 1];
            System.arraycopy(controls, 0, newControls, 0, index);
            System.arraycopy(controls, index + 1, newControls, index, controls.length - index - 1);
            controls = newControls;

            int[] newAlignments = new int[controls.length];
            System.arraycopy(controlAlignments, 0, newAlignments, 0, index);
            System.arraycopy(controlAlignments, index + 1, newAlignments, index, controls.length - index);
            controlAlignments = newAlignments;

            Rectangle[] newRects = new Rectangle[controls.length];
            System.arraycopy(controlRects, 0, newRects, 0, index);
            System.arraycopy(controlRects, index + 1, newRects, index, controls.length - index);
            controlRects = newRects;

            Image[] newBkImages = new Image[controls.length];
            System.arraycopy(controlBkImages, 0, newBkImages, 0, index);
            System.arraycopy(controlBkImages, index + 1, newBkImages, index, controls.length - index);
            controlBkImages = newBkImages;
        }
        if (update) {
            updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
        }
    }

    int getWrappedHeight(Point size) {
        boolean[][] positions = new boolean[1][];
        Rectangle[] rects = computeControlBounds(size, positions);
        int minY = Integer.MAX_VALUE, maxY = 0, wrapHeight = 0;
        for (int i = 0; i < rects.length; i++) {
            if (positions[0][i]) {
                minY = Math.min(minY, rects[i].y);
                maxY = Math.max(maxY, rects[i].y + rects[i].height);
                wrapHeight = maxY - minY;
            }
        }
        return wrapHeight;
    }

    /**
     * Sets whether a chevron is shown when there are more items to be displayed.
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_RANGE - if the index is out of range</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
     *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
     * </ul>
     *
     */
    /*public*/ void setChevronVisible(boolean visible) {
        checkWidget();
        if (chevronVisible == visible)
            return;
        chevronVisible = visible;
        updateFolder(UPDATE_TAB_HEIGHT | REDRAW);
    }

    boolean shouldHighlight() {
        return this.highlight && highlightEnabled;
    }

    /**
     * Sets whether the selected tab is rendered as highlighted.
     *
     * @param enabled
     *            {@code true} if the selected tab should be highlighted,
     *            {@code false} otherwise.
     * @exception SWTException
     *                <ul>
     *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been
     *                disposed</li>
     *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *                thread that created the receiver</li>
     *                </ul>
     * @since 3.106
     */
    public void setHighlightEnabled(boolean enabled) {
        checkWidget();
        if (highlightEnabled == enabled) {
            return;
        }
        highlightEnabled = enabled;
        updateFolder(REDRAW);
    }

    /**
     * Returns <code>true</code> if the selected tab is rendered as
     * highlighted.
     *
     * @return <code>true</code> if the selected tab is rendered as
     *         highlighted
     *
     * @exception SWTException
     *                <ul>
     *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been
     *                disposed</li>
     *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
     *                thread that created the receiver</li>
     *                </ul>
     * @since 3.106
     */
    public boolean getHighlightEnabled() {
        checkWidget();
        return highlightEnabled;
    }
}