1 /*******************************************************************************
2 * Copyright (c) 2011, 2016 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.custom;
15
16
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.accessibility.*;
19 import org.eclipse.swt.events.*;
20 import org.eclipse.swt.graphics.*;
21 import org.eclipse.swt.widgets.*;
22
23 /**
24 * A TreeCursor provides a way for the user to navigate around a Tree with columns using the
25 * keyboard. It also provides a mechanism for selecting an individual cell in a tree.
26 * <p>
27 * For a detailed example of using a TreeCursor to navigate to a cell and then edit it see
28 * http://git.eclipse.org/c/platform/eclipse.platform.swt.git/tree/examples/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet360.java .
29 *
30 * <dl>
31 * <dt><b>Styles:</b></dt>
32 * <dd>BORDER</dd>
33 * <dt><b>Events:</b></dt>
34 * <dd>Selection, DefaultSelection</dd>
35 * </dl>
36 *
37 * @since 3.8
38 */
39 public class TreeCursor extends Canvas {
40 Tree tree;
41 TreeItem row;
42 TreeColumn column;
43 Listener listener, treeListener, resizeListener, disposeItemListener, disposeColumnListener;
44
45 Color background = null;
46 Color foreground = null;
47
48 /* By default, invert the list selection colors */
49 static final int BACKGROUND = SWT.COLOR_LIST_SELECTION_TEXT;
50 static final int FOREGROUND = SWT.COLOR_LIST_SELECTION;
51
52 /**
53 * Constructs a new instance of this class given its parent tree and a style value describing
54 * its behavior and appearance.
55 * <p>
56 * The style value is either one of the style constants defined in class <code>SWT</code> which
57 * is applicable to instances of this class, or must be built by <em>bitwise OR</em>'ing
58 * together (that is, using the <code>int</code> "|" operator) two or more of those
59 * <code>SWT</code> style constants. The class description lists the style constants that are
60 * applicable to the class. Style bits are also inherited from superclasses.
61 * </p>
62 *
63 * @param parent a Tree control which will be the parent of the new instance (cannot be null)
64 * @param style the style of control to construct
65 *
66 * @exception IllegalArgumentException <ul>
67 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
68 * </ul>
69 * @exception SWTException <ul>
70 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
71 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
72 * </ul>
73 *
74 * @see SWT#BORDER
75 * @see Widget#checkSubclass()
76 * @see Widget#getStyle()
77 */
TreeCursor(Tree parent, int style)78 public TreeCursor(Tree parent, int style) {
79 super(parent, style);
80 tree = parent;
81 setBackground(null);
82 setForeground(null);
83
84 listener = event -> {
85 if (row != null) {
86 /*
87 * Detect cases where the cursor position has become invalid and fix it.
88 * The typical cause of this is programmatic tree changes, such as
89 * expanding/collapsing and item and creating/disposing items.
90 */
91 if (row.isDisposed()) {
92 unhookRowColumnListeners();
93 _resize();
94 tree.setFocus();
95 return;
96 }
97 TreeItem current = row;
98 TreeItem parentItem = row.getParentItem();
99 while (parentItem != null && !parentItem.getExpanded()) {
100 current = parentItem;
101 parentItem = current.getParentItem();
102 }
103 if (current != row) {
104 setRowColumn(current, column, false);
105 }
106 }
107 switch (event.type) {
108 case SWT.Dispose:
109 onDispose(event);
110 break;
111 case SWT.FocusIn:
112 case SWT.FocusOut:
113 redraw();
114 break;
115 case SWT.KeyDown:
116 keyDown(event);
117 break;
118 case SWT.Paint:
119 paint(event);
120 break;
121 case SWT.Traverse:
122 event.doit = true;
123 switch (event.detail) {
124 case SWT.TRAVERSE_ARROW_NEXT:
125 case SWT.TRAVERSE_ARROW_PREVIOUS:
126 case SWT.TRAVERSE_RETURN:
127 event.doit = false;
128 break;
129 }
130 break;
131 }
132 };
133 int[] events = new int[] { SWT.Dispose, SWT.FocusIn, SWT.FocusOut, SWT.KeyDown, SWT.Paint, SWT.Traverse };
134 for (int event : events) {
135 addListener(event, listener);
136 }
137
138 treeListener = event -> {
139 switch (event.type) {
140 case SWT.Collapse:
141 treeCollapse(event);
142 break;
143 case SWT.Expand:
144 treeExpand(event);
145 break;
146 case SWT.FocusIn:
147 treeFocusIn(event);
148 break;
149 case SWT.MouseDown:
150 treeMouseDown(event);
151 break;
152 }
153 };
154 tree.addListener(SWT.Collapse, treeListener);
155 tree.addListener(SWT.Expand, treeListener);
156 tree.addListener(SWT.FocusIn, treeListener);
157 tree.addListener(SWT.MouseDown, treeListener);
158
159 disposeItemListener = event -> {
160 TreeItem currentItem = row;
161 while (currentItem != null) {
162 currentItem.removeListener(SWT.Dispose, disposeItemListener);
163 currentItem = currentItem.getParentItem();
164 }
165 TreeItem disposedItem = (TreeItem)event.widget;
166 TreeItem parentItem = disposedItem.getParentItem();
167 if (parentItem != null) {
168 setRowColumn(parentItem, column, true);
169 } else {
170 if (tree.getItemCount() == 1) {
171 unhookRowColumnListeners();
172 } else {
173 TreeItem newFocus = null;
174 int rowIndex = tree.indexOf(disposedItem);
175 if (rowIndex != 0) {
176 TreeItem previousItem = tree.getItem(rowIndex - 1);
177 if (!previousItem.isDisposed()) {
178 newFocus = previousItem;
179 }
180 }
181 if (newFocus == null && rowIndex + 1 < tree.getItemCount()) {
182 TreeItem nextItem = tree.getItem(rowIndex + 1);
183 if (!nextItem.isDisposed()) {
184 newFocus = nextItem;
185 }
186 }
187 if (newFocus != null) {
188 setRowColumn(newFocus, column, true);
189 } else {
190 unhookRowColumnListeners();
191 }
192 }
193 }
194 _resize();
195 };
196 disposeColumnListener = event -> {
197 if (column != null) {
198 if (tree.getColumnCount() == 1) {
199 column = null;
200 } else {
201 int columnIndex = tree.indexOf(column);
202 int positionIndex = columnIndex;
203 int[] columnOrder = tree.getColumnOrder();
204 for (int i = 0; i < columnOrder.length; i++) {
205 if (columnOrder[i] == columnIndex) {
206 positionIndex = i;
207 break;
208 }
209 }
210 if (positionIndex == columnOrder.length - 1) {
211 setRowColumn(row, tree.getColumn(columnOrder[positionIndex - 1]), true);
212 } else {
213 setRowColumn(row, tree.getColumn(columnOrder[positionIndex + 1]), true);
214 }
215 }
216 }
217 _resize();
218 };
219 resizeListener = event -> _resize();
220 ScrollBar hBar = tree.getHorizontalBar();
221 if (hBar != null) {
222 hBar.addListener(SWT.Selection, resizeListener);
223 }
224 ScrollBar vBar = tree.getVerticalBar();
225 if (vBar != null) {
226 vBar.addListener(SWT.Selection, resizeListener);
227 }
228
229 getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() {
230 @Override
231 public void getRole(AccessibleControlEvent e) {
232 e.detail = ACC.ROLE_TABLECELL;
233 }
234 });
235 getAccessible().addAccessibleListener(new AccessibleAdapter() {
236 @Override
237 public void getName(AccessibleEvent e) {
238 if (row == null) return;
239 int columnIndex = column == null ? 0 : tree.indexOf(column);
240 e.result = row.getText(columnIndex);
241 }
242 });
243 }
244
245 /**
246 * Adds the listener to the collection of listeners who will be notified when the receiver's
247 * selection changes, by sending it one of the messages defined in the
248 * <code>SelectionListener</code> interface.
249 * <p>
250 * When <code>widgetSelected</code> is called, the item field of the event object is valid. If
251 * the receiver has <code>SWT.CHECK</code> style set and the check selection changes, the event
252 * object detail field contains the value <code>SWT.CHECK</code>.
253 * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked.
254 * </p>
255 *
256 * @param listener the listener which should be notified
257 *
258 * @exception IllegalArgumentException <ul>
259 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
260 * </ul>
261 * @exception SWTException <ul>
262 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
263 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
264 * </ul>
265 *
266 * @see SelectionListener
267 * @see SelectionEvent
268 * @see #removeSelectionListener(SelectionListener)
269 */
addSelectionListener(SelectionListener listener)270 public void addSelectionListener(SelectionListener listener) {
271 checkWidget();
272 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
273 TypedListener typedListener = new TypedListener(listener);
274 addListener(SWT.Selection, typedListener);
275 addListener(SWT.DefaultSelection, typedListener);
276 }
277
countSubTreePages(TreeItem root)278 int countSubTreePages(TreeItem root) {
279 int pages = 1;
280 if (root == null) return 0;
281 if (root.getItemCount() == 0) return 1;
282 if (!root.getExpanded()) return 1;
283 for (TreeItem item : root.getItems()) {
284 pages += countSubTreePages(item);
285 }
286 return pages;
287 }
288
findIndex(TreeItem[] items, TreeItem treeItem)289 int findIndex(TreeItem[] items, TreeItem treeItem) {
290 if (items == null || treeItem == null) return -1;
291 Rectangle rect = treeItem.getBounds();
292 int index = 0;
293 for (int i = 0; i < items.length; i++) {
294 TreeItem previousItem = null;
295 TreeItem currentItem = items[i];
296 if (i > 0) previousItem = items[i - 1];
297 Rectangle rect1 = currentItem.getBounds();
298 if (rect.y == rect1.y) return index;
299 if (rect.y < rect1.y) {
300 return index - 1 + findIndex(previousItem.getItems(), treeItem);
301 }
302 if (rect.y > rect1.y && i == items.length - 1) {
303 return index + findIndex(currentItem.getItems(), treeItem);
304 }
305 if (rect.y >= rect1.y + (1 + currentItem.getItemCount()) * tree.getItemHeight() && currentItem.getExpanded()) {
306 index += countSubTreePages(currentItem);
307 continue;
308 }
309 index++;
310 }
311 return -1;
312 }
313
findItem(TreeItem[] items, Point pt)314 TreeItem findItem(TreeItem[] items, Point pt) {
315 int start = 0, end = items.length - 1;
316 int index = end / 2;
317 while (end - start > 1) {
318 TreeItem currentItem = items[index];
319 Rectangle bounds = currentItem.getBounds();
320 if (pt.y < bounds.y) {
321 end = index;
322 index = (end - start) / 2;
323 } else {
324 start = index;
325 index = start + ((end - start) / 2);
326 }
327 }
328
329 Rectangle endBounds = items[end].getBounds();
330 if (endBounds.y < pt.y) {
331 if (endBounds.y + endBounds.height < pt.y) {
332 if (!items[end].getExpanded()) return null;
333 return findItem(items[end].getItems(), pt);
334 }
335 int[] columnOrder = tree.getColumnOrder();
336 Rectangle bounds = null;
337 if (columnOrder.length > 0) {
338 Rectangle rect1 = items[end].getBounds(columnOrder[0]);
339 Rectangle rect2 = items[end].getBounds(columnOrder[columnOrder.length - 1]);
340 bounds = rect1.union(rect2);
341 bounds.height += tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
342 } else {
343 bounds = items[end].getBounds();
344 }
345 return bounds.contains(pt) ? items[end] : null;
346 }
347
348 Rectangle startBounds = items[start].getBounds();
349 if (startBounds.y + startBounds.height < pt.y) {
350 return findItem(items[start].getItems(), pt);
351 }
352 int[] columnOrder = tree.getColumnOrder();
353 Rectangle bounds = null;
354 if (columnOrder.length > 0) {
355 Rectangle rect1 = items[start].getBounds(columnOrder[0]);
356 Rectangle rect2 = items[start].getBounds(columnOrder[columnOrder.length - 1]);
357 bounds = rect1.union(rect2);
358 bounds.height += tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
359 } else {
360 bounds = items[start].getBounds();
361 }
362 return bounds.contains(pt) ? items[start] : null;
363 }
364
365 /**
366 * Returns the background color that the receiver will use to draw.
367 *
368 * @return the receiver's background color
369 */
370 @Override
getBackground()371 public Color getBackground() {
372 checkWidget();
373 if (background == null) {
374 return getDisplay().getSystemColor(BACKGROUND);
375 }
376 return background;
377 }
378
379 /**
380 * Returns the index of the column over which the TreeCursor is positioned.
381 *
382 * @return the column index for the current position
383 *
384 * @exception SWTException <ul>
385 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
386 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
387 * </ul>
388 */
getColumn()389 public int getColumn() {
390 checkWidget();
391 return column == null ? 0 : tree.indexOf(column);
392 }
393
394 /**
395 * Returns the foreground color that the receiver will use to draw.
396 *
397 * @return the receiver's foreground color
398 */
399 @Override
getForeground()400 public Color getForeground() {
401 checkWidget();
402 if (foreground == null) {
403 return getDisplay().getSystemColor(FOREGROUND);
404 }
405 return foreground;
406 }
407
getLastVisibleItem(TreeItem[] items)408 TreeItem getLastVisibleItem(TreeItem[] items) {
409 if (items == null) return null;
410 TreeItem last = items[items.length - 1];
411 if (last.getExpanded() && last.getItemCount() > 0) {
412 return getLastVisibleItem(last.getItems());
413 }
414 return last;
415 }
416
getNextItem(TreeItem item)417 TreeItem getNextItem(TreeItem item) {
418 if (item == null) return null;
419 if (item.getExpanded() && item.getItemCount() > 0) {
420 return item.getItem(0);
421 }
422
423 TreeItem parentItem = item.getParentItem();
424 while (parentItem != null) {
425 int index = parentItem.indexOf(item);
426 if (index == -1) return null;
427 if (index < parentItem.getItemCount() - 1) {
428 return parentItem.getItem(index + 1);
429 }
430 item = parentItem;
431 parentItem = item.getParentItem();
432 }
433 int index = tree.indexOf(item);
434 if (index == -1) return null;
435 if (index == tree.getItemCount() - 1) return null;
436 return tree.getItem(index + 1);
437 }
438
getPreviousItem(TreeItem item)439 TreeItem getPreviousItem(TreeItem item) {
440 if (item == null) return null;
441 TreeItem parentItem = item.getParentItem();
442 if (parentItem == null) {
443 int index = tree.indexOf(item);
444 if (index == -1 || index == 0) return null;
445 item = tree.getItem(index - 1);
446 if (item.getExpanded() && item.getItemCount() > 0) {
447 return getLastVisibleItem(item.getItems());
448 }
449 return item;
450 }
451 int index = parentItem.indexOf(item);
452 if (index == -1) return null;
453 if (index == 0) return parentItem;
454 item = parentItem.getItem(index - 1);
455 if (item.getExpanded() && item.getItemCount() > 0) {
456 return getLastVisibleItem(item.getItems());
457 }
458 return item;
459 }
460
461 /**
462 * Returns the row over which the TreeCursor is positioned.
463 *
464 * @return the item for the current position, or <code>null</code> if none
465 *
466 * @exception SWTException <ul>
467 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
468 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
469 * </ul>
470 */
getRow()471 public TreeItem getRow() {
472 checkWidget();
473 return row;
474 }
475
keyDown(Event event)476 void keyDown(Event event) {
477 if (row == null) return;
478 switch (event.character) {
479 case SWT.CR:
480 notifyListeners(SWT.DefaultSelection, new Event());
481 return;
482 }
483 switch (event.keyCode) {
484 case SWT.ARROW_UP:
485 TreeItem previousItem = getPreviousItem(row);
486 if (previousItem != null) {
487 setRowColumn(previousItem, column, true);
488 }
489 break;
490 case SWT.ARROW_DOWN:
491 TreeItem nextItem = getNextItem(row);
492 if (nextItem != null) {
493 setRowColumn(nextItem, column, true);
494 }
495 break;
496 case SWT.ARROW_LEFT:
497 case SWT.ARROW_RIGHT: {
498 if ((event.stateMask & SWT.MOD1) != 0) {
499 row.setExpanded (event.keyCode == SWT.ARROW_RIGHT);
500 break;
501 }
502 int columnCount = tree.getColumnCount();
503 if (columnCount == 0) break;
504 int columnIndex = column == null ? 0 : tree.indexOf(column);
505 int[] columnOrder = tree.getColumnOrder();
506 int index = 0;
507 while (index < columnOrder.length) {
508 if (columnOrder[index] == columnIndex) break;
509 index++;
510 }
511 if (index == columnOrder.length) index = 0;
512 int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
513 TreeItem parentRow = row.getParentItem();
514 int rowIndex = tree.indexOf(row);
515 if (event.keyCode == leadKey) {
516 if (parentRow != null) {
517 setRowColumn(row, tree.getColumn(columnOrder[Math.max(0, index - 1)]), true);
518 } else {
519 setRowColumn(rowIndex, columnOrder[Math.max(0, index - 1)], true);
520 }
521 } else {
522 if (parentRow != null) {
523 setRowColumn(row, tree.getColumn(columnOrder[Math.min(columnCount - 1, index + 1)]), true);
524 } else {
525 setRowColumn(rowIndex, columnOrder[Math.min(columnCount - 1, index + 1)], true);
526 }
527 }
528 break;
529 }
530 case SWT.HOME:
531 int columnIndex = column == null ? 0 : tree.indexOf(column);
532 setRowColumn(0, columnIndex, true);
533 break;
534 case SWT.END: {
535 TreeItem[] items = tree.getItems();
536 setRowColumn(getLastVisibleItem(items), column, true);
537 break;
538 }
539 case SWT.PAGE_UP: {
540 Rectangle rect = tree.getClientArea();
541 Rectangle itemRect = tree.getTopItem().getBounds();
542 TreeItem item = row;
543 int index = findIndex(tree.getItems(), item);
544 int itemHeight = tree.getItemHeight();
545 rect.height -= itemRect.y;
546 int page = Math.max(1, rect.height / itemHeight);
547 if (index - page <= 0) {
548 TreeItem first = tree.getItem(0);
549 setRowColumn(first, column, true);
550 break;
551 }
552 for (int i = 0; i < page; i++) {
553 item = getPreviousItem(item);
554 }
555 setRowColumn(item, column, true);
556 break;
557 }
558 case SWT.PAGE_DOWN: {
559 Rectangle rect = tree.getClientArea();
560 Rectangle itemRect = tree.getTopItem().getBounds();
561 TreeItem item = row;
562 int index = findIndex(tree.getItems(), item);
563 int height = tree.getItemHeight();
564 rect.height -= itemRect.y;
565 TreeItem last = getLastVisibleItem(tree.getItems());
566 int page = Math.max(1, rect.height / height);
567 int end = findIndex(tree.getItems(), last);
568 if (end <= index + page) {
569 setRowColumn(last, column, true);
570 break;
571 }
572 for (int i = 0; i < page; i++) {
573 item = getNextItem(item);
574 }
575 setRowColumn(item, column, true);
576 break;
577 }
578 }
579 }
580
onDispose(Event event)581 void onDispose(Event event) {
582 removeListener(SWT.Dispose, listener);
583 notifyListeners(SWT.Dispose, event);
584 event.type = SWT.None;
585
586 tree.removeListener(SWT.Collapse, treeListener);
587 tree.removeListener(SWT.Expand, treeListener);
588 tree.removeListener(SWT.FocusIn, treeListener);
589 tree.removeListener(SWT.MouseDown, treeListener);
590 unhookRowColumnListeners();
591 ScrollBar hBar = tree.getHorizontalBar();
592 if (hBar != null) {
593 hBar.removeListener(SWT.Selection, resizeListener);
594 }
595 ScrollBar vBar = tree.getVerticalBar();
596 if (vBar != null) {
597 vBar.removeListener(SWT.Selection, resizeListener);
598 }
599 }
600
paint(Event event)601 void paint(Event event) {
602 if (row == null) return;
603 int columnIndex = column == null ? 0 : tree.indexOf(column);
604 int orderedIndex = columnIndex;
605 int[] columnOrder = tree.getColumnOrder();
606 for (int i = 0; i < columnOrder.length; i++) {
607 if (columnOrder[i] == columnIndex) {
608 orderedIndex = i;
609 break;
610 }
611 }
612 GC gc = event.gc;
613 gc.setBackground(getBackground());
614 gc.setForeground(getForeground());
615 gc.fillRectangle(event.x, event.y, event.width, event.height);
616 Image image = row.getImage(columnIndex);
617 int x = 0;
618 // Temporary code - need a better way to determine trim
619 String platform = SWT.getPlatform();
620 if (image != null) {
621 if ("win32".equals(platform)) { //$NON-NLS-1$
622 if (orderedIndex > 0) {
623 x += 2;
624 }
625 } else {
626 x += 2;
627 }
628 }
629 Point size = getSize();
630 if (image != null) {
631 Rectangle imageSize = image.getBounds();
632 int imageY = (size.y - imageSize.height) / 2;
633 gc.drawImage(image, x, imageY);
634 x += imageSize.width;
635 }
636 String text = row.getText(columnIndex);
637 if (text.length() > 0) {
638 Rectangle bounds = row.getBounds(columnIndex);
639 Point extent = gc.stringExtent(text);
640 // Temporary code - need a better way to determine trim
641 if ("win32".equals(platform)) { //$NON-NLS-1$
642 if (tree.getColumnCount() == 0 || orderedIndex == 0) {
643 x += image == null ? 2 : 5;
644 } else {
645 int alignmnent = column.getAlignment();
646 switch (alignmnent) {
647 case SWT.LEFT:
648 x += image == null ? 5 : 3;
649 break;
650 case SWT.RIGHT:
651 x = bounds.width - extent.x - 2;
652 break;
653 case SWT.CENTER:
654 x += Math.ceil((bounds.width - x - extent.x) / 2.0);
655 break;
656 }
657 }
658 } else {
659 if (tree.getColumnCount() == 0) {
660 x += image == null ? 4 : 3;
661 } else {
662 int alignmnent = column.getAlignment();
663 switch (alignmnent) {
664 case SWT.LEFT:
665 x += image == null ? 5 : 3;
666 break;
667 case SWT.RIGHT:
668 x = bounds.width - extent.x - 2;
669 break;
670 case SWT.CENTER:
671 x += (bounds.width - x - extent.x) / 2 + 2;
672 break;
673 }
674 }
675 }
676 int textY = (size.y - extent.y) / 2;
677 gc.drawString(text, x, textY);
678 }
679 if (isFocusControl()) {
680 Display display = getDisplay();
681 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
682 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
683 gc.drawFocus(0, 0, size.x, size.y);
684 }
685 }
686
687 /**
688 * Removes the listener from the collection of listeners who will be notified when the
689 * receiver's selection changes.
690 *
691 * @param listener the listener which should no longer be notified
692 *
693 * @exception IllegalArgumentException <ul>
694 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
695 * </ul>
696 * @exception SWTException <ul>
697 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
698 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
699 * </ul>
700 *
701 * @see SelectionListener
702 * @see #addSelectionListener(SelectionListener)
703 */
removeSelectionListener(SelectionListener listener)704 public void removeSelectionListener(SelectionListener listener) {
705 checkWidget();
706 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
707 removeListener(SWT.Selection, listener);
708 removeListener(SWT.DefaultSelection, listener);
709 }
710
_resize()711 void _resize() {
712 if (row == null) {
713 setBounds(-200, -200, 0, 0);
714 } else {
715 int columnIndex = column == null ? 0 : tree.indexOf(column);
716 setBounds(row.getBounds(columnIndex));
717 }
718 }
719
720 /**
721 * Sets the receiver's background color to the color specified
722 * by the argument, or to the default system color for the control
723 * if the argument is null.
724 * <p>
725 * Note: This operation is a hint and may be overridden by the platform.
726 * For example, on Windows the background of a Button cannot be changed.
727 * </p>
728 * @param color the new color (or null)
729 *
730 * @exception IllegalArgumentException <ul>
731 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
732 * </ul>
733 * @exception SWTException <ul>
734 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
735 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
736 * </ul>
737 */
738 @Override
setBackground(Color color)739 public void setBackground (Color color) {
740 background = color;
741 super.setBackground(getBackground());
742 redraw();
743 }
744 /**
745 * Sets the receiver's foreground color to the color specified
746 * by the argument, or to the default system color for the control
747 * if the argument is null.
748 * <p>
749 * Note: This operation is a hint and may be overridden by the platform.
750 * </p>
751 * @param color the new color (or null)
752 *
753 * @exception IllegalArgumentException <ul>
754 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
755 * </ul>
756 * @exception SWTException <ul>
757 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
758 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
759 * </ul>
760 */
761 @Override
setForeground(Color color)762 public void setForeground (Color color) {
763 foreground = color;
764 super.setForeground(getForeground());
765 redraw();
766 }
767
setRowColumn(int row, int column, boolean notify)768 void setRowColumn(int row, int column, boolean notify) {
769 TreeItem item = row == -1 ? null : tree.getItem(row);
770 TreeColumn col = column == -1 || tree.getColumnCount() == 0 ? null : tree.getColumn(column);
771 setRowColumn(item, col, notify);
772 }
773
setRowColumn(TreeItem row, TreeColumn column, boolean notify)774 void setRowColumn(TreeItem row, TreeColumn column, boolean notify) {
775 if (this.row != null && this.row != row) {
776 TreeItem currentItem = this.row;
777 while (currentItem != null) {
778 currentItem.removeListener(SWT.Dispose, disposeItemListener);
779 currentItem = currentItem.getParentItem();
780 }
781 this.row = null;
782 }
783 if (this.column != null && this.column != column) {
784 this.column.removeListener(SWT.Dispose, disposeColumnListener);
785 this.column.removeListener(SWT.Move, resizeListener);
786 this.column.removeListener(SWT.Resize, resizeListener);
787 this.column = null;
788 }
789 if (row != null) {
790 if (this.row != row) {
791 this.row = row;
792 TreeItem currentItem = row;
793 while (currentItem != null) {
794 currentItem.addListener(SWT.Dispose, disposeItemListener);
795 currentItem = currentItem.getParentItem();
796 }
797 tree.showItem(row);
798 }
799 if (this.column != column && column != null) {
800 this.column = column;
801 column.addListener(SWT.Dispose, disposeColumnListener);
802 column.addListener(SWT.Move, resizeListener);
803 column.addListener(SWT.Resize, resizeListener);
804 tree.showColumn(column);
805 }
806 int columnIndex = column == null ? 0 : tree.indexOf(column);
807 setBounds(row.getBounds(columnIndex));
808 redraw();
809 if (notify) notifyListeners(SWT.Selection, new Event());
810 }
811 }
812
813 /**
814 * Positions the TreeCursor over the root-level cell at the given row and column in the parent tree.
815 *
816 * @param row the index of the root-level row for the cell to select
817 * @param column the index of column for the cell to select
818 *
819 * @exception SWTException <ul>
820 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
821 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
822 * </ul>
823 */
setSelection(int row, int column)824 public void setSelection(int row, int column) {
825 checkWidget();
826 int columnCount = tree.getColumnCount();
827 int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
828 if (row < 0 || row >= tree.getItemCount() || column < 0 || column > maxColumnIndex) {
829 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
830 }
831 setRowColumn(row, column, false);
832 }
833
834 /**
835 * Positions the TreeCursor over the cell at the given row and column in the parent tree.
836 *
837 * @param row the TreeItem of the row for the cell to select
838 * @param column the index of column for the cell to select
839 *
840 * @exception SWTException <ul>
841 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
842 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
843 * </ul>
844 */
setSelection(TreeItem row, int column)845 public void setSelection(TreeItem row, int column) {
846 checkWidget();
847 int columnCount = tree.getColumnCount();
848 int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
849 if (row == null || row.isDisposed() || column < 0 || column > maxColumnIndex) {
850 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
851 }
852 TreeColumn col = tree.getColumnCount() == 0 ? null : tree.getColumn(column);
853 setRowColumn(row, col, false);
854 }
855
856 @Override
setVisible(boolean visible)857 public void setVisible(boolean visible) {
858 checkWidget();
859 if (visible) {
860 _resize();
861 }
862 super.setVisible(visible);
863 }
864
treeCollapse(Event event)865 void treeCollapse(Event event) {
866 if (row == null) return;
867 TreeItem root = (TreeItem)event.item;
868 TreeItem parentItem = row.getParentItem();
869 while (parentItem != null) {
870 if (parentItem == root) {
871 setRowColumn(root, column, true);
872 return;
873 }
874 parentItem = parentItem.getParentItem();
875 }
876
877 getDisplay().asyncExec(() -> {
878 if (isDisposed()) return;
879 setRowColumn(row, column, true);
880 });
881 }
882
treeExpand(Event event)883 void treeExpand(Event event) {
884 getDisplay().asyncExec(() -> {
885 if (isDisposed()) return;
886 setRowColumn(row, column, true);
887 });
888 }
889
treeFocusIn(Event event)890 void treeFocusIn(Event event) {
891 if (isVisible()) {
892 if (row == null && column == null) return;
893 setFocus();
894 }
895 }
896
treeMouseDown(Event event)897 void treeMouseDown(Event event) {
898 if (tree.getItemCount() == 0) return;
899 Point pt = new Point(event.x, event.y);
900 TreeItem item = tree.getItem(pt);
901 if (item == null && (tree.getStyle() & SWT.FULL_SELECTION) == 0) {
902 TreeItem currentItem = tree.getTopItem();
903 TreeItem parentItem = currentItem.getParentItem();
904 while (parentItem != null) {
905 currentItem = parentItem;
906 parentItem = currentItem.getParentItem();
907 }
908 int start = tree.indexOf(currentItem);
909 int viewportItemCount = tree.getClientArea().height / tree.getItemHeight();
910 int end = Math.min(start + viewportItemCount, tree.getItemCount() - 1);
911 TreeItem[] allItems = tree.getItems();
912 TreeItem[] items = new TreeItem[end - start + 1];
913 System.arraycopy(allItems, start, items, 0, end - start + 1);
914 item = findItem(items, pt);
915 }
916 if (item == null) return;
917
918 TreeColumn newColumn = null;
919 int lineWidth = tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
920 int columnCount = tree.getColumnCount();
921 if (columnCount > 0) {
922 for (int i = 0; i < columnCount; i++) {
923 Rectangle rect = item.getBounds(i);
924 rect.width += lineWidth;
925 rect.height += lineWidth;
926 if (rect.contains(pt)) {
927 newColumn = tree.getColumn(i);
928 break;
929 }
930 }
931 if (newColumn == null) {
932 newColumn = tree.getColumn(0);
933 }
934 }
935 setRowColumn(item, newColumn, true);
936 setFocus();
937 }
938
unhookRowColumnListeners()939 void unhookRowColumnListeners() {
940 if (column != null && !column.isDisposed()) {
941 column.removeListener(SWT.Dispose, disposeColumnListener);
942 column.removeListener(SWT.Move, resizeListener);
943 column.removeListener(SWT.Resize, resizeListener);
944 }
945 column = null;
946 if (row != null && !row.isDisposed()) {
947 TreeItem currentItem = row;
948 while (currentItem != null) {
949 currentItem.removeListener(SWT.Dispose, disposeItemListener);
950 currentItem = currentItem.getParentItem();
951 }
952 }
953 row = null;
954 }
955
956 }
957