org.eclipse.swt.widgets.TabFolder.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.swt.widgets.TabFolder.java

Source

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

import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.win32.*;

/**
 * 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>TabItem</code>.
 * <code>Control</code> children are created and then set into a
 * tab item using <code>TabItem#setControl</code>.
 * </p><p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>TOP, BOTTOM</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles TOP and BOTTOM may be specified.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="http://www.eclipse.org/swt/snippets/#tabfolder">TabFolder, TabItem snippets</a>
 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class TabFolder extends Composite {
    TabItem[] items;
    ImageList imageList;
    static final long TabFolderProc;
    static final TCHAR TabFolderClass = new TCHAR(0, OS.WC_TABCONTROL, true);
    boolean createdAsRTL;

    /*
    * These are the undocumented control id's for the children of
    * a tab control.  Since there are no constants for these values,
    * they may change with different versions of Windows.
    */
    static final int ID_UPDOWN = 1;

    static {
        WNDCLASS lpWndClass = new WNDCLASS();
        OS.GetClassInfo(0, TabFolderClass, lpWndClass);
        TabFolderProc = lpWndClass.lpfnWndProc;
        /*
        * Feature in Windows.  The tab control window class
        * uses the CS_HREDRAW and CS_VREDRAW style bits to
        * force a full redraw of the control and all children
        * when resized.  This causes flashing.  The fix is to
        * register a new window class without these bits and
        * implement special code that damages only the exposed
        * area.
        *
        * NOTE:  Screen readers look for the exact class name
        * of the control in order to provide the correct kind
        * of assistance.  Therefore, it is critical that the
        * new window class have the same name.  It is possible
        * to register a local window class with the same name
        * as a global class.  Since bits that affect the class
        * are being changed, it is possible that other native
        * code, other than SWT, could create a control with
        * this class name, and fail unexpectedly.
        */
        lpWndClass.hInstance = OS.GetModuleHandle(null);
        lpWndClass.style &= ~(OS.CS_HREDRAW | OS.CS_VREDRAW | OS.CS_GLOBALCLASS);
        OS.RegisterClass(TabFolderClass, lpWndClass);
    }

    /**
     * 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 composite control which will be the parent of the new instance (cannot be null)
     * @param style the style of control 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>
     *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
     * </ul>
     *
     * @see SWT
     * @see SWT#TOP
     * @see SWT#BOTTOM
     * @see Widget#checkSubclass
     * @see Widget#getStyle
     */
    public TabFolder(Composite parent, int style) {
        super(parent, checkStyle(style));
    }

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

    @Override
    long callWindowProc(long hwnd, int msg, long wParam, long lParam) {
        if (handle == 0)
            return 0;
        return OS.CallWindowProc(TabFolderProc, hwnd, msg, wParam, lParam);
    }

    static int checkStyle(int style) {
        style = checkBits(style, SWT.TOP, SWT.BOTTOM, 0, 0, 0, 0);

        /*
        * Even though it is legal to create this widget
        * with scroll bars, they serve no useful purpose
        * because they do not automatically scroll the
        * widget's client area.  The fix is to clear
        * the SWT style.
        */
        return style & ~(SWT.H_SCROLL | SWT.V_SCROLL);
    }

    @Override
    protected void checkSubclass() {
        if (!isValidSubclass())
            error(SWT.ERROR_INVALID_SUBCLASS);
    }

    @Override
    Point computeSizeInPixels(int wHint, int hHint, boolean changed) {
        checkWidget();
        Point size = super.computeSizeInPixels(wHint, hHint, changed);
        RECT insetRect = new RECT(), itemRect = new RECT();
        OS.SendMessage(handle, OS.TCM_ADJUSTRECT, 0, insetRect);
        int width = insetRect.left - insetRect.right;
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        if (count != 0) {
            OS.SendMessage(handle, OS.TCM_GETITEMRECT, count - 1, itemRect);
            width = Math.max(width, itemRect.right - insetRect.right);
        }
        RECT rect = new RECT();
        OS.SetRect(rect, 0, 0, width, size.y);
        OS.SendMessage(handle, OS.TCM_ADJUSTRECT, 1, rect);
        int border = getBorderWidthInPixels();
        rect.left -= border;
        rect.right += border;
        width = rect.right - rect.left;
        size.x = Math.max(width, size.x);
        return size;
    }

    @Override
    Rectangle computeTrimInPixels(int x, int y, int width, int height) {
        checkWidget();
        RECT rect = new RECT();
        OS.SetRect(rect, x, y, x + width, y + height);
        OS.SendMessage(handle, OS.TCM_ADJUSTRECT, 1, rect);
        int border = getBorderWidthInPixels();
        rect.left -= border;
        rect.right += border;
        rect.top -= border;
        rect.bottom += border;
        int newWidth = rect.right - rect.left;
        int newHeight = rect.bottom - rect.top;
        return new Rectangle(rect.left, rect.top, newWidth, newHeight);
    }

    void createItem(TabItem item, int index) {
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        if (!(0 <= index && index <= count))
            error(SWT.ERROR_INVALID_RANGE);
        if (count == items.length) {
            TabItem[] newItems = new TabItem[items.length + 4];
            System.arraycopy(items, 0, newItems, 0, items.length);
            items = newItems;
        }
        TCITEM tcItem = new TCITEM();
        if (OS.SendMessage(handle, OS.TCM_INSERTITEM, index, tcItem) == -1) {
            error(SWT.ERROR_ITEM_NOT_ADDED);
        }
        System.arraycopy(items, index, items, index + 1, count - index);
        items[index] = item;

        /*
        * Send a selection event when the item that is added becomes
        * the new selection.  This only happens when the first item
        * is added.
        */
        if (count == 0) {
            Event event = new Event();
            event.item = items[0];
            sendSelectionEvent(SWT.Selection, event, true);
            // the widget could be destroyed at this point
        }
    }

    @Override
    void createHandle() {
        super.createHandle();
        state &= ~(CANVAS | THEME_BACKGROUND);

        /*
        * Feature in Windows.  Despite the fact that the
        * tool tip text contains \r\n, the tooltip will
        * not honour the new line unless TTM_SETMAXTIPWIDTH
        * is set.  The fix is to set TTM_SETMAXTIPWIDTH to
        * a large value.
        */
        long hwndToolTip = OS.SendMessage(handle, OS.TCM_GETTOOLTIPS, 0, 0);
        OS.SendMessage(hwndToolTip, OS.TTM_SETMAXTIPWIDTH, 0, 0x7FFF);

        createdAsRTL = (style & SWT.RIGHT_TO_LEFT) != 0;
    }

    @Override
    void createWidget() {
        super.createWidget();
        items = new TabItem[4];
    }

    void destroyItem(TabItem item) {
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        int index = 0;
        while (index < count) {
            if (items[index] == item)
                break;
            index++;
        }
        if (index == count)
            return;
        int selectionIndex = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
        if (OS.SendMessage(handle, OS.TCM_DELETEITEM, index, 0) == 0) {
            error(SWT.ERROR_ITEM_NOT_REMOVED);
        }
        System.arraycopy(items, index + 1, items, index, --count - index);
        items[count] = null;
        if (count == 0) {
            if (imageList != null) {
                OS.SendMessage(handle, OS.TCM_SETIMAGELIST, 0, 0);
                display.releaseImageList(imageList);
            }
            imageList = null;
            items = new TabItem[4];
        }
        if (count > 0 && index == selectionIndex) {
            setSelection(Math.max(0, selectionIndex - 1), true);
        }
    }

    @Override
    void drawThemeBackground(long hDC, long hwnd, RECT rect) {
        RECT rect2 = new RECT();
        OS.GetClientRect(handle, rect2);
        OS.MapWindowPoints(handle, hwnd, rect2, 2);
        if (OS.IntersectRect(new RECT(), rect2, rect)) {
            OS.DrawThemeBackground(display.hTabTheme(), hDC, OS.TABP_BODY, 0, rect2, null);
        }
    }

    @Override
    Control findThemeControl() {
        /* It is not possible to change the background of this control */
        return this;
    }

    @Override
    Rectangle getClientAreaInPixels() {
        checkWidget();
        forceResize();
        RECT rect = new RECT();
        OS.GetClientRect(handle, rect);
        OS.SendMessage(handle, OS.TCM_ADJUSTRECT, 0, rect);
        int width = rect.right - rect.left;
        int height = rect.bottom - rect.top;
        return new Rectangle(rect.left, rect.top, width, height);
    }

    /**
     * Returns the item at the given, zero-relative index in the
     * receiver. Throws an exception if the index is out of range.
     *
     * @param index the index of the item to return
     * @return the item at the given index
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</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 TabItem getItem(int index) {
        checkWidget();
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        if (!(0 <= index && index < count))
            error(SWT.ERROR_INVALID_RANGE);
        return items[index];
    }

    /**
     * Returns the tab item at the given point in the receiver
     * or null if no such item exists. The point is in the
     * coordinate system of the receiver.
     *
     * @param point the point used to locate the item
     * @return the tab item at the given point, or null if the point is not in a tab item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.4
     */
    public TabItem getItem(Point point) {
        checkWidget();
        if (point == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        TCHITTESTINFO pinfo = new TCHITTESTINFO();
        pinfo.x = point.x;
        pinfo.y = point.y;
        int index = (int) OS.SendMessage(handle, OS.TCM_HITTEST, 0, pinfo);
        if (index == -1)
            return null;
        return items[index];
    }

    /**
     * Returns the number of items contained in the receiver.
     *
     * @return the number of items
     *
     * @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 int getItemCount() {
        checkWidget();
        return (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
    }

    /**
     * Returns an array of <code>TabItem</code>s which are the items
     * in the receiver.
     * <p>
     * Note: This is not the actual structure used by the receiver
     * to maintain its list of items, so modifying the array will
     * not affect the receiver.
     * </p>
     *
     * @return the items in 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 TabItem[] getItems() {
        checkWidget();
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        TabItem[] result = new TabItem[count];
        System.arraycopy(items, 0, result, 0, count);
        return result;
    }

    /**
     * Returns an array of <code>TabItem</code>s that are currently
     * selected in the receiver. An empty array indicates that no
     * items are selected.
     * <p>
     * Note: This is not the actual structure used by the receiver
     * to maintain its selection, so modifying the array will
     * not affect the receiver.
     * </p>
     * @return an array representing the selection
     *
     * @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 TabItem[] getSelection() {
        checkWidget();
        int index = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
        if (index == -1)
            return new TabItem[0];
        return new TabItem[] { items[index] };
    }

    /**
     * Returns the zero-relative index of the item which is currently
     * selected in the receiver, or -1 if no item is selected.
     *
     * @return the index of the selected item
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int getSelectionIndex() {
        checkWidget();
        return (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
    }

    int imageIndex(Image image) {
        /*
         * Bug 497387: Return -1 if there is no image for the tab, for more details
         * refer: https://msdn.microsoft.com/pt-br/library/windows/hardware/bb760554
         */
        if (image == null)
            return -1;
        if (imageList == null) {
            Rectangle bounds = image.getBoundsInPixels();
            imageList = display.getImageList(style & SWT.RIGHT_TO_LEFT, bounds.width, bounds.height);
            int index = imageList.add(image);
            long hImageList = imageList.getHandle();
            OS.SendMessage(handle, OS.TCM_SETIMAGELIST, 0, hImageList);
            return index;
        }
        int index = imageList.indexOf(image);
        if (index == -1) {
            index = imageList.add(image);
        } else {
            imageList.put(index, image);
        }
        return index;
    }

    /**
     * Searches the receiver's list starting at the first item
     * (index 0) until an item is found that is equal to the
     * argument, and returns the index of that item. If no item
     * is found, returns -1.
     *
     * @param item the search item
     * @return the index of the item
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public int indexOf(TabItem item) {
        checkWidget();
        if (item == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        for (int i = 0; i < count; i++) {
            if (items[i] == item)
                return i;
        }
        return -1;
    }

    @Override
    Point minimumSize(int wHint, int hHint, boolean flushCache) {
        Control[] children = _getChildren();
        int width = 0, height = 0;
        for (int i = 0; i < children.length; i++) {
            Control child = children[i];
            int index = 0;
            int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
            while (index < count) {
                if (items[index].control == child)
                    break;
                index++;
            }
            if (index == count) {
                Rectangle rect = DPIUtil.autoScaleUp(child.getBounds());
                width = Math.max(width, rect.x + rect.width);
                height = Math.max(height, rect.y + rect.height);
            } else {
                /*
                 * Since computeSize can be overridden by subclasses, we cannot
                 * call computeSizeInPixels directly.
                 */
                Point size = DPIUtil.autoScaleUp(
                        child.computeSize(DPIUtil.autoScaleDown(wHint), DPIUtil.autoScaleDown(hHint), flushCache));
                width = Math.max(width, size.x);
                height = Math.max(height, size.y);
            }
        }
        return new Point(width, height);
    }

    @Override
    boolean mnemonicHit(char key) {
        for (int i = 0; i < items.length; i++) {
            TabItem item = items[i];
            if (item != null) {
                char ch = findMnemonic(item.getText());
                if (Character.toUpperCase(key) == Character.toUpperCase(ch)) {
                    if (forceFocus()) {
                        if (i != getSelectionIndex())
                            setSelection(i, true);
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    boolean mnemonicMatch(char key) {
        for (int i = 0; i < items.length; i++) {
            TabItem item = items[i];
            if (item != null) {
                char ch = findMnemonic(item.getText());
                if (Character.toUpperCase(key) == Character.toUpperCase(ch)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    void releaseChildren(boolean destroy) {
        if (items != null) {
            int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
            for (int i = 0; i < count; i++) {
                TabItem item = items[i];
                if (item != null && !item.isDisposed()) {
                    item.release(false);
                }
            }
            items = null;
        }
        super.releaseChildren(destroy);
    }

    @Override
    void releaseWidget() {
        super.releaseWidget();
        if (imageList != null) {
            OS.SendMessage(handle, OS.TCM_SETIMAGELIST, 0, 0);
            display.releaseImageList(imageList);
        }
        imageList = null;
    }

    @Override
    void removeControl(Control control) {
        super.removeControl(control);
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        for (int i = 0; i < count; i++) {
            TabItem item = items[i];
            if (item.control == control)
                item.setControl(null);
        }
    }

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

    @Override
    void reskinChildren(int flags) {
        if (items != null) {
            int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
            for (int i = 0; i < count; i++) {
                TabItem item = items[i];
                if (item != null)
                    item.reskin(flags);
            }
        }
        super.reskinChildren(flags);
    }

    /**
     * Sets the receiver's selection to the given item.
     * The current selected is first cleared, then the new item is
     * selected.
     *
     * @param item the item to select
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 3.2
     */
    public void setSelection(TabItem item) {
        checkWidget();
        if (item == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        setSelection(new TabItem[] { item });
    }

    /**
     * Sets the receiver's selection to be the given array of items.
     * The current selected is first cleared, then the new items are
     * selected.
     *
     * @param items the array of items
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the items array is null</li>
     * </ul>
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     */
    public void setSelection(TabItem[] items) {
        checkWidget();
        if (items == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        if (items.length == 0) {
            setSelection(-1, false);
        } else {
            for (int i = items.length - 1; i >= 0; --i) {
                int index = indexOf(items[i]);
                if (index != -1)
                    setSelection(index, false);
            }
        }
    }

    @Override
    public void setFont(Font font) {
        checkWidget();
        Rectangle oldRect = getClientAreaInPixels();
        super.setFont(font);
        Rectangle newRect = getClientAreaInPixels();
        if (!oldRect.equals(newRect)) {
            sendResize();
            int index = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
            if (index != -1) {
                TabItem item = items[index];
                Control control = item.control;
                if (control != null && !control.isDisposed()) {
                    control.setBoundsInPixels(getClientAreaInPixels());
                }
            }
        }
    }

    /**
     * Selects the item at the given zero-relative index in the receiver.
     * If the item at the index was already selected, it remains selected.
     * The current selection is first cleared, then the new items are
     * selected. Indices that are out of range are ignored.
     *
     * @param index the index of the item to select
     *
     * @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();
        int count = (int) OS.SendMessage(handle, OS.TCM_GETITEMCOUNT, 0, 0);
        if (!(0 <= index && index < count))
            return;
        setSelection(index, false);
    }

    void setSelection(int index, boolean notify) {
        int oldIndex = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
        if (oldIndex == index)
            return;
        if (oldIndex != -1) {
            TabItem item = items[oldIndex];
            Control control = item.control;
            if (control != null && !control.isDisposed()) {
                control.setVisible(false);
            }
        }
        OS.SendMessage(handle, OS.TCM_SETCURSEL, index, 0);
        int newIndex = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
        if (newIndex != -1) {
            TabItem item = items[newIndex];
            Control control = item.control;
            if (control != null && !control.isDisposed()) {
                control.setBoundsInPixels(getClientAreaInPixels());
                control.setVisible(true);
            }
            if (notify) {
                Event event = new Event();
                event.item = item;
                sendSelectionEvent(SWT.Selection, event, true);
            }
        }
    }

    @Override
    boolean updateTextDirection(int textDirection) {
        if (super.updateTextDirection(textDirection)) {
            if (textDirection != AUTO_TEXT_DIRECTION) {
                textDirection = style & SWT.FLIP_TEXT_DIRECTION;
            }
            for (int i = 0, n = items.length; i < n && items[i] != null; i++) {
                items[i].updateTextDirection(textDirection);
            }
            return true;
        }
        return false;
    }

    @Override
    String toolTipText(NMTTDISPINFO hdr) {
        if ((hdr.uFlags & OS.TTF_IDISHWND) != 0) {
            return null;
        }
        int index = (int) hdr.idFrom;
        long hwndToolTip = OS.SendMessage(handle, OS.TCM_GETTOOLTIPS, 0, 0);
        if (hwndToolTip == hdr.hwndFrom) {
            /*
            * Bug in Windows. For some reason the reading order
            * in NMTTDISPINFO is sometimes set incorrectly.  The
            * reading order seems to change every time the mouse
            * enters the control from the top edge.  The fix is
            * to explicitly set TTF_RTLREADING.
            */
            int flags = SWT.RIGHT_TO_LEFT | SWT.FLIP_TEXT_DIRECTION;
            if ((style & flags) != 0 && (style & flags) != flags) {
                hdr.uFlags |= OS.TTF_RTLREADING;
            } else {
                hdr.uFlags &= ~OS.TTF_RTLREADING;
            }
            if (toolTipText != null)
                return "";
            if (0 <= index && index < items.length) {
                TabItem item = items[index];
                if (item != null)
                    return item.toolTipText;
            }
        }
        return super.toolTipText(hdr);
    }

    @Override
    boolean traversePage(boolean next) {
        int count = getItemCount();
        if (count <= 1)
            return false;
        int index = getSelectionIndex();
        if (index == -1) {
            index = 0;
        } else {
            int offset = (next) ? 1 : -1;
            index = (index + offset + count) % count;
        }
        setSelection(index, true);
        if (index == getSelectionIndex()) {
            OS.SendMessage(handle, OS.WM_CHANGEUISTATE, OS.UIS_INITIALIZE, 0);
            return true;
        }
        return false;
    }

    @Override
    void updateOrientation() {
        super.updateOrientation();
        long hwndChild = OS.GetWindow(handle, OS.GW_CHILD);
        while (hwndChild != 0) {
            char[] buffer = new char[128];
            int length = OS.GetClassName(hwndChild, buffer, buffer.length);
            String className = new String(buffer, 0, length);
            if (className.equals("msctls_updown32")) { //$NON-NLS-1$
                int bits = OS.GetWindowLong(hwndChild, OS.GWL_EXSTYLE);
                if ((style & SWT.RIGHT_TO_LEFT) != 0) {
                    bits |= OS.WS_EX_LAYOUTRTL;
                } else {
                    bits &= ~OS.WS_EX_LAYOUTRTL;
                }
                bits &= ~OS.WS_EX_RTLREADING;
                OS.SetWindowLong(hwndChild, OS.GWL_EXSTYLE, bits);
                OS.InvalidateRect(hwndChild, null, true);
                break;
            }
            hwndChild = OS.GetWindow(hwndChild, OS.GW_HWNDNEXT);
        }
        RECT rect = new RECT();
        OS.GetWindowRect(handle, rect);
        int width = rect.right - rect.left, height = rect.bottom - rect.top;
        OS.SetWindowPos(handle, 0, 0, 0, width - 1, height - 1, OS.SWP_NOMOVE | OS.SWP_NOZORDER);
        OS.SetWindowPos(handle, 0, 0, 0, width, height, OS.SWP_NOMOVE | OS.SWP_NOZORDER);
        if (imageList != null) {
            Point size = imageList.getImageSize();
            display.releaseImageList(imageList);
            imageList = display.getImageList(style & SWT.RIGHT_TO_LEFT, size.x, size.y);
            long hImageList = imageList.getHandle();
            OS.SendMessage(handle, OS.TCM_SETIMAGELIST, 0, hImageList);
            TCITEM tcItem = new TCITEM();
            tcItem.mask = OS.TCIF_IMAGE;
            for (int i = 0; i < items.length; i++) {
                TabItem item = items[i];
                if (item == null)
                    break;
                Image image = item.image;
                if (image != null) {
                    tcItem.iImage = imageIndex(image);
                    OS.SendMessage(handle, OS.TCM_SETITEM, i, tcItem);
                }
            }
        }
    }

    @Override
    int widgetStyle() {
        /*
        * Bug in Windows.  Under certain circumstances,
        * when TCM_SETITEM is used to change the text
        * in a tab item, the tab folder draws on top
        * of the client area.  The fix is ensure that
        * this cannot happen by setting WS_CLIPCHILDREN.
        */
        int bits = super.widgetStyle() | OS.WS_CLIPCHILDREN;
        if ((style & SWT.NO_FOCUS) != 0)
            bits |= OS.TCS_FOCUSNEVER;
        if ((style & SWT.BOTTOM) != 0)
            bits |= OS.TCS_BOTTOM;
        return bits | OS.TCS_TABS | OS.TCS_TOOLTIPS;
    }

    @Override
    TCHAR windowClass() {
        return TabFolderClass;
    }

    @Override
    long windowProc() {
        return TabFolderProc;
    }

    @Override
    LRESULT WM_GETDLGCODE(long wParam, long lParam) {
        LRESULT result = super.WM_GETDLGCODE(wParam, lParam);
        /*
        * Return DLGC_BUTTON so that mnemonics will be
        * processed without needing to press the ALT key
        * when the widget has focus.
        */
        if (result != null)
            return result;
        return new LRESULT(OS.DLGC_BUTTON | OS.DLGC_WANTARROWS);
    }

    @Override
    LRESULT WM_GETOBJECT(long wParam, long lParam) {
        /*
        * Ensure that there is an accessible object created for this
        * control because support for publishing the keyboard shortcut
        * for page switching is implemented in the accessibility package.
        */
        if (accessible == null)
            accessible = new_Accessible(this);
        return super.WM_GETOBJECT(wParam, lParam);
    }

    @Override
    LRESULT WM_KEYDOWN(long wParam, long lParam) {
        LRESULT result = super.WM_KEYDOWN(wParam, lParam);
        if (result != null)
            return result;
        switch ((int) wParam) {
        case OS.VK_LEFT:
        case OS.VK_RIGHT:
            /*
            * Bug in Windows. The behavior for the left and right keys is not
            * changed if the orientation changes after the control was created.
            * The fix is to replace VK_LEFT by VK_RIGHT and VK_RIGHT by VK_LEFT
            * when the current orientation differs from the orientation used to
            * create the control.
            */
            boolean isRTL = (style & SWT.RIGHT_TO_LEFT) != 0;
            if (isRTL != createdAsRTL) {
                long code = callWindowProc(handle, OS.WM_KEYDOWN, wParam == OS.VK_RIGHT ? OS.VK_LEFT : OS.VK_RIGHT,
                        lParam);
                return new LRESULT(code);
            }
            break;
        }
        return result;
    }

    @Override
    LRESULT WM_MOUSELEAVE(long wParam, long lParam) {
        LRESULT result = super.WM_MOUSELEAVE(wParam, lParam);
        if (result != null)
            return result;
        /*
        * Bug in Windows.  On XP, when a tooltip is
        * hidden due to a time out or mouse press,
        * the tooltip remains active although no
        * longer visible and won't show again until
        * another tooltip becomes active.  If there
        * is only one tooltip in the window,  it will
        * never show again.  The fix is to remove the
        * current tooltip and add it again every time
        * the mouse leaves the control.
        */
        TOOLINFO lpti = new TOOLINFO();
        lpti.cbSize = TOOLINFO.sizeof;
        long hwndToolTip = OS.SendMessage(handle, OS.TCM_GETTOOLTIPS, 0, 0);
        if (OS.SendMessage(hwndToolTip, OS.TTM_GETCURRENTTOOL, 0, lpti) != 0) {
            if ((lpti.uFlags & OS.TTF_IDISHWND) == 0) {
                OS.SendMessage(hwndToolTip, OS.TTM_DELTOOL, 0, lpti);
                OS.SendMessage(hwndToolTip, OS.TTM_ADDTOOL, 0, lpti);
            }
        }
        return result;
    }

    @Override
    LRESULT WM_NCHITTEST(long wParam, long lParam) {
        LRESULT result = super.WM_NCHITTEST(wParam, lParam);
        if (result != null)
            return result;
        /*
        * Feature in Windows.  The tab control implements
        * WM_NCHITTEST to return HTCLIENT when the cursor
        * is inside the tab buttons.  This causes mouse
        * events like WM_MOUSEMOVE to be delivered to the
        * parent.  Also, tool tips for the tab control are
        * never invoked because tool tips rely on mouse
        * events to be delivered to the window that wants
        * to display the tool tip.  The fix is to call the
        * default window proc that returns HTCLIENT when
        * the mouse is in the client area.
        */
        long hittest = OS.DefWindowProc(handle, OS.WM_NCHITTEST, wParam, lParam);
        return new LRESULT(hittest);
    }

    @Override
    LRESULT WM_NOTIFY(long wParam, long lParam) {
        /*
        * Feature in Windows.  When the tab folder window
        * proc processes WM_NOTIFY, it forwards this
        * message to its parent.  This is done so that
        * children of this control that send this message
        * type to their parent will notify not only
        * this control but also the parent of this control,
        * which is typically the application window and
        * the window that is looking for the message.
        * If the control did not forward the message,
        * applications would have to subclass the control
        * window to see the message. Because the control
        * window is subclassed by SWT, the message
        * is delivered twice, once by SWT and once when
        * the message is forwarded by the window proc.
        * The fix is to avoid calling the window proc
        * for this control.
        */
        LRESULT result = super.WM_NOTIFY(wParam, lParam);
        if (result != null)
            return result;
        return LRESULT.ZERO;
    }

    @Override
    LRESULT WM_PARENTNOTIFY(long wParam, long lParam) {
        LRESULT result = super.WM_PARENTNOTIFY(wParam, lParam);
        if (result != null)
            return result;
        /*
        * Feature in Windows.  Windows does not explicitly set the orientation of
        * the buddy control.  Instead, the orientation is inherited when WS_EX_LAYOUTRTL
        * is specified for the tab folder.  This means that when both WS_EX_LAYOUTRTL
        * and WS_EX_NOINHERITLAYOUT are specified for the tab folder, the buddy control
        * will not be oriented correctly.  The fix is to explicitly set the orientation
        * for the buddy control.
        */
        if ((style & SWT.RIGHT_TO_LEFT) != 0) {
            int code = OS.LOWORD(wParam);
            switch (code) {
            case OS.WM_CREATE: {
                int id = OS.HIWORD(wParam);
                long hwnd = lParam;
                if (id == ID_UPDOWN) {
                    int bits = OS.GetWindowLong(hwnd, OS.GWL_EXSTYLE);
                    OS.SetWindowLong(hwnd, OS.GWL_EXSTYLE, bits | OS.WS_EX_LAYOUTRTL);
                }
                break;
            }
            }
        }
        return result;
    }

    @Override
    LRESULT WM_SIZE(long wParam, long lParam) {
        LRESULT result = super.WM_SIZE(wParam, lParam);
        /*
        * It is possible (but unlikely), that application
        * code could have disposed the widget in the resize
        * event.  If this happens, end the processing of the
        * Windows message by returning the result of the
        * WM_SIZE message.
        */
        if (isDisposed())
            return result;
        int index = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
        if (index != -1) {
            TabItem item = items[index];
            Control control = item.control;
            if (control != null && !control.isDisposed()) {
                control.setBoundsInPixels(getClientAreaInPixels());
            }
        }
        return result;
    }

    @Override
    LRESULT WM_WINDOWPOSCHANGING(long wParam, long lParam) {
        LRESULT result = super.WM_WINDOWPOSCHANGING(wParam, lParam);
        if (result != null)
            return result;
        if (!OS.IsWindowVisible(handle))
            return result;
        WINDOWPOS lpwp = new WINDOWPOS();
        OS.MoveMemory(lpwp, lParam, WINDOWPOS.sizeof);
        if ((lpwp.flags & (OS.SWP_NOSIZE | OS.SWP_NOREDRAW)) != 0) {
            return result;
        }
        // TEMPORARY CODE
        //   if (OS.IsAppThemed ()) {
        //      OS.InvalidateRect (handle, null, true);
        //      return result;
        //   }
        int bits = OS.GetWindowLong(handle, OS.GWL_STYLE);
        if ((bits & OS.TCS_MULTILINE) != 0) {
            OS.InvalidateRect(handle, null, true);
            return result;
        }
        RECT rect = new RECT();
        OS.SetRect(rect, 0, 0, lpwp.cx, lpwp.cy);
        OS.SendMessage(handle, OS.WM_NCCALCSIZE, 0, rect);
        int newWidth = rect.right - rect.left;
        int newHeight = rect.bottom - rect.top;
        OS.GetClientRect(handle, rect);
        int oldWidth = rect.right - rect.left;
        int oldHeight = rect.bottom - rect.top;
        if (newWidth == oldWidth && newHeight == oldHeight) {
            return result;
        }
        RECT inset = new RECT();
        OS.SendMessage(handle, OS.TCM_ADJUSTRECT, 0, inset);
        int marginX = -inset.right, marginY = -inset.bottom;
        if (newWidth != oldWidth) {
            int left = oldWidth;
            if (newWidth < oldWidth)
                left = newWidth;
            OS.SetRect(rect, left - marginX, 0, newWidth, newHeight);
            OS.InvalidateRect(handle, rect, true);
        }
        if (newHeight != oldHeight) {
            int bottom = oldHeight;
            if (newHeight < oldHeight)
                bottom = newHeight;
            if (newWidth < oldWidth)
                oldWidth -= marginX;
            OS.SetRect(rect, 0, bottom - marginY, oldWidth, newHeight);
            OS.InvalidateRect(handle, rect, true);
        }
        return result;
    }

    @Override
    LRESULT wmNotifyChild(NMHDR hdr, long wParam, long lParam) {
        int code = hdr.code;
        switch (code) {
        case OS.TCN_SELCHANGE:
        case OS.TCN_SELCHANGING:
            TabItem item = null;
            int index = (int) OS.SendMessage(handle, OS.TCM_GETCURSEL, 0, 0);
            if (index != -1)
                item = items[index];
            if (item != null) {
                Control control = item.control;
                if (control != null && !control.isDisposed()) {
                    if (code == OS.TCN_SELCHANGE) {
                        control.setBoundsInPixels(getClientAreaInPixels());
                    }
                    control.setVisible(code == OS.TCN_SELCHANGE);
                }
            }
            if (code == OS.TCN_SELCHANGE) {
                Event event = new Event();
                event.item = item;
                sendSelectionEvent(SWT.Selection, event, false);
            }
        }
        return super.wmNotifyChild(hdr, wParam, lParam);
    }

}