Source code: org/eclipse/swt/widgets/CoolBar.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.swt.widgets;
12
13
14 import org.eclipse.swt.*;
15 import org.eclipse.swt.graphics.*;
16
17 /**
18 * Instances of this class provide an area for dynamically
19 * positioning the items they contain.
20 * <p>
21 * The item children that may be added to instances of this class
22 * must be of type <code>CoolItem</code>.
23 * </p><p>
24 * Note that although this class is a subclass of <code>Composite</code>,
25 * it does not make sense to add <code>Control</code> children to it,
26 * or set a layout on it.
27 * </p><p>
28 * <dl>
29 * <dt><b>Styles:</b></dt>
30 * <dd>FLAT</dd>
31 * <dt><b>Events:</b></dt>
32 * <dd>(none)</dd>
33 * </dl>
34 * <p>
35 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
36 * </p>
37 */
38 public class CoolBar extends Composite {
39 CoolItem[][] items = new CoolItem[0][0];
40 CoolItem[] originalItems = new CoolItem[0];
41 Cursor hoverCursor, dragCursor;
42 CoolItem dragging = null;
43 int mouseXOffset, itemXOffset;
44 static final int ROW_SPACING = 2;
45 static final int CLICK_DISTANCE = 3;
46
47 boolean isLocked = false;
48 boolean inDispose = false;
49 /**
50 * Constructs a new instance of this class given its parent
51 * and a style value describing its behavior and appearance.
52 * <p>
53 * The style value is either one of the style constants defined in
54 * class <code>SWT</code> which is applicable to instances of this
55 * class, or must be built by <em>bitwise OR</em>'ing together
56 * (that is, using the <code>int</code> "|" operator) two or more
57 * of those <code>SWT</code> style constants. The class description
58 * lists the style constants that are applicable to the class.
59 * Style bits are also inherited from superclasses.
60 * </p>
61 *
62 * @param parent a composite control which will be the parent of the new instance (cannot be null)
63 * @param style the style of control to construct
64 *
65 * @exception IllegalArgumentException <ul>
66 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
67 * </ul>
68 * @exception SWTException <ul>
69 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
70 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
71 * </ul>
72 *
73 * @see SWT
74 * @see Widget#checkSubclass
75 * @see Widget#getStyle
76 */
77 public CoolBar (Composite parent, int style) {
78 super (parent, checkStyle(style));
79 hoverCursor = new Cursor(display, SWT.CURSOR_SIZEWE);
80 dragCursor = new Cursor(display, SWT.CURSOR_SIZEALL);
81 Listener listener = new Listener() {
82 public void handleEvent(Event event) {
83 switch (event.type) {
84 case SWT.Dispose: onDispose(); break;
85 case SWT.MouseDown: onMouseDown(event); break;
86 case SWT.MouseExit: onMouseExit(); break;
87 case SWT.MouseMove: onMouseMove(event); break;
88 case SWT.MouseUp: onMouseUp(event); break;
89 case SWT.MouseDoubleClick: onMouseDoubleClick(event); break;
90 case SWT.Paint: onPaint(event); break;
91 }
92 }
93 };
94 int[] events = new int[] {
95 SWT.Dispose,
96 SWT.MouseDown,
97 SWT.MouseExit,
98 SWT.MouseMove,
99 SWT.MouseUp,
100 SWT.MouseDoubleClick,
101 SWT.Paint
102 };
103 for (int i = 0; i < events.length; i++) {
104 addListener(events[i], listener);
105 }
106 }
107 private static int checkStyle (int style) {
108 style |= SWT.NO_FOCUS;
109 return (style | SWT.NO_REDRAW_RESIZE) & ~(SWT.V_SCROLL | SWT.H_SCROLL);
110 }
111 protected void checkSubclass () {
112 if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
113 }
114 public Point computeSize (int wHint, int hHint, boolean changed) {
115 checkWidget();
116 int width = 0, height = 0;
117 wrapItems(wHint);
118 boolean flat = (style & SWT.FLAT) != 0;
119 for (int row = 0; row < items.length; row++) {
120 int rowWidth = 0, rowHeight = 0;
121 for (int i = 0; i < items[row].length; i++) {
122 CoolItem item = items[row][i];
123 rowWidth += item.preferredWidth;
124 rowHeight = Math.max(rowHeight, item.preferredHeight);
125 }
126 height += rowHeight;
127 if (!flat && row > 0) height += ROW_SPACING;
128 width = Math.max(width, rowWidth);
129 }
130 wrapItems(getSize().x);
131 if (width == 0) width = DEFAULT_WIDTH;
132 if (height == 0) height = DEFAULT_HEIGHT;
133 if (wHint != SWT.DEFAULT) width = wHint;
134 if (hHint != SWT.DEFAULT) height = hHint;
135 Rectangle trim = computeTrim(0, 0, width, height);
136 return new Point(trim.width, trim.height);
137 }
138 CoolItem getGrabbedItem(int x, int y) {
139 for (int row = 0; row < items.length; row++) {
140 for (int i = 0; i < items[row].length; i++) {
141 CoolItem item = items[row][i];
142 Rectangle bounds = item.getBounds();
143 bounds.width = CoolItem.MINIMUM_WIDTH;
144 if (bounds.x > x) break;
145 if (bounds.y > y) return null;
146 if (bounds.contains(x, y)) {
147 return item;
148 }
149 }
150 }
151 return null;
152 }
153 /**
154 * Returns the item that is currently displayed at the given,
155 * zero-relative index. Throws an exception if the index is
156 * out of range.
157 *
158 * @param index the visual index of the item to return
159 * @return the item at the given visual index
160 *
161 * @exception IllegalArgumentException <ul>
162 * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
163 * </ul>
164 * @exception SWTException <ul>
165 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
166 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
167 * </ul>
168 * @exception SWTError <ul>
169 * <li>ERROR_CANNOT_GET_ITEM - if the operation fails because of an operating system failure</li>
170 * </ul>
171 */
172 public CoolItem getItem (int index) {
173 checkWidget();
174 if (index < 0) error (SWT.ERROR_INVALID_RANGE);
175 for (int row = 0; row < items.length; row++) {
176 if (items[row].length > index) {
177 return items[row][index];
178 } else {
179 index -= items[row].length;
180 }
181 }
182 error (SWT.ERROR_INVALID_RANGE);
183 return null;
184 }
185 /**
186 * Returns the number of items contained in the receiver.
187 *
188 * @return the number of items
189 *
190 * @exception SWTException <ul>
191 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
192 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
193 * </ul>
194 * @exception SWTError <ul>
195 * <li>ERROR_CANNOT_GET_COUNT - if the operation fails because of an operating system failure</li>
196 * </ul>
197 */
198 public int getItemCount () {
199 checkWidget();
200 return originalItems.length;
201 }
202 /**
203 * Returns an array of <code>CoolItem</code>s in the order
204 * in which they are currently being displayed.
205 * <p>
206 * Note: This is not the actual structure used by the receiver
207 * to maintain its list of items, so modifying the array will
208 * not affect the receiver.
209 * </p>
210 *
211 * @return the receiver's items in their current visual order
212 *
213 * @exception SWTException <ul>
214 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
215 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
216 * </ul>
217 * @exception SWTError <ul>
218 * <li>ERROR_CANNOT_GET_ITEM - if the operation fails because of an operating system failure</li>
219 * </ul>
220 */
221 public CoolItem [] getItems () {
222 checkWidget();
223 CoolItem [] result = new CoolItem [getItemCount()];
224 int offset = 0;
225 for (int row = 0; row < items.length; row++) {
226 System.arraycopy(items[row], 0, result, offset, items[row].length);
227 offset += items[row].length;
228 }
229 return result;
230 }
231 Point findItem (CoolItem item) {
232 for (int row = 0; row < items.length; row++) {
233 for (int i = 0; i < items[row].length; i++) {
234 if (items[row][i].equals(item)) return new Point(i, row);
235 }
236 }
237 return new Point(-1, -1);
238 }
239 /**
240 * Searches the receiver's items in the order they are currently
241 * being displayed, starting at the first item (index 0), until
242 * an item is found that is equal to the argument, and returns
243 * the index of that item. If no item is found, returns -1.
244 *
245 * @param item the search item
246 * @return the visual order index of the search item, or -1 if the item is not found
247 *
248 * @exception IllegalArgumentException <ul>
249 * <li>ERROR_NULL_ARGUMENT - if the item is null</li>
250 * <li>ERROR_INVALID_ARGUMENT - if the item is disposed</li>
251 * </ul>
252 * @exception SWTException <ul>
253 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
254 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
255 * </ul>
256 */
257 public int indexOf (CoolItem item) {
258 checkWidget();
259 if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
260 if (item.isDisposed()) error (SWT.ERROR_INVALID_ARGUMENT);
261 int answer = 0;
262 for (int row = 0; row < items.length; row++) {
263 for (int i = 0; i < items[row].length; i++) {
264 if (items[row][i].equals(item)) {
265 return answer;
266 } else {
267 answer++;
268 }
269 }
270 }
271 return -1;
272 }
273 /**
274 * Insert the item into the row. Adjust the x and width values
275 * appropriately.
276 */
277 void insertItemIntoRow(CoolItem item, int rowIndex, int x_root) {
278 int barWidth = getSize().x;
279 int rowY = items[rowIndex][0].getBounds().y;
280 int x = Math.max(0, x_root - toDisplay(new Point(0, 0)).x);
281
282 /* Find the insertion index and add the item. */
283 int index;
284 for (index = 0; index < items[rowIndex].length; index++) {
285 if (x < items[rowIndex][index].getBounds().x) break;
286 }
287 if (index == 0) {
288 item.wrap = true;
289 items[rowIndex][0].wrap = false;
290 }
291 int oldLength = items[rowIndex].length;
292 CoolItem[] newRow = new CoolItem[oldLength + 1];
293 System.arraycopy(items[rowIndex], 0, newRow, 0, index);
294 newRow[index] = item;
295 System.arraycopy(items[rowIndex], index, newRow, index + 1, oldLength - index);
296 items[rowIndex] = newRow;
297
298 /* Adjust the width of the item to the left. */
299 if (index > 0) {
300 CoolItem left = items[rowIndex][index - 1];
301 Rectangle leftBounds = left.getBounds();
302 int newWidth = x - leftBounds.x;
303 if (newWidth < left.internalGetMinimumWidth()) {
304 x += left.internalGetMinimumWidth() - newWidth;
305 newWidth = left.internalGetMinimumWidth();
306 }
307 left.setBounds(leftBounds.x, leftBounds.y, newWidth, leftBounds.height);
308 left.requestedWidth = newWidth;
309 }
310
311 /* Set the item's bounds. */
312 int width = 0, height = item.getSize().y;
313 if (index < items[rowIndex].length - 1) {
314 CoolItem right = items[rowIndex][index + 1];
315 width = right.getBounds().x - x;
316 if (width < right.internalGetMinimumWidth()) {
317 moveRight(right, right.internalGetMinimumWidth() - width);
318 width = right.getBounds().x - x;
319 }
320 item.setBounds(x, rowY, width, height);
321 if (width < item.internalGetMinimumWidth()) moveLeft(item, item.internalGetMinimumWidth() - width);
322 } else {
323 width = Math.max(item.internalGetMinimumWidth(), barWidth - x);
324 item.setBounds(x, rowY, width, height);
325 if (x + width > barWidth) moveLeft(item, x + width - barWidth);
326 }
327 Rectangle bounds = item.getBounds();
328 item.requestedWidth = bounds.width;
329 redraw(bounds.x, bounds.y, item.internalGetMinimumWidth(), bounds.height, false);
330 }
331 void createItem (CoolItem item, int index) {
332 int itemCount = getItemCount(), row = 0;
333 if (!(0 <= index && index <= itemCount)) error (SWT.ERROR_INVALID_RANGE);
334 if (items.length == 0) {
335 items = new CoolItem[1][1];
336 items[0][0] = item;
337 } else {
338 int i = index;
339 /* find the row to insert into */
340 if (index < itemCount) {
341 while (i > items[row].length) {
342 i -= items[row].length;
343 row++;
344 }
345 } else {
346 row = items.length - 1;
347 i = items[row].length;
348 }
349
350 // Set the last item in the row to the preferred size
351 // and add the new one just to it's right
352 int lastIndex = items[row].length - 1;
353 CoolItem lastItem = items[row][lastIndex];
354 if (lastItem.ideal) {
355 Rectangle bounds = lastItem.getBounds();
356 bounds.width = lastItem.preferredWidth;
357 bounds.height = lastItem.preferredHeight;
358 lastItem.requestedWidth = lastItem.preferredWidth;
359 lastItem.setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
360 }
361 if (i == 0) {
362 item.wrap = true;
363 items[row][0].wrap = false;
364 }
365 int oldLength = items[row].length;
366 CoolItem[] newRow = new CoolItem[oldLength + 1];
367 System.arraycopy(items[row], 0, newRow, 0, i);
368 newRow[i] = item;
369 System.arraycopy(items[row], i, newRow, i + 1, oldLength - i);
370 items[row] = newRow;
371 }
372 item.requestedWidth = CoolItem.MINIMUM_WIDTH;
373
374 int length = originalItems.length;
375 CoolItem [] newOriginals = new CoolItem [length + 1];
376 System.arraycopy (originalItems, 0, newOriginals, 0, index);
377 System.arraycopy (originalItems, index, newOriginals, index + 1, length - index);
378 newOriginals [index] = item;
379 originalItems = newOriginals;
380 layoutItems();
381
382 }
383 void destroyItem(CoolItem item) {
384 if (inDispose) return;
385 int row = findItem(item).y;
386 if (row == -1) return;
387 Rectangle bounds = item.getBounds();
388 removeItemFromRow(item, row, true);
389
390 int index = 0;
391 while (index < originalItems.length) {
392 if (originalItems [index] == item) break;
393 index++;
394 }
395 int length = originalItems.length - 1;
396 CoolItem [] newOriginals = new CoolItem [length];
397 System.arraycopy (originalItems, 0, newOriginals, 0, index);
398 System.arraycopy (originalItems, index + 1, newOriginals, index, length - index);
399 originalItems = newOriginals;
400
401 redraw(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height, false);
402 relayout();
403 }
404 void moveDown(CoolItem item, int x_root) {
405 int oldRowIndex = findItem(item).y;
406 boolean resize = false;
407 if (items[oldRowIndex].length == 1) {
408 resize = true;
409 /* If this is the only item in the bottom row, don't move it. */
410 if (oldRowIndex == items.length - 1) return;
411 }
412 int newRowIndex = (items[oldRowIndex].length == 1) ? oldRowIndex : oldRowIndex + 1;
413 removeItemFromRow(item, oldRowIndex, false);
414 Rectangle old = item.getBounds();
415 redraw(old.x, old.y, CoolItem.MINIMUM_WIDTH, old.height, false);
416 if (newRowIndex == items.length) {
417 /* Create a new bottom row for the item. */
418 CoolItem[][] newRows = new CoolItem[items.length + 1][];
419 System.arraycopy(items, 0, newRows, 0, items.length);
420 int row = items.length;
421 newRows[row] = new CoolItem[1];
422 newRows[row][0] = item;
423 items = newRows;
424 resize = true;
425 item.wrap = true;
426 } else {
427 insertItemIntoRow(item, newRowIndex, x_root);
428 }
429 if (resize) {
430 relayout();
431 } else {
432 layoutItems();
433 }
434 }
435 void moveLeft(CoolItem item, int pixels) {
436 Point point = findItem(item);
437 int row = point.y;
438 int index = point.x;
439 if (index == 0) return;
440 Rectangle bounds = item.getBounds();
441 int minSpaceOnLeft = 0;
442 for (int i = 0; i < index; i++) {
443 minSpaceOnLeft += items[row][i].internalGetMinimumWidth();
444 }
445 int x = Math.max(minSpaceOnLeft, bounds.x - pixels);
446 CoolItem left = items[row][index - 1];
447 Rectangle leftBounds = left.getBounds();
448 if (leftBounds.x + left.internalGetMinimumWidth() > x) {
449 int shift = leftBounds.x + left.internalGetMinimumWidth() - x;
450 moveLeft(left, shift);
451 leftBounds = left.getBounds();
452 }
453 int leftWidth = Math.max(left.internalGetMinimumWidth(), leftBounds.width - pixels);
454 left.setBounds(leftBounds.x, leftBounds.y, leftWidth, leftBounds.height);
455 left.requestedWidth = leftWidth;
456 int width = bounds.width + (bounds.x - x);
457 item.setBounds(x, bounds.y, width, bounds.height);
458 item.requestedWidth = width;
459
460 int damagedWidth = bounds.x - x + CoolItem.MINIMUM_WIDTH;
461 if (damagedWidth > CoolItem.MINIMUM_WIDTH) {
462 redraw(x, bounds.y, damagedWidth, bounds.height, false);
463 }
464 }
465 void moveRight(CoolItem item, int pixels) {
466 Point point = findItem(item);
467 int row = point.y;
468 int index = point.x;
469 if (index == 0) return;
470 Rectangle bounds = item.getBounds();
471 int minSpaceOnRight = 0;
472 for (int i = index; i < items[row].length; i++) {
473 minSpaceOnRight += items[row][i].internalGetMinimumWidth();
474 }
475 int max = getBounds().width - minSpaceOnRight;
476 int x = Math.min(max, bounds.x + pixels);
477 int width = 0;
478 if (index + 1 == items[row].length) {
479 width = getBounds().width - x;
480 } else {
481 CoolItem right = items[row][index + 1];
482 Rectangle rightBounds = right.getBounds();
483 if (x + item.internalGetMinimumWidth() > rightBounds.x) {
484 int shift = x + item.internalGetMinimumWidth() - rightBounds.x;
485 moveRight(right, shift);
486 rightBounds = right.getBounds();
487 }
488 width = rightBounds.x - x;
489 }
490 item.setBounds(x, bounds.y, width, bounds.height);
491 item.requestedWidth = width;
492 CoolItem left = items[row][index - 1];
493 Rectangle leftBounds = left.getBounds();
494 int leftWidth = x - leftBounds.x;
495 left.setBounds(leftBounds.x, leftBounds.y, leftWidth, leftBounds.height);
496 left.requestedWidth = leftWidth;
497
498 int damagedWidth = x - bounds.x + CoolItem.MINIMUM_WIDTH + CoolItem.MARGIN_WIDTH;
499 if (x - bounds.x > 0) {
500 redraw(bounds.x - CoolItem.MARGIN_WIDTH, bounds.y, damagedWidth, bounds.height, false);
501 }
502 }
503 void moveUp(CoolItem item, int x_root) {
504 Point point = findItem(item);
505 int oldRowIndex = point.y;
506 boolean resize = false;
507 if (items[oldRowIndex].length == 1) {
508 resize = true;
509 /* If this is the only item in the top row, don't move it. */
510 if (oldRowIndex == 0) return;
511 }
512 removeItemFromRow(item, oldRowIndex, false);
513 Rectangle old = item.getBounds();
514 redraw(old.x, old.y, CoolItem.MINIMUM_WIDTH, old.height, false);
515 int newRowIndex = Math.max(0, oldRowIndex - 1);
516 if (oldRowIndex == 0) {
517 /* Create a new top row for the item. */
518 CoolItem[][] newRows = new CoolItem[items.length + 1][];
519 System.arraycopy(items, 0, newRows, 1, items.length);
520 newRows[0] = new CoolItem[1];
521 newRows[0][0] = item;
522 items = newRows;
523 resize = true;
524 item.wrap = true;
525 } else {
526 insertItemIntoRow(item, newRowIndex, x_root);
527 }
528 if (resize) {
529 relayout();
530 } else {
531 layoutItems();
532 }
533 }
534 void onDispose() {
535 /*
536 * Usually when an item is disposed, destroyItem will change the size of the items array
537 * and reset the bounds of all the remaining cool items.
538 * Since the whole cool bar is being disposed, this is not necessary. For speed
539 * the inDispose flag is used to skip over this part of the item dispose.
540 */
541 inDispose = true;
542 for (int i = 0; i < items.length; i++) {
543 for (int j = 0; j < items[i].length; j++) {
544 items[i][j].dispose();
545 }
546 }
547 hoverCursor.dispose();
548 dragCursor.dispose();
549 }
550 void onMouseDown(Event event) {
551 if (isLocked || event.button != 1) return;
552 dragging = getGrabbedItem(event.x, event.y);
553 if (dragging != null) {
554 mouseXOffset = event.x;
555 itemXOffset = mouseXOffset - dragging.getBounds().x;
556 setCursor(dragCursor);
557 }
558 }
559 void onMouseExit() {
560 if (dragging == null) setCursor(null);
561 }
562 void onMouseMove(Event event) {
563 if (isLocked) return;
564 CoolItem grabbed = getGrabbedItem(event.x, event.y);
565 if (dragging != null) {
566 int left_root = toDisplay(new Point(event.x, event.y)).x - itemXOffset;
567 Rectangle bounds = dragging.getBounds();
568 if (event.y < bounds.y) {
569 moveUp(dragging, left_root);
570 } else if (event.y > bounds.y + bounds.height){
571 moveDown(dragging, left_root);
572 } else if (event.x < mouseXOffset) {
573 int distance = Math.min(mouseXOffset, bounds.x + itemXOffset) - event.x;
574 if (distance > 0) moveLeft(dragging, distance);
575 } else if (event.x > mouseXOffset) {
576 int distance = event.x - Math.max(mouseXOffset, bounds.x + itemXOffset);
577 if (distance > 0) moveRight(dragging, distance);
578 }
579 mouseXOffset = event.x;
580 return;
581 }
582 if (grabbed != null) {
583 setCursor(hoverCursor);
584 } else {
585 setCursor(null);
586 }
587 }
588 void onMouseUp(Event event) {
589 setCursor(null);
590 dragging = null;
591 }
592 void onMouseDoubleClick(Event event) {
593 if (isLocked) return;
594 dragging = null;
595 CoolItem target = getGrabbedItem(event.x, event.y);
596 if (target == null) {
597 setCursor(null);
598 return;
599 }
600
601 Point location = findItem(target);
602 int row = location.y;
603 int index = location.x;
604 if (items[row].length > 1) {
605 Point size = target.getSize();
606 int maxSize = getSize().x;
607 for (int i = 0; i < items[row].length; i++) {
608 if (i != index) {
609 maxSize -= items[row][i].internalGetMinimumWidth();
610 }
611 }
612 if (size.x == maxSize) {
613 /* The item is at its maximum width. It should be resized to its minimum width. */
614 int distance = size.x - target.internalGetMinimumWidth();
615 if (index + 1 < items[row].length) {
616 /* There is an item to the right. Maximize it. */
617 CoolItem right = items[row][index + 1];
618 moveLeft(right, distance);
619 } else {
620 /* There is no item to the right. Move the item all the way right. */
621 moveRight(target, distance);
622 }
623 } else if (size.x < target.preferredWidth) {
624 /* The item is less than its preferredWidth. Resize to preferredWidth. */
625 int distance = target.preferredWidth - size.x;
626 if (index + 1 < items[row].length) {
627 CoolItem right = items[row][index + 1];
628 moveRight(right, distance);
629 distance = target.preferredWidth - target.getSize().x;
630 }
631 if (distance > 0) {
632 moveLeft(target, distance);
633 }
634 } else {
635 /* The item is at its minimum width. Maximize it. */
636 for (int i = 0; i < items[row].length; i++) {
637 if (i != index) {
638 CoolItem item = items[row][i];
639 item.requestedWidth = Math.max(item.internalGetMinimumWidth(), CoolItem.MINIMUM_WIDTH);
640 }
641 }
642 target.requestedWidth = maxSize;
643 layoutItems();
644 }
645 setCursor(hoverCursor);
646 }
647 }
648 void onPaint(Event event) {
649 GC gc = event.gc;
650 if (items.length == 0) return;
651 Color shadowColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
652 Color highlightColor = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
653
654 boolean flat = (style & SWT.FLAT) != 0;
655 int stopX = getBounds().width;
656 Rectangle clipping = gc.getClipping();
657 for (int row = 0; row < items.length; row++) {
658 Rectangle bounds = new Rectangle(0, 0, 0, 0);
659 for (int i = 0; i < items[row].length; i++) {
660 bounds = items[row][i].getBounds();
661 if (!clipping.intersects(bounds)) continue;
662 boolean nativeGripper = false;
663
664 /* Draw gripper. */
665 if (!isLocked) {
666 if (!flat) nativeGripper = drawGripper(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height);
667 if (!nativeGripper) {
668 int grabberTrim = 2;
669 int grabberHeight = bounds.height - (2 * grabberTrim) - 1;
670 gc.setForeground(shadowColor);
671 gc.drawRectangle(
672 bounds.x + CoolItem.MARGIN_WIDTH,
673 bounds.y + grabberTrim,
674 2,
675 grabberHeight);
676 gc.setForeground(highlightColor);
677 gc.drawLine(
678 bounds.x + CoolItem.MARGIN_WIDTH,
679 bounds.y + grabberTrim + 1,
680 bounds.x + CoolItem.MARGIN_WIDTH,
681 bounds.y + grabberTrim + grabberHeight - 1);
682 gc.drawLine(
683 bounds.x + CoolItem.MARGIN_WIDTH,
684 bounds.y + grabberTrim,
685 bounds.x + CoolItem.MARGIN_WIDTH + 1,
686 bounds.y + grabberTrim);
687 }
688 }
689
690 /* Draw separator. */
691 if (!flat && !nativeGripper && i != 0) {
692 gc.setForeground(shadowColor);
693 gc.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height - 1);
694 gc.setForeground(highlightColor);
695 gc.drawLine(bounds.x + 1, bounds.y, bounds.x + 1, bounds.y + bounds.height - 1);
696 }
697 }
698 if (!flat && row + 1 < items.length) {
699 /* Draw row separator. */
700 int separatorY = bounds.y + bounds.height;
701 gc.setForeground(shadowColor);
702 gc.drawLine(0, separatorY, stopX, separatorY);
703 gc.setForeground(highlightColor);
704 gc.drawLine(0, separatorY + 1, stopX, separatorY + 1);
705 }
706 }
707 }
708 /**
709 * Remove the item from the row. Adjust the x and width values
710 * appropriately.
711 */
712 void removeItemFromRow(CoolItem item, int rowIndex, boolean disposed) {
713 int index = findItem(item).x;
714 int newLength = items[rowIndex].length - 1;
715 Rectangle itemBounds = item.getBounds();
716 item.wrap = false;
717 if (newLength > 0) {
718 CoolItem[] newRow = new CoolItem[newLength];
719 System.arraycopy(items[rowIndex], 0, newRow, 0, index);
720 System.arraycopy(items[rowIndex], index + 1, newRow, index, newRow.length - index);
721 items[rowIndex] = newRow;
722 items[rowIndex][0].wrap = true;
723 } else {
724 CoolItem[][] newRows = new CoolItem[items.length - 1][];
725 System.arraycopy(items, 0, newRows, 0, rowIndex);
726 System.arraycopy(items, rowIndex + 1, newRows, rowIndex, newRows.length - rowIndex);
727 items = newRows;
728 return;
729 }
730 if (!disposed) {
731 if (index == 0) {
732 CoolItem first = items[rowIndex][0];
733 Rectangle bounds = first.getBounds();
734 int width = bounds.x + bounds.width;
735 first.setBounds(0, bounds.y, width, bounds.height);
736 first.requestedWidth = width;
737 redraw(bounds.x, bounds.y, CoolItem.MINIMUM_WIDTH, bounds.height, false);
738 } else {
739 CoolItem previous = items[rowIndex][index - 1];
740 Rectangle bounds = previous.getBounds();
741 int width = bounds.width + itemBounds.width;
742 previous.setBounds(bounds.x, bounds.y, width, bounds.height);
743 previous.requestedWidth = width;
744 }
745 }
746 }
747 /**
748 * Return the height of the bar after it has
749 * been properly layed out for the given width.
750 */
751 int layoutItems () {
752 int y = 0, width = getSize().x;
753 wrapItems(width);
754 int rowSpacing = (style & SWT.FLAT) != 0 ? 0 : ROW_SPACING;
755 for (int row = 0; row < items.length; row++) {
756 int count = items[row].length;
757 int x = 0;
758
759 /* determine the height and the available width for the row */
760 int rowHeight = 0;
761 int available = width;
762 for (int i = 0; i < count; i++) {
763 CoolItem item = items[row][i];
764 rowHeight = Math.max(rowHeight, item.getSize().y);
765 available -= item.internalGetMinimumWidth();
766 }
767 if (row > 0) y += rowSpacing;
768
769 /* lay the items out */
770 for (int i = 0; i < count; i++) {
771 CoolItem child = items[row][i];
772 int newWidth = available + child.internalGetMinimumWidth();
773 if (i + 1 < count) {
774 newWidth = Math.min(newWidth, child.requestedWidth);
775 available -= (newWidth - child.internalGetMinimumWidth());
776 }
777 Rectangle oldBounds = child.getBounds();
778 Rectangle newBounds = new Rectangle(x, y, newWidth, rowHeight);
779 if (!oldBounds.equals(newBounds)) {
780 child.setBounds(newBounds.x, newBounds.y, newBounds.width, newBounds.height);
781 Rectangle damage = new Rectangle(0, 0, 0, 0);
782 /* Cases are in descending order from most area to redraw to least. */
783 if (oldBounds.y != newBounds.y) {
784 damage = newBounds;
785 damage.add(oldBounds);
786 /* Redraw the row separator as well. */
787 damage.y -= rowSpacing;
788 damage.height += 2 * rowSpacing;
789 } else if (oldBounds.height != newBounds.height) {
790 /*
791 * Draw from the bottom of the gripper to the bottom of the new area.
792 * (Bottom of the gripper is -3 from the bottom of the item).
793 */
794 damage.y = newBounds.y + Math.min(oldBounds.height, newBounds.height) - 3;
795 damage.height = newBounds.y + newBounds.height + rowSpacing;
796 damage.x = oldBounds.x - CoolItem.MARGIN_WIDTH;
797 damage.width = oldBounds.width + CoolItem.MARGIN_WIDTH;
798 } else if (oldBounds.x != newBounds.x) {
799 /* Redraw only the difference between the separators. */
800 damage.x = Math.min(oldBounds.x, newBounds.x);
801 damage.width = Math.abs(oldBounds.x - newBounds.x) + CoolItem.MINIMUM_WIDTH;
802 damage.y = oldBounds.y;
803 damage.height = oldBounds.height;
804 }
805 redraw(damage.x, damage.y, damage.width, damage.height, false);
806 }
807 x += newWidth;
808 }
809 y += rowHeight;
810 }
811 return y;
812 }
813 void relayout() {
814 Point size = getSize();
815 int height = layoutItems();
816 Rectangle trim = computeTrim (0, 0, 0, height);
817 if (height != size.y) super.setSize(size.x, trim.height);
818 }
819 public void setBounds (int x, int y, int width, int height) {
820 super.setBounds (x, y, width, height);
821 layoutItems();
822 }
823 public void setSize (int width, int height) {
824 super.setSize (width, height);
825 layoutItems();
826 }
827 /**
828 * Returns an array of zero-relative ints that map
829 * the creation order of the receiver's items to the
830 * order in which they are currently being displayed.
831 * <p>
832 * Specifically, the indices of the returned array represent
833 * the current visual order of the items, and the contents
834 * of the array represent the creation order of the items.
835 * </p><p>
836 * Note: This is not the actual structure used by the receiver
837 * to maintain its list of items, so modifying the array will
838 * not affect the receiver.
839 * </p>
840 *
841 * @return the current visual order of the receiver's items
842 *
843 * @exception SWTException <ul>
844 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
845 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
846 * </ul>
847 * @exception SWTError <ul>
848 * <li>ERROR_CANNOT_GET_ITEM - if the operation fails because of an operating system failure</li>
849 * </ul>
850 */
851 public int[] getItemOrder () {
852 checkWidget ();
853 int count = getItemCount ();
854 int [] indices = new int [count];
855 count = 0;
856 for (int i = 0; i < items.length; i++) {
857 for (int j = 0; j < items[i].length; j++) {
858 CoolItem item = items[i][j];
859 int index = 0;
860 while (index<originalItems.length) {
861 if (originalItems [index] == item) break;
862 index++;
863 }
864 if (index == originalItems.length) error (SWT.ERROR_CANNOT_GET_ITEM);
865 indices [count++] = index;
866 }
867 }
868 return indices;
869 }
870 void setItemOrder (int[] itemOrder) {
871 if (itemOrder == null) error(SWT.ERROR_NULL_ARGUMENT);
872 int count = originalItems.length;
873 if (itemOrder.length != count) error(SWT.ERROR_INVALID_ARGUMENT);
874
875 /* Ensure that itemOrder does not contain any duplicates. */
876 boolean [] set = new boolean [count];
877 for (int i = 0; i < set.length; i++) set [i] = false;
878 for (int i = 0; i < itemOrder.length; i++) {
879 if (itemOrder [i] < 0 || itemOrder [i] >= count) error (SWT.ERROR_INVALID_ARGUMENT);
880 if (set [itemOrder [i]]) error (SWT.ERROR_INVALID_ARGUMENT);
881 set [itemOrder [i]] = true;
882 }
883
884 CoolItem[] row = new CoolItem[count];
885 for (int i = 0; i < count; i++) {
886 row[i] = originalItems[itemOrder[i]];
887 }
888 items = new CoolItem[1][count];
889 items[0] = row;
890 }
891 /**
892 * Returns an array of points whose x and y coordinates describe
893 * the widths and heights (respectively) of the items in the receiver
894 * in the order in which they are currently being displayed.
895 *
896 * @return the receiver's item sizes in their current visual order
897 *
898 * @exception SWTException <ul>
899 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
900 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
901 * </ul>
902 */
903 public Point[] getItemSizes () {
904 checkWidget();
905 CoolItem[] items = getItems();
906 Point[] sizes = new Point[items.length];
907 for (int i = 0; i < items.length; i++) {
908 sizes[i] = items[i].getSize();
909 }
910 return sizes;
911 }
912 void setItemSizes (Point[] sizes) {
913 if (sizes == null) error(SWT.ERROR_NULL_ARGUMENT);
914 CoolItem[] items = getItems();
915 if (sizes.length != items.length) error(SWT.ERROR_INVALID_ARGUMENT);
916 for (int i = 0; i < items.length; i++) {
917 items[i].setSize(sizes[i]);
918 }
919 }
920 /**
921 * Returns whether or not the receiver is 'locked'. When a coolbar
922 * is locked, its items cannot be repositioned.
923 *
924 * @return true if the coolbar is locked, false otherwise
925 *
926 * @exception SWTException <ul>
927 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
928 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
929 * </ul>
930 *
931 * @since 2.0
932 */
933 public boolean getLocked () {
934 checkWidget ();
935 return isLocked;
936 }
937 /**
938 * Returns an array of ints that describe the zero-relative
939 * indices of any item(s) in the receiver that will begin on
940 * a new row. The 0th visible item always begins the first row,
941 * therefore it does not count as a wrap index.
942 *
943 * @return an array containing the receiver's wrap indices, or an empty array if all items are in one row
944 *
945 * @exception SWTException <ul>
946 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
947 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
948 * </ul>
949 */
950 public int[] getWrapIndices () {
951 checkWidget();
952 if (items.length <= 1) return new int[]{};
953 int[] wrapIndices = new int[items.length - 1];
954 int i = 0, nextWrap = items[0].length;
955 for (int row = 1; row < items.length; row++) {
956 if (items[row][0].wrap) wrapIndices[i++] = nextWrap;
957 nextWrap += items[row].length;
958 }
959 if (i != wrapIndices.length) {
960 int[] tmp = new int[i];
961 System.arraycopy(wrapIndices, 0, tmp, 0, i);
962 return tmp;
963 }
964 return wrapIndices;
965 }
966 /**
967 * Sets whether or not the receiver is 'locked'. When a coolbar
968 * is locked, its items cannot be repositioned.
969 *
970 * @param locked lock the coolbar if true, otherwise unlock the coolbar
971 *
972 * @exception SWTException <ul>
973 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
974 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
975 * </ul>
976 *
977 * @since 2.0
978 */
979 public void setLocked (boolean locked) {
980 checkWidget ();
981 if (isLocked != locked) {
982 redraw();
983 }
984 isLocked = locked;
985
986 }
987 /**
988 * Sets the indices of all item(s) in the receiver that will
989 * begin on a new row. The indices are given in the order in
990 * which they are currently being displayed. The 0th item
991 * always begins the first row, therefore it does not count
992 * as a wrap index. If indices is null or empty, the items
993 * will be placed on one line.
994 *
995 * @param indices an array of wrap indices, or null
996 *
997 * @exception SWTException <ul>
998 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
999 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1000 * </ul>
1001 */
1002public void setWrapIndices (int[] indices) {
1003 checkWidget();
1004 if (indices == null) indices = new int[0];
1005 int count = originalItems.length;
1006 for (int i=0; i<indices.length; i++) {
1007 if (indices[i] < 0 || indices[i] >= count) {
1008 error (SWT.ERROR_INVALID_ARGUMENT);
1009 }
1010 }
1011 for (int i=0; i<originalItems.length; i++) {
1012 originalItems[i].wrap = false;
1013 }
1014 for (int i=0; i<indices.length; i++) {
1015 int index = indices[i];
1016 for (int row = 0; row < items.length; row++) {
1017 if (items[row].length > index) {
1018 items[row][index].wrap = true;
1019 break;
1020 } else {
1021 index -= items[row].length;
1022 }
1023 }
1024 }
1025 relayout();
1026}
1027/**
1028 * Sets the receiver's item order, wrap indices, and item sizes
1029 * all at once. This method is typically used to restore the
1030 * displayed state of the receiver to a previously stored state.
1031 * <p>
1032 * The item order is the order in which the items in the receiver
1033 * should be displayed, given in terms of the zero-relative ordering
1034 * of when the items were added.
1035 * </p><p>
1036 * The wrap indices are the indices of all item(s) in the receiver
1037 * that will begin on a new row. The indices are given in the order
1038 * specified by the item order. The 0th item always begins the first
1039 * row, therefore it does not count as a wrap index. If wrap indices
1040 * is null or empty, the items will be placed on one line.
1041 * </p><p>
1042 * The sizes are specified in an array of points whose x and y
1043 * coordinates describe the new widths and heights (respectively)
1044 * of the receiver's items in the order specified by the item order.
1045 * </p>
1046 *
1047 * @param itemOrder an array of indices that describe the new order to display the items in
1048 * @param wrapIndices an array of wrap indices, or null
1049 * @param sizes an array containing the new sizes for each of the receiver's items in visual order
1050 *
1051 * @exception SWTException <ul>
1052 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1053 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1054 * </ul>
1055 * @exception IllegalArgumentException <ul>
1056 * <li>ERROR_NULL_ARGUMENT - if item order or sizes is null</li>
1057 * <li>ERROR_INVALID_ARGUMENT - if item order or sizes is not the same length as the number of items</li>
1058 * </ul>
1059 * @exception SWTError <ul>
1060 * <li>ERROR_CANNOT_GET_ITEM - if the operation fails because of an operating system failure</li>
1061 * </ul>
1062 */
1063public void setItemLayout (int[] itemOrder, int[] wrapIndices, Point[] sizes) {
1064 checkWidget();
1065 setItemOrder(itemOrder);
1066 setWrapIndices(wrapIndices);
1067 setItemSizes(sizes);
1068 relayout();
1069}
1070void wrapItems (int maxWidth) {
1071 int itemCount = originalItems.length;
1072 if (itemCount < 2) return;
1073 CoolItem[] itemsVisual = new CoolItem[itemCount];
1074 int start = 0;
1075 for (int row = 0; row < items.length; row++) {
1076 System.arraycopy(items[row], 0, itemsVisual, start, items[row].length);
1077 start += items[row].length;
1078 }
1079 CoolItem[][] newItems = new CoolItem[itemCount][];
1080 int rowCount = 0, rowWidth = 0;
1081 start = 0;
1082 for (int i = 0; i < itemCount; i++) {
1083 CoolItem item = itemsVisual[i];
1084 int itemWidth = item.internalGetMinimumWidth();
1085 if ((i > 0 && item.wrap) || (maxWidth != SWT.DEFAULT && rowWidth + itemWidth > maxWidth)) {
1086 if (i == start) {
1087 newItems[rowCount] = new CoolItem[1];
1088 newItems[rowCount][0] = item;
1089 start = i + 1;
1090 rowWidth = 0;
1091 } else {
1092 int count = i - start;
1093 newItems[rowCount] = new CoolItem[count];
1094 System.arraycopy(itemsVisual, start, newItems[rowCount], 0, count);
1095 start = i;
1096 rowWidth = itemWidth;
1097 }
1098 rowCount++;
1099 } else {
1100 rowWidth += itemWidth;
1101 }
1102 }
1103 if (start < itemCount) {
1104 int count = itemCount - start;
1105 newItems[rowCount] = new CoolItem[count];
1106 System.arraycopy(itemsVisual, start, newItems[rowCount], 0, count);
1107 rowCount++;
1108 }
1109 if (newItems.length != rowCount) {
1110 CoolItem[][] tmp = new CoolItem[rowCount][];
1111 System.arraycopy(newItems, 0, tmp, 0, rowCount);
1112 items = tmp;
1113 } else {
1114 items = newItems;
1115 }
1116}
1117}