26ad4a3089e9974694c177085900761575fa3446
[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 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.util.IPropertyChangeListener;
20 import org.eclipse.jface.util.PropertyChangeEvent;
21 import org.eclipse.jface.viewers.CellEditor;
22 import org.eclipse.jface.viewers.DoubleClickEvent;
23 import org.eclipse.jface.viewers.ICellEditorListener;
24 import org.eclipse.jface.viewers.ICellModifier;
25 import org.eclipse.jface.viewers.IContentProvider;
26 import org.eclipse.jface.viewers.IDoubleClickListener;
27 import org.eclipse.jface.viewers.StructuredSelection;
28 import org.eclipse.jface.viewers.TextCellEditor;
29 import org.eclipse.jface.viewers.TreeSelection;
30 import org.eclipse.jface.viewers.TreeViewer;
31 import org.eclipse.jface.viewers.ViewerComparator;
32 import org.eclipse.swt.SWT;
33 import org.eclipse.swt.dnd.DND;
34 import org.eclipse.swt.dnd.DragSource;
35 import org.eclipse.swt.dnd.DragSourceAdapter;
36 import org.eclipse.swt.dnd.DragSourceEvent;
37 import org.eclipse.swt.dnd.DropTarget;
38 import org.eclipse.swt.dnd.DropTargetAdapter;
39 import org.eclipse.swt.dnd.DropTargetEvent;
40 import org.eclipse.swt.dnd.Transfer;
41 import org.eclipse.swt.graphics.Point;
42 import org.eclipse.swt.graphics.Rectangle;
43 import org.eclipse.swt.layout.FillLayout;
44 import org.eclipse.swt.widgets.Composite;
45 import org.eclipse.swt.widgets.Display;
46 import org.eclipse.swt.widgets.Event;
47 import org.eclipse.swt.widgets.Item;
48 import org.eclipse.swt.widgets.Label;
49 import org.eclipse.swt.widgets.Listener;
50 import org.eclipse.swt.widgets.Menu;
51 import org.eclipse.swt.widgets.Shell;
52 import org.eclipse.swt.widgets.Tree;
53 import org.eclipse.swt.widgets.TreeItem;
54 import org.springframework.transaction.TransactionStatus;
55
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.actions.TaxonTransfer;
63 import eu.etaxonomy.taxeditor.actions.cdm.DeleteTaxonAction;
64 import eu.etaxonomy.taxeditor.actions.cdm.MoveTaxonAction;
65 import eu.etaxonomy.taxeditor.actions.cdm.SaveTaxonAction;
66 import eu.etaxonomy.taxeditor.actions.ui.AddQuickNameAction;
67 import eu.etaxonomy.taxeditor.actions.ui.OpenNewChildNameEditorAction;
68 import eu.etaxonomy.taxeditor.actions.ui.OpenTaxonEditorAction;
69 import eu.etaxonomy.taxeditor.editor.ContextMenu;
70 import eu.etaxonomy.taxeditor.editor.name.IterableSynonymyList;
71 import eu.etaxonomy.taxeditor.model.CdmUtil;
72 import eu.etaxonomy.taxeditor.model.TaxonomicTreeContentProvider;
73
74 /**
75 * Taxon tree viewer which responds to events within individual taxa.
76 *
77 * Good overview of TreeViewer:
78 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
79 *
80 * @author p.ciardelli
81 * @created 06.05.2008
82 * @version 1.0
83 */
84 public class TaxonomicTreeViewer extends TreeViewer {
85 private static final Logger logger = Logger
86 .getLogger(TaxonomicTreeViewer.class);
87
88 private Tree tree;
89 private ContextMenu contextMenu;
90
91 private boolean quickAddMode;
92
93 private boolean initialized = false;
94
95 /**
96 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
97 * which listens for name changes and new taxa
98 *
99 * @param parent
100 */
101 public TaxonomicTreeViewer(Composite parent) {
102
103 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
104 super(parent, SWT.VIRTUAL);
105
106 tree = this.getTree();
107 tree.setLinesVisible(false);
108 tree.setHeaderVisible(false);
109
110 createContent();
111 createToolTips();
112
113 initialized = true;
114 }
115
116 public boolean isInitialized() {
117 return initialized;
118 }
119
120 /**
121 * Tool tips are used to show warnings; there is no tooltip mechanism for
122 * individual tree items out-of-the-box, so tipListener below builds one
123 */
124 private void createToolTips() {
125 Tree tree = this.getTree();
126
127 // Disable native tooltip
128 tree.setToolTipText("");
129
130 tree.addListener(SWT.Dispose, tipListener);
131 tree.addListener(SWT.KeyDown, tipListener);
132 tree.addListener(SWT.MouseMove, tipListener);
133 tree.addListener(SWT.MouseHover, tipListener);
134 }
135
136 void createDragAndDrop() {
137 final Tree tree = this.getTree();
138 Transfer[] types = new Transfer[] { TaxonTransfer.getInstance() };
139 int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
140
141 final DragSource source = new DragSource(tree, operations);
142 source.setTransfer(types);
143 final TreeItem[] dragSourceItem = new TreeItem[1];
144 source.addDragListener(new DragSourceAdapter() {
145 public void dragStart(DragSourceEvent event) {
146 TreeItem[] selection = tree.getSelection();
147 if (selection.length > 0) {
148 event.doit = true;
149 dragSourceItem[0] = selection[0];
150 } else {
151 event.doit = false;
152 }
153 }
154
155 public void dragSetData(DragSourceEvent event) {
156 Taxon taxon = (Taxon) dragSourceItem[0].getData();
157 TaxonTransfer.getInstance().setTaxon(taxon);
158 }
159 });
160
161 DropTarget target = new DropTarget(tree, operations);
162 target.setTransfer(types);
163 target.addDropListener(new DropTargetAdapter() {
164 public void drop(DropTargetEvent event) {
165
166 // Only drop ONTO another TreeItem OR outside the tree area.
167 // Dropping between TreeItems makes no sense because tree is
168 // sorted.
169 Taxon taxon = (Taxon) dragSourceItem[0].getData();
170 Taxon parentTaxon = null;
171
172 // If event.item == null, user has dragged outside of the tree
173 if (event.item == null) {
174 parentTaxon = null;
175 } else {
176 parentTaxon = (Taxon) event.item.getData();
177 }
178
179 new MoveTaxonAction(taxon, parentTaxon).run();
180 }
181 });
182 }
183
184 Taxon quickAddTaxon;
185
186 private void removeQuicklyAddedTaxon() {
187 if (quickAddTaxon != null) {
188 new DeleteTaxonAction(quickAddTaxon).run();
189 // if above doesn't work, do remove session taxon but also w remove children
190 // TaxEditorPlugin.getDefault().removeSessionTaxon(quickAddTaxon);
191 quickAddTaxon = null;
192 }
193 }
194
195 /**
196 * Taxa with no NameCache - i.e. added with quick name - can be edited in
197 * place in the tax. tree
198 */
199 public void createQuickAdd() {
200
201 final Tree tree = this.getTree();
202 final TextCellEditor taxTreeNodeEditor = new TextCellEditor(tree);
203 taxTreeNodeEditor.addListener(new ICellEditorListener() {
204 public void applyEditorValue() {
205 Object value = taxTreeNodeEditor.getValue();
206 if (value instanceof String) {
207 String trimmedValue = ((String) value).trim();
208
209 if (trimmedValue.length() == 0) {
210 removeQuicklyAddedTaxon();
211 } else {
212
213 NonViralName name = (NonViralName) quickAddTaxon.getName();
214 // CdmUtil.parseFullName(name, trimmedValue, null, true);
215 CdmUtil.parseFullReference(name, trimmedValue, null, true);
216
217 // TaxonNameBase name = CdmUtil.parseFullReference(
218 // trimmedValue, null, null);
219 // name.addTaxonBase(quickAddTaxon);
220
221 new SaveTaxonAction(quickAddTaxon).run();
222 quickAddTaxon = null;
223 }
224 }
225 }
226
227 public void cancelEditor() {
228 removeQuicklyAddedTaxon();
229 }
230
231 public void editorValueChanged(boolean oldValidState,
232 boolean newValidState) {
233 }
234 });
235
236 this.setCellEditors(new CellEditor[] { taxTreeNodeEditor });
237 this.setColumnProperties(new String[] { "col1" });
238 this.setCellModifier(new ICellModifier() {
239 public boolean canModify(Object element, String property) {
240
241 if (element instanceof Taxon) {
242 Taxon taxon = (Taxon) element;
243
244 // If name element has not been initialized,
245 // this is a taxon added with QuickAdd
246 // if (taxon.getName() == null) {
247 // quickAddTaxon = taxon;
248 // return true;
249 // }
250
251 // BUG Quickly added taxon now retrieved via the quick add action's
252 // firePropertyChange - using NULL name to signal this is the
253 // quick name taxon was causing odd hibernate errors when quickly
254 // added taxon was created and deleted in the same session
255 if (taxon.equals(quickAddTaxon)) {
256 return true;
257 }
258
259 }
260 quickAddTaxon = null;
261 return false;
262 }
263
264 public Object getValue(Object element, String property) {
265 // If this node is editable, TitleCache is by definition empty
266 return "";
267 }
268
269 public void modify(Object element, String property, Object value) {
270 }
271 });
272 }
273
274 /**
275 * On double click, open name editor
276 */
277 public void createDoubleClickListener() {
278 this.addDoubleClickListener(new IDoubleClickListener() {
279
280 public void doubleClick(DoubleClickEvent event) {
281 Taxon taxon = null;
282 if (event.getSelection() instanceof StructuredSelection) {
283 Object element = ((StructuredSelection) event
284 .getSelection()).getFirstElement();
285 if (element instanceof Taxon) {
286 new OpenTaxonEditorAction((Taxon) element).run();
287 }
288 }
289 }
290 });
291 }
292
293 /**
294 * Set up content providers and viewer input
295 */
296 private void createContent() {
297
298 // Yet another custom content provider ...
299 IContentProvider viewerContentProviderList = new TaxonomicTreeContentProvider();
300 this.setContentProvider(viewerContentProviderList);
301
302 // Label provider that listens for changes to name cache
303 IObservableSet observableTaxonSet = TaxEditorPlugin.getDefault()
304 .getObservableSessionTaxa();
305 this.setLabelProvider(new TaxonomicTreeLabelProvider(
306 observableTaxonSet));
307
308 // TaxonTreeList added to every time a node is opened with its
309 // children, or when a new taxon is added
310 this.setInput(TaxEditorPlugin.getDefault().getSessionRootTaxa());
311
312 // Sort according to "getColumnText" above, i.e. by NameCache
313 this.setComparator(new ViewerComparator());
314 }
315
316 /**
317 * If taxon node in the tree is hidden, open and select it
318 *
319 * @param taxon
320 */
321 public void revealTaxon(Taxon taxon) {
322 this.setSelection(new StructuredSelection(taxon), true);
323 this.reveal(taxon);
324 }
325
326 // Implement a "fake" tooltip
327 final Listener labelListener = new Listener() {
328 public void handleEvent(Event event) {
329 Label label = (Label) event.widget;
330 Shell shell = label.getShell();
331 switch (event.type) {
332 case SWT.MouseDown:
333 Event e = new Event();
334 e.item = (TreeItem) label.getData("_TREEITEM");
335 // Assuming table is single select, set the selection as if
336 // the mouse down event went through to the table
337 // Tree tree = TaxonomicTreeViewer.this.getTree();
338 Tree tree = getTree();
339 tree.setSelection(new TreeItem[] { (TreeItem) e.item });
340 tree.notifyListeners(SWT.Selection, e);
341 shell.dispose();
342 tree.setFocus();
343 break;
344 case SWT.MouseExit:
345 shell.dispose();
346 break;
347 }
348 }
349 };
350
351 Listener tipListener = new Listener() {
352 Shell tip = null;
353 Label label = null;
354
355 public void handleEvent(Event event) {
356 switch (event.type) {
357 case SWT.Dispose:
358 case SWT.KeyDown:
359 case SWT.MouseMove: {
360 if (tip == null)
361 break;
362 tip.dispose();
363 tip = null;
364 label = null;
365 break;
366 }
367 case SWT.MouseHover: {
368 // Item item = TaxonomicTreeViewer.this.getItemAt(new
369 // Point(event.x, event.y));
370 Item item = getItemAt(new Point(event.x, event.y));
371 if (item != null) {
372 if (tip != null && !tip.isDisposed())
373 tip.dispose();
374 tip = new Shell(Display.getCurrent(), SWT.ON_TOP
375 | SWT.NO_FOCUS | SWT.TOOL);
376 tip.setBackground(Display.getCurrent().getSystemColor(
377 SWT.COLOR_INFO_BACKGROUND));
378 FillLayout layout = new FillLayout();
379 layout.marginWidth = 2;
380 tip.setLayout(layout);
381 label = new Label(tip, SWT.NONE);
382 label.setForeground(Display.getCurrent().getSystemColor(
383 SWT.COLOR_INFO_FOREGROUND));
384 label.setBackground(Display.getCurrent().getSystemColor(
385 SWT.COLOR_INFO_BACKGROUND));
386 label.setData("_TABLEITEM", item);
387 label.setText(item.getText());
388 if (item.getData() instanceof Taxon) {
389 Taxon taxon = (Taxon) item.getData();
390 IterableSynonymyList synonymyList = new IterableSynonymyList(
391 taxon);
392 String synonymyListDisplay = CdmUtil
393 .getDisplayName(taxon);
394 ;
395 for (TaxonBase synonymOrMisName : synonymyList) {
396 TaxonNameBase name = synonymOrMisName.getName();
397 if (name != null) {
398 synonymyListDisplay += "\n "
399 + CdmUtil.getDisplayName(name);
400 }
401 }
402 label.setText(synonymyListDisplay);
403 }
404 // label.setText ("");
405 label.addListener(SWT.MouseExit, labelListener);
406 label.addListener(SWT.MouseDown, labelListener);
407 Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
408 Rectangle rect = ((TreeItem) item).getBounds(0);
409 // Point pt = TaxonomicTreeViewer.this.getTree().toDisplay
410 // (rect.x, rect.y);
411 Point pt = getTree().toDisplay(rect.x, rect.y);
412 tip.setBounds(pt.x, pt.y, size.x, size.y);
413 tip.setVisible(true);
414 }
415 }
416 }
417 }
418 };
419
420 public void createMenu() {
421 final MenuManager menuManager = new MenuManager();
422 menuManager.setRemoveAllWhenShown(true);
423 final Menu menu = menuManager.createContextMenu(tree);
424 tree.setMenu(menu);
425
426 menuManager.addMenuListener(new IMenuListener() {
427 public void menuAboutToShow(IMenuManager manager) {
428
429 Object selection = ((TreeSelection) getSelection())
430 .getFirstElement();
431 if (!(selection instanceof Taxon)) {
432 return;
433 }
434 Taxon taxon = (Taxon) selection;
435
436 Action openEditorAction = new OpenTaxonEditorAction(taxon);
437 manager.add(openEditorAction);
438
439 Action openNewChildEditorAction = new OpenNewChildNameEditorAction(
440 taxon);
441 manager.add(openNewChildEditorAction);
442
443 Action openQuickNameAction = new AddQuickNameAction(taxon);
444 manager.add(openQuickNameAction);
445 openQuickNameAction
446 .addPropertyChangeListener(new IPropertyChangeListener() {
447 public void propertyChange(PropertyChangeEvent event) {
448 if (event.getProperty().equals(
449 ITaxEditorConstants.QUICK_NAME_TAXON)
450 && event.getNewValue() instanceof Taxon) {
451 quickAddTaxon = (Taxon) event.getNewValue();
452 }
453 }
454 });
455
456 manager.add(new Separator());
457
458 Action deleteTaxonAction = new DeleteTaxonAction(taxon);
459 manager.add(deleteTaxonAction);
460 }
461 });
462 }
463 }