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