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
;
12 import org
.apache
.log4j
.Logger
;
13 import org
.eclipse
.core
.databinding
.observable
.set
.IObservableSet
;
14 import org
.eclipse
.jface
.action
.Action
;
15 import org
.eclipse
.jface
.action
.IMenuListener
;
16 import org
.eclipse
.jface
.action
.IMenuManager
;
17 import org
.eclipse
.jface
.action
.MenuManager
;
18 import org
.eclipse
.jface
.action
.Separator
;
19 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
20 import org
.eclipse
.jface
.util
.IPropertyChangeListener
;
21 import org
.eclipse
.jface
.util
.PropertyChangeEvent
;
22 import org
.eclipse
.jface
.viewers
.CellEditor
;
23 import org
.eclipse
.jface
.viewers
.DoubleClickEvent
;
24 import org
.eclipse
.jface
.viewers
.ICellEditorListener
;
25 import org
.eclipse
.jface
.viewers
.ICellModifier
;
26 import org
.eclipse
.jface
.viewers
.IContentProvider
;
27 import org
.eclipse
.jface
.viewers
.IDoubleClickListener
;
28 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
29 import org
.eclipse
.jface
.viewers
.TextCellEditor
;
30 import org
.eclipse
.jface
.viewers
.TreeSelection
;
31 import org
.eclipse
.jface
.viewers
.TreeViewer
;
32 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
33 import org
.eclipse
.swt
.SWT
;
34 import org
.eclipse
.swt
.dnd
.DND
;
35 import org
.eclipse
.swt
.dnd
.DragSource
;
36 import org
.eclipse
.swt
.dnd
.DragSourceAdapter
;
37 import org
.eclipse
.swt
.dnd
.DragSourceEvent
;
38 import org
.eclipse
.swt
.dnd
.DropTarget
;
39 import org
.eclipse
.swt
.dnd
.DropTargetAdapter
;
40 import org
.eclipse
.swt
.dnd
.DropTargetEvent
;
41 import org
.eclipse
.swt
.dnd
.Transfer
;
42 import org
.eclipse
.swt
.graphics
.Point
;
43 import org
.eclipse
.swt
.graphics
.Rectangle
;
44 import org
.eclipse
.swt
.layout
.FillLayout
;
45 import org
.eclipse
.swt
.widgets
.Composite
;
46 import org
.eclipse
.swt
.widgets
.Display
;
47 import org
.eclipse
.swt
.widgets
.Event
;
48 import org
.eclipse
.swt
.widgets
.Item
;
49 import org
.eclipse
.swt
.widgets
.Label
;
50 import org
.eclipse
.swt
.widgets
.Listener
;
51 import org
.eclipse
.swt
.widgets
.Menu
;
52 import org
.eclipse
.swt
.widgets
.Shell
;
53 import org
.eclipse
.swt
.widgets
.Tree
;
54 import org
.eclipse
.swt
.widgets
.TreeItem
;
56 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
57 import eu
.etaxonomy
.cdm
.model
.name
.TaxonNameBase
;
58 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
59 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
60 import eu
.etaxonomy
.taxeditor
.ITaxEditorConstants
;
61 import eu
.etaxonomy
.taxeditor
.TaxEditorPlugin
;
62 import eu
.etaxonomy
.taxeditor
.UiUtil
;
63 import eu
.etaxonomy
.taxeditor
.actions
.TaxonTransfer
;
64 import eu
.etaxonomy
.taxeditor
.actions
.cdm
.DeleteTaxonAction
;
65 import eu
.etaxonomy
.taxeditor
.actions
.cdm
.MoveTaxonAction
;
66 import eu
.etaxonomy
.taxeditor
.actions
.cdm
.SaveTaxonAction
;
67 import eu
.etaxonomy
.taxeditor
.actions
.ui
.AddQuickNameAction
;
68 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenNewChildNameEditorAction
;
69 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenTaxonEditorAction
;
70 import eu
.etaxonomy
.taxeditor
.editor
.ContextMenu
;
71 import eu
.etaxonomy
.taxeditor
.editor
.name
.IterableSynonymyList
;
72 import eu
.etaxonomy
.taxeditor
.model
.CdmUtil
;
73 import eu
.etaxonomy
.taxeditor
.model
.TaxonomicTreeContentProvider
;
76 * Taxon tree viewer which responds to events within individual taxa.
78 * Good overview of TreeViewer:
79 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
85 public class TaxonomicTreeViewer
extends TreeViewer
{
86 private static final Logger logger
= Logger
87 .getLogger(TaxonomicTreeViewer
.class);
90 private ContextMenu contextMenu
;
92 private boolean quickAddMode
;
94 private boolean initialized
= false;
97 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
98 * which listens for name changes and new taxa
102 public TaxonomicTreeViewer(Composite parent
) {
104 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
105 super(parent
, SWT
.VIRTUAL
);
107 tree
= this.getTree();
108 tree
.setLinesVisible(false);
109 tree
.setHeaderVisible(false);
117 public boolean isInitialized() {
122 * Tool tips are used to show warnings; there is no tooltip mechanism for
123 * individual tree items out-of-the-box, so tipListener below builds one
125 private void createToolTips() {
126 Tree tree
= this.getTree();
128 // Disable native tooltip
129 tree
.setToolTipText("");
131 tree
.addListener(SWT
.Dispose
, tipListener
);
132 tree
.addListener(SWT
.KeyDown
, tipListener
);
133 tree
.addListener(SWT
.MouseMove
, tipListener
);
134 tree
.addListener(SWT
.MouseHover
, tipListener
);
137 void createDragAndDrop() {
138 final Tree tree
= this.getTree();
139 Transfer
[] types
= new Transfer
[] { TaxonTransfer
.getInstance() };
140 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
142 final DragSource source
= new DragSource(tree
, operations
);
143 source
.setTransfer(types
);
144 final TreeItem
[] dragSourceItem
= new TreeItem
[1];
145 source
.addDragListener(new DragSourceAdapter() {
146 public void dragStart(DragSourceEvent event
) {
147 TreeItem
[] selection
= tree
.getSelection();
148 if (selection
.length
> 0) {
150 dragSourceItem
[0] = selection
[0];
156 public void dragSetData(DragSourceEvent event
) {
157 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
158 TaxonTransfer
.getInstance().setTaxon(taxon
);
162 DropTarget target
= new DropTarget(tree
, operations
);
163 target
.setTransfer(types
);
164 target
.addDropListener(new DropTargetAdapter() {
165 public void drop(DropTargetEvent event
) {
167 // Only drop ONTO another TreeItem OR outside the tree area.
168 // Dropping between TreeItems makes no sense because tree is
170 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
171 Taxon parentTaxon
= null;
173 // If event.item == null, user has dragged outside of the tree
174 if (event
.item
== null) {
177 parentTaxon
= (Taxon
) event
.item
.getData();
179 // Make sure parentTaxon is not a child
180 if (CdmUtil
.isTaxonChildOfTaxon(parentTaxon
, taxon
)) {
182 MessageDialog
.openError(UiUtil
.getShell(), "Can't move taxon.",
183 "'" + CdmUtil
.getDisplayName(taxon
) + "' sits above " +
184 "'" + CdmUtil
.getDisplayName(parentTaxon
) + "' " +
185 "in the taxonomic hierarchy.");
190 // Make sure taxon is not being dropped onto itself
191 if (taxon
.equals(parentTaxon
)) {
196 new MoveTaxonAction(taxon
, parentTaxon
).run();
203 private void removeQuicklyAddedTaxon() {
204 if (quickAddTaxon
!= null) {
205 new DeleteTaxonAction(quickAddTaxon
).run();
206 // if above doesn't work, do remove session taxon but also w remove children
207 // TaxEditorPlugin.getDefault().removeSessionTaxon(quickAddTaxon);
208 quickAddTaxon
= null;
213 * Taxa with no NameCache - i.e. added with quick name - can be edited in
214 * place in the tax. tree
216 public void createQuickAdd() {
218 final Tree tree
= this.getTree();
219 final TextCellEditor taxTreeNodeEditor
= new TextCellEditor(tree
);
220 taxTreeNodeEditor
.addListener(new ICellEditorListener() {
221 public void applyEditorValue() {
222 Object value
= taxTreeNodeEditor
.getValue();
223 if (value
instanceof String
) {
224 String trimmedValue
= ((String
) value
).trim();
226 if (trimmedValue
.length() == 0) {
227 removeQuicklyAddedTaxon();
230 NonViralName name
= (NonViralName
) quickAddTaxon
.getName();
231 CdmUtil
.parseFullReference(name
, trimmedValue
);
233 new SaveTaxonAction(quickAddTaxon
).run();
234 quickAddTaxon
= null;
239 public void cancelEditor() {
240 removeQuicklyAddedTaxon();
243 public void editorValueChanged(boolean oldValidState
,
244 boolean newValidState
) {
248 this.setCellEditors(new CellEditor
[] { taxTreeNodeEditor
});
249 this.setColumnProperties(new String
[] { "col1" });
250 this.setCellModifier(new ICellModifier() {
251 public boolean canModify(Object element
, String property
) {
253 if (element
instanceof Taxon
) {
254 Taxon taxon
= (Taxon
) element
;
256 // If name element has not been initialized,
257 // this is a taxon added with QuickAdd
258 // if (taxon.getName() == null) {
259 // quickAddTaxon = taxon;
263 // BUG Quickly added taxon now retrieved via the quick add action's
264 // firePropertyChange - using NULL name to signal this is the
265 // quick name taxon was causing odd hibernate errors when quickly
266 // added taxon was created and deleted in the same session
267 if (taxon
.equals(quickAddTaxon
)) {
272 quickAddTaxon
= null;
276 public Object
getValue(Object element
, String property
) {
277 // If this node is editable, TitleCache is by definition empty
281 public void modify(Object element
, String property
, Object value
) {
287 * On double click, open name editor
289 public void createDoubleClickListener() {
290 this.addDoubleClickListener(new IDoubleClickListener() {
292 public void doubleClick(DoubleClickEvent event
) {
294 if (event
.getSelection() instanceof StructuredSelection
) {
295 Object element
= ((StructuredSelection
) event
296 .getSelection()).getFirstElement();
297 if (element
instanceof Taxon
) {
298 new OpenTaxonEditorAction((Taxon
) element
).run();
306 * Set up content providers and viewer input
308 private void createContent() {
310 // Yet another custom content provider ...
311 IContentProvider viewerContentProviderList
= new TaxonomicTreeContentProvider();
312 this.setContentProvider(viewerContentProviderList
);
314 // Label provider that listens for changes to name cache
315 IObservableSet observableTaxonSet
= TaxEditorPlugin
.getDefault()
316 .getObservableSessionTaxa();
317 this.setLabelProvider(new TaxonomicTreeLabelProvider(
318 observableTaxonSet
));
320 // TaxonTreeList added to every time a node is opened with its
321 // children, or when a new taxon is added
322 this.setInput(TaxEditorPlugin
.getDefault().getSessionRootTaxa());
324 // Sort according to "getColumnText" above, i.e. by NameCache
325 this.setComparator(new ViewerComparator());
329 * If taxon node in the tree is hidden, open and select it
333 public void revealTaxon(Taxon taxon
) {
334 this.setSelection(new StructuredSelection(taxon
), true);
338 // Implement a "fake" tooltip
339 final Listener labelListener
= new Listener() {
340 public void handleEvent(Event event
) {
341 Label label
= (Label
) event
.widget
;
342 Shell shell
= label
.getShell();
343 switch (event
.type
) {
345 Event e
= new Event();
346 e
.item
= (TreeItem
) label
.getData("_TREEITEM");
347 // Assuming table is single select, set the selection as if
348 // the mouse down event went through to the table
349 // Tree tree = TaxonomicTreeViewer.this.getTree();
350 Tree tree
= getTree();
351 tree
.setSelection(new TreeItem
[] { (TreeItem
) e
.item
});
352 tree
.notifyListeners(SWT
.Selection
, e
);
363 Listener tipListener
= new Listener() {
367 public void handleEvent(Event event
) {
368 switch (event
.type
) {
371 case SWT
.MouseMove
: {
379 case SWT
.MouseHover
: {
381 // TODO make disappear on ESC
383 // Item item = TaxonomicTreeViewer.this.getItemAt(new
384 // Point(event.x, event.y));
385 Item item
= getItemAt(new Point(event
.x
, event
.y
));
387 if (tip
!= null && !tip
.isDisposed())
389 tip
= new Shell(Display
.getCurrent(), SWT
.ON_TOP
390 | SWT
.NO_FOCUS
| SWT
.TOOL
);
391 tip
.setBackground(Display
.getCurrent().getSystemColor(
392 SWT
.COLOR_INFO_BACKGROUND
));
393 FillLayout layout
= new FillLayout();
394 layout
.marginWidth
= 2;
395 tip
.setLayout(layout
);
396 label
= new Label(tip
, SWT
.NONE
);
397 label
.setForeground(Display
.getCurrent().getSystemColor(
398 SWT
.COLOR_INFO_FOREGROUND
));
399 label
.setBackground(Display
.getCurrent().getSystemColor(
400 SWT
.COLOR_INFO_BACKGROUND
));
401 label
.setData("_TABLEITEM", item
);
402 label
.setText(item
.getText());
403 if (item
.getData() instanceof Taxon
) {
404 Taxon taxon
= (Taxon
) item
.getData();
405 IterableSynonymyList synonymyList
= new IterableSynonymyList(
407 String synonymyListDisplay
= CdmUtil
408 .getDisplayName(taxon
);
410 for (TaxonBase synonymOrMisName
: synonymyList
) {
411 TaxonNameBase name
= synonymOrMisName
.getName();
413 synonymyListDisplay
+= "\n "
414 + CdmUtil
.getDisplayName(name
);
417 label
.setText(synonymyListDisplay
);
419 // label.setText ("");
420 label
.addListener(SWT
.MouseExit
, labelListener
);
421 label
.addListener(SWT
.MouseDown
, labelListener
);
422 Point size
= tip
.computeSize(SWT
.DEFAULT
, SWT
.DEFAULT
);
423 Rectangle rect
= ((TreeItem
) item
).getBounds(0);
424 Point pt
= getTree().toDisplay(rect
.x
, rect
.y
);
426 // Move tip 100px to the right so as not to interfere w clicking
427 tip
.setBounds(pt
.x
+100, pt
.y
, size
.x
, size
.y
);
428 tip
.setVisible(true);
435 public void createMenu() {
436 final MenuManager menuManager
= new MenuManager();
437 menuManager
.setRemoveAllWhenShown(true);
438 final Menu menu
= menuManager
.createContextMenu(tree
);
441 menuManager
.addMenuListener(new IMenuListener() {
442 public void menuAboutToShow(IMenuManager manager
) {
444 Object selection
= ((TreeSelection
) getSelection())
446 if (!(selection
instanceof Taxon
)) {
449 Taxon taxon
= (Taxon
) selection
;
451 Action openEditorAction
= new OpenTaxonEditorAction(taxon
);
452 manager
.add(openEditorAction
);
454 Action openNewChildEditorAction
= new OpenNewChildNameEditorAction(
456 manager
.add(openNewChildEditorAction
);
458 Action openQuickNameAction
= new AddQuickNameAction(taxon
);
459 manager
.add(openQuickNameAction
);
461 .addPropertyChangeListener(new IPropertyChangeListener() {
462 public void propertyChange(PropertyChangeEvent event
) {
463 if (event
.getProperty().equals(
464 ITaxEditorConstants
.QUICK_NAME_TAXON
)
465 && event
.getNewValue() instanceof Taxon
) {
466 quickAddTaxon
= (Taxon
) event
.getNewValue();
471 manager
.add(new Separator());
473 Action deleteTaxonAction
= new DeleteTaxonAction(taxon
);
474 manager
.add(deleteTaxonAction
);