1 package eu
.etaxonomy
.taxeditor
.view
;
3 import org
.eclipse
.core
.databinding
.beans
.BeansObservables
;
4 import org
.eclipse
.core
.databinding
.observable
.map
.IObservableMap
;
5 import org
.eclipse
.jface
.databinding
.viewers
.ObservableListContentProvider
;
6 import org
.eclipse
.jface
.databinding
.viewers
.ObservableMapLabelProvider
;
7 import org
.eclipse
.jface
.viewers
.CellEditor
;
8 import org
.eclipse
.jface
.viewers
.DoubleClickEvent
;
9 import org
.eclipse
.jface
.viewers
.ICellEditorListener
;
10 import org
.eclipse
.jface
.viewers
.ICellModifier
;
11 import org
.eclipse
.jface
.viewers
.IDoubleClickListener
;
12 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
13 import org
.eclipse
.jface
.viewers
.TextCellEditor
;
14 import org
.eclipse
.jface
.viewers
.TreeViewer
;
15 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
16 import org
.eclipse
.swt
.SWT
;
17 import org
.eclipse
.swt
.dnd
.DND
;
18 import org
.eclipse
.swt
.dnd
.DragSource
;
19 import org
.eclipse
.swt
.dnd
.DragSourceEvent
;
20 import org
.eclipse
.swt
.dnd
.DragSourceListener
;
21 import org
.eclipse
.swt
.dnd
.DropTarget
;
22 import org
.eclipse
.swt
.dnd
.DropTargetAdapter
;
23 import org
.eclipse
.swt
.dnd
.DropTargetEvent
;
24 import org
.eclipse
.swt
.dnd
.TextTransfer
;
25 import org
.eclipse
.swt
.dnd
.Transfer
;
26 import org
.eclipse
.swt
.events
.MenuAdapter
;
27 import org
.eclipse
.swt
.events
.MenuEvent
;
28 import org
.eclipse
.swt
.events
.SelectionEvent
;
29 import org
.eclipse
.swt
.events
.SelectionListener
;
30 import org
.eclipse
.swt
.graphics
.Image
;
31 import org
.eclipse
.swt
.graphics
.Point
;
32 import org
.eclipse
.swt
.graphics
.Rectangle
;
33 import org
.eclipse
.swt
.layout
.FillLayout
;
34 import org
.eclipse
.swt
.layout
.GridData
;
35 import org
.eclipse
.swt
.widgets
.Composite
;
36 import org
.eclipse
.swt
.widgets
.Display
;
37 import org
.eclipse
.swt
.widgets
.Event
;
38 import org
.eclipse
.swt
.widgets
.Item
;
39 import org
.eclipse
.swt
.widgets
.Label
;
40 import org
.eclipse
.swt
.widgets
.Listener
;
41 import org
.eclipse
.swt
.widgets
.Menu
;
42 import org
.eclipse
.swt
.widgets
.MenuItem
;
43 import org
.eclipse
.swt
.widgets
.Shell
;
44 import org
.eclipse
.swt
.widgets
.TableItem
;
45 import org
.eclipse
.swt
.widgets
.Tree
;
46 import org
.eclipse
.swt
.widgets
.TreeItem
;
47 import org
.eclipse
.ui
.PlatformUI
;
49 import com
.swtdesigner
.ResourceManager
;
51 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
52 import eu
.etaxonomy
.taxeditor
.Activator
;
53 import eu
.etaxonomy
.taxeditor
.controller
.ActionAddQuickName
;
54 import eu
.etaxonomy
.taxeditor
.controller
.ActionMoveTaxon
;
55 import eu
.etaxonomy
.taxeditor
.controller
.ActionOpenNameEditor
;
56 import eu
.etaxonomy
.taxeditor
.controller
.ActionOpenNewChildNameEditor
;
57 import eu
.etaxonomy
.taxeditor
.controller
.ActionRemoveTaxonFromTree
;
58 import eu
.etaxonomy
.taxeditor
.controller
.ActionSaveTaxon
;
59 import eu
.etaxonomy
.taxeditor
.model
.NameTreeContentProvider
;
62 * Taxon tree viewer which responds to events within individual taxa.
64 * Good overview of TreeViewer:
65 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
70 public class TaxonomicTreeViewer
extends TreeViewer
{
73 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache,
74 * and which listens for name changes and new taxa
78 public TaxonomicTreeViewer(Composite parent
) {
80 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
81 super(parent
, SWT
.VIRTUAL
);
84 final Tree tree
= this.getTree();
85 tree
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, false, true));
86 tree
.setLinesVisible(false);
87 tree
.setHeaderVisible(false);
90 createDoubleClickListener();
91 createRightClickListener();
98 * Tool tips are used to show warnings; there is no tooltip mechanism for
99 * individual tree items out-of-the-bo, so tipListener belows builds one
101 private void createToolTips() {
102 Tree tree
= this.getTree();
104 // Disable native tooltip
105 tree
.setToolTipText ("");
107 tree
.addListener (SWT
.Dispose
, tipListener
);
108 tree
.addListener (SWT
.KeyDown
, tipListener
);
109 tree
.addListener (SWT
.MouseMove
, tipListener
);
110 tree
.addListener (SWT
.MouseHover
, tipListener
);
114 * Add drag and drop functionality
116 private void createDragAndDrop() {
117 final Tree tree
= this.getTree();
118 Transfer
[] types
= new Transfer
[] {TextTransfer
.getInstance()};
119 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
121 final DragSource source
= new DragSource (tree
, operations
);
122 source
.setTransfer(types
);
123 final TreeItem
[] dragSourceItem
= new TreeItem
[1];
124 source
.addDragListener (new DragSourceListener () {
125 public void dragStart(DragSourceEvent event
) {
126 TreeItem
[] selection
= tree
.getSelection();
127 if (selection
.length
> 0) {
129 dragSourceItem
[0] = selection
[0];
134 public void dragSetData (DragSourceEvent event
) {
135 event
.data
= dragSourceItem
[0].getText();
137 public void dragFinished(DragSourceEvent event
) {
138 if (event
.detail
== DND
.DROP_MOVE
)
139 dragSourceItem
[0].dispose();
140 dragSourceItem
[0] = null; // should this be put into above conditional clause?
145 DropTarget target
= new DropTarget(tree
, operations
);
146 target
.setTransfer(types
);
147 target
.addDropListener (new DropTargetAdapter() {
148 Display display
= PlatformUI
.getWorkbench().getDisplay();
149 public void dragOver(DropTargetEvent event
) {
150 event
.feedback
= DND
.FEEDBACK_EXPAND
| DND
.FEEDBACK_SCROLL
;
151 if (event
.item
!= null) {
152 TreeItem item
= (TreeItem
)event
.item
;
153 Point pt
= display
.map(null, tree
, event
.x
, event
.y
);
154 Rectangle bounds
= item
.getBounds();
155 if (pt
.y
< bounds
.y
+ bounds
.height
/3) {
156 event
.feedback
|= DND
.FEEDBACK_INSERT_BEFORE
;
157 } else if (pt
.y
> bounds
.y
+ 2*bounds
.height
/3) {
158 event
.feedback
|= DND
.FEEDBACK_INSERT_AFTER
;
160 event
.feedback
|= DND
.FEEDBACK_SELECT
;
164 public void drop(DropTargetEvent event
) {
166 if (event
.data
== null) {
167 event
.detail
= DND
.DROP_NONE
;
170 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
171 Taxon parentTaxon
= (Taxon
) event
.item
.getData();
173 new ActionMoveTaxon(taxon
, parentTaxon
).run();
175 // TODO choose appropriate parentTaxon depending on where in tree,
176 // esp. BETWEEN entries, taxon is dragged
178 // if (event.item == null) {
179 // // not sure which case this is ...
180 // TreeItem item = new TreeItem(tree, SWT.NONE);
181 // item.setText("unclear");
183 // TreeItem item = (TreeItem)event.item;
184 // Point pt = display.map(null, tree, event.x, event.y);
185 // Rectangle bounds = item.getBounds();
186 // TreeItem parent = item.getParentItem();
187 // System.out.println(((Taxon) parent.getData()).getName().getNameCache());
188 // if (parent != null) {
189 // TreeItem[] items = parent.getItems();
191 // for (int i = 0; i < items.length; i++) {
192 // if (items[i] == item) {
197 // if (pt.y < bounds.y + bounds.height/3) {
198 // TreeItem newItem = new TreeItem(parent, SWT.NONE, index);
199 // newItem.setText(text);
200 // } else if (pt.y > bounds.y + 2*bounds.height/3) {
201 // TreeItem newItem = new TreeItem(parent, SWT.NONE, index+1);
202 // newItem.setText(text);
204 // TreeItem newItem = new TreeItem(item, SWT.NONE);
205 // newItem.setText(text);
209 // System.out.println("y");
210 // TreeItem[] items = tree.getItems();
212 // for (int i = 0; i < items.length; i++) {
213 // if (items[i] == item) {
218 // if (pt.y < bounds.y + bounds.height/3) {
219 // TreeItem newItem = new TreeItem(tree, SWT.NONE, index);
220 // newItem.setText(text);
221 // } else if (pt.y > bounds.y + 2*bounds.height/3) {
222 // TreeItem newItem = new TreeItem(tree, SWT.NONE, index+1);
223 // newItem.setText(text);
225 // TreeItem newItem = new TreeItem(item, SWT.NONE);
226 // newItem.setText(text);
235 * Taxa with no NameCache - i.e. added with quick name -
236 * can be edited in place in the tax. tree
238 private void createQuickAdd() {
240 final Tree tree
= this.getTree();
241 final TextCellEditor taxTreeNodeEditor
= new TextCellEditor(tree
);
242 taxTreeNodeEditor
.addListener(new ICellEditorListener() {
243 public void applyEditorValue() {}
244 public void cancelEditor() {
245 // user has pressed ESC - transfer focus to parent item,
246 // remove taxon from tree
247 Taxon taxon
= (Taxon
) tree
.getSelection()[0].getData();
248 tree
.setSelection(tree
.getSelection()[0].getParentItem());
249 new ActionRemoveTaxonFromTree(taxon
).run();
251 public void editorValueChanged(boolean oldValidState
,
252 boolean newValidState
) {}
255 this.setCellEditors(new CellEditor
[] {taxTreeNodeEditor
});
256 this.setColumnProperties(new String
[] {"col1"});
257 this.setCellModifier(new ICellModifier() {
258 public boolean canModify(Object element
, String property
) {
259 // If name element has not been initialized,
260 // this is a taxon added with QuickAdd
261 if (((Taxon
) element
).getName().getTitleCache() == null)
265 public Object
getValue(Object element
, String property
) {
266 // If this node is editable, TitleCache is by definition empty
269 public void modify(Object element
, String property
, Object value
) {
271 // Get Taxon object from TreeItem
272 Taxon taxon
= (Taxon
)((Item
) element
).getData();
274 if (((String
) value
).equals("")) {
275 // Remove temporary Taxon from tree
276 tree
.setSelection(tree
.getSelection()[0].getParentItem());
277 new ActionRemoveTaxonFromTree(taxon
).run();
280 // Set Taxon's TitleCache, save it to CDM
281 taxon
.getName().setTitleCache((String
) value
);
282 new ActionSaveTaxon(taxon
).run();
290 * On double click, open name editor
292 private void createDoubleClickListener() {
293 this.addDoubleClickListener(new IDoubleClickListener(){
295 public void doubleClick(DoubleClickEvent event
) {
298 taxon
= (Taxon
) ((StructuredSelection
)event
.getSelection()).getFirstElement();
299 }catch (Exception e
){
303 new ActionOpenNameEditor(taxon
).run();
309 * Make right-click menu for tree nodes
311 private void createRightClickListener() {
313 final Tree tree
= this.getTree();
314 final Menu menu
= new Menu(tree
);
316 menu
.addMenuListener(new MenuAdapter() {
317 public void menuShown(MenuEvent e
) {
319 // Get rid of existing menu items -
320 // same menu is re-used for all nodes
321 MenuItem
[] items
= menu
.getItems();
322 for (int i
= 0; i
< items
.length
; i
++) {
323 ((MenuItem
) items
[i
]).dispose();
326 // Get Taxon associated with this node
327 final Taxon taxon
= (Taxon
) tree
.getSelection()[0].getData();
328 System
.out
.println("open " + taxon
.toString());
330 // Add menu item to edit Taxon
331 MenuItem itemEditTaxon
= new MenuItem(menu
, SWT
.NONE
);
332 itemEditTaxon
.setText("Edit taxon");
333 itemEditTaxon
.addSelectionListener(new SelectionListener() {
334 public void widgetDefaultSelected(SelectionEvent e
) {}
335 public void widgetSelected(SelectionEvent e
) {
336 new ActionOpenNameEditor(taxon
).run();
340 new MenuItem(menu
, SWT
.SEPARATOR
);
342 // Add menu item to add a child node
343 MenuItem itemAddChildTaxon
= new MenuItem(menu
, SWT
.NONE
);
344 itemAddChildTaxon
.setText("Add child taxon");
345 itemAddChildTaxon
.addSelectionListener(new SelectionListener() {
346 public void widgetDefaultSelected(SelectionEvent e
) {}
347 public void widgetSelected(SelectionEvent e
) {
348 new ActionOpenNewChildNameEditor(taxon
).run();
352 // Add menu item to add a child node with edit name in place
353 MenuItem itemQuickAdd
= new MenuItem(menu
, SWT
.NONE
);
354 itemQuickAdd
.setText("Add child taxon with quick name");
355 itemQuickAdd
.addSelectionListener(new SelectionListener() {
356 public void widgetDefaultSelected(SelectionEvent e
) {}
357 public void widgetSelected(SelectionEvent e
) {
358 new ActionAddQuickName(taxon
).run();
366 * Set up content providers and viewer input
368 private void createContent() {
369 // Use custom content provider extended to add tree node retrieval
370 ObservableListContentProvider viewerContentProviderList
= new NameTreeContentProvider();
371 this.setContentProvider(viewerContentProviderList
);
373 // Label provider that listens for changes to name cache
374 IObservableMap
[] viewerLabelProviderMaps
= BeansObservables
.observeMaps(viewerContentProviderList
.getKnownElements(),
375 Taxon
.class, new String
[]{"name"});
376 this.setLabelProvider(new ObservableMapLabelProvider(viewerLabelProviderMaps
) {
378 * TODO only show warning if there is something taxonomically amiss
380 * @see org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider#getColumnImage(java.lang.Object, int)
382 public Image
getColumnImage(Object element
, int columnIndex
) {
383 if (((Taxon
) element
).getName().getHasProblem())
384 return ResourceManager
.getPluginImage(Activator
.getDefault(), "icons/warn_tsk.gif");
388 * JFace databinding syntax makes it to difficult to retrieve
389 * Taxon.getName().getNameCache, so override function that returns
393 * @see org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider#getColumnText(java.lang.Object, int)
395 public String
getColumnText(Object element
, int columnIndex
) {
396 return ((Taxon
) element
).getName().getTitleCache();
400 // TaxonTreeList added to every time a node is opened with its
401 // children, or when a new taxon is added
402 this.setInput(Activator
.getDefault().getObservableTaxonTreeList());
404 // Sort according to "getColumnText" above, i.e. by NameCache
405 this.setComparator(new ViewerComparator());
409 * If taxon node in the tree is hidden, open and select it
413 public void revealTaxon(Taxon taxon
) {
414 this.setSelection(new StructuredSelection(taxon
), true);
418 // Implement a "fake" tooltip
419 final Listener labelListener
= new Listener () {
420 public void handleEvent (Event event
) {
421 Label label
= (Label
)event
.widget
;
422 Shell shell
= label
.getShell ();
423 switch (event
.type
) {
425 Event e
= new Event ();
426 e
.item
= (TableItem
) label
.getData ("_TABLEITEM");
427 // Assuming table is single select, set the selection as if
428 // the mouse down event went through to the table
429 Tree tree
= TaxonomicTreeViewer
.this.getTree();
430 tree
.setSelection(new TreeItem
[] {(TreeItem
) e
.item
});
431 tree
.notifyListeners (SWT
.Selection
, e
);
442 Listener tipListener
= new Listener () {
445 public void handleEvent (Event event
) {
446 switch (event
.type
) {
449 case SWT
.MouseMove
: {
450 if (tip
== null) break;
456 case SWT
.MouseHover
: {
457 Item item
= TaxonomicTreeViewer
.this.getItemAt(new Point(event
.x
, event
.y
));
459 if (tip
!= null && !tip
.isDisposed ()) tip
.dispose ();
460 tip
= new Shell (Display
.getCurrent(), SWT
.ON_TOP
| SWT
.NO_FOCUS
| SWT
.TOOL
);
461 tip
.setBackground (Display
.getCurrent().getSystemColor (SWT
.COLOR_INFO_BACKGROUND
));
462 FillLayout layout
= new FillLayout ();
463 layout
.marginWidth
= 2;
464 tip
.setLayout (layout
);
465 label
= new Label (tip
, SWT
.NONE
);
466 label
.setForeground (Display
.getCurrent().getSystemColor (SWT
.COLOR_INFO_FOREGROUND
));
467 label
.setBackground (Display
.getCurrent().getSystemColor (SWT
.COLOR_INFO_BACKGROUND
));
468 label
.setData ("_TABLEITEM", item
);
469 // label.setText (item.getText ());
470 label
.setText ("Joe mama");
471 label
.addListener (SWT
.MouseExit
, labelListener
);
472 label
.addListener (SWT
.MouseDown
, labelListener
);
473 Point size
= tip
.computeSize (SWT
.DEFAULT
, SWT
.DEFAULT
);
474 Rectangle rect
= ((TreeItem
) item
).getBounds (0);
475 Point pt
= TaxonomicTreeViewer
.this.getTree().toDisplay (rect
.x
, rect
.y
);
476 tip
.setBounds (pt
.x
, pt
.y
, size
.x
, size
.y
);
477 tip
.setVisible (true);