2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
10 package eu
.etaxonomy
.taxeditor
.navigation
;
14 import org
.apache
.log4j
.Logger
;
15 import org
.eclipse
.core
.commands
.operations
.IUndoContext
;
16 import org
.eclipse
.core
.commands
.operations
.IUndoableOperation
;
17 import org
.eclipse
.core
.databinding
.observable
.set
.IObservableSet
;
18 import org
.eclipse
.jface
.action
.Action
;
19 import org
.eclipse
.jface
.action
.IMenuListener
;
20 import org
.eclipse
.jface
.action
.IMenuManager
;
21 import org
.eclipse
.jface
.action
.MenuManager
;
22 import org
.eclipse
.jface
.action
.Separator
;
23 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
24 import org
.eclipse
.jface
.resource
.ImageDescriptor
;
25 import org
.eclipse
.jface
.viewers
.CellEditor
;
26 import org
.eclipse
.jface
.viewers
.DoubleClickEvent
;
27 import org
.eclipse
.jface
.viewers
.ICellEditorListener
;
28 import org
.eclipse
.jface
.viewers
.ICellModifier
;
29 import org
.eclipse
.jface
.viewers
.IDoubleClickListener
;
30 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
31 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
32 import org
.eclipse
.jface
.viewers
.TextCellEditor
;
33 import org
.eclipse
.jface
.viewers
.TreePath
;
34 import org
.eclipse
.jface
.viewers
.TreeSelection
;
35 import org
.eclipse
.jface
.viewers
.TreeViewer
;
36 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
37 import org
.eclipse
.swt
.SWT
;
38 import org
.eclipse
.swt
.dnd
.DND
;
39 import org
.eclipse
.swt
.dnd
.DragSource
;
40 import org
.eclipse
.swt
.dnd
.DragSourceAdapter
;
41 import org
.eclipse
.swt
.dnd
.DragSourceEvent
;
42 import org
.eclipse
.swt
.dnd
.DropTarget
;
43 import org
.eclipse
.swt
.dnd
.DropTargetAdapter
;
44 import org
.eclipse
.swt
.dnd
.DropTargetEvent
;
45 import org
.eclipse
.swt
.dnd
.Transfer
;
46 import org
.eclipse
.swt
.graphics
.Point
;
47 import org
.eclipse
.swt
.graphics
.Rectangle
;
48 import org
.eclipse
.swt
.layout
.FillLayout
;
49 import org
.eclipse
.swt
.widgets
.Composite
;
50 import org
.eclipse
.swt
.widgets
.Display
;
51 import org
.eclipse
.swt
.widgets
.Event
;
52 import org
.eclipse
.swt
.widgets
.Item
;
53 import org
.eclipse
.swt
.widgets
.Label
;
54 import org
.eclipse
.swt
.widgets
.Listener
;
55 import org
.eclipse
.swt
.widgets
.Menu
;
56 import org
.eclipse
.swt
.widgets
.Shell
;
57 import org
.eclipse
.swt
.widgets
.Tree
;
58 import org
.eclipse
.swt
.widgets
.TreeItem
;
60 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
61 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
62 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceBase
;
63 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
64 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
65 import eu
.etaxonomy
.taxeditor
.ITaxEditorConstants
;
66 import eu
.etaxonomy
.taxeditor
.TaxEditorPlugin
;
67 import eu
.etaxonomy
.taxeditor
.actions
.TaxonTransfer
;
68 import eu
.etaxonomy
.taxeditor
.actions
.cdm
.DeleteTaxonAction
;
69 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenNewTaxonEditorAction
;
70 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenTaxonEditorAction
;
71 import eu
.etaxonomy
.taxeditor
.controller
.EditorController
;
72 import eu
.etaxonomy
.taxeditor
.controller
.GlobalController
;
73 import eu
.etaxonomy
.taxeditor
.editor
.name
.CdmParserController
;
74 import eu
.etaxonomy
.taxeditor
.editor
.name
.IterableSynonymyList
;
75 import eu
.etaxonomy
.taxeditor
.model
.CdmSessionDataRepository
;
76 import eu
.etaxonomy
.taxeditor
.model
.CdmUtil
;
77 import eu
.etaxonomy
.taxeditor
.model
.ICdmTaxonSetListener
;
78 import eu
.etaxonomy
.taxeditor
.operations
.MoveTaxonOperation
;
79 import eu
.etaxonomy
.taxeditor
.preference
.PreferencesUtil
;
82 * Taxon tree viewer which responds to events within individual taxa.
84 * Good overview of TreeViewer:
85 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
91 public class TaxonomicTreeViewer
extends TreeViewer
{
92 private static final Logger logger
= Logger
93 .getLogger(TaxonomicTreeViewer
.class);
97 private IUndoContext undoContext
;
99 private boolean initialized
= false;
102 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
103 * which listens for name changes and new taxa
107 public TaxonomicTreeViewer(Composite parent
) {
109 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
110 super(parent
, SWT
.VIRTUAL
);
112 undoContext
= GlobalController
.getWorkbenchUndoContext();
114 tree
= this.getTree();
115 tree
.setLinesVisible(false);
116 tree
.setHeaderVisible(false);
124 public boolean isInitialized() {
129 * Tool tips are used to show warnings; there is no tooltip mechanism for
130 * individual tree items out-of-the-box, so tipListener below builds one
132 private void createToolTips() {
133 Tree tree
= this.getTree();
135 // Disable native tooltip
136 tree
.setToolTipText("");
138 tree
.addListener(SWT
.Dispose
, tipListener
);
139 tree
.addListener(SWT
.KeyDown
, tipListener
);
140 tree
.addListener(SWT
.MouseMove
, tipListener
);
141 tree
.addListener(SWT
.MouseHover
, tipListener
);
144 void createDragAndDrop() {
145 final Tree tree
= this.getTree();
146 Transfer
[] types
= new Transfer
[] { TaxonTransfer
.getInstance() };
147 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
149 final DragSource source
= new DragSource(tree
, operations
);
150 source
.setTransfer(types
);
151 final TreeItem
[] dragSourceItem
= new TreeItem
[1];
152 source
.addDragListener(new DragSourceAdapter() {
153 public void dragStart(DragSourceEvent event
) {
154 TreeItem
[] selection
= tree
.getSelection();
155 if (selection
.length
> 0) {
157 dragSourceItem
[0] = selection
[0];
163 public void dragSetData(DragSourceEvent event
) {
164 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
165 TaxonTransfer
.getInstance().setTaxon(taxon
);
169 DropTarget target
= new DropTarget(tree
, operations
);
170 target
.setTransfer(types
);
171 target
.addDropListener(new DropTargetAdapter() {
172 public void drop(DropTargetEvent event
) {
174 // Only drop ONTO another TreeItem OR outside the tree area.
175 // Dropping between TreeItems makes no sense because tree is
177 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
178 Taxon parentTaxon
= null;
180 // If event.item == null, user has dragged outside of the tree
181 if (event
.item
== null) {
184 parentTaxon
= (Taxon
) event
.item
.getData();
186 // Make sure parentTaxon is not a child
187 if (CdmUtil
.isTaxonChildOfTaxon(parentTaxon
, taxon
)) {
189 MessageDialog
.openError(GlobalController
.getShell(), "Can't move taxon.",
190 "'" + CdmUtil
.getDisplayName(taxon
) + "' sits above " +
191 "'" + CdmUtil
.getDisplayName(parentTaxon
) + "' " +
192 "in the taxonomic hierarchy.");
197 // Make sure taxon is not being dropped onto itself
198 if (taxon
.equals(parentTaxon
)) {
203 String text
= "Move taxon"; //$NON-NLS-1$
205 IUndoableOperation operation
= new MoveTaxonOperation
206 (text
, undoContext
, taxon
, parentTaxon
);
208 GlobalController
.executeOperation(operation
);
214 * Taxa with no NameCache - i.e. added with quick name - can be edited in
215 * place in the tax. tree
217 public void createQuickAdd() {
219 final Tree tree
= this.getTree();
220 final TextCellEditor taxTreeNodeEditor
= new TextCellEditor(tree
);
222 // Create cell editor for nodes with the QuickNameTaxon singleton
223 setCellEditors(new CellEditor
[] { taxTreeNodeEditor
});
224 setColumnProperties(new String
[] { "col1" });
225 setCellModifier(new ICellModifier() {
226 public boolean canModify(Object element
, String property
) {
227 if (element
instanceof QuickNameTaxon
) {
234 public Object
getValue(Object element
, String property
) {
235 // If this node is modifiable, name is by definition empty
239 public void modify(Object element
, String property
, Object value
) {
243 // If the node loses focus and there is a non zero-length string, create a new taxon
244 taxTreeNodeEditor
.addListener(new ICellEditorListener() {
245 public void applyEditorValue() {
246 Object value
= taxTreeNodeEditor
.getValue();
248 // Only focus on parent if no taxon is created
249 boolean focusParent
= false;
251 if (value
instanceof String
) {
252 String trimmedValue
= ((String
) value
).trim();
254 if (trimmedValue
.length() > 0) {
256 // TODO move this to a create new name operation
259 PreferencesUtil
.getInstanceOfPreferredNameClass();
260 CdmParserController
.parseFullReference(name
, trimmedValue
);
262 ReferenceBase sec
= QuickNameTaxon
.getInstance().getSec();
264 Taxon taxon
= Taxon
.NewInstance(name
, sec
);
265 taxon
.setTaxonomicParent(QuickNameTaxon
.getInstance().getTaxonomicParent(), null, null);
267 CdmSessionDataRepository
.getDefault().saveTaxon(taxon
);
269 // User has likely hit carriage return
273 removeQuickName(focusParent
);
276 public void cancelEditor() {
277 removeQuickName(true);
280 private void removeQuickName(boolean focusParent
) {
281 // Set focus to quick name's parent?
283 setSelection(new StructuredSelection(
284 QuickNameTaxon
.getInstance().getTaxonomicParent()), true);
287 // Clear singleton's parent
288 QuickNameTaxon
.getInstance().setParent(null);
290 // Remove quick name from tree
291 remove(QuickNameTaxon
.getInstance());
294 public void editorValueChanged(boolean oldValidState
,
295 boolean newValidState
) {
301 * On double click, open name editor
303 public void createDoubleClickListener() {
304 this.addDoubleClickListener(new IDoubleClickListener() {
306 public void doubleClick(DoubleClickEvent event
) {
307 if (event
.getSelection() instanceof StructuredSelection
) {
308 Object element
= ((StructuredSelection
) event
309 .getSelection()).getFirstElement();
310 if (element
instanceof Taxon
) {
311 EditorController
.open((Taxon
) element
);
319 * Set up content providers and viewer input
321 private void createContent() {
323 // Custom content provider
324 ITreeContentProvider viewerContentProviderList
= new TaxonomicTreeContentProvider();
325 setContentProvider(viewerContentProviderList
);
327 // Label provider that listens for changes to name cache
328 IObservableSet observableTaxonSet
=
329 CdmSessionDataRepository
.getDefault().getObservableTaxa();
330 setLabelProvider(new TaxonomicTreeLabelProvider(
331 observableTaxonSet
));
333 // TaxonTreeList added to every time a node is opened with its
334 // children, or when a new taxon is added
335 setInput(CdmSessionDataRepository
.getDefault().getRootTaxa());
337 // Sort according to "getColumnText" above, i.e. by NameCache
338 setComparator(new ViewerComparator());
340 // Listen for changes to taxa in session data repository
341 CdmSessionDataRepository
.getDefault().
342 addTaxonSetListener(new ICdmTaxonSetListener() {
345 public void taxaAdded(Set
<Taxon
> taxa
) {
346 Taxon lastTaxon
= null;
347 for (Taxon taxon
: taxa
) {
348 addTaxonToTree(taxon
, taxon
.getTaxonomicParent());
352 // Show last taxon added
353 expandToLevel(lastTaxon
, 1);
357 public void taxaRemoved(Set
<Taxon
> taxa
) {
358 for (Taxon taxon
: taxa
) {
364 public void taxonMoved(Taxon taxon
, Taxon newParentTaxon
) {
366 // Save any selection and expansion info ...
367 // NOTE: may be superfluous
368 // Object[] viewerExpandedElements = null;
369 // ISelection viewerSelection = null;
370 // viewerExpandedElements = getExpandedElements();
371 // viewerSelection = getSelection();
374 addTaxonToTree(taxon
, newParentTaxon
);
376 // ... to restore after taxon has been removed then re-added
377 // setExpandedElements(viewerExpandedElements);
378 // setSelection(viewerSelection, true);
383 private void addTaxonToTree(Taxon taxon
, Taxon parentTaxon
) {
384 if (parentTaxon
== null) {
385 add(TreePath
.EMPTY
, taxon
);
387 // New root effectively changes the viewer's input, theerefore refresh
390 add(parentTaxon
, taxon
);
396 * If taxon node in the tree is hidden, open and select it
400 public void revealTaxon(Taxon taxon
) {
404 this.setSelection(new StructuredSelection(taxon
), true);
408 // Implement a "fake" tooltip
409 final Listener labelListener
= new Listener() {
410 public void handleEvent(Event event
) {
411 Label label
= (Label
) event
.widget
;
412 Shell shell
= label
.getShell();
413 switch (event
.type
) {
415 Event e
= new Event();
416 e
.item
= (TreeItem
) label
.getData("_TREEITEM");
417 // Assuming table is single select, set the selection as if
418 // the mouse down event went through to the table
419 // Tree tree = TaxonomicTreeViewer.this.getTree();
420 Tree tree
= getTree();
421 tree
.setSelection(new TreeItem
[] { (TreeItem
) e
.item
});
422 tree
.notifyListeners(SWT
.Selection
, e
);
433 Listener tipListener
= new Listener() {
437 public void handleEvent(Event event
) {
438 switch (event
.type
) {
441 case SWT
.MouseMove
: {
449 case SWT
.MouseHover
: {
451 // TODO make disappear on ESC
453 // Item item = TaxonomicTreeViewer.this.getItemAt(new
454 // Point(event.x, event.y));
455 Item item
= getItemAt(new Point(event
.x
, event
.y
));
457 if (tip
!= null && !tip
.isDisposed())
459 tip
= new Shell(Display
.getCurrent(), SWT
.ON_TOP
460 | SWT
.NO_FOCUS
| SWT
.TOOL
);
461 tip
.setBackground(Display
.getCurrent().getSystemColor(
462 SWT
.COLOR_INFO_BACKGROUND
));
463 FillLayout layout
= new FillLayout();
464 layout
.marginWidth
= 2;
465 tip
.setLayout(layout
);
466 label
= new Label(tip
, SWT
.NONE
);
467 label
.setForeground(Display
.getCurrent().getSystemColor(
468 SWT
.COLOR_INFO_FOREGROUND
));
469 label
.setBackground(Display
.getCurrent().getSystemColor(
470 SWT
.COLOR_INFO_BACKGROUND
));
471 label
.setData("_TABLEITEM", item
);
472 label
.setText(item
.getText());
473 if (item
.getData() instanceof Taxon
) {
474 Taxon taxon
= (Taxon
) item
.getData();
475 IterableSynonymyList synonymyList
= new IterableSynonymyList(
477 String synonymyListDisplay
= CdmUtil
478 .getDisplayName(taxon
);
480 for (TaxonBase synonymOrMisName
: synonymyList
) {
481 TaxonNameBase name
= synonymOrMisName
.getName();
483 synonymyListDisplay
+= "\n "
484 + CdmUtil
.getDisplayName(name
);
487 label
.setText(synonymyListDisplay
);
489 // label.setText ("");
490 label
.addListener(SWT
.MouseExit
, labelListener
);
491 label
.addListener(SWT
.MouseDown
, labelListener
);
492 Point size
= tip
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
);
493 Rectangle rect
= ((TreeItem
) item
).getBounds(0);
494 Point pt
= getTree().toDisplay(rect
.x
, rect
.y
);
496 // Move tip 100px to the right so as not to interfere w clicking
497 tip
.setBounds(pt
.x
+100, pt
.y
, size
.x
, size
.y
);
498 tip
.setVisible(true);
505 public void createMenu() {
506 final MenuManager menuManager
= new MenuManager();
507 menuManager
.setRemoveAllWhenShown(true);
508 final Menu menu
= menuManager
.createContextMenu(tree
);
511 menuManager
.addMenuListener(new IMenuListener() {
512 public void menuAboutToShow(IMenuManager manager
) {
514 Object selection
= ((TreeSelection
) getSelection())
516 if (!(selection
instanceof Taxon
)) {
519 final Taxon taxon
= (Taxon
) selection
;
521 // Open new taxon editor window
522 Action openEditorAction
= new OpenTaxonEditorAction(taxon
);
523 manager
.add(openEditorAction
);
525 // Open new editor with new child taxon. The taxon will not appear in the tree until saved
526 Action openNewChildEditorAction
= new OpenNewTaxonEditorAction(
528 manager
.add(openNewChildEditorAction
);
530 // Create a new node in the tree to enter taxon name directly in the tree.
531 manager
.add(new QuickNameAction(taxon
));
533 manager
.add(new Separator());
535 // Remove taxon from tree
536 Action deleteTaxonAction
= new DeleteTaxonAction(taxon
);
537 manager
.add(deleteTaxonAction
);
543 * Opens a child node for the parent taxon using the quick name singleton
545 class QuickNameAction
extends Action
{
547 private static final String text
= "Add child taxon with quick name";
548 private final ImageDescriptor image
= TaxEditorPlugin
.getDefault()
549 .getImageDescriptor(ITaxEditorConstants
.QUICK_ADD_ICON
);
551 private Taxon parentTaxon
;
553 QuickNameAction(Taxon parentTaxon
) {
555 setImageDescriptor(image
);
557 this.parentTaxon
= parentTaxon
;
562 QuickNameTaxon quickNameTaxon
= QuickNameTaxon
.getInstance();
563 quickNameTaxon
.setParent(parentTaxon
);
565 add(parentTaxon
, quickNameTaxon
);
566 reveal(quickNameTaxon
);
567 editElement(quickNameTaxon
, 0);