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
.ColumnViewerToolTipSupport
;
27 import org
.eclipse
.jface
.viewers
.DoubleClickEvent
;
28 import org
.eclipse
.jface
.viewers
.ICellEditorListener
;
29 import org
.eclipse
.jface
.viewers
.ICellModifier
;
30 import org
.eclipse
.jface
.viewers
.IDoubleClickListener
;
31 import org
.eclipse
.jface
.viewers
.ITreeContentProvider
;
32 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
33 import org
.eclipse
.jface
.viewers
.TextCellEditor
;
34 import org
.eclipse
.jface
.viewers
.TreePath
;
35 import org
.eclipse
.jface
.viewers
.TreeSelection
;
36 import org
.eclipse
.jface
.viewers
.TreeViewer
;
37 import org
.eclipse
.jface
.viewers
.ViewerComparator
;
38 import org
.eclipse
.swt
.SWT
;
39 import org
.eclipse
.swt
.dnd
.DND
;
40 import org
.eclipse
.swt
.dnd
.DragSource
;
41 import org
.eclipse
.swt
.dnd
.DragSourceAdapter
;
42 import org
.eclipse
.swt
.dnd
.DragSourceEvent
;
43 import org
.eclipse
.swt
.dnd
.DropTarget
;
44 import org
.eclipse
.swt
.dnd
.DropTargetAdapter
;
45 import org
.eclipse
.swt
.dnd
.DropTargetEvent
;
46 import org
.eclipse
.swt
.dnd
.Transfer
;
47 import org
.eclipse
.swt
.widgets
.Composite
;
48 import org
.eclipse
.swt
.widgets
.Menu
;
49 import org
.eclipse
.swt
.widgets
.Tree
;
50 import org
.eclipse
.swt
.widgets
.TreeItem
;
52 import eu
.etaxonomy
.cdm
.model
.name
.NonViralName
;
53 import eu
.etaxonomy
.cdm
.model
.reference
.ReferenceBase
;
54 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
55 import eu
.etaxonomy
.taxeditor
.ITaxEditorConstants
;
56 import eu
.etaxonomy
.taxeditor
.TaxEditorPlugin
;
57 import eu
.etaxonomy
.taxeditor
.actions
.TaxonTransfer
;
58 import eu
.etaxonomy
.taxeditor
.actions
.cdm
.DeleteTaxonAction
;
59 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenNewTaxonEditorAction
;
60 import eu
.etaxonomy
.taxeditor
.actions
.ui
.OpenTaxonEditorAction
;
61 import eu
.etaxonomy
.taxeditor
.controller
.EditorController
;
62 import eu
.etaxonomy
.taxeditor
.controller
.GlobalController
;
63 import eu
.etaxonomy
.taxeditor
.controller
.PreferencesController
;
64 import eu
.etaxonomy
.taxeditor
.editor
.name
.CdmParserController
;
65 import eu
.etaxonomy
.taxeditor
.model
.CdmSessionDataRepository
;
66 import eu
.etaxonomy
.taxeditor
.model
.CdmUtil
;
67 import eu
.etaxonomy
.taxeditor
.model
.ICdmTaxonSetListener
;
68 import eu
.etaxonomy
.taxeditor
.operations
.MoveTaxonOperation
;
71 * Taxon tree viewer which responds to events within individual taxa.
73 * Good overview of TreeViewer:
74 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
80 public class TaxonomicTreeViewer
extends TreeViewer
{
81 private static final Logger logger
= Logger
82 .getLogger(TaxonomicTreeViewer
.class);
86 private IUndoContext undoContext
;
88 private boolean initialized
= false;
91 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
92 * which listens for name changes and new taxa
96 public TaxonomicTreeViewer(Composite parent
) {
98 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
99 super(parent
, SWT
.VIRTUAL
);
101 undoContext
= GlobalController
.getWorkbenchUndoContext();
103 tree
= this.getTree();
104 tree
.setLinesVisible(false);
105 tree
.setHeaderVisible(false);
113 public boolean isInitialized() {
118 * Tool tips are used to show warnings; there is no tooltip mechanism for
119 * individual tree items out-of-the-box, so tipListener below builds one
121 private void createToolTips() {
122 ColumnViewerToolTipSupport
.enableFor(this);
125 void createDragAndDrop() {
126 final Tree tree
= this.getTree();
127 Transfer
[] types
= new Transfer
[] { TaxonTransfer
.getInstance() };
128 int operations
= DND
.DROP_MOVE
| DND
.DROP_COPY
| DND
.DROP_LINK
;
130 final DragSource source
= new DragSource(tree
, operations
);
131 source
.setTransfer(types
);
132 final TreeItem
[] dragSourceItem
= new TreeItem
[1];
133 source
.addDragListener(new DragSourceAdapter() {
134 public void dragStart(DragSourceEvent event
) {
135 TreeItem
[] selection
= tree
.getSelection();
136 if (selection
.length
> 0) {
138 dragSourceItem
[0] = selection
[0];
144 public void dragSetData(DragSourceEvent event
) {
145 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
146 TaxonTransfer
.getInstance().setTaxon(taxon
);
150 DropTarget target
= new DropTarget(tree
, operations
);
151 target
.setTransfer(types
);
152 target
.addDropListener(new DropTargetAdapter() {
153 public void drop(DropTargetEvent event
) {
155 // Only drop ONTO another TreeItem OR outside the tree area.
156 // Dropping between TreeItems makes no sense because tree is
158 Taxon taxon
= (Taxon
) dragSourceItem
[0].getData();
159 Taxon parentTaxon
= null;
161 // If event.item == null, user has dragged outside of the tree
162 if (event
.item
== null) {
165 parentTaxon
= (Taxon
) event
.item
.getData();
167 // Make sure parentTaxon is not a child
168 if (CdmUtil
.isTaxonChildOfTaxon(parentTaxon
, taxon
)) {
170 MessageDialog
.openError(GlobalController
.getShell(), "Can't move taxon.",
171 "'" + CdmUtil
.getDisplayName(taxon
) + "' sits above " +
172 "'" + CdmUtil
.getDisplayName(parentTaxon
) + "' " +
173 "in the taxonomic hierarchy.");
178 // Make sure taxon is not being dropped onto itself
179 if (taxon
.equals(parentTaxon
)) {
184 String text
= "Move taxon"; //$NON-NLS-1$
186 IUndoableOperation operation
= new MoveTaxonOperation
187 (text
, undoContext
, taxon
, parentTaxon
);
189 GlobalController
.executeOperation(operation
);
195 * Taxa with no NameCache - i.e. added with quick name - can be edited in
196 * place in the tax. tree
198 public void createQuickAdd() {
200 final Tree tree
= this.getTree();
201 final TextCellEditor taxTreeNodeEditor
= new TextCellEditor(tree
);
203 // Create cell editor for nodes with the QuickNameTaxon singleton
204 setCellEditors(new CellEditor
[] { taxTreeNodeEditor
});
205 setColumnProperties(new String
[] { "col1" });
206 setCellModifier(new ICellModifier() {
207 public boolean canModify(Object element
, String property
) {
208 if (element
instanceof QuickNameTaxon
) {
215 public Object
getValue(Object element
, String property
) {
216 // If this node is modifiable, name is by definition empty
220 public void modify(Object element
, String property
, Object value
) {
224 // If the node loses focus and there is a non zero-length string, create a new taxon
225 taxTreeNodeEditor
.addListener(new ICellEditorListener() {
226 public void applyEditorValue() {
227 Object value
= taxTreeNodeEditor
.getValue();
229 // Only focus on parent if no taxon is created
230 boolean focusParent
= false;
232 if (value
instanceof String
) {
233 String trimmedValue
= ((String
) value
).trim();
235 if (trimmedValue
.length() > 0) {
237 // TODO move this to a create new name operation
240 PreferencesController
.getInstanceOfPreferredNameClass();
241 CdmParserController
.parseFullReference(name
, trimmedValue
);
243 ReferenceBase sec
= QuickNameTaxon
.getInstance().getSec();
245 Taxon taxon
= Taxon
.NewInstance(name
, sec
);
246 taxon
.setTaxonomicParent(QuickNameTaxon
.getInstance().getTaxonomicParent(), null, null);
248 CdmSessionDataRepository
.getDefault().saveTaxon(taxon
);
250 // User has likely hit carriage return
254 removeQuickName(focusParent
);
257 public void cancelEditor() {
258 removeQuickName(true);
261 private void removeQuickName(boolean focusParent
) {
262 // Set focus to quick name's parent?
264 setSelection(new StructuredSelection(
265 QuickNameTaxon
.getInstance().getTaxonomicParent()), true);
268 // Clear singleton's parent
269 QuickNameTaxon
.getInstance().setParent(null);
271 // Remove quick name from tree
272 remove(QuickNameTaxon
.getInstance());
275 public void editorValueChanged(boolean oldValidState
,
276 boolean newValidState
) {
282 * On double click, open name editor
284 public void createDoubleClickListener() {
285 this.addDoubleClickListener(new IDoubleClickListener() {
287 public void doubleClick(DoubleClickEvent event
) {
288 if (event
.getSelection() instanceof StructuredSelection
) {
289 Object element
= ((StructuredSelection
) event
290 .getSelection()).getFirstElement();
291 if (element
instanceof Taxon
) {
292 EditorController
.open((Taxon
) element
);
300 * Set up content providers and viewer input
302 private void createContent() {
304 // Custom content provider
305 ITreeContentProvider viewerContentProviderList
= new TaxonomicTreeContentProvider();
306 setContentProvider(viewerContentProviderList
);
308 // Label provider that listens for changes to name cache
309 IObservableSet observableTaxonSet
=
310 CdmSessionDataRepository
.getDefault().getObservableTaxa();
311 setLabelProvider(new TaxonomicTreeLabelProvider(
312 observableTaxonSet
));
314 // TaxonTreeList added to every time a node is opened with its
315 // children, or when a new taxon is added
316 setInput(CdmSessionDataRepository
.getDefault().getRootTaxa());
318 // Sort according to "getColumnText" above, i.e. by NameCache
319 setComparator(new ViewerComparator());
321 // Listen for changes to taxa in session data repository
322 CdmSessionDataRepository
.getDefault().
323 addTaxonSetListener(new ICdmTaxonSetListener() {
326 public void taxaAdded(Set
<Taxon
> taxa
) {
327 Taxon lastTaxon
= null;
328 for (Taxon taxon
: taxa
) {
329 addTaxonToTree(taxon
, taxon
.getTaxonomicParent());
333 // Show last taxon added
334 expandToLevel(lastTaxon
, 1);
338 public void taxaRemoved(Set
<Taxon
> taxa
) {
339 for (Taxon taxon
: taxa
) {
345 public void taxonMoved(Taxon taxon
, Taxon newParentTaxon
) {
347 // Save any selection and expansion info ...
348 // NOTE: may be superfluous
349 // Object[] viewerExpandedElements = null;
350 // ISelection viewerSelection = null;
351 // viewerExpandedElements = getExpandedElements();
352 // viewerSelection = getSelection();
355 addTaxonToTree(taxon
, newParentTaxon
);
357 // ... to restore after taxon has been removed then re-added
358 // setExpandedElements(viewerExpandedElements);
359 // setSelection(viewerSelection, true);
364 private void addTaxonToTree(Taxon taxon
, Taxon parentTaxon
) {
365 if (parentTaxon
== null) {
366 add(TreePath
.EMPTY
, taxon
);
368 // New root effectively changes the viewer's input, theerefore refresh
371 add(parentTaxon
, taxon
);
377 * If taxon node in the tree is hidden, open and select it
381 public void revealTaxon(Taxon taxon
) {
385 this.setSelection(new StructuredSelection(taxon
), true);
389 public void createMenu() {
390 final MenuManager menuManager
= new MenuManager();
391 menuManager
.setRemoveAllWhenShown(true);
392 final Menu menu
= menuManager
.createContextMenu(tree
);
395 menuManager
.addMenuListener(new IMenuListener() {
396 public void menuAboutToShow(IMenuManager manager
) {
398 Object selection
= ((TreeSelection
) getSelection())
400 if (!(selection
instanceof Taxon
)) {
403 final Taxon taxon
= (Taxon
) selection
;
405 // Open new taxon editor window
406 Action openEditorAction
= new OpenTaxonEditorAction(taxon
);
407 manager
.add(openEditorAction
);
409 // Open new editor with new child taxon. The taxon will not appear in the tree until saved
410 Action openNewChildEditorAction
= new OpenNewTaxonEditorAction(
412 manager
.add(openNewChildEditorAction
);
414 // Create a new node in the tree to enter taxon name directly in the tree.
415 manager
.add(new QuickNameAction(taxon
));
417 manager
.add(new Separator());
419 // Remove taxon from tree
420 Action deleteTaxonAction
= new DeleteTaxonAction(taxon
);
421 manager
.add(deleteTaxonAction
);
428 * Opens a child node for the parent taxon using the quick name singleton
430 class QuickNameAction
extends Action
{
432 private static final String text
= "Add child taxon with quick name";
433 private final ImageDescriptor image
= TaxEditorPlugin
.getDefault()
434 .getImageDescriptor(ITaxEditorConstants
.QUICK_ADD_ICON
);
436 private Taxon parentTaxon
;
438 QuickNameAction(Taxon parentTaxon
) {
440 setImageDescriptor(image
);
442 this.parentTaxon
= parentTaxon
;
447 QuickNameTaxon quickNameTaxon
= QuickNameTaxon
.getInstance();
448 quickNameTaxon
.setParent(parentTaxon
);
450 add(parentTaxon
, quickNameTaxon
);
451 reveal(quickNameTaxon
);
452 editElement(quickNameTaxon
, 0);