]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/custom/TreeCursor.java
Remove invalid SHA-256-Digests
[simantics/platform.git] / bundles / org.eclipse.swt.win32.win32.x86_64 / src / org / eclipse / swt / custom / TreeCursor.java
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  */
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 i = 0; i < events.length; i++) {
135                 addListener(events[i], 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  */
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
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         TreeItem[] items = root.getItems();
284         for (int i = 0; i < items.length; i++) {
285                 pages += countSubTreePages(items[i]);
286         }
287         return pages;
288 }
289
290 int findIndex(TreeItem[] items, TreeItem treeItem) {
291         if (items == null || treeItem == null) return -1;
292         Rectangle rect = treeItem.getBounds();
293         int index = 0;
294         for (int i = 0; i < items.length; i++) {
295                 TreeItem previousItem = null;
296                 TreeItem currentItem = items[i];
297                 if (i > 0) previousItem = items[i - 1];
298                 Rectangle rect1 = currentItem.getBounds();
299                 if (rect.y == rect1.y) return index;
300                 if (rect.y < rect1.y) {
301                         return index - 1 + findIndex(previousItem.getItems(), treeItem);
302                 }
303                 if (rect.y > rect1.y && i == items.length - 1) {
304                         return index + findIndex(currentItem.getItems(), treeItem);
305                 }
306                 if (rect.y >= rect1.y + (1 + currentItem.getItemCount()) * tree.getItemHeight() && currentItem.getExpanded()) {
307                         index += countSubTreePages(currentItem);
308                         continue;
309                 }
310                 index++;
311         }
312         return -1;
313 }
314
315 TreeItem findItem(TreeItem[] items, Point pt) {
316         int start = 0, end = items.length - 1;
317         int index = end / 2;
318         while (end - start > 1) {
319                 TreeItem currentItem = items[index];
320                 Rectangle bounds = currentItem.getBounds();
321                 if (pt.y < bounds.y) {
322                         end = index;
323                         index = (end - start) / 2;
324                 } else {
325                         start = index;
326                         index = start + ((end - start) / 2);
327                 }
328         }
329
330         Rectangle endBounds = items[end].getBounds();
331         if (endBounds.y < pt.y) {
332                 if (endBounds.y + endBounds.height < pt.y) {
333                         if (!items[end].getExpanded()) return null;
334                         return findItem(items[end].getItems(), pt);
335                 }
336                 int[] columnOrder = tree.getColumnOrder();
337                 Rectangle bounds = null;
338                 if (columnOrder.length > 0) {
339                         Rectangle rect1 = items[end].getBounds(columnOrder[0]);
340                         Rectangle rect2 = items[end].getBounds(columnOrder[columnOrder.length - 1]);
341                         bounds = rect1.union(rect2);
342                         bounds.height += tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
343                 } else {
344                         bounds = items[end].getBounds();
345                 }
346                 return bounds.contains(pt) ? items[end] : null;
347         }
348
349         Rectangle startBounds = items[start].getBounds();
350         if (startBounds.y + startBounds.height < pt.y) {
351                 return findItem(items[start].getItems(), pt);
352         }
353         int[] columnOrder = tree.getColumnOrder();
354         Rectangle bounds = null;
355         if (columnOrder.length > 0) {
356                 Rectangle rect1 = items[start].getBounds(columnOrder[0]);
357                 Rectangle rect2 = items[start].getBounds(columnOrder[columnOrder.length - 1]);
358                 bounds = rect1.union(rect2);
359                 bounds.height += tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
360         } else {
361                 bounds = items[start].getBounds();
362         }
363         return bounds.contains(pt) ? items[start] : null;
364 }
365
366 /**
367  * Returns the background color that the receiver will use to draw.
368  *
369  * @return the receiver's background color
370  */
371 @Override
372 public Color getBackground() {
373         checkWidget();
374         if (background == null) {
375                 return getDisplay().getSystemColor(BACKGROUND);
376         }
377         return background;
378 }
379
380 /**
381  * Returns the index of the column over which the TreeCursor is positioned.
382  *
383  * @return the column index for the current position
384  *
385  * @exception SWTException <ul>
386  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
387  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
388  * </ul>
389  */
390 public int getColumn() {
391         checkWidget();
392         return column == null ? 0 : tree.indexOf(column);
393 }
394
395 /**
396  * Returns the foreground color that the receiver will use to draw.
397  *
398  * @return the receiver's foreground color
399  */
400 @Override
401 public Color getForeground() {
402         checkWidget();
403         if (foreground == null) {
404                 return getDisplay().getSystemColor(FOREGROUND);
405         }
406         return foreground;
407 }
408
409 TreeItem getLastVisibleItem(TreeItem[] items) {
410         if (items == null) return null;
411         TreeItem last = items[items.length - 1];
412         if (last.getExpanded() && last.getItemCount() > 0) {
413                 return getLastVisibleItem(last.getItems());
414         }
415         return last;
416 }
417
418 TreeItem getNextItem(TreeItem item) {
419         if (item == null) return null;
420         if (item.getExpanded() && item.getItemCount() > 0) {
421                 return item.getItem(0);
422         }
423
424         TreeItem parentItem = item.getParentItem();
425         while (parentItem != null) {
426                 int index = parentItem.indexOf(item);
427                 if (index == -1) return null;
428                 if (index < parentItem.getItemCount() - 1) {
429                         return parentItem.getItem(index + 1);
430                 }
431                 item = parentItem;
432                 parentItem = item.getParentItem();
433         }
434         int index = tree.indexOf(item);
435         if (index == -1) return null;
436         if (index == tree.getItemCount() - 1) return null;
437         return tree.getItem(index + 1);
438 }
439
440 TreeItem getPreviousItem(TreeItem item) {
441         if (item == null) return null;
442         TreeItem parentItem = item.getParentItem();
443         if (parentItem == null) {
444                 int index = tree.indexOf(item);
445                 if (index == -1 || index == 0) return null;
446                 item = tree.getItem(index - 1);
447                 if (item.getExpanded() && item.getItemCount() > 0) {
448                         return getLastVisibleItem(item.getItems());
449                 }
450                 return item;
451         }
452         int index = parentItem.indexOf(item);
453         if (index == -1) return null;
454         if (index == 0) return parentItem;
455         item = parentItem.getItem(index - 1);
456         if (item.getExpanded() && item.getItemCount() > 0) {
457                 return getLastVisibleItem(item.getItems());
458         }
459         return item;
460 }
461
462 /**
463  * Returns the row over which the TreeCursor is positioned.
464  *
465  * @return the item for the current position, or <code>null</code> if none
466  *
467  * @exception SWTException <ul>
468  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
469  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
470  * </ul>
471  */
472 public TreeItem getRow() {
473         checkWidget();
474         return row;
475 }
476
477 void keyDown(Event event) {
478         if (row == null) return;
479         switch (event.character) {
480                 case SWT.CR:
481                         notifyListeners(SWT.DefaultSelection, new Event());
482                         return;
483         }
484         switch (event.keyCode) {
485                 case SWT.ARROW_UP:
486                         TreeItem previousItem = getPreviousItem(row);
487                         if (previousItem != null) {
488                                 setRowColumn(previousItem, column, true);
489                         }
490                         break;
491                 case SWT.ARROW_DOWN:
492                         TreeItem nextItem = getNextItem(row);
493                         if (nextItem != null) {
494                                 setRowColumn(nextItem, column, true);
495                         }
496                         break;
497                 case SWT.ARROW_LEFT:
498                 case SWT.ARROW_RIGHT: {
499                         if ((event.stateMask & SWT.MOD1) != 0) {
500                                 row.setExpanded (event.keyCode == SWT.ARROW_RIGHT);
501                                 break;
502                         }
503                         int columnCount = tree.getColumnCount();
504                         if (columnCount == 0) break;
505                         int columnIndex = column == null ? 0 : tree.indexOf(column);
506                         int[] columnOrder = tree.getColumnOrder();
507                         int index = 0;
508                         while (index < columnOrder.length) {
509                                 if (columnOrder[index] == columnIndex) break;
510                                 index++;
511                         }
512                         if (index == columnOrder.length) index = 0;
513                         int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
514                         TreeItem parentRow = row.getParentItem();
515                         int rowIndex = tree.indexOf(row);
516                         if (event.keyCode == leadKey) {
517                                 if (parentRow != null) {
518                                         setRowColumn(row, tree.getColumn(columnOrder[Math.max(0, index - 1)]), true);
519                                 } else {
520                                         setRowColumn(rowIndex, columnOrder[Math.max(0, index - 1)], true);
521                                 }
522                         } else {
523                                 if (parentRow != null) {
524                                         setRowColumn(row, tree.getColumn(columnOrder[Math.min(columnCount - 1, index + 1)]), true);
525                                 } else {
526                                         setRowColumn(rowIndex, columnOrder[Math.min(columnCount - 1, index + 1)], true);
527                                 }
528                         }
529                         break;
530                 }
531                 case SWT.HOME:
532                         int columnIndex = column == null ? 0 : tree.indexOf(column);
533                         setRowColumn(0, columnIndex, true);
534                         break;
535                 case SWT.END: {
536                         TreeItem[] items = tree.getItems();
537                         setRowColumn(getLastVisibleItem(items), column, true);
538                         break;
539                 }
540                 case SWT.PAGE_UP: {
541                         Rectangle rect = tree.getClientArea();
542                         Rectangle itemRect = tree.getTopItem().getBounds();
543                         TreeItem item = row;
544                         int index = findIndex(tree.getItems(), item);
545                         int itemHeight = tree.getItemHeight();
546                         rect.height -= itemRect.y;
547                         int page = Math.max(1, rect.height / itemHeight);
548                         if (index - page <= 0) {
549                                 TreeItem first = tree.getItem(0);
550                                 setRowColumn(first, column, true);
551                                 break;
552                         }
553                         for (int i = 0; i < page; i++) {
554                                 item = getPreviousItem(item);
555                         }
556                         setRowColumn(item, column, true);
557                         break;
558                 }
559                 case SWT.PAGE_DOWN: {
560                         Rectangle rect = tree.getClientArea();
561                         Rectangle itemRect = tree.getTopItem().getBounds();
562                         TreeItem item = row;
563                         int index = findIndex(tree.getItems(), item);
564                         int height = tree.getItemHeight();
565                         rect.height -= itemRect.y;
566                         TreeItem last = getLastVisibleItem(tree.getItems());
567                         int page = Math.max(1, rect.height / height);
568                         int end = findIndex(tree.getItems(), last);
569                         if (end <= index + page) {
570                                 setRowColumn(last, column, true);
571                                 break;
572                         }
573                         for (int i = 0; i < page; i++) {
574                                 item = getNextItem(item);
575                         }
576                         setRowColumn(item, column, true);
577                         break;
578                 }
579         }
580 }
581
582 void onDispose(Event event) {
583         removeListener(SWT.Dispose, listener);
584         notifyListeners(SWT.Dispose, event);
585         event.type = SWT.None;
586
587         tree.removeListener(SWT.Collapse, treeListener);
588         tree.removeListener(SWT.Expand, treeListener);
589         tree.removeListener(SWT.FocusIn, treeListener);
590         tree.removeListener(SWT.MouseDown, treeListener);
591         unhookRowColumnListeners();
592         ScrollBar hBar = tree.getHorizontalBar();
593         if (hBar != null) {
594                 hBar.removeListener(SWT.Selection, resizeListener);
595         }
596         ScrollBar vBar = tree.getVerticalBar();
597         if (vBar != null) {
598                 vBar.removeListener(SWT.Selection, resizeListener);
599         }
600 }
601
602 void paint(Event event) {
603         if (row == null) return;
604         int columnIndex = column == null ? 0 : tree.indexOf(column);
605         int orderedIndex = columnIndex;
606         int[] columnOrder = tree.getColumnOrder();
607         for (int i = 0; i < columnOrder.length; i++) {
608                 if (columnOrder[i] == columnIndex) {
609                         orderedIndex = i;
610                         break;
611                 }
612         }
613         GC gc = event.gc;
614         gc.setBackground(getBackground());
615         gc.setForeground(getForeground());
616         gc.fillRectangle(event.x, event.y, event.width, event.height);
617         Image image = row.getImage(columnIndex);
618         int x = 0;
619         // Temporary code - need a better way to determine trim
620         String platform = SWT.getPlatform();
621         if (image != null) {
622                 if ("win32".equals(platform)) { //$NON-NLS-1$
623                         if (orderedIndex > 0) {
624                                 x += 2;
625                         }
626                 } else {
627                         x += 2;
628                 }
629         }
630         Point size = getSize();
631         if (image != null) {
632                 Rectangle imageSize = image.getBounds();
633                 int imageY = (size.y - imageSize.height) / 2;
634                 gc.drawImage(image, x, imageY);
635                 x += imageSize.width;
636         }
637         String text = row.getText(columnIndex);
638         if (text.length() > 0) {
639                 Rectangle bounds = row.getBounds(columnIndex);
640                 Point extent = gc.stringExtent(text);
641                 // Temporary code - need a better way to determine trim
642                 if ("win32".equals(platform)) { //$NON-NLS-1$
643                         if (tree.getColumnCount() == 0 || orderedIndex == 0) {
644                                 x += image == null ? 2 : 5;
645                         } else {
646                                 int alignmnent = column.getAlignment();
647                                 switch (alignmnent) {
648                                         case SWT.LEFT:
649                                                 x += image == null ? 5 : 3;
650                                                 break;
651                                         case SWT.RIGHT:
652                                                 x = bounds.width - extent.x - 2;
653                                                 break;
654                                         case SWT.CENTER:
655                                                 x += Math.ceil((bounds.width - x - extent.x) / 2.0);
656                                                 break;
657                                 }
658                         }
659                 } else {
660                         if (tree.getColumnCount() == 0) {
661                                 x += image == null ? 4 : 3;
662                         } else {
663                                 int alignmnent = column.getAlignment();
664                                 switch (alignmnent) {
665                                         case SWT.LEFT:
666                                                 x += image == null ? 5 : 3;
667                                                 break;
668                                         case SWT.RIGHT:
669                                                 x = bounds.width - extent.x - 2;
670                                                 break;
671                                         case SWT.CENTER:
672                                                 x += (bounds.width - x - extent.x) / 2 + 2;
673                                                 break;
674                                 }
675                         }
676                 }
677                 int textY = (size.y - extent.y) / 2;
678                 gc.drawString(text, x, textY);
679         }
680         if (isFocusControl()) {
681                 Display display = getDisplay();
682                 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
683                 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
684                 gc.drawFocus(0, 0, size.x, size.y);
685         }
686 }
687
688 /**
689  * Removes the listener from the collection of listeners who will be notified when the
690  * receiver's selection changes.
691  *
692  * @param listener the listener which should no longer be notified
693  *
694  * @exception IllegalArgumentException <ul>
695  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
696  * </ul>
697  * @exception SWTException <ul>
698  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
699  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
700  * </ul>
701  *
702  * @see SelectionListener
703  * @see #addSelectionListener(SelectionListener)
704  */
705 public void removeSelectionListener(SelectionListener listener) {
706         checkWidget();
707         if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
708         removeListener(SWT.Selection, listener);
709         removeListener(SWT.DefaultSelection, listener);
710 }
711
712 void _resize() {
713         if (row == null) {
714                 setBounds(-200, -200, 0, 0);
715         } else {
716                 int columnIndex = column == null ? 0 : tree.indexOf(column);
717                 setBounds(row.getBounds(columnIndex));
718         }
719 }
720
721 /**
722  * Sets the receiver's background color to the color specified
723  * by the argument, or to the default system color for the control
724  * if the argument is null.
725  * <p>
726  * Note: This operation is a hint and may be overridden by the platform.
727  * For example, on Windows the background of a Button cannot be changed.
728  * </p>
729  * @param color the new color (or null)
730  *
731  * @exception IllegalArgumentException <ul>
732  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
733  * </ul>
734  * @exception SWTException <ul>
735  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
736  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
737  * </ul>
738  */
739 @Override
740 public void setBackground (Color color) {
741         background = color;
742         super.setBackground(getBackground());
743         redraw();
744 }
745 /**
746  * Sets the receiver's foreground color to the color specified
747  * by the argument, or to the default system color for the control
748  * if the argument is null.
749  * <p>
750  * Note: This operation is a hint and may be overridden by the platform.
751  * </p>
752  * @param color the new color (or null)
753  *
754  * @exception IllegalArgumentException <ul>
755  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
756  * </ul>
757  * @exception SWTException <ul>
758  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
759  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
760  * </ul>
761  */
762 @Override
763 public void setForeground (Color color) {
764         foreground = color;
765         super.setForeground(getForeground());
766         redraw();
767 }
768
769 void setRowColumn(int row, int column, boolean notify) {
770         TreeItem item = row == -1 ? null : tree.getItem(row);
771         TreeColumn col = column == -1 || tree.getColumnCount() == 0 ? null : tree.getColumn(column);
772         setRowColumn(item, col, notify);
773 }
774
775 void setRowColumn(TreeItem row, TreeColumn column, boolean notify) {
776         if (this.row != null && this.row != row) {
777                 TreeItem currentItem = this.row;
778                 while (currentItem != null) {
779                         currentItem.removeListener(SWT.Dispose, disposeItemListener);
780                         currentItem = currentItem.getParentItem();
781                 }
782                 this.row = null;
783         }
784         if (this.column != null && this.column != column) {
785                 this.column.removeListener(SWT.Dispose, disposeColumnListener);
786                 this.column.removeListener(SWT.Move, resizeListener);
787                 this.column.removeListener(SWT.Resize, resizeListener);
788                 this.column = null;
789         }
790         if (row != null) {
791                 if (this.row != row) {
792                         this.row = row;
793                         TreeItem currentItem = row;
794                         while (currentItem != null) {
795                                 currentItem.addListener(SWT.Dispose, disposeItemListener);
796                                 currentItem = currentItem.getParentItem();
797                         }
798                         tree.showItem(row);
799                 }
800                 if (this.column != column && column != null) {
801                         this.column = column;
802                         column.addListener(SWT.Dispose, disposeColumnListener);
803                         column.addListener(SWT.Move, resizeListener);
804                         column.addListener(SWT.Resize, resizeListener);
805                         tree.showColumn(column);
806                 }
807                 int columnIndex = column == null ? 0 : tree.indexOf(column);
808                 setBounds(row.getBounds(columnIndex));
809                 redraw();
810                 if (notify) notifyListeners(SWT.Selection, new Event());
811         }
812 }
813
814 /**
815  * Positions the TreeCursor over the root-level cell at the given row and column in the parent tree.
816  *
817  * @param row the index of the root-level row for the cell to select
818  * @param column the index of column for the cell to select
819  *
820  * @exception SWTException <ul>
821  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
822  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
823  * </ul>
824  */
825 public void setSelection(int row, int column) {
826         checkWidget();
827         int columnCount = tree.getColumnCount();
828         int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
829         if (row < 0 || row >= tree.getItemCount() || column < 0 || column > maxColumnIndex) {
830                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
831         }
832         setRowColumn(row, column, false);
833 }
834
835 /**
836  * Positions the TreeCursor over the cell at the given row and column in the parent tree.
837  *
838  * @param row the TreeItem of the row for the cell to select
839  * @param column the index of column for the cell to select
840  *
841  * @exception SWTException <ul>
842  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
843  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
844  * </ul>
845  */
846 public void setSelection(TreeItem row, int column) {
847         checkWidget();
848         int columnCount = tree.getColumnCount();
849         int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1;
850         if (row == null || row.isDisposed() || column < 0 || column > maxColumnIndex) {
851                 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
852         }
853         TreeColumn col = tree.getColumnCount() == 0 ? null : tree.getColumn(column);
854         setRowColumn(row, col, false);
855 }
856
857 @Override
858 public void setVisible(boolean visible) {
859         checkWidget();
860         if (visible) {
861                 _resize();
862         }
863         super.setVisible(visible);
864 }
865
866 void treeCollapse(Event event) {
867         if (row == null) return;
868         TreeItem root = (TreeItem)event.item;
869         TreeItem parentItem = row.getParentItem();
870         while (parentItem != null) {
871                 if (parentItem == root) {
872                         setRowColumn(root, column, true);
873                         return;
874                 }
875                 parentItem = parentItem.getParentItem();
876         }
877
878         getDisplay().asyncExec(() -> {
879                 if (isDisposed()) return;
880                 setRowColumn(row, column, true);
881         });
882 }
883
884 void treeExpand(Event event) {
885         getDisplay().asyncExec(() -> {
886                 if (isDisposed()) return;
887                 setRowColumn(row, column, true);
888         });
889 }
890
891 void treeFocusIn(Event event) {
892         if (isVisible()) {
893                 if (row == null && column == null) return;
894                 setFocus();
895         }
896 }
897
898 void treeMouseDown(Event event) {
899         if (tree.getItemCount() == 0) return;
900         Point pt = new Point(event.x, event.y);
901         TreeItem item = tree.getItem(pt);
902         if (item == null && (tree.getStyle() & SWT.FULL_SELECTION) == 0) {
903                 TreeItem currentItem = tree.getTopItem();
904                 TreeItem parentItem = currentItem.getParentItem();
905                 while (parentItem != null) {
906                         currentItem = parentItem;
907                         parentItem = currentItem.getParentItem();
908                 }
909                 int start = tree.indexOf(currentItem);
910                 int viewportItemCount = tree.getClientArea().height / tree.getItemHeight();
911                 int end = Math.min(start + viewportItemCount, tree.getItemCount() - 1);
912                 TreeItem[] allItems = tree.getItems();
913                 TreeItem[] items = new TreeItem[end - start + 1];
914                 System.arraycopy(allItems, start, items, 0, end - start + 1);
915                 item = findItem(items, pt);
916         }
917         if (item == null) return;
918
919         TreeColumn newColumn = null;
920         int lineWidth = tree.getLinesVisible() ? tree.getGridLineWidth() : 0;
921         int columnCount = tree.getColumnCount();
922         if (columnCount > 0) {
923                 for (int i = 0; i < columnCount; i++) {
924                         Rectangle rect = item.getBounds(i);
925                         rect.width += lineWidth;
926                         rect.height += lineWidth;
927                         if (rect.contains(pt)) {
928                                 newColumn = tree.getColumn(i);
929                                 break;
930                         }
931                 }
932                 if (newColumn == null) {
933                         newColumn = tree.getColumn(0);
934                 }
935         }
936         setRowColumn(item, newColumn, true);
937         setFocus();
938 }
939
940 void unhookRowColumnListeners() {
941         if (column != null && !column.isDisposed()) {
942                 column.removeListener(SWT.Dispose, disposeColumnListener);
943                 column.removeListener(SWT.Move, resizeListener);
944                 column.removeListener(SWT.Resize, resizeListener);
945         }
946         column = null;
947         if (row != null && !row.isDisposed()) {
948                 TreeItem currentItem = row;
949                 while (currentItem != null) {
950                         currentItem.removeListener(SWT.Dispose, disposeItemListener);
951                         currentItem = currentItem.getParentItem();
952                 }
953         }
954         row = null;
955 }
956
957 }