1) Separated out transaction stuff to CdmTransactionController, 2) Put data source...
[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.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;
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.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;
74
75 /**
76 * Taxon tree viewer which responds to events within individual taxa.
77 *
78 * Good overview of TreeViewer:
79 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
80 *
81 * @author p.ciardelli
82 * @created 06.05.2008
83 * @version 1.0
84 */
85 public class TaxonomicTreeViewer extends TreeViewer {
86 private static final Logger logger = Logger
87 .getLogger(TaxonomicTreeViewer.class);
88
89 private Tree tree;
90 private ContextMenu contextMenu;
91
92 private boolean quickAddMode;
93
94 private boolean initialized = false;
95
96 /**
97 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache, and
98 * which listens for name changes and new taxa
99 *
100 * @param parent
101 */
102 public TaxonomicTreeViewer(Composite parent) {
103
104 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
105 super(parent, SWT.VIRTUAL);
106
107 tree = this.getTree();
108 tree.setLinesVisible(false);
109 tree.setHeaderVisible(false);
110
111 createContent();
112 createToolTips();
113
114 initialized = true;
115 }
116
117 public boolean isInitialized() {
118 return initialized;
119 }
120
121 /**
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
124 */
125 private void createToolTips() {
126 Tree tree = this.getTree();
127
128 // Disable native tooltip
129 tree.setToolTipText("");
130
131 tree.addListener(SWT.Dispose, tipListener);
132 tree.addListener(SWT.KeyDown, tipListener);
133 tree.addListener(SWT.MouseMove, tipListener);
134 tree.addListener(SWT.MouseHover, tipListener);
135 }
136
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;
141
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) {
149 event.doit = true;
150 dragSourceItem[0] = selection[0];
151 } else {
152 event.doit = false;
153 }
154 }
155
156 public void dragSetData(DragSourceEvent event) {
157 Taxon taxon = (Taxon) dragSourceItem[0].getData();
158 TaxonTransfer.getInstance().setTaxon(taxon);
159 }
160 });
161
162 DropTarget target = new DropTarget(tree, operations);
163 target.setTransfer(types);
164 target.addDropListener(new DropTargetAdapter() {
165 public void drop(DropTargetEvent event) {
166
167 // Only drop ONTO another TreeItem OR outside the tree area.
168 // Dropping between TreeItems makes no sense because tree is
169 // sorted.
170 Taxon taxon = (Taxon) dragSourceItem[0].getData();
171 Taxon parentTaxon = null;
172
173 // If event.item == null, user has dragged outside of the tree
174 if (event.item == null) {
175 parentTaxon = null;
176 } else {
177 parentTaxon = (Taxon) event.item.getData();
178
179 // Make sure parentTaxon is not a child
180 if (CdmUtil.isTaxonChildOfTaxon(parentTaxon, taxon)) {
181
182 MessageDialog.openError(UiUtil.getShell(), "Can't move taxon.",
183 "'" + CdmUtil.getDisplayName(taxon) + "' sits above " +
184 "'" + CdmUtil.getDisplayName(parentTaxon) + "' " +
185 "in the taxonomic hierarchy.");
186
187 return;
188 }
189
190 // Make sure taxon is not being dropped onto itself
191 if (taxon.equals(parentTaxon)) {
192 return;
193 }
194 }
195
196 new MoveTaxonAction(taxon, parentTaxon).run();
197 }
198 });
199 }
200
201 Taxon quickAddTaxon;
202
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;
209 }
210 }
211
212 /**
213 * Taxa with no NameCache - i.e. added with quick name - can be edited in
214 * place in the tax. tree
215 */
216 public void createQuickAdd() {
217
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();
225
226 if (trimmedValue.length() == 0) {
227 removeQuicklyAddedTaxon();
228 } else {
229
230 NonViralName name = (NonViralName) quickAddTaxon.getName();
231 CdmUtil.parseFullReference(name, trimmedValue);
232
233 new SaveTaxonAction(quickAddTaxon).run();
234 quickAddTaxon = null;
235 }
236 }
237 }
238
239 public void cancelEditor() {
240 removeQuicklyAddedTaxon();
241 }
242
243 public void editorValueChanged(boolean oldValidState,
244 boolean newValidState) {
245 }
246 });
247
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) {
252
253 if (element instanceof Taxon) {
254 Taxon taxon = (Taxon) element;
255
256 // If name element has not been initialized,
257 // this is a taxon added with QuickAdd
258 // if (taxon.getName() == null) {
259 // quickAddTaxon = taxon;
260 // return true;
261 // }
262
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)) {
268 return true;
269 }
270
271 }
272 quickAddTaxon = null;
273 return false;
274 }
275
276 public Object getValue(Object element, String property) {
277 // If this node is editable, TitleCache is by definition empty
278 return "";
279 }
280
281 public void modify(Object element, String property, Object value) {
282 }
283 });
284 }
285
286 /**
287 * On double click, open name editor
288 */
289 public void createDoubleClickListener() {
290 this.addDoubleClickListener(new IDoubleClickListener() {
291
292 public void doubleClick(DoubleClickEvent event) {
293 Taxon taxon = null;
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();
299 }
300 }
301 }
302 });
303 }
304
305 /**
306 * Set up content providers and viewer input
307 */
308 private void createContent() {
309
310 // Yet another custom content provider ...
311 IContentProvider viewerContentProviderList = new TaxonomicTreeContentProvider();
312 this.setContentProvider(viewerContentProviderList);
313
314 // Label provider that listens for changes to name cache
315 IObservableSet observableTaxonSet = TaxEditorPlugin.getDefault()
316 .getObservableSessionTaxa();
317 this.setLabelProvider(new TaxonomicTreeLabelProvider(
318 observableTaxonSet));
319
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());
323
324 // Sort according to "getColumnText" above, i.e. by NameCache
325 this.setComparator(new ViewerComparator());
326 }
327
328 /**
329 * If taxon node in the tree is hidden, open and select it
330 *
331 * @param taxon
332 */
333 public void revealTaxon(Taxon taxon) {
334 this.setSelection(new StructuredSelection(taxon), true);
335 this.reveal(taxon);
336 }
337
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) {
344 case SWT.MouseDown:
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);
353 shell.dispose();
354 tree.setFocus();
355 break;
356 case SWT.MouseExit:
357 shell.dispose();
358 break;
359 }
360 }
361 };
362
363 Listener tipListener = new Listener() {
364 Shell tip = null;
365 Label label = null;
366
367 public void handleEvent(Event event) {
368 switch (event.type) {
369 case SWT.Dispose:
370 case SWT.KeyDown:
371 case SWT.MouseMove: {
372 if (tip == null)
373 break;
374 tip.dispose();
375 tip = null;
376 label = null;
377 break;
378 }
379 case SWT.MouseHover: {
380
381 // TODO make disappear on ESC
382
383 // Item item = TaxonomicTreeViewer.this.getItemAt(new
384 // Point(event.x, event.y));
385 Item item = getItemAt(new Point(event.x, event.y));
386 if (item != null) {
387 if (tip != null && !tip.isDisposed())
388 tip.dispose();
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(
406 taxon);
407 String synonymyListDisplay = CdmUtil
408 .getDisplayName(taxon);
409
410 for (TaxonBase synonymOrMisName : synonymyList) {
411 TaxonNameBase name = synonymOrMisName.getName();
412 if (name != null) {
413 synonymyListDisplay += "\n "
414 + CdmUtil.getDisplayName(name);
415 }
416 }
417 label.setText(synonymyListDisplay);
418 }
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);
425
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);
429 }
430 }
431 }
432 }
433 };
434
435 public void createMenu() {
436 final MenuManager menuManager = new MenuManager();
437 menuManager.setRemoveAllWhenShown(true);
438 final Menu menu = menuManager.createContextMenu(tree);
439 tree.setMenu(menu);
440
441 menuManager.addMenuListener(new IMenuListener() {
442 public void menuAboutToShow(IMenuManager manager) {
443
444 Object selection = ((TreeSelection) getSelection())
445 .getFirstElement();
446 if (!(selection instanceof Taxon)) {
447 return;
448 }
449 Taxon taxon = (Taxon) selection;
450
451 Action openEditorAction = new OpenTaxonEditorAction(taxon);
452 manager.add(openEditorAction);
453
454 Action openNewChildEditorAction = new OpenNewChildNameEditorAction(
455 taxon);
456 manager.add(openNewChildEditorAction);
457
458 Action openQuickNameAction = new AddQuickNameAction(taxon);
459 manager.add(openQuickNameAction);
460 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();
467 }
468 }
469 });
470
471 manager.add(new Separator());
472
473 Action deleteTaxonAction = new DeleteTaxonAction(taxon);
474 manager.add(deleteTaxonAction);
475 }
476 });
477 }
478 }