/******************************************************************************* * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * 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><p> * <dl> * <dt><b>Styles:</b></dt> * <dd>FLAT, HORIZONTAL, VERTICAL</dd> * <dt><b>Events:</b></dt> * <dd>(none)</dd> * </dl> * </p><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 = new Listener() { public void handleEvent(Event 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; } 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; } } }