p2izing the editor
[taxeditor.git] / eclipseprojects / eu.etaxonomy.taxeditor / src / eu / etaxonomy / taxeditor / navigation / TaxonomicTreeViewer.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9
10 package eu.etaxonomy.taxeditor.navigation;
11
12 import java.util.Set;
13
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;
51
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;
69
70 /**
71 * Taxon tree viewer which responds to events within individual taxa.
72 *
73 * Good overview of TreeViewer:
74 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
75 *
76 * @author p.ciardelli
77 * @created 06.05.2008
78 * @version 1.0
79 */
80 public class TaxonomicTreeViewer extends TreeViewer{
81 private static final Logger logger = Logger
82 .getLogger(TaxonomicTreeViewer.class);
83
84 private Tree tree;
85
86 private IUndoContext undoContext;
87
88 private boolean initialized = false;
89
90 /**
91 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
92 * which listens for name changes and new taxa
93 *
94 * @param parent
95 */
96 public TaxonomicTreeViewer(Composite parent) {
97
98 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
99 super(parent, SWT.VIRTUAL);
100
101 undoContext = GlobalController.getWorkbenchUndoContext();
102
103 tree = this.getTree();
104 tree.setLinesVisible(false);
105 tree.setHeaderVisible(false);
106
107 createContent();
108 createToolTips();
109
110 initialized = true;
111 }
112
113 public boolean isInitialized() {
114 return initialized;
115 }
116
117 /**
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
120 */
121 private void createToolTips() {
122 ColumnViewerToolTipSupport.enableFor(this);
123 }
124
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;
129
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) {
137 event.doit = true;
138 dragSourceItem[0] = selection[0];
139 } else {
140 event.doit = false;
141 }
142 }
143
144 public void dragSetData(DragSourceEvent event) {
145 Taxon taxon = (Taxon) dragSourceItem[0].getData();
146 TaxonTransfer.getInstance().setTaxon(taxon);
147 }
148 });
149
150 DropTarget target = new DropTarget(tree, operations);
151 target.setTransfer(types);
152 target.addDropListener(new DropTargetAdapter() {
153 public void drop(DropTargetEvent event) {
154
155 // Only drop ONTO another TreeItem OR outside the tree area.
156 // Dropping between TreeItems makes no sense because tree is
157 // sorted.
158 Taxon taxon = (Taxon) dragSourceItem[0].getData();
159 Taxon parentTaxon = null;
160
161 // If event.item == null, user has dragged outside of the tree
162 if (event.item == null) {
163 parentTaxon = null;
164 } else {
165 parentTaxon = (Taxon) event.item.getData();
166
167 // Make sure parentTaxon is not a child
168 if (CdmUtil.isTaxonChildOfTaxon(parentTaxon, taxon)) {
169
170 MessageDialog.openError(GlobalController.getShell(), "Can't move taxon.",
171 "'" + CdmUtil.getDisplayName(taxon) + "' sits above " +
172 "'" + CdmUtil.getDisplayName(parentTaxon) + "' " +
173 "in the taxonomic hierarchy.");
174
175 return;
176 }
177
178 // Make sure taxon is not being dropped onto itself
179 if (taxon.equals(parentTaxon)) {
180 return;
181 }
182 }
183
184 String text = "Move taxon"; //$NON-NLS-1$
185
186 IUndoableOperation operation = new MoveTaxonOperation
187 (text, undoContext, taxon, parentTaxon);
188
189 GlobalController.executeOperation(operation);
190 }
191 });
192 }
193
194 /**
195 * Taxa with no NameCache - i.e. added with quick name - can be edited in
196 * place in the tax. tree
197 */
198 public void createQuickAdd() {
199
200 final Tree tree = this.getTree();
201 final TextCellEditor taxTreeNodeEditor = new TextCellEditor(tree);
202
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) {
209 return true;
210 } else {
211 return false;
212 }
213 }
214
215 public Object getValue(Object element, String property) {
216 // If this node is modifiable, name is by definition empty
217 return "";
218 }
219
220 public void modify(Object element, String property, Object value) {
221 }
222 });
223
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();
228
229 // Only focus on parent if no taxon is created
230 boolean focusParent = false;
231
232 if (value instanceof String) {
233 String trimmedValue = ((String) value).trim();
234
235 if (trimmedValue.length() > 0) {
236
237 // TODO move this to a create new name operation
238
239 NonViralName name =
240 PreferencesController.getInstanceOfPreferredNameClass();
241 CdmParserController.parseFullReference(name, trimmedValue);
242
243 ReferenceBase sec = QuickNameTaxon.getInstance().getSec();
244
245 Taxon taxon = Taxon.NewInstance(name, sec);
246 taxon.setTaxonomicParent(QuickNameTaxon.getInstance().getTaxonomicParent(), null, null);
247
248 CdmSessionDataRepository.getDefault().saveTaxon(taxon);
249 } else {
250 // User has likely hit carriage return
251 focusParent = true;
252 }
253 }
254 removeQuickName(focusParent);
255 }
256
257 public void cancelEditor() {
258 removeQuickName(true);
259 }
260
261 private void removeQuickName(boolean focusParent) {
262 // Set focus to quick name's parent?
263 if (focusParent) {
264 setSelection(new StructuredSelection(
265 QuickNameTaxon.getInstance().getTaxonomicParent()), true);
266 }
267
268 // Clear singleton's parent
269 QuickNameTaxon.getInstance().setParent(null);
270
271 // Remove quick name from tree
272 remove(QuickNameTaxon.getInstance());
273 }
274
275 public void editorValueChanged(boolean oldValidState,
276 boolean newValidState) {
277 }
278 });
279 }
280
281 /**
282 * On double click, open name editor
283 */
284 public void createDoubleClickListener() {
285 this.addDoubleClickListener(new IDoubleClickListener() {
286
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);
293 }
294 }
295 }
296 });
297 }
298
299 /**
300 * Set up content providers and viewer input
301 */
302 private void createContent() {
303
304 // Custom content provider
305 ITreeContentProvider viewerContentProviderList = new TaxonomicTreeContentProvider();
306 setContentProvider(viewerContentProviderList);
307
308 // Label provider that listens for changes to name cache
309 IObservableSet observableTaxonSet =
310 CdmSessionDataRepository.getDefault().getObservableTaxa();
311 setLabelProvider(new TaxonomicTreeLabelProvider(
312 observableTaxonSet));
313
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());
317
318 // Sort according to "getColumnText" above, i.e. by NameCache
319 setComparator(new ViewerComparator());
320
321 // Listen for changes to taxa in session data repository
322 CdmSessionDataRepository.getDefault().
323 addTaxonSetListener(new ICdmTaxonSetListener() {
324
325
326 public void taxaAdded(Set<Taxon> taxa) {
327 Taxon lastTaxon = null;
328 for (Taxon taxon : taxa) {
329 addTaxonToTree(taxon, taxon.getTaxonomicParent());
330 lastTaxon = taxon;
331 }
332
333 // Show last taxon added
334 expandToLevel(lastTaxon, 1);
335 }
336
337
338 public void taxaRemoved(Set<Taxon> taxa) {
339 for (Taxon taxon : taxa) {
340 remove(taxon);
341 }
342 }
343
344
345 public void taxonMoved(Taxon taxon, Taxon newParentTaxon) {
346
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();
353
354 remove(taxon);
355 addTaxonToTree(taxon, newParentTaxon);
356
357 // ... to restore after taxon has been removed then re-added
358 // setExpandedElements(viewerExpandedElements);
359 // setSelection(viewerSelection, true);
360 }
361 });
362 }
363
364 private void addTaxonToTree(Taxon taxon, Taxon parentTaxon) {
365 if (parentTaxon == null) {
366 add(TreePath.EMPTY, taxon);
367
368 // New root effectively changes the viewer's input, theerefore refresh
369 refresh();
370 } else {
371 add(parentTaxon, taxon);
372 }
373
374 }
375
376 /**
377 * If taxon node in the tree is hidden, open and select it
378 *
379 * @param taxon
380 */
381 public void revealTaxon(Taxon taxon) {
382 if (taxon == null) {
383 return;
384 }
385 this.setSelection(new StructuredSelection(taxon), true);
386 this.reveal(taxon);
387 }
388
389 public void createMenu() {
390 final MenuManager menuManager = new MenuManager();
391 menuManager.setRemoveAllWhenShown(true);
392 final Menu menu = menuManager.createContextMenu(tree);
393 tree.setMenu(menu);
394
395 menuManager.addMenuListener(new IMenuListener() {
396 public void menuAboutToShow(IMenuManager manager) {
397
398 Object selection = ((TreeSelection) getSelection())
399 .getFirstElement();
400 if (!(selection instanceof Taxon)) {
401 return;
402 }
403 final Taxon taxon = (Taxon) selection;
404
405 // Open new taxon editor window
406 Action openEditorAction = new OpenTaxonEditorAction(taxon);
407 manager.add(openEditorAction);
408
409 // Open new editor with new child taxon. The taxon will not appear in the tree until saved
410 Action openNewChildEditorAction = new OpenNewTaxonEditorAction(
411 taxon);
412 manager.add(openNewChildEditorAction);
413
414 // Create a new node in the tree to enter taxon name directly in the tree.
415 manager.add(new QuickNameAction(taxon));
416
417 manager.add(new Separator());
418
419 // Remove taxon from tree
420 Action deleteTaxonAction = new DeleteTaxonAction(taxon);
421 manager.add(deleteTaxonAction);
422 }
423 });
424 }
425
426
427 /**
428 * Opens a child node for the parent taxon using the quick name singleton
429 */
430 class QuickNameAction extends Action {
431
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);
435
436 private Taxon parentTaxon;
437
438 QuickNameAction(Taxon parentTaxon) {
439 super(text);
440 setImageDescriptor(image);
441
442 this.parentTaxon = parentTaxon;
443 }
444
445 public void run() {
446
447 QuickNameTaxon quickNameTaxon = QuickNameTaxon.getInstance();
448 quickNameTaxon.setParent(parentTaxon);
449
450 add(parentTaxon, quickNameTaxon);
451 reveal(quickNameTaxon);
452 editElement(quickNameTaxon, 0);
453 }
454 }
455 }