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) 2002, 2017 Innoopract Informationssysteme GmbH and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Innoopract Informationssysteme GmbH - initial API and implementation
 *    EclipseSource - ongoing development
 ******************************************************************************/
package org.eclipse.swt.custom;

import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA;
import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil;
import org.eclipse.rap.rwt.internal.theme.ThemeAdapter;
import org.eclipse.rap.rwt.theme.BoxDimensions;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.custom.ICTabFolderAdapter;
import org.eclipse.swt.internal.custom.ctabfolderkit.CTabFolderLCA;
import org.eclipse.swt.internal.custom.ctabfolderkit.CTabFolderThemeAdapter;
import org.eclipse.swt.internal.events.EventTypes;
import org.eclipse.swt.internal.widgets.IItemHolderAdapter;
import org.eclipse.swt.internal.widgets.IWidgetGraphicsAdapter;
import org.eclipse.swt.internal.widgets.ItemHolder;
import org.eclipse.swt.internal.widgets.WidgetGraphicsAdapter;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TypedListener;

/**
 * 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><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p><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>
 * </p>
 * <p>
 * Note: Only one of the styles TOP and BOTTOM
 * may be specified.
 * </p>
 * <hr/>
 * <p>Implementation Status: </p>
 * <p>Attributes, found in SWT, that are not supported</p>
 * <ul>
 * <li>simple (treated as <code>true</code>)</li>
 * </ul>
 *
 * @since 1.0
 */
public class CTabFolder extends Composite {

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

    static final int SELECTION_FOREGROUND = SWT.COLOR_LIST_FOREGROUND;
    static final int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND;
    static final int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW;
    static final int FOREGROUND = SWT.COLOR_WIDGET_FOREGROUND;
    static final int BACKGROUND = SWT.COLOR_WIDGET_BACKGROUND;
    static final int BUTTON_BORDER = SWT.COLOR_WIDGET_DARK_SHADOW;
    static final int BUTTON_FILL = SWT.COLOR_LIST_BACKGROUND;

    /**
     * marginWidth specifies the number of pixels 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 pixels 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;

    private transient ICTabFolderAdapter tabFolderAdapter;
    private final IWidgetGraphicsAdapter selectionGraphicsAdapter;
    private final ItemHolder<CTabItem> itemHolder = new ItemHolder<>(CTabItem.class);
    private final ControlListener resizeListener;
    private FocusListener focusListener;
    private Menu showMenu;
    int selectedIndex = -1;
    private int firstIndex = -1; // index of the left most visible tab
    private boolean mru;
    private int[] priority = new int[0];
    final boolean showClose;
    boolean showUnselectedClose = true;
    boolean showUnselectedImage = true;
    boolean showMax;
    boolean showMin;
    private boolean inDispose;
    boolean minimized;
    boolean maximized;
    boolean onBottom;
    final boolean simple = true; // no curvy tab items supported (yet)
    boolean single;
    private final Rectangle maxRect = new Rectangle(0, 0, 0, 0);
    private final Rectangle minRect = new Rectangle(0, 0, 0, 0);
    // Chevron
    private final Rectangle chevronRect = new Rectangle(0, 0, 0, 0);
    private boolean showChevron;
    // Tab bar
    private int fixedTabHeight = SWT.DEFAULT;
    int tabHeight = 0;
    int minChars = 20;
    // TopRight control
    Control topRight;
    private int topRightAlignment = SWT.RIGHT;
    private final Rectangle topRightRect = new Rectangle(0, 0, 0, 0);
    // Client origin and border dimensions
    private int xClient;
    private int yClient;
    private final int highlight_margin;
    private int highlight_header;
    private int borderRight;
    private int borderLeft;
    private int borderBottom;
    private int borderTop;
    // keep track of size changes in order to redraw only affected area
    // on Resize
    //  Point oldSize;
    // Colors
    private Color selectionBackground = null;
    private Color selectionForeground = null;
    private Image selectionBgImage = null;

    /**
     * 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(style));
        super.setLayout(new CTabFolderLayout());
        onBottom = (super.getStyle() & SWT.BOTTOM) != 0;
        single = (super.getStyle() & SWT.SINGLE) != 0;
        showClose = (super.getStyle() & SWT.CLOSE) != 0;
        borderRight = (style & SWT.BORDER) != 0 ? 1 : 0;
        borderLeft = borderRight;
        borderTop = onBottom ? borderLeft : 0;
        borderBottom = onBottom ? 0 : borderLeft;
        highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3;
        highlight_margin = (style & SWT.FLAT) != 0 ? 0 : 2;
        updateTabHeight(false);
        resizeListener = new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent event) {
                onResize();
            }
        };
        addControlListener(resizeListener);
        registerDisposeListener();
        selectionGraphicsAdapter = new WidgetGraphicsAdapter();
    }

    //////////////////
    // Item management

    /**
     * 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() {
        checkWidget();
        return itemHolder.getItems();
    }

    /**
     * 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) {
        checkWidget();
        return itemHolder.getItem(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>
     * @since 1.2
     */
    public CTabItem getItem(Point pt) {
        // checkWidget();
        CTabItem result = null;
        Point size = getSize();
        boolean onChevron = showChevron && chevronRect.contains(pt);
        int itemCount = itemHolder.size();
        if (itemCount > 0 && size.x > borderLeft + borderRight && !onChevron) {
            CTabItem[] items = itemHolder.getItems();
            for (int i = 0; result == null && i < priority.length; i++) {
                CTabItem item = items[priority[i]];
                Rectangle rect = item.getBounds();
                if (rect.contains(pt)) {
                    result = item;
                }
            }
        }
        return result;
    }

    /**
     * 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 itemHolder.size();
    }

    /**
     * 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();
        return itemHolder.indexOf(item);
    }

    ///////////////////////
    // Selection management

    /**
     * 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 <= itemHolder.size() - 1) {
            if (selectedIndex != index) {
                int oldSelectionIndex = selectedIndex;
                selectedIndex = index;
                getItem(selectedIndex).showing = false;
                Control control = getItem(selectedIndex).getControl();
                // Adjust bounds of selected control and make it visible (if any)
                if (control != null && !control.isDisposed()) {
                    control.setBounds(getClientArea());
                    control.setVisible(true);
                }
                // Hide control of previous selection (if any)
                if (oldSelectionIndex >= 0 && oldSelectionIndex < getItemCount()) {
                    Control oldControl = getItem(oldSelectionIndex).getControl();
                    if (oldControl != null && !oldControl.isDisposed()) {
                        oldControl.setVisible(false);
                    }
                }
            }
            showItem(getSelection());
        }
    }

    /**
     * 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() {
        checkWidget();
        return selectedIndex;
    }

    /**
     * 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 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 void setSelection(CTabItem item) {
        checkWidget();
        if (item == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        int index = itemHolder.indexOf(item);
        setSelection(index);
    }

    /**
     * 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() {
        checkWidget();
        CTabItem result = null;
        if (selectedIndex != -1) {
            result = itemHolder.getItem(selectedIndex);
        }
        return result;
    }

    /**
     * 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)
     */
    public void showSelection() {
        checkWidget();
        if (selectedIndex != -1) {
            showItem(getSelection());
        }
    }

    /**
     * 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()
     */
    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; idx == -1 && i < priority.length; i++) {
            if (priority[i] == index) {
                idx = i;
            }
        }
        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.isShowing()) {
            updateItems(index);
        }
    }

    //////////////////////////////
    // Most recently used settings

    /**
     * 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>
     */
    public void setMRUVisible(boolean show) {
        checkWidget();
        if (mru != show) {
            mru = show;
            if (!mru) {
                int idx = firstIndex;
                int next = 0;
                for (int i = firstIndex; i < priority.length; i++) {
                    priority[next++] = i;
                }
                for (int i = 0; i < idx; i++) {
                    priority[next++] = i;
                }
                updateItems();
            }
        }
    }

    /**
     * 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>
     */
    public boolean getMRUVisible() {
        checkWidget();
        return mru;
    }

    ////////////////////////////////
    // Minimize / Maximize / Restore

    /**
     * Marks the receiver's maximize button as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     *
     * @param maximizeVisible 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>
     */
    public void setMaximizeVisible(boolean maximizeVisible) {
        checkWidget();
        if (showMax != maximizeVisible) {
            showMax = maximizeVisible;
            updateItems();
        }
    }

    /**
     * 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>
     */
    public boolean getMaximizeVisible() {
        checkWidget();
        return showMax;
    }

    /**
     * Marks the receiver's minimize button as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     *
     * @param minimizeVisible 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>
     */
    public void setMinimizeVisible(boolean minimizeVisible) {
        checkWidget();
        if (showMin != minimizeVisible) {
            showMin = minimizeVisible;
            updateItems();
        }
    }

    /**
     * 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>
     */
    public boolean getMinimizeVisible() {
        checkWidget();
        return showMin;
    }

    /**
     * Marks the receiver's minimize button as visible if the argument is <code>true</code>,
     * and marks it invisible otherwise.
     *
     * @param minimized 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>
     */
    public void setMinimized(boolean minimized) {
        checkWidget();
        if (this.minimized != minimized) {
            if (minimized && maximized) {
                setMaximized(false);
            }
            this.minimized = minimized;
        }
    }

    /**
     * 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>
     */
    public boolean getMinimized() {
        checkWidget();
        return minimized;
    }

    /**
     * Sets the maximized state of the receiver.
     *
     * @param maximized 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>
     */
    public void setMaximized(boolean maximized) {
        checkWidget();
        if (this.maximized != maximized) {
            if (maximized && minimized) {
                setMinimized(false);
            }
            this.maximized = maximized;
        }
    }

    /**
     * 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>
     */
    public boolean getMaximized() {
        checkWidget();
        return maximized;
    }

    //////////////////////////////////////
    // Appearance and dimension properties

    /**
     * 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();
        // ignore - CTabFolder manages its own layout
    }

    /**
     * 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 pixel 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;
        updateTabHeight(false);
    }

    /**
     * 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();
        int result;
        if (fixedTabHeight != SWT.DEFAULT) {
            result = fixedTabHeight;
        } else {
            result = tabHeight - 1; // -1 for line drawn across top of tab
        }
        return result;
    }

    /**
     * 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
     */
    public int getMinimumCharacters() {
        checkWidget();
        return minChars;
    }

    /**
     * Sets the minimum number of characters that will
     * be displayed in a fully compressed tab.
     *
     * @param minimumCharacters 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>
     */
    public void setMinimumCharacters(int minimumCharacters) {
        checkWidget();
        if (minimumCharacters < 0) {
            SWT.error(SWT.ERROR_INVALID_RANGE);
        }
        if (minChars != minimumCharacters) {
            minChars = minimumCharacters;
            updateItems();
        }
    }

    @Override
    public int getStyle() {
        checkWidget();
        int result = super.getStyle();
        result &= ~(SWT.TOP | SWT.BOTTOM);
        result |= onBottom ? SWT.BOTTOM : SWT.TOP;
        result &= ~(SWT.SINGLE | SWT.MULTI);
        result |= single ? SWT.SINGLE : SWT.MULTI;
        if (borderLeft != 0) {
            result |= SWT.BORDER;
        }
        return result;
    }

    /**
     * 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
     */
    public boolean getSingle() {
        checkWidget();
        return single;
    }

    /**
     * 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>
     */
    public void setSingle(boolean single) {
        checkWidget();
        if (this.single != single) {
            this.single = single;
            updateItemsWithResizeEvent();
        }
    }

    /**
     * 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;
    }

    /**
     * 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>
     */
    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;
            borderTop = onBottom ? borderLeft : 0;
            borderBottom = onBottom ? 0 : borderRight;
            updateTabHeight(true);
            updateItemsWithResizeEvent();
        }
    }

    /**
     * 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>
     */
    public boolean getBorderVisible() {
        checkWidget();
        return borderLeft == 1;
    }

    /**
     * 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 ((borderLeft != 1) != !show) {
            borderLeft = borderRight = show ? 1 : 0;
            borderTop = onBottom ? borderLeft : 0;
            borderBottom = onBottom ? 0 : borderLeft;
            updateItemsWithResizeEvent();
        }
    }

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

    /**
     * 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 1.3
     */
    public void setUnselectedImageVisible(boolean visible) {
        checkWidget();
        if (showUnselectedImage != visible) {
            showUnselectedImage = visible;
            updateItems();
        }
    }

    /**
     * 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
     */
    public boolean getUnselectedCloseVisible() {
        checkWidget();
        return showUnselectedClose;
    }

    /**
     * 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>
     */
    public void setUnselectedCloseVisible(boolean visible) {
        checkWidget();
        if (showUnselectedClose != visible) {
            showUnselectedClose = visible;
            updateItems();
        }
    }

    @Override
    public Rectangle computeTrim(int x, int y, int width, int height) {
        checkWidget();
        int trimX = x - marginWidth - highlight_margin - borderLeft;
        int trimWidth = width + borderLeft + borderRight + 2 * marginWidth + 2 * highlight_margin;
        int trimY;
        int trimHeight;
        if (minimized) {
            trimY = onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop;
            trimHeight = borderTop + borderBottom + tabHeight + highlight_header;
        } else {
            trimY = onBottom ? y - marginHeight - highlight_margin - borderTop
                    : y - marginHeight - highlight_header - tabHeight - borderTop;
            trimHeight = height + borderTop + borderBottom + 2 * marginHeight + tabHeight + highlight_header
                    + highlight_margin;
        }
        return new Rectangle(trimX, trimY, trimWidth, trimHeight);
    }

    ///////////////////
    // Selection colors

    /**
     * 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>
     */
    public void setSelectionBackground(Color color) {
        checkWidget();
        if (null != color && color.isDisposed()) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        selectionBackground = color;
    }

    /**
     * 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>
     */
    public Color getSelectionBackground() {
        checkWidget();
        Color result = selectionBackground;
        if (result == null) {
            result = getThemeAdapter().getSelectedBackground();
        }
        if (result == null) {
            // Should never happen as the theming must prevent transparency for
            // this color
            throw new IllegalStateException("Transparent selection background color");
        }
        return result;
    }

    /**
     * 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.
     * @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>
     *  @since 1.3
     */
    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 top to bottom.  The value <code>null</code> clears the
     *               background gradient.
     * @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 IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
     *
     * @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 1.3
     */
    public void setSelectionBackground(Color[] colors, int[] percents, boolean vertical) {
        checkWidget();
        if (colors != null) {
            for (int i = 0; i < colors.length; i++) {
                if (colors[i] != null && colors[i].isDisposed()) {
                    SWT.error(SWT.ERROR_INVALID_ARGUMENT);
                }
            }
            // 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 (colors == null) {
            selectionGraphicsAdapter.setBackgroundGradient(null, null, vertical);
            setSelectionBackground((Color) null);
        } else {
            int colorsLength = colors.length;
            if (percents.length == colors.length - 2) {
                colorsLength = colors.length - 1;
            }
            Color[] gradientColors = new Color[colorsLength];
            System.arraycopy(colors, 0, gradientColors, 0, colorsLength);
            int[] gradientPercents = new int[gradientColors.length];
            if (gradientColors.length > 0) {
                gradientPercents[0] = 0;
                for (int i = 1; i < gradientPercents.length; i++) {
                    gradientPercents[i] = percents[i - 1];
                }
                selectionGraphicsAdapter.setBackgroundGradient(gradientColors, gradientPercents, vertical);
                setSelectionBackground(gradientColors[gradientColors.length - 1]);
            }
        }
    }

    /**
     * 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>
     * @since 1.3
     */
    public void setSelectionBackground(Image image) {
        checkWidget();
        selectionBgImage = image;
    }

    /**
     * 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();
        selectionForeground = color;
    }

    /**
     * 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>
     */
    public Color getSelectionForeground() {
        checkWidget();
        Color result = selectionForeground;
        if (result == null) {
            result = getThemeAdapter().getSelectedForeground();
        }
        if (result == null) {
            // Should never happen as the theming must prevent transparency for
            // this color
            throw new IllegalStateException("Transparent selection foreground color");
        }
        return result;
    }

    //////////////////////////////////
    // Manipulation of topRight control

    /**
     * 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 not a child of this CTabFolder</li>
     * </ul>
     */
    public void setTopRight(Control control) {
        checkWidget();
        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.
     * </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>
     *
     * @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 setTopRight(Control control, int alignment) {
        checkWidget();
        if (alignment != SWT.RIGHT && alignment != SWT.FILL) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (control != null && control.getParent() != this) {
            SWT.error(SWT.ERROR_INVALID_PARENT);
        }
        if (topRight != control || topRightAlignment != alignment) {
            topRight = control;
            topRightAlignment = alignment;
            if (updateItems()) {
                redraw();
            }
        }
    }

    /**
     * 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>
     */
    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 1.3
     */
    public int getTopRightAlignment() {
        checkWidget();
        return topRightAlignment;
    }

    ///////////////////////////
    // Adaptable implementation

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getAdapter(Class<T> adapter) {
        if (adapter == IItemHolderAdapter.class) {
            return (T) itemHolder;
        }
        if (adapter == ICTabFolderAdapter.class) {
            if (tabFolderAdapter == null) {
                tabFolderAdapter = new CTabFolderAdapter();
            }
            return (T) tabFolderAdapter;
        }
        if (adapter == WidgetLCA.class) {
            return (T) CTabFolderLCA.INSTANCE;
        }
        return super.getAdapter(adapter);
    }

    ////////////////////
    // Control overrides

    @Override
    public void setFont(Font font) {
        checkWidget();
        if (font != getFont()) {
            super.setFont(font);
            if (!updateTabHeight(false)) {
                updateItems();
            }
        }
    }

    @Override
    public int getBorderWidth() {
        checkWidget();
        return 0;
    }

    //////////////////////
    // Composite overrides

    @Override
    public Rectangle getClientArea() {
        checkWidget();
        Rectangle result;
        if (minimized) {
            result = new Rectangle(xClient, yClient, 0, 0);
        } else {
            Point size = getSize();
            int width = size.x - borderLeft - borderRight - 2 * marginWidth - 2 * highlight_margin;
            int height = size.y - borderTop - borderBottom - 2 * marginHeight - highlight_margin - highlight_header;
            height -= tabHeight;
            result = new Rectangle(xClient, yClient, width, height);
        }
        return result;
    }

    ///////////////////////////////////////
    // Listener registration/deregistration

    /**
     * Adds the listener to receive events.
     * <p>
     *
     * @param listener the listener
     *
     * @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 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);
    }

    /**
     * Removes the listener.
     *
     * @param listener the listener
     *
     * @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 void removeSelectionListener(SelectionListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(SWT.Selection, listener);
        removeListener(SWT.DefaultSelection, listener);
    }

    /**
     *
     * 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)
     */
    public void addCTabFolder2Listener(CTabFolder2Listener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        TypedCTabFolderListener typedListener = new TypedCTabFolderListener(listener);
        addListener(EventTypes.CTAB_FOLDER_MINIMIZE, typedListener);
        addListener(EventTypes.CTAB_FOLDER_MAXIMIZE, typedListener);
        addListener(EventTypes.CTAB_FOLDER_RESTORE, typedListener);
        addListener(EventTypes.CTAB_FOLDER_CLOSE, typedListener);
        addListener(EventTypes.CTAB_FOLDER_SHOW_LIST, typedListener);
    }

    /**
     * Removes the listener.
     *
     * @param listener the listener
     *
     * @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)
     */
    public void removeCTabFolder2Listener(CTabFolder2Listener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        removeListener(EventTypes.CTAB_FOLDER_MINIMIZE, listener);
        removeListener(EventTypes.CTAB_FOLDER_MAXIMIZE, listener);
        removeListener(EventTypes.CTAB_FOLDER_RESTORE, listener);
        removeListener(EventTypes.CTAB_FOLDER_CLOSE, listener);
        removeListener(EventTypes.CTAB_FOLDER_SHOW_LIST, listener);
    }

    ///////////////////////////////////
    // Helping methods to arrange items

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

    boolean updateItems(int showIndex) {
        CTabItem[] items = itemHolder.getItems();
        if (!single && !mru && showIndex != -1) {
            // make sure selected item will be showing
            int firstIndex = showIndex;
            if (priority[0] < showIndex) {
                int maxWidth = getRightItemEdge() - borderLeft;
                //        if (!simple) maxWidth -= curveWidth - 2*curveIndent;
                int width = 0;
                int[] widths = new int[items.length];
                //        GC gc = new GC(this);
                for (int i = priority[0]; i <= showIndex; i++) {
                    //          widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true);
                    widths[i] = items[i].preferredWidth(i == selectedIndex, true);
                    width += widths[i];
                    if (width > maxWidth) {
                        break;
                    }
                }
                if (width > maxWidth) {
                    width = 0;
                    for (int i = showIndex; i >= 0; i--) {
                        //            if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true);
                        if (widths[i] == 0) {
                            widths[i] = items[i].preferredWidth(i == selectedIndex, true);
                        }
                        width += widths[i];
                        if (width > maxWidth) {
                            break;
                        }
                        firstIndex = i;
                    }
                } else {
                    firstIndex = priority[0];
                    for (int i = showIndex + 1; i < items.length; i++) {
                        //            widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true);
                        widths[i] = items[i].preferredWidth(i == selectedIndex, true);
                        width += widths[i];
                        if (width >= maxWidth) {
                            break;
                        }
                    }
                    if (width < maxWidth) {
                        for (int i = priority[0] - 1; i >= 0; i--) {
                            //              if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true);
                            if (widths[i] == 0) {
                                widths[i] = items[i].preferredWidth(i == selectedIndex, true);
                            }
                            width += widths[i];
                            if (width > maxWidth) {
                                break;
                            }
                            firstIndex = i;
                        }
                    }
                }
                //        gc.dispose();
            }
            if (firstIndex != priority[0]) {
                int index = 0;
                for (int i = firstIndex; i < items.length; i++) {
                    priority[index++] = i;
                }
                for (int i = 0; i < firstIndex; i++) {
                    priority[index++] = i;
                }
            }
        }

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

    boolean setItemLocation() {
        CTabItem[] items = itemHolder.getItems();
        boolean changed = false;
        if (items.length == 0) {
            return false;
        }
        Point size = getSize();
        int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop;
        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 = borderLeft;
                    item.y = y;
                    item.showing = true;
                    if (showClose || item.showClose) {
                        item.closeRect.x = borderLeft + getItemPaddingLeft(true);
                        item.closeRect.y = onBottom
                                ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2
                                : borderTop + (tabHeight - BUTTON_SIZE) / 2;
                    }
                    if (item.x != oldX || item.y != oldY) {
                        changed = true;
                    }
                } else {
                    item.x = defaultX;
                    item.showing = false;
                }
            }
        } else {
            int rightItemEdge = getRightItemEdge();
            int maxWidth = rightItemEdge - borderLeft;
            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;
                //        if (!simple && priority[i] == selectedIndex) width += curveWidth - 2*curveIndent;
            }
            int x = 0;
            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;
                    if (i == selectedIndex) {
                        int edge = Math.min(item.x + item.width, rightItemEdge);
                        item.closeRect.x = edge - getItemPaddingRight(true) - BUTTON_SIZE;
                    } else {
                        int rightPadding = getItemPaddingRight(false);
                        item.closeRect.x = item.x + item.width - rightPadding - BUTTON_SIZE;
                    }
                    item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2
                            : borderTop + (tabHeight - BUTTON_SIZE) / 2;
                    x = x + item.width;
                    //          if (!simple && i == selectedIndex) x += curveWidth - 2*curveIndent;
                }
            }
        }
        return changed;
    }

    boolean setItemSize() {
        CTabItem[] items = itemHolder.getItems();
        boolean changed = false;
        if (isDisposed()) {
            return changed;
        }
        Point size = getSize();
        if (size.x <= 0 || size.y <= 0) {
            return changed;
        }
        // Note [rst] We have to substract the border here because on the client,
        // the position 0,0 is not the origin of the border but the origin of the
        // area inside the border.
        xClient = borderLeft + marginWidth + highlight_margin;
        if (onBottom) {
            yClient = borderTop + highlight_margin + marginHeight;
        } else {
            yClient = borderTop + tabHeight + highlight_header + marginHeight;
        }
        showChevron = false;
        if (single) {
            showChevron = true;
            if (selectedIndex != -1) {
                CTabItem tab = items[selectedIndex];
                //        GC gc = new GC(this);
                //        int width = tab.preferredWidth(gc, true, false);
                //        gc.dispose();
                int width = tab.preferredWidth(true, false);
                width = Math.min(width, getRightItemEdge() - borderLeft);
                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) {
                        tab.closeRect.width = BUTTON_SIZE;
                        tab.closeRect.height = BUTTON_SIZE;
                    }
                }
            }
            return changed;
        }

        if (items.length == 0) {
            return changed;
        }

        int[] widths;
        //    GC gc = new GC(this);
        int tabAreaWidth = size.x - borderLeft - borderRight - 3;
        if (showMin) {
            tabAreaWidth -= BUTTON_SIZE;
        }
        if (showMax) {
            tabAreaWidth -= BUTTON_SIZE;
        }
        if (topRightAlignment == SWT.RIGHT && topRight != null) {
            Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT, false);
            tabAreaWidth -= rightSize.x + 3;
        }
        //    if (!simple) tabAreaWidth -= curveWidth - 2*curveIndent;
        tabAreaWidth = Math.max(0, tabAreaWidth);

        // 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];
            //      minWidths[index] = items[index].preferredWidth(gc, index == selectedIndex, true);
            minWidths[index] = items[index].preferredWidth(index == selectedIndex, true);
            minWidth += minWidths[index];
            if (minWidth > tabAreaWidth) {
                break;
            }
        }
        if (minWidth > tabAreaWidth) {
            // full compression required and a chevron
            showChevron = items.length > 1;
            if (showChevron) {
                tabAreaWidth -= 3 * BUTTON_SIZE / 2;
            }
            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++) {
                //        maxWidths[i] = items[i].preferredWidth(gc, i == selectedIndex, false);
                maxWidths[i] = items[i].preferredWidth(i == selectedIndex, false);
                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);
                }
            }
        }
        //    gc.dispose();

        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) {
                        tab.closeRect.width = BUTTON_SIZE;
                        tab.closeRect.height = BUTTON_SIZE;
                    }
                }
            }
        }
        return changed;
    }

    @SuppressWarnings("unused")
    void setButtonBounds() {
        CTabItem[] items = itemHolder.getItems();
        Point size = getSize();
        //    int oldX, oldY, oldWidth, oldHeight;
        // max button
        //    oldX = maxRect.x;
        //    oldY = maxRect.y;
        //    oldWidth = maxRect.width;
        //    oldHeight = maxRect.height;
        maxRect.x = maxRect.y = maxRect.width = maxRect.height = 0;
        if (showMax) {
            maxRect.x = size.x - borderRight - BUTTON_SIZE - 3;
            if (borderRight > 0) {
                maxRect.x += 1;
            }
            maxRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2
                    : borderTop + (tabHeight - BUTTON_SIZE) / 2;
            maxRect.width = BUTTON_SIZE;
            maxRect.height = BUTTON_SIZE;
        }
        //    if (oldX != maxRect.x || oldWidth != maxRect.width ||
        //        oldY != maxRect.y || oldHeight != maxRect.height) {
        //      int left = Math.min(oldX, maxRect.x);
        //      int right = Math.max(oldX + oldWidth, maxRect.x + maxRect.width);
        //      int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
        //      redraw(left, top, right - left, tabHeight, false);
        //    }

        // min button
        //    oldX = minRect.x;
        //    oldY = minRect.y;
        //    oldWidth = minRect.width;
        //    oldHeight = minRect.height;
        minRect.x = minRect.y = minRect.width = minRect.height = 0;
        if (showMin) {
            minRect.x = size.x - borderRight - maxRect.width - BUTTON_SIZE - 3;
            if (borderRight > 0) {
                minRect.x += 1;
            }
            minRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2
                    : borderTop + (tabHeight - BUTTON_SIZE) / 2;
            minRect.width = BUTTON_SIZE;
            minRect.height = BUTTON_SIZE;
        }
        //    if (oldX != minRect.x || oldWidth != minRect.width ||
        //        oldY != minRect.y || oldHeight != minRect.height) {
        //      int left = Math.min(oldX, minRect.x);
        //      int right = Math.max(oldX + oldWidth, minRect.x + minRect.width);
        //      int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
        //      redraw(left, top, right - left, tabHeight, false);
        //    }

        // top right control
        //    oldX = topRightRect.x;
        //    oldY = topRightRect.y;
        //    oldWidth = topRightRect.width;
        //    oldHeight = topRightRect.height;
        topRightRect.x = topRightRect.y = topRightRect.width = topRightRect.height = 0;
        if (topRight != null) {
            switch (topRightAlignment) {
            case SWT.FILL: {
                int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
                if (!simple && borderRight > 0 && !showMax && !showMin) {
                    rightEdge -= 2;
                }
                if (single) {
                    if (items.length == 0 || selectedIndex == -1) {
                        topRightRect.x = borderLeft + 3;
                        topRightRect.width = rightEdge - topRightRect.x;
                    } else {
                        // fill size is 0 if item compressed
                        CTabItem item = items[selectedIndex];
                        if (item.x + item.width + 7 + 3 * BUTTON_SIZE / 2 >= rightEdge) {
                            break;
                        }
                        topRightRect.x = item.x + item.width + 7 + 3 * BUTTON_SIZE / 2;
                        topRightRect.width = rightEdge - topRightRect.x;
                    }
                } else {
                    // fill size is 0 if chevron showing
                    if (showChevron) {
                        break;
                    }
                    if (items.length == 0) {
                        topRightRect.x = borderLeft + 3;
                    } else {
                        CTabItem item = items[items.length - 1];
                        topRightRect.x = item.x + item.width;
                        //              if (!simple && items.length - 1 == selectedIndex) topRightRect.x += curveWidth - curveIndent;
                    }
                    topRightRect.width = Math.max(0, rightEdge - topRightRect.x);
                }
                topRightRect.y = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1;
                topRightRect.height = tabHeight - 1;
                break;
            }
            case SWT.RIGHT: {
                Point topRightSize = topRight.computeSize(SWT.DEFAULT, tabHeight, false);
                int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width;
                if (!simple && borderRight > 0 && !showMax && !showMin) {
                    rightEdge -= 2;
                }
                topRightRect.x = rightEdge - topRightSize.x;
                topRightRect.width = topRightSize.x;
                topRightRect.y = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1;
                topRightRect.height = tabHeight - 1;
            }
            }
            topRight.setBounds(topRightRect);
        }
        //    if (oldX != topRightRect.x || oldWidth != topRightRect.width ||
        //      oldY != topRightRect.y || oldHeight != topRightRect.height) {
        //      int left = Math.min(oldX, topRightRect.x);
        //      int right = Math.max(oldX + oldWidth, topRightRect.x + topRightRect.width);
        //      int top = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1;
        //      redraw(left, top, right - left, tabHeight, false);
        //    }

        // chevron button
        //    oldX = chevronRect.x;
        //    oldY = chevronRect.y;
        //    oldWidth = chevronRect.width;
        //    oldHeight = chevronRect.height;
        chevronRect.x = chevronRect.y = chevronRect.height = chevronRect.width = 0;
        if (single) {
            if (selectedIndex == -1 || items.length > 1) {
                chevronRect.width = 3 * BUTTON_SIZE / 2;
                chevronRect.height = BUTTON_SIZE;
                chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height) / 2
                        : borderTop + (tabHeight - chevronRect.height) / 2;
                if (selectedIndex == -1) {
                    chevronRect.x = size.x - borderRight - 3 - minRect.width - maxRect.width - topRightRect.width
                            - chevronRect.width;
                } else {
                    CTabItem item = items[selectedIndex];
                    int w = size.x - borderRight - 3 - minRect.width - maxRect.width - chevronRect.width;
                    if (topRightRect.width > 0) {
                        w -= topRightRect.width + 3;
                    }
                    chevronRect.x = Math.min(item.x + item.width + 3, w);
                }
                if (borderRight > 0) {
                    chevronRect.x += 1;
                }
            }
        } else {
            if (showChevron) {
                chevronRect.width = 3 * BUTTON_SIZE / 2;
                chevronRect.height = BUTTON_SIZE;
                int i = 0, lastIndex = -1;
                while (i < priority.length && items[priority[i]].showing) {
                    lastIndex = Math.max(lastIndex, priority[i++]);
                }
                if (lastIndex == -1) {
                    lastIndex = firstIndex;
                }
                CTabItem lastItem = items[lastIndex];
                int w = lastItem.x + lastItem.width + 3;
                //        if (!simple && lastIndex == selectedIndex) w += curveWidth - 2*curveIndent;
                chevronRect.x = Math.min(w, getRightItemEdge());
                chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height) / 2
                        : borderTop + (tabHeight - chevronRect.height) / 2;
            }
        }
        //    if (oldX != chevronRect.x || oldWidth != chevronRect.width ||
        //        oldY != chevronRect.y || oldHeight != chevronRect.height) {
        //      int left = Math.min(oldX, chevronRect.x);
        //      int right = Math.max(oldX + oldWidth, chevronRect.x + chevronRect.width);
        //      int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1;
        //      redraw(left, top, right - left, tabHeight, false);
        //    }
    }

    boolean updateTabHeight(boolean force) {
        CTabItem[] items = itemHolder.getItems();
        int style = getStyle();
        if (fixedTabHeight == 0 && (style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) {
            highlight_header = 0;
        }
        int oldHeight = tabHeight;
        if (fixedTabHeight != SWT.DEFAULT) {
            tabHeight = fixedTabHeight == 0 ? 0 : fixedTabHeight + 1; // +1 for line drawn across top of tab
        } else {
            int tempHeight = 0;
            //      GC gc = new GC(this);
            if (items.length == 0) {
                //        tempHeight = gc.textExtent("Default", CTabItem.FLAGS).y + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN; //$NON-NLS-1$
                BoxDimensions padding = getItemPadding(false);
                tempHeight = TextSizeUtil.getCharHeight(getFont()) + padding.top + padding.bottom;
            } else {
                for (int i = 0; i < items.length; i++) {
                    //          tempHeight = Math.max(tempHeight, items[i].preferredHeight(gc));
                    tempHeight = Math.max(tempHeight, items[i].preferredHeight(i == selectedIndex));
                }
            }
            //      gc.dispose();
            tabHeight = tempHeight;
        }
        if (!force && tabHeight == oldHeight) {
            return false;
        }

        //    oldSize = null;
        //    if (onBottom) {
        //      int d = tabHeight - 12;
        //      curve = new int[]{0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d,
        //                    11,7+d, 11+d,7,
        //                12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1};
        //      curveWidth = 26+d;
        //      curveIndent = curveWidth/3;
        //    } else {
        //      int d = tabHeight - 12;
        //      curve = new int[]{0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5,
        //                    11,6, 11+d,6+d,
        //                    12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d};
        //      curveWidth = 26+d;
        //      curveIndent = curveWidth/3;
        //
        //      //this could be static but since values depend on curve, better to keep in one place
        //      topCurveHighlightStart = new int[] {
        //          0, 2,  1, 2,  2, 2,
        //          3, 3,  4, 3,  5, 3,
        //          6, 4,  7, 4,
        //          8, 5,
        //          9, 6, 10, 6};
        //
        //      //also, by adding in 'd' here we save some math cost when drawing the curve
        //      topCurveHighlightEnd = new int[] {
        //          10+d, 6+d,
        //          11+d, 7+d,
        //          12+d, 8+d,  13+d, 8+d,
        //          14+d, 9+d,
        //          15+d, 10+d,  16+d, 10+d,
        //          17+d, 11+d,  18+d, 11+d,  19+d, 11+d,
        //          20+d, 12+d,  21+d, 12+d,  22+d,  12+d };
        //    }

        notifyListeners(SWT.Resize, new Event());

        return true;
    }

    int getRightItemEdge() {
        int x = getSize().x - borderRight - 3;
        if (showMin) {
            x -= BUTTON_SIZE;
        }
        if (showMax) {
            x -= BUTTON_SIZE;
        }
        if (showChevron) {
            x -= 3 * BUTTON_SIZE / 2;
        }
        if (topRight != null && topRightAlignment != SWT.FILL) {
            Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
            x -= rightSize.x + 3;
        }
        return Math.max(0, x);
    }

    private void updateItemsWithResizeEvent() {
        Rectangle rectBefore = getClientArea();
        updateItems();
        Rectangle rectAfter = getClientArea();
        if (!rectBefore.equals(rectAfter)) {
            notifyListeners(SWT.Resize, new Event());
        }
    }

    void onResize() {
        //  if (updateItems()) redrawTabs();
        updateItems();

        //    Point size = getSize();
        //    if (oldSize == null) {
        //    redraw();
        //    } else {
        //      if (onBottom && size.y != oldSize.y) {
        //      redraw();
        //      } else {
        //        int x1 = Math.min(size.x, oldSize.x);
        //        if (size.x != oldSize.x) x1 -= borderRight + highlight_margin + 2;
        //        if (!simple) x1 -= 5; // rounded top right corner
        //        int y1 = Math.min(size.y, oldSize.y);
        //        if (size.y != oldSize.y) y1 -= borderBottom + highlight_margin;
        //      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);
        //      }
        //    }
        //    oldSize = size;

        // Differs from SWT code: adjust bounds of the selected items' control
        if (selectedIndex != -1) {
            CTabItem item = itemHolder.getItem(selectedIndex);
            Control control = item.getControl();
            if (control != null && !control.isDisposed()) {
                control.setBounds(getClientArea());
            }
        }
    }

    ///////////
    // Disposal

    void onDispose() {
        /*
         * Usually when an item is disposed of, 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;
        removeControlListener(resizeListener);
        unregisterFocusListener();
        if (showMenu != null && !showMenu.isDisposed()) {
            showMenu.dispose();
            showMenu = null;
        }
        while (itemHolder.size() > 0) {
            CTabItem item = itemHolder.getItem(0);
            item.dispose();
            itemHolder.remove(item);
        }
    }

    //////////////////
    // Helping methods

    private static int checkStyle(int style) {
        int mask = SWT.CLOSE | SWT.TOP | SWT.BOTTOM | SWT.FLAT | SWT.LEFT_TO_RIGHT
        //      | SWT.RIGHT_TO_LEFT
                | SWT.SINGLE | SWT.MULTI;
        int result = style & mask;
        // TOP and BOTTOM are mutually exclusive, TOP is the default
        if ((result & SWT.TOP) != 0) {
            result = result & ~SWT.BOTTOM;
        }
        // SINGLE and MULTI are mutually exclusive, MULTI is the default
        if ((result & SWT.MULTI) != 0) {
            result = result & ~SWT.SINGLE;
        }
        return result;
    }

    private void registerDisposeListener() {
        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent event) {
                onDispose();
                CTabFolder.this.removeDisposeListener(this);
            }
        });
    }

    private void registerFocusListener() {
        if (focusListener == null) {
            focusListener = new FocusListener() {
                @Override
                public void focusGained(FocusEvent event) {
                    onFocus();
                }

                @Override
                public void focusLost(FocusEvent event) {
                    onFocus();
                }
            };
            addFocusListener(focusListener);
        }
    }

    private void onFocus() {
        if (selectedIndex < 0) {
            setSelection(0, true);
        }
        unregisterFocusListener();
    }

    private void unregisterFocusListener() {
        if (focusListener != null) {
            addFocusListener(focusListener);
            focusListener = null;
        }
    }

    private void showListMenu() {
        CTabItem[] items = getItems();
        if (items.length == 0 || !showChevron) {
            return;
        }
        if (showMenu == null || showMenu.isDisposed()) {
            showMenu = new Menu(this);
        } else {
            // TODO [rh] optimize: reuse existing menuItems if possible
            MenuItem[] menuItems = showMenu.getItems();
            for (int i = 0; i < menuItems.length; i++) {
                menuItems[i].dispose();
            }
        }
        showMenu.setOrientation(getOrientation());
        final String id = "CTabFolder_showList_Index"; //$NON-NLS-1$
        for (int i = 0; i < items.length; i++) {
            CTabItem tab = items[i];
            if (!tab.showing) {
                MenuItem item = new MenuItem(showMenu, SWT.NONE);
                item.setText(tab.getText());
                item.setImage(tab.getImage());
                item.setData(id, tab);
                item.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent event) {
                        MenuItem menuItem = (MenuItem) event.getSource();
                        int index = indexOf((CTabItem) menuItem.getData(id));
                        setSelection(index, true);
                    }
                });
            }
        }
        // show menu if it contains any item
        if (showMenu.getItemCount() > 0) {
            int x = chevronRect.x;
            int y = chevronRect.y + chevronRect.height + 1;
            Point location = getDisplay().map(this, null, x, y);
            showMenu.setLocation(location.x, location.y);
            showMenu.setVisible(true);
        }
    }

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

    void createItem(CTabItem item, int index) {
        itemHolder.insert(item, index);
        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;
        updateItems();
        if (getItemCount() == 1) {
            registerFocusListener(); // RWT specific
            if (!updateTabHeight(false)) {
                updateItems();
            }
            redraw();
        } else {
            updateItems();
            //      redrawTabs();
            redraw();
        }

    }

    void destroyItem(CTabItem item) {
        int index = indexOf(item);
        if (!inDispose && index != -1) {
            CTabItem[] items = getItems();
            if (items.length == 1) {
                itemHolder.remove(item);
                priority = new int[0];
                firstIndex = -1;
                selectedIndex = -1;
                Control control = item.getControl();
                if (control != null && !control.isDisposed()) {
                    control.setVisible(false);
                }
                setToolTipText(null);
                setButtonBounds();
            } else {
                itemHolder.remove(item);
                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--;
                }
            }
            updateItems();
            if (getItemCount() == 0) {
                unregisterFocusListener();
            }
        }
    }

    //////////////////
    // Theming related

    int getItemPaddingLeft(boolean selected) {
        return getItemPadding(selected).left;
    }

    int getItemPaddingRight(boolean selected) {
        return getItemPadding(selected).right;
    }

    BoxDimensions getItemPadding(boolean selected) {
        return getThemeAdapter().getItemPadding(selected);
    }

    int getItemSpacing(boolean selected) {
        return getThemeAdapter().getItemSpacing(selected);
    }

    Font getItemFont(boolean selected) {
        return getThemeAdapter().getItemFont(selected);
    }

    private CTabFolderThemeAdapter getThemeAdapter() {
        return (CTabFolderThemeAdapter) getAdapter(ThemeAdapter.class);
    }

    ///////////////////
    // Skinning support

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

    ////////////////
    // Inner classes

    private final class CTabFolderAdapter implements ICTabFolderAdapter {

        @Override
        public Rectangle getChevronRect() {
            Rectangle rect = chevronRect;
            return new Rectangle(rect.x, rect.y, rect.width, rect.height);
        }

        @Override
        public boolean getChevronVisible() {
            return showChevron;
        }

        @Override
        public Rectangle getMinimizeRect() {
            Rectangle rect = minRect;
            return new Rectangle(rect.x, rect.y, rect.width, rect.height);
        }

        @Override
        public Rectangle getMaximizeRect() {
            Rectangle rect = maxRect;
            return new Rectangle(rect.x, rect.y, rect.width, rect.height);
        }

        @Override
        public void showListMenu() {
            CTabFolder.this.showListMenu();
        }

        @Override
        public boolean showItemImage(CTabItem item) {
            return item.showImage();
        }

        @Override
        public boolean showItemClose(CTabItem item) {
            return item.parent.showClose || item.showClose;
        }

        @Override
        public String getShortenedItemText(CTabItem item) {
            return item.getShortenedText(item.getParent().getSelection() == item);
        }

        @Override
        public Color getUserSelectionForeground() {
            return selectionForeground;
        }

        @Override
        public Color getUserSelectionBackground() {
            return selectionBackground;
        }

        @Override
        public Image getUserSelectionBackgroundImage() {
            return selectionBgImage;
        }

        @Override
        public IWidgetGraphicsAdapter getUserSelectionBackgroundGradient() {
            return selectionGraphicsAdapter;
        }

        @Override
        public void doRedraw() {
            setButtonBounds();
        }

    }

}