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

Java tutorial

Introduction

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

Source

/*******************************************************************************
 * Copyright (c) 2000, 2016 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.graphics.*;

/**
 * Instances of this class provide an area for dynamically
 * positioning the items they contain.
 * <p>
 * The item children that may be added to instances of this class
 * must be of type <code>CoolItem</code>.
 * </p><p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to add <code>Control</code> children to it,
 * or set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>FLAT, HORIZONTAL, VERTICAL</dd>
 * <dt><b>Events:</b></dt>
 * <dd>(none)</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles HORIZONTAL and VERTICAL 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/#coolbar">CoolBar 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 CoolBar extends Composite {
    CoolItem[][] items = new CoolItem[0][0];
    CoolItem[] originalItems = new CoolItem[0];
    Cursor hoverCursor, dragCursor, cursor;
    CoolItem dragging = null;
    int mouseXOffset, itemXOffset;
    boolean isLocked = false;
    boolean inDispose = false;
    static final int ROW_SPACING = 2;
    static final int CLICK_DISTANCE = 3;
    static final int DEFAULT_COOLBAR_WIDTH = 0;
    static final int DEFAULT_COOLBAR_HEIGHT = 0;

    /**
     * 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#FLAT
     * @see SWT#HORIZONTAL
     * @see SWT#VERTICAL
     * @see Widget#checkSubclass
     * @see Widget#getStyle
     */
    public CoolBar(Composite parent, int style) {
        super(parent, checkStyle(style));
        if ((style & SWT.VERTICAL) != 0) {
            this.style |= SWT.VERTICAL;
            hoverCursor = new Cursor(display, SWT.CURSOR_SIZENS);
        } else {
            this.style |= SWT.HORIZONTAL;
            hoverCursor = new Cursor(display, SWT.CURSOR_SIZEWE);
        }
        dragCursor = new Cursor(display, SWT.CURSOR_SIZEALL);
        Listener listener = event -> {
            switch (event.type) {
            case SWT.Dispose:
                onDispose(event);
                break;
            case SWT.MouseDown:
                onMouseDown(event);
                break;
            case SWT.MouseExit:
                onMouseExit();
                break;
            case SWT.MouseMove:
                onMouseMove(event);
                break;
            case SWT.MouseUp:
                onMouseUp(event);
                break;
            case SWT.MouseDoubleClick:
                onMouseDoubleClick(event);
                break;
            case SWT.Paint:
                onPaint(event);
                break;
            case SWT.Resize:
                onResize();
                break;
            }
        };
        int[] events = new int[] { SWT.Dispose, SWT.MouseDown, SWT.MouseExit, SWT.MouseMove, SWT.MouseUp,
                SWT.MouseDoubleClick, SWT.Paint, SWT.Resize };
        for (int i = 0; i < events.length; i++) {
            addListener(events[i], listener);
        }
    }

    static int checkStyle(int style) {
        style |= SWT.NO_FOCUS;
        return (style | SWT.NO_REDRAW_RESIZE) & ~(SWT.V_SCROLL | SWT.H_SCROLL);
    }

    void _setCursor(Cursor cursor) {
        if (this.cursor != null)
            return;
        super.setCursor(cursor);
    }

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

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        checkWidget();
        int width = 0, height = 0;
        wrapItems((style & SWT.VERTICAL) != 0 ? hHint : wHint);
        boolean flat = (style & SWT.FLAT) != 0;
        for (int row = 0; row < items.length; row++) {
            int rowWidth = 0, rowHeight = 0;
            for (int i = 0; i < items[row].length; i++) {
                CoolItem item = items[row][i];
                rowWidth += item.preferredWidth;
                rowHeight = Math.max(rowHeight, item.preferredHeight);
            }
            height += rowHeight;
            if (!flat && row > 0)
                height += ROW_SPACING;
            width = Math.max(width, rowWidth);
        }
        wrapItems(getWidth());
        if (width == 0)
            width = DEFAULT_COOLBAR_WIDTH;
        if (height == 0)
            height = DEFAULT_COOLBAR_HEIGHT;
        Point size = fixPoint(width, height);
        if (wHint != SWT.DEFAULT)
            size.x = wHint;
        if (hHint != SWT.DEFAULT)
            size.y = hHint;
        Rectangle trim = computeTrim(0, 0, size.x, size.y);
        return new Point(trim.width, trim.height);
    }

    CoolItem getGrabbedItem(int x, int y) {
        for (int row = 0; row < items.length; row++) {
            for (int i = 0; i < items[row].length; i++) {
                CoolItem item = items[row][i];
                Rectangle bounds = item.internalGetBounds();
                bounds.width = CoolItem.MINIMUM_WIDTH;
                if (bounds.x > x)
                    break;
                if (bounds.y > y)
                    return null;
                if (bounds.contains(x, y)) {
                    return item;
                }
            }
        }
        return null;
    }

    /**
     * Returns the item that is currently displayed at the given,
     * zero-relative index. Throws an exception if the index is
     * out of range.
     *
     * @param index the visual index of the item to return
     * @return the item at the given visual 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 CoolItem getItem(int index) {
        checkWidget();
        if (index < 0)
            error(SWT.ERROR_INVALID_RANGE);
        for (int row = 0; row < items.length; row++) {
            if (items[row].length > index) {
                return items[row][index];
            } else {
                index -= items[row].length;
            }
        }
        error(SWT.ERROR_INVALID_RANGE);
        return null;
    }

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

    /**
     * Returns an array of <code>CoolItem</code>s in the order
     * in which they are currently being displayed.
     * <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 receiver's items in their current visual order
     *
     * @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 CoolItem[] getItems() {
        checkWidget();
        CoolItem[] result = new CoolItem[getItemCount()];
        int offset = 0;
        for (int row = 0; row < items.length; row++) {
            System.arraycopy(items[row], 0, result, offset, items[row].length);
            offset += items[row].length;
        }
        return result;
    }

    Point findItem(CoolItem item) {
        for (int row = 0; row < items.length; row++) {
            for (int i = 0; i < items[row].length; i++) {
                if (items[row][i].equals(item))
                    return new Point(i, row);
            }
        }
        return new Point(-1, -1);
    }

    void fixEvent(Event event) {
        if ((style & SWT.VERTICAL) != 0) {
            int tmp = event.x;
            event.x = event.y;
            event.y = tmp;
        }
    }

    Rectangle fixRectangle(int x, int y, int width, int height) {
        if ((style & SWT.VERTICAL) != 0) {
            return new Rectangle(y, x, height, width);
        }
        return new Rectangle(x, y, width, height);
    }

    Point fixPoint(int x, int y) {
        if ((style & SWT.VERTICAL) != 0) {
            return new Point(y, x);
        }
        return new Point(x, y);
    }

    /**
     * Searches the receiver's items in the order they are currently
     * being displayed, 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 visual order index of the search item, or -1 if the item is not found
     *
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if the item is 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 int indexOf(CoolItem item) {
        checkWidget();
        if (item == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        if (item.isDisposed())
            error(SWT.ERROR_INVALID_ARGUMENT);
        int answer = 0;
        for (int row = 0; row < items.length; row++) {
            for (int i = 0; i < items[row].length; i++) {
                if (items[row][i].equals(item)) {
                    return answer;
                } else {
                    answer++;
                }
            }
        }
        return -1;
    }

    /**
     * Insert the item into the row. Adjust the x and width values
     * appropriately.
     */
    boolean insertItemIntoRow(CoolItem item, int rowIndex, int x_root) {
        if (rowIndex < 0 || rowIndex >= items.length) {
            /* Create a new row for the item. */
            boolean bottom = rowIndex >= items.length;
            CoolItem[][] newRows = new CoolItem[items.length + 1][];
            System.arraycopy(items, 0, newRows, bottom ? 0 : 1, items.length);
            int row = bottom ? items.length : 0;
            newRows[row] = new CoolItem[1];
            newRows[row][0] = item;
            items = newRows;
            item.wrap = true;
            return true;
        }

        int barWidth = getWidth();
        Rectangle bounds = items[rowIndex][0].internalGetBounds();
        int rowY = bounds.y;
        int oldRowHeight = bounds.height;
        int x = Math.max(0, Math.abs(x_root - toDisplay(new Point(0, 0)).x));

        /* Find the insertion index and add the item. */
        int index;
        for (index = 0; index < items[rowIndex].length; index++) {
            if (x < items[rowIndex][index].internalGetBounds().x)
                break;
        }
        if (index == 0) {
            item.wrap = true;
            items[rowIndex][0].wrap = false;
        }
        int oldLength = items[rowIndex].length;
        CoolItem[] newRow = new CoolItem[oldLength + 1];
        System.arraycopy(items[rowIndex], 0, newRow, 0, index);
        newRow[index] = item;
        System.arraycopy(items[rowIndex], index, newRow, index + 1, oldLength - index);
        items[rowIndex] = newRow;

        /* Adjust the width of the item to the left. */
        if (index > 0) {
            CoolItem left = items[rowIndex][index - 1];
            Rectangle leftBounds = left.internalGetBounds();
            int newWidth = x - leftBounds.x;
            if (newWidth < left.internalGetMinimumWidth()) {
                x += left.internalGetMinimumWidth() - newWidth;
                newWidth = left.internalGetMinimumWidth();
            }
            left.setBounds(leftBounds.x, leftBounds.y, newWidth, leftBounds.height);
            left.requestedWidth = newWidth;
        }

        /* Set the item's bounds. */
        int width = 0, height = item.preferredHeight;
        if (index < items[rowIndex].length - 1) {
            CoolItem right = items[rowIndex][index + 1];
            width = right.internalGetBounds().x - x;
            if (width < right.internalGetMinimumWidth()) {
                moveRight(right, right.internalGetMinimumWidth() - width);
                width = right.internalGetBounds().x - x;
            }
            item.setBounds(x, rowY, width, height);
            if (width < item.internalGetMinimumWidth())
                moveLeft(item, item.internalGetMinimumWidth() - width);
        } else {
            width = Math.max(item.internalGetMinimumWidth(), barWidth - x);
            item.setBounds(x, rowY, width, height);
            if (x + width > barWidth)
                moveLeft(item, x + width - barWidth);
        }
        bounds = item.internalGetBounds();
        item.requestedWidth = bounds.width;
        internalRedraw(bounds.x, bounds.y, item.internalGetMinimumWidth(), bounds.height);
        return height > oldRowHeight;
    }

    void internalRedraw(int x, int y, int width, int height) {
        if ((style & SWT.VERTICAL) != 0) {
            redraw(y, x, height, width, false);
        } else {
            redraw(x, y, width, height, false);
        }
    }

    void createItem(CoolItem item, int index) {
        int itemCount = getItemCount(), row = 0;
        if (!(0 <= index && index <= itemCount))
            error(SWT.ERROR_INVALID_RANGE);
        if (items.length == 0) {
            items = new CoolItem[1][1];
            items[0][0] = item;
        } else {
            int i = index;
            /* find the row to insert into */
            if (index < itemCount) {
                while (i > items[row].length) {
                    i -= items[row].length;
                    row++;
                }
            } else {
                row = items.length - 1;
                i = items[row].length;
            }

            // Set the last item in the row to the preferred size
            // and add the new one just to it's right
            int lastIndex = items[row].length - 1;
            CoolItem lastItem = items[row][lastIndex];
            if (lastItem.ideal) {
                Rectangle bounds = lastItem.internalGetBounds();
                bounds.width = lastItem.preferredWidth;
                bounds.height = lastItem.preferredHeight;
                lastItem.requestedWidth = lastItem.preferredWidth;
                lastItem.setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
            }
            if (i == 0) {
                item.wrap = true;
                items[row][0].wrap = false;
            }
            int oldLength = items[row].length;
            CoolItem[] newRow = new CoolItem[oldLength + 1];
            System.arraycopy(items[row], 0, newRow, 0, i);
            newRow[i] = item;
            System.arraycopy(items[row], i, newRow, i + 1, oldLength - i);
            items[row] = newRow;
        }
        item.requestedWidth = CoolItem.MINIMUM_WIDTH;

        int length = originalItems.length;
        CoolItem[] newOriginals = new CoolItem[length + 1];
        System.arraycopy(originalItems, 0, newOriginals, 0, index);
        System.arraycopy(originalItems, index, newOriginals, index + 1, length - index);
        newOriginals[index] = item;
        originalItems = newOriginals;
        layoutItems();

    }

    void destroyItem(CoolItem item) {
        if (inDispose)
            return;
        int row = findItem(item).y;
        if (row == -1)
            return;
        Rectangle bounds = item.internalGetBounds();
        removeItemFromRow(item, row, true);

        int index = 0;
        while (index < originalItems.length) {
            if (originalItems[index] == item)
                break;
            index++;
        }
        int length = originalItems.length - 1;
        CoolItem[] newOriginals = new CoolItem[length];
        System.arraycopy(originalItems, 0, newOriginals, 0, index);
        System.arraycopy(originalItems, index + 1, newOriginals, index, length - index);
        originalItems = newOriginals;

        internalRedraw(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height);
        relayout();
    }

    void moveDown(CoolItem item, int x_root) {
        int oldRowIndex = findItem(item).y;
        if (items[oldRowIndex].length == 1) {
            /* If this is the only item in the bottom row, don't move it. */
            if (oldRowIndex == items.length - 1)
                return;
        }
        int newRowIndex = (items[oldRowIndex].length == 1) ? oldRowIndex : oldRowIndex + 1;
        boolean resize = removeItemFromRow(item, oldRowIndex, false);
        Rectangle old = item.internalGetBounds();
        internalRedraw(old.x, old.y, CoolItem.MINIMUM_WIDTH, old.height);
        resize |= insertItemIntoRow(item, newRowIndex, x_root);
        if (resize) {
            relayout();
        } else {
            layoutItems();
        }
    }

    void moveLeft(CoolItem item, int pixels) {
        Point point = findItem(item);
        int row = point.y;
        int index = point.x;
        if (index == 0)
            return;
        Rectangle bounds = item.internalGetBounds();
        int minSpaceOnLeft = 0;
        for (int i = 0; i < index; i++) {
            minSpaceOnLeft += items[row][i].internalGetMinimumWidth();
        }
        int x = Math.max(minSpaceOnLeft, bounds.x - pixels);
        CoolItem left = items[row][index - 1];
        Rectangle leftBounds = left.internalGetBounds();
        if (leftBounds.x + left.internalGetMinimumWidth() > x) {
            int shift = leftBounds.x + left.internalGetMinimumWidth() - x;
            moveLeft(left, shift);
            leftBounds = left.internalGetBounds();
        }
        int leftWidth = Math.max(left.internalGetMinimumWidth(), leftBounds.width - pixels);
        left.setBounds(leftBounds.x, leftBounds.y, leftWidth, leftBounds.height);
        left.requestedWidth = leftWidth;
        int width = bounds.width + (bounds.x - x);
        item.setBounds(x, bounds.y, width, bounds.height);
        item.requestedWidth = width;

        int damagedWidth = bounds.x - x + CoolItem.MINIMUM_WIDTH;
        if (damagedWidth > CoolItem.MINIMUM_WIDTH) {
            internalRedraw(x, bounds.y, damagedWidth, bounds.height);
        }
    }

    void moveRight(CoolItem item, int pixels) {
        Point point = findItem(item);
        int row = point.y;
        int index = point.x;
        if (index == 0)
            return;
        Rectangle bounds = item.internalGetBounds();
        int minSpaceOnRight = 0;
        for (int i = index; i < items[row].length; i++) {
            minSpaceOnRight += items[row][i].internalGetMinimumWidth();
        }
        int max = getWidth() - minSpaceOnRight;
        int x = Math.min(max, bounds.x + pixels);
        int width = 0;
        if (index + 1 == items[row].length) {
            width = getWidth() - x;
        } else {
            CoolItem right = items[row][index + 1];
            Rectangle rightBounds = right.internalGetBounds();
            if (x + item.internalGetMinimumWidth() > rightBounds.x) {
                int shift = x + item.internalGetMinimumWidth() - rightBounds.x;
                moveRight(right, shift);
                rightBounds = right.internalGetBounds();
            }
            width = rightBounds.x - x;
        }
        item.setBounds(x, bounds.y, width, bounds.height);
        item.requestedWidth = width;
        CoolItem left = items[row][index - 1];
        Rectangle leftBounds = left.internalGetBounds();
        int leftWidth = x - leftBounds.x;
        left.setBounds(leftBounds.x, leftBounds.y, leftWidth, leftBounds.height);
        left.requestedWidth = leftWidth;

        int damagedWidth = x - bounds.x + CoolItem.MINIMUM_WIDTH + CoolItem.MARGIN_WIDTH;
        if (x - bounds.x > 0) {
            internalRedraw(bounds.x - CoolItem.MARGIN_WIDTH, bounds.y, damagedWidth, bounds.height);
        }
    }

    void moveUp(CoolItem item, int x_root) {
        Point point = findItem(item);
        int oldRowIndex = point.y;
        if (items[oldRowIndex].length == 1) {
            /* If this is the only item in the top row, don't move it. */
            if (oldRowIndex == 0)
                return;
        }
        boolean resize = removeItemFromRow(item, oldRowIndex, false);
        Rectangle old = item.internalGetBounds();
        internalRedraw(old.x, old.y, CoolItem.MINIMUM_WIDTH, old.height);
        int newRowIndex = oldRowIndex - 1;
        resize |= insertItemIntoRow(item, newRowIndex, x_root);
        if (resize) {
            relayout();
        } else {
            layoutItems();
        }
    }

    void onDispose(Event event) {
        /*
         * Usually when an item is disposed, destroyItem will change the size of the items array
         * and reset the bounds of all the remaining cool items.
         * Since the whole cool bar is being disposed, this is not necessary.  For speed
         * the inDispose flag is used to skip over this part of the item dispose.
         */
        if (inDispose)
            return;
        inDispose = true;
        notifyListeners(SWT.Dispose, event);
        event.type = SWT.None;
        for (int i = 0; i < items.length; i++) {
            for (int j = 0; j < items[i].length; j++) {
                items[i][j].dispose();
            }
        }
        hoverCursor.dispose();
        dragCursor.dispose();
        cursor = null;
    }

    void onMouseDown(Event event) {
        if (isLocked || event.button != 1)
            return;
        fixEvent(event);
        dragging = getGrabbedItem(event.x, event.y);
        if (dragging != null) {
            mouseXOffset = event.x;
            itemXOffset = mouseXOffset - dragging.internalGetBounds().x;
            _setCursor(dragCursor);
        }
        fixEvent(event);
    }

    void onMouseExit() {
        if (dragging == null)
            _setCursor(null);
    }

    void onMouseMove(Event event) {
        if (isLocked)
            return;
        fixEvent(event);
        CoolItem grabbed = getGrabbedItem(event.x, event.y);
        if (dragging != null) {
            int left_root = toDisplay(new Point(event.x - itemXOffset, event.y)).x;
            Rectangle bounds = dragging.internalGetBounds();
            if (event.y < bounds.y) {
                moveUp(dragging, left_root);
            } else if (event.y > bounds.y + bounds.height) {
                moveDown(dragging, left_root);
            } else if (event.x < mouseXOffset) {
                int distance = Math.min(mouseXOffset, bounds.x + itemXOffset) - event.x;
                if (distance > 0)
                    moveLeft(dragging, distance);
            } else if (event.x > mouseXOffset) {
                int distance = event.x - Math.max(mouseXOffset, bounds.x + itemXOffset);
                if (distance > 0)
                    moveRight(dragging, distance);
            }
            mouseXOffset = event.x;
        } else {
            if (grabbed != null) {
                _setCursor(hoverCursor);
            } else {
                _setCursor(null);
            }
        }
        fixEvent(event);
    }

    void onMouseUp(Event event) {
        dragging = null;
        CoolItem grabbed = getGrabbedItem(event.x, event.y);
        if (grabbed != null) {
            _setCursor(hoverCursor);
        } else {
            _setCursor(null);
        }
    }

    void onMouseDoubleClick(Event event) {
        if (isLocked)
            return;
        dragging = null;
        fixEvent(event);
        CoolItem target = getGrabbedItem(event.x, event.y);
        if (target == null) {
            _setCursor(null);
        } else {
            Point location = findItem(target);
            int row = location.y;
            int index = location.x;
            if (items[row].length > 1) {
                Rectangle bounds = target.internalGetBounds();
                int maxSize = getWidth();
                for (int i = 0; i < items[row].length; i++) {
                    if (i != index) {
                        maxSize -= items[row][i].internalGetMinimumWidth();
                    }
                }
                if (bounds.width == maxSize) {
                    /* The item is at its maximum width. It should be resized to its minimum width. */
                    int distance = bounds.width - target.internalGetMinimumWidth();
                    if (index + 1 < items[row].length) {
                        /* There is an item to the right. Maximize it. */
                        CoolItem right = items[row][index + 1];
                        moveLeft(right, distance);
                    } else {
                        /* There is no item to the right. Move the item all the way right. */
                        moveRight(target, distance);
                    }
                } else if (bounds.width < target.preferredWidth) {
                    /* The item is less than its preferredWidth. Resize to preferredWidth. */
                    int distance = target.preferredWidth - bounds.width;
                    if (index + 1 < items[row].length) {
                        CoolItem right = items[row][index + 1];
                        moveRight(right, distance);
                        distance = target.preferredWidth - target.internalGetBounds().width;
                    }
                    if (distance > 0) {
                        moveLeft(target, distance);
                    }
                } else {
                    /* The item is at its minimum width. Maximize it. */
                    for (int i = 0; i < items[row].length; i++) {
                        if (i != index) {
                            CoolItem item = items[row][i];
                            item.requestedWidth = Math.max(item.internalGetMinimumWidth(), CoolItem.MINIMUM_WIDTH);
                        }
                    }
                    target.requestedWidth = maxSize;
                    layoutItems();
                }
                _setCursor(hoverCursor);
            }
        }
        fixEvent(event);
    }

    void onPaint(Event event) {
        GC gc = event.gc;
        if (items.length == 0)
            return;
        Color shadowColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
        Color highlightColor = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
        boolean vertical = (style & SWT.VERTICAL) != 0;
        boolean flat = (style & SWT.FLAT) != 0;
        int stopX = getWidth();
        Rectangle rect;
        Rectangle clipping = gc.getClipping();
        for (int row = 0; row < items.length; row++) {
            Rectangle bounds = new Rectangle(0, 0, 0, 0);
            for (int i = 0; i < items[row].length; i++) {
                bounds = items[row][i].internalGetBounds();
                rect = fixRectangle(bounds.x, bounds.y, bounds.width, bounds.height);
                if (!clipping.intersects(rect))
                    continue;
                boolean nativeGripper = false;

                /* Draw gripper. */
                if (!isLocked) {
                    rect = fixRectangle(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height);
                    if (!flat)
                        nativeGripper = drawGripper(gc, rect.x, rect.y, rect.width, rect.height, vertical);
                    if (!nativeGripper) {
                        int grabberTrim = 2;
                        int grabberHeight = bounds.height - (2 * grabberTrim) - 1;
                        gc.setForeground(shadowColor);
                        rect = fixRectangle(bounds.x + CoolItem.MARGIN_WIDTH, bounds.y + grabberTrim, 2,
                                grabberHeight);
                        gc.drawRectangle(rect);
                        gc.setForeground(highlightColor);
                        rect = fixRectangle(bounds.x + CoolItem.MARGIN_WIDTH, bounds.y + grabberTrim + 1,
                                bounds.x + CoolItem.MARGIN_WIDTH, bounds.y + grabberTrim + grabberHeight - 1);
                        gc.drawLine(rect.x, rect.y, rect.width, rect.height);
                        rect = fixRectangle(bounds.x + CoolItem.MARGIN_WIDTH, bounds.y + grabberTrim,
                                bounds.x + CoolItem.MARGIN_WIDTH + 1, bounds.y + grabberTrim);
                        gc.drawLine(rect.x, rect.y, rect.width, rect.height);
                    }
                }

                /* Draw separator. */
                if (!flat && !nativeGripper && i != 0) {
                    gc.setForeground(shadowColor);
                    rect = fixRectangle(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height - 1);
                    gc.drawLine(rect.x, rect.y, rect.width, rect.height);
                    gc.setForeground(highlightColor);
                    rect = fixRectangle(bounds.x + 1, bounds.y, bounds.x + 1, bounds.y + bounds.height - 1);
                    gc.drawLine(rect.x, rect.y, rect.width, rect.height);
                }
            }
            if (!flat && row + 1 < items.length) {
                /* Draw row separator. */
                int separatorY = bounds.y + bounds.height;
                gc.setForeground(shadowColor);
                rect = fixRectangle(0, separatorY, stopX, separatorY);
                gc.drawLine(rect.x, rect.y, rect.width, rect.height);
                gc.setForeground(highlightColor);
                rect = fixRectangle(0, separatorY + 1, stopX, separatorY + 1);
                gc.drawLine(rect.x, rect.y, rect.width, rect.height);
            }
        }
    }

    void onResize() {
        layoutItems();
    }

    @Override
    void removeControl(Control control) {
        super.removeControl(control);
        CoolItem[] items = getItems();
        for (int i = 0; i < items.length; i++) {
            CoolItem item = items[i];
            if (item.control == control)
                item.setControl(null);
        }
    }

    /**
     * Remove the item from the row. Adjust the x and width values
     * appropriately.
     */
    boolean removeItemFromRow(CoolItem item, int rowIndex, boolean disposed) {
        int index = findItem(item).x;
        int newLength = items[rowIndex].length - 1;
        Rectangle itemBounds = item.internalGetBounds();
        int oldRowHeight = itemBounds.height;
        item.wrap = false;
        if (newLength > 0) {
            CoolItem[] newRow = new CoolItem[newLength];
            System.arraycopy(items[rowIndex], 0, newRow, 0, index);
            System.arraycopy(items[rowIndex], index + 1, newRow, index, newRow.length - index);
            items[rowIndex] = newRow;
            items[rowIndex][0].wrap = true;
        } else {
            CoolItem[][] newRows = new CoolItem[items.length - 1][];
            System.arraycopy(items, 0, newRows, 0, rowIndex);
            System.arraycopy(items, rowIndex + 1, newRows, rowIndex, newRows.length - rowIndex);
            items = newRows;
            return true;
        }
        if (!disposed) {
            if (index == 0) {
                CoolItem first = items[rowIndex][0];
                Rectangle bounds = first.internalGetBounds();
                int width = bounds.x + bounds.width;
                first.setBounds(0, bounds.y, width, bounds.height);
                first.requestedWidth = width;
                internalRedraw(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height);
            } else {
                CoolItem previous = items[rowIndex][index - 1];
                Rectangle bounds = previous.internalGetBounds();
                int width = bounds.width + itemBounds.width;
                previous.setBounds(bounds.x, bounds.y, width, bounds.height);
                previous.requestedWidth = width;
            }
        }
        int newRowHeight = 0;
        for (int i = 0; i < newLength; i++) {
            newRowHeight = Math.max(newRowHeight, items[rowIndex][i].preferredHeight);
        }
        return newRowHeight != oldRowHeight;
    }

    /**
     * Return the height of the bar after it has
     * been properly laid out for the given width.
     */
    int layoutItems() {
        int y = 0, width;
        if ((style & SWT.VERTICAL) != 0) {
            width = getClientArea().height;
        } else {
            width = getClientArea().width;
        }
        wrapItems(width);
        int rowSpacing = (style & SWT.FLAT) != 0 ? 0 : ROW_SPACING;
        for (int row = 0; row < items.length; row++) {
            int count = items[row].length;
            int x = 0;

            /* determine the height and the available width for the row */
            int rowHeight = 0;
            int available = width;
            for (int i = 0; i < count; i++) {
                CoolItem item = items[row][i];
                rowHeight = Math.max(rowHeight, item.preferredHeight);
                available -= item.internalGetMinimumWidth();
            }
            if (row > 0)
                y += rowSpacing;

            /* lay the items out */
            for (int i = 0; i < count; i++) {
                CoolItem child = items[row][i];
                int newWidth = available + child.internalGetMinimumWidth();
                if (i + 1 < count) {
                    newWidth = Math.min(newWidth, child.requestedWidth);
                    available -= (newWidth - child.internalGetMinimumWidth());
                }
                Rectangle oldBounds = child.internalGetBounds();
                Rectangle newBounds = new Rectangle(x, y, newWidth, rowHeight);
                if (!oldBounds.equals(newBounds)) {
                    child.setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height);
                    Rectangle damage = new Rectangle(0, 0, 0, 0);
                    /* Cases are in descending order from most area to redraw to least. */
                    if (oldBounds.y != newBounds.y) {
                        damage = newBounds;
                        damage.add(oldBounds);
                        /* Redraw the row separator as well. */
                        damage.y -= rowSpacing;
                        damage.height += 2 * rowSpacing;
                    } else if (oldBounds.height != newBounds.height) {
                        /*
                         * Draw from the bottom of the gripper to the bottom of the new area.
                         * (Bottom of the gripper is -3 from the bottom of the item).
                         */
                        damage.y = newBounds.y + Math.min(oldBounds.height, newBounds.height) - 3;
                        damage.height = newBounds.y + newBounds.height + rowSpacing;
                        damage.x = oldBounds.x - CoolItem.MARGIN_WIDTH;
                        damage.width = oldBounds.width + CoolItem.MARGIN_WIDTH;
                    } else if (oldBounds.x != newBounds.x) {
                        /* Redraw only the difference between the separators. */
                        damage.x = Math.min(oldBounds.x, newBounds.x);
                        damage.width = Math.abs(oldBounds.x - newBounds.x) + CoolItem.MINIMUM_WIDTH;
                        damage.y = oldBounds.y;
                        damage.height = oldBounds.height;
                    }
                    internalRedraw(damage.x, damage.y, damage.width, damage.height);
                }
                x += newWidth;
            }
            y += rowHeight;
        }
        return y;
    }

    void relayout() {
        Point size = getSize();
        int height = layoutItems();
        if ((style & SWT.VERTICAL) != 0) {
            Rectangle trim = computeTrim(0, 0, height, 0);
            if (height != size.x)
                super.setSize(trim.width, size.y);
        } else {
            Rectangle trim = computeTrim(0, 0, 0, height);
            if (height != size.y)
                super.setSize(size.x, trim.height);
        }
    }

    /**
     * Returns an array of zero-relative ints that map
     * the creation order of the receiver's items to the
     * order in which they are currently being displayed.
     * <p>
     * Specifically, the indices of the returned array represent
     * the current visual order of the items, and the contents
     * of the array represent the creation order of the items.
     * </p><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 current visual order of the receiver's 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[] getItemOrder() {
        checkWidget();
        int count = getItemCount();
        int[] indices = new int[count];
        count = 0;
        for (int i = 0; i < items.length; i++) {
            for (int j = 0; j < items[i].length; j++) {
                CoolItem item = items[i][j];
                int index = 0;
                while (index < originalItems.length) {
                    if (originalItems[index] == item)
                        break;
                    index++;
                }
                if (index == originalItems.length)
                    error(SWT.ERROR_CANNOT_GET_ITEM);
                indices[count++] = index;
            }
        }
        return indices;
    }

    @Override
    public void setBackground(Color color) {
        for (CoolItem ci : originalItems) {
            Control ctrl = ci.getControl();
            if (ctrl != null) {
                ctrl.setBackground(color);
            }
        }
        super.setBackground(color);
    }

    void setItemOrder(int[] itemOrder) {
        if (itemOrder == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        int count = originalItems.length;
        if (itemOrder.length != count)
            error(SWT.ERROR_INVALID_ARGUMENT);

        /* Ensure that itemOrder does not contain any duplicates. */
        boolean[] set = new boolean[count];
        for (int i = 0; i < set.length; i++)
            set[i] = false;
        for (int i = 0; i < itemOrder.length; i++) {
            if (itemOrder[i] < 0 || itemOrder[i] >= count)
                error(SWT.ERROR_INVALID_ARGUMENT);
            if (set[itemOrder[i]])
                error(SWT.ERROR_INVALID_ARGUMENT);
            set[itemOrder[i]] = true;
        }

        CoolItem[] row = new CoolItem[count];
        for (int i = 0; i < count; i++) {
            row[i] = originalItems[itemOrder[i]];
        }
        items = new CoolItem[1][count];
        items[0] = row;
    }

    /**
     * Returns an array of points whose x and y coordinates describe
     * the widths and heights (respectively) of the items in the receiver
     * in the order in which they are currently being displayed.
     *
     * @return the receiver's item sizes in their current visual order
     *
     * @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 Point[] getItemSizes() {
        checkWidget();
        CoolItem[] items = getItems();
        Point[] sizes = new Point[items.length];
        for (int i = 0; i < items.length; i++) {
            sizes[i] = items[i].getSize();
        }
        return sizes;
    }

    void setItemSizes(Point[] sizes) {
        if (sizes == null)
            error(SWT.ERROR_NULL_ARGUMENT);
        CoolItem[] items = getItems();
        if (sizes.length != items.length)
            error(SWT.ERROR_INVALID_ARGUMENT);
        for (int i = 0; i < items.length; i++) {
            items[i].setSize(sizes[i]);
        }
    }

    /**
     * Returns whether or not the receiver is 'locked'. When a coolbar
     * is locked, its items cannot be repositioned.
     *
     * @return true if the coolbar is locked, false otherwise
     *
     * @exception SWTException <ul>
     *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
     *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
     * </ul>
     *
     * @since 2.0
     */
    public boolean getLocked() {
        checkWidget();
        return isLocked;
    }

    int getWidth() {
        if ((style & SWT.VERTICAL) != 0)
            return getSize().y;
        return getSize().x;
    }

    /**
     * Returns an array of ints that describe the zero-relative
     * indices of any item(s) in the receiver that will begin on
     * a new row. The 0th visible item always begins the first row,
     * therefore it does not count as a wrap index.
     *
     * @return an array containing the receiver's wrap indices, or an empty array if all items are in one row
     *
     * @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[] getWrapIndices() {
        checkWidget();
        if (items.length <= 1)
            return new int[] {};
        int[] wrapIndices = new int[items.length - 1];
        int i = 0, nextWrap = items[0].length;
        for (int row = 1; row < items.length; row++) {
            if (items[row][0].wrap)
                wrapIndices[i++] = nextWrap;
            nextWrap += items[row].length;
        }
        if (i != wrapIndices.length) {
            int[] tmp = new int[i];
            System.arraycopy(wrapIndices, 0, tmp, 0, i);
            return tmp;
        }
        return wrapIndices;
    }

    /**
     * Sets whether or not the receiver is 'locked'. When a coolbar
     * is locked, its items cannot be repositioned.
     *
     * @param locked lock the coolbar if true, otherwise unlock the coolbar
     *
     * @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 2.0
     */
    public void setLocked(boolean locked) {
        checkWidget();
        if (isLocked != locked) {
            redraw();
        }
        isLocked = locked;

    }

    /**
     * Sets the indices of all item(s) in the receiver that will
     * begin on a new row. The indices are given in the order in
     * which they are currently being displayed. The 0th item
     * always begins the first row, therefore it does not count
     * as a wrap index. If indices is null or empty, the items
     * will be placed on one line.
     *
     * @param indices an array of wrap indices, 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>
     */
    public void setWrapIndices(int[] indices) {
        checkWidget();
        if (indices == null)
            indices = new int[0];
        int count = originalItems.length;
        for (int i = 0; i < indices.length; i++) {
            if (indices[i] < 0 || indices[i] >= count) {
                error(SWT.ERROR_INVALID_ARGUMENT);
            }
        }
        for (int i = 0; i < originalItems.length; i++) {
            originalItems[i].wrap = false;
        }
        for (int i = 0; i < indices.length; i++) {
            int index = indices[i];
            for (int row = 0; row < items.length; row++) {
                if (items[row].length > index) {
                    items[row][index].wrap = true;
                    break;
                } else {
                    index -= items[row].length;
                }
            }
        }
        relayout();
    }

    @Override
    void reskinChildren(int flags) {
        if (items != null) {
            for (int row = 0; row < items.length; row++) {
                for (int i = 0; i < items[row].length; i++) {
                    CoolItem item = items[row][i];
                    if (item != null)
                        item.reskin(flags);
                }
            }
        }
        super.reskinChildren(flags);
    }

    @Override
    public void setCursor(Cursor cursor) {
        checkWidget();
        super.setCursor(this.cursor = cursor);
    }

    /**
     * Sets the receiver's item order, wrap indices, and item sizes
     * all at once. This method is typically used to restore the
     * displayed state of the receiver to a previously stored state.
     * <p>
     * The item order is the order in which the items in the receiver
     * should be displayed, given in terms of the zero-relative ordering
     * of when the items were added.
     * </p><p>
     * The wrap indices are the indices of all item(s) in the receiver
     * that will begin on a new row. The indices are given in the order
     * specified by the item order. The 0th item always begins the first
     * row, therefore it does not count as a wrap index. If wrap indices
     * is null or empty, the items will be placed on one line.
     * </p><p>
     * The sizes are specified in an array of points whose x and y
     * coordinates describe the new widths and heights (respectively)
     * of the receiver's items in the order specified by the item order.
     * </p>
     *
     * @param itemOrder an array of indices that describe the new order to display the items in
     * @param wrapIndices an array of wrap indices, or null
     * @param sizes an array containing the new sizes for each of the receiver's items in visual order
     *
     * @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>
     * @exception IllegalArgumentException <ul>
     *    <li>ERROR_NULL_ARGUMENT - if item order or sizes is null</li>
     *    <li>ERROR_INVALID_ARGUMENT - if item order or sizes is not the same length as the number of items</li>
     * </ul>
     */
    public void setItemLayout(int[] itemOrder, int[] wrapIndices, Point[] sizes) {
        checkWidget();
        setItemOrder(itemOrder);
        setWrapIndices(wrapIndices);
        setItemSizes(sizes);
        relayout();
    }

    @Override
    public void setOrientation(int orientation) {
        super.setOrientation(orientation);
        for (int row = 0; row < items.length; row++) {
            for (int column = 0; column < items[row].length; column++) {
                CoolItem item = items[row][column];
                if (item.arrowImage != null) {
                    item.arrowImage.dispose();
                    item.arrowImage = null;
                }
                item.updateChevron();
            }
        }
    }

    void wrapItems(int maxWidth) {
        int itemCount = originalItems.length;
        if (itemCount < 2)
            return;
        CoolItem[] itemsVisual = new CoolItem[itemCount];
        int start = 0;
        for (int row = 0; row < items.length; row++) {
            System.arraycopy(items[row], 0, itemsVisual, start, items[row].length);
            start += items[row].length;
        }
        CoolItem[][] newItems = new CoolItem[itemCount][];
        int rowCount = 0, rowWidth = 0;
        start = 0;
        for (int i = 0; i < itemCount; i++) {
            CoolItem item = itemsVisual[i];
            int itemWidth = item.internalGetMinimumWidth();
            if ((i > 0 && item.wrap) || (maxWidth != SWT.DEFAULT && rowWidth + itemWidth > maxWidth)) {
                if (i == start) {
                    newItems[rowCount] = new CoolItem[1];
                    newItems[rowCount][0] = item;
                    start = i + 1;
                    rowWidth = 0;
                } else {
                    int count = i - start;
                    newItems[rowCount] = new CoolItem[count];
                    System.arraycopy(itemsVisual, start, newItems[rowCount], 0, count);
                    start = i;
                    rowWidth = itemWidth;
                }
                rowCount++;
            } else {
                rowWidth += itemWidth;
            }
        }
        if (start < itemCount) {
            int count = itemCount - start;
            newItems[rowCount] = new CoolItem[count];
            System.arraycopy(itemsVisual, start, newItems[rowCount], 0, count);
            rowCount++;
        }
        if (newItems.length != rowCount) {
            CoolItem[][] tmp = new CoolItem[rowCount][];
            System.arraycopy(newItems, 0, tmp, 0, rowCount);
            items = tmp;
        } else {
            items = newItems;
        }
    }
}