(no commit message)
[taxeditor.git] / eclipseprojects / eu.etaxonomy.taxeditor / src / eu / etaxonomy / taxeditor / view / TaxonomicTreeViewer.java
1 package eu.etaxonomy.taxeditor.view;
2
3 import org.eclipse.core.databinding.beans.BeansObservables;
4 import org.eclipse.core.databinding.observable.map.IObservableMap;
5 import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
6 import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
7 import org.eclipse.jface.viewers.CellEditor;
8 import org.eclipse.jface.viewers.DoubleClickEvent;
9 import org.eclipse.jface.viewers.ICellEditorListener;
10 import org.eclipse.jface.viewers.ICellModifier;
11 import org.eclipse.jface.viewers.IDoubleClickListener;
12 import org.eclipse.jface.viewers.StructuredSelection;
13 import org.eclipse.jface.viewers.TextCellEditor;
14 import org.eclipse.jface.viewers.TreeViewer;
15 import org.eclipse.jface.viewers.ViewerComparator;
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.dnd.DND;
18 import org.eclipse.swt.dnd.DragSource;
19 import org.eclipse.swt.dnd.DragSourceEvent;
20 import org.eclipse.swt.dnd.DragSourceListener;
21 import org.eclipse.swt.dnd.DropTarget;
22 import org.eclipse.swt.dnd.DropTargetAdapter;
23 import org.eclipse.swt.dnd.DropTargetEvent;
24 import org.eclipse.swt.dnd.TextTransfer;
25 import org.eclipse.swt.dnd.Transfer;
26 import org.eclipse.swt.events.MenuAdapter;
27 import org.eclipse.swt.events.MenuEvent;
28 import org.eclipse.swt.events.SelectionEvent;
29 import org.eclipse.swt.events.SelectionListener;
30 import org.eclipse.swt.graphics.Image;
31 import org.eclipse.swt.graphics.Point;
32 import org.eclipse.swt.graphics.Rectangle;
33 import org.eclipse.swt.layout.FillLayout;
34 import org.eclipse.swt.layout.GridData;
35 import org.eclipse.swt.widgets.Composite;
36 import org.eclipse.swt.widgets.Display;
37 import org.eclipse.swt.widgets.Event;
38 import org.eclipse.swt.widgets.Item;
39 import org.eclipse.swt.widgets.Label;
40 import org.eclipse.swt.widgets.Listener;
41 import org.eclipse.swt.widgets.Menu;
42 import org.eclipse.swt.widgets.MenuItem;
43 import org.eclipse.swt.widgets.Shell;
44 import org.eclipse.swt.widgets.TableItem;
45 import org.eclipse.swt.widgets.Tree;
46 import org.eclipse.swt.widgets.TreeItem;
47 import org.eclipse.ui.PlatformUI;
48
49 import com.swtdesigner.ResourceManager;
50
51 import eu.etaxonomy.cdm.model.taxon.Taxon;
52 import eu.etaxonomy.taxeditor.Activator;
53 import eu.etaxonomy.taxeditor.controller.ActionAddQuickName;
54 import eu.etaxonomy.taxeditor.controller.ActionMoveTaxon;
55 import eu.etaxonomy.taxeditor.controller.ActionOpenNameEditor;
56 import eu.etaxonomy.taxeditor.controller.ActionOpenNewChildNameEditor;
57 import eu.etaxonomy.taxeditor.controller.ActionRemoveTaxonFromTree;
58 import eu.etaxonomy.taxeditor.controller.ActionSaveTaxon;
59 import eu.etaxonomy.taxeditor.model.NameTreeContentProvider;
60
61 /**
62 * Taxon tree viewer which responds to events within individual taxa.
63 *
64 * Good overview of TreeViewer:
65 * http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
66 *
67 * @author p.ciardelli
68 *
69 */
70 public class TaxonomicTreeViewer extends TreeViewer {
71
72 /**
73 * Creates a lazy-loading taxonomic tree that is sorted by TitleCache,
74 * and which listens for name changes and new taxa
75 *
76 * @param parent
77 */
78 public TaxonomicTreeViewer(Composite parent) {
79
80 // SW.VIRTUAL causes nodes to be loaded on-demand, improving performance
81 super(parent, SWT.VIRTUAL);
82
83 // Tree settings
84 final Tree tree = this.getTree();
85 tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
86 tree.setLinesVisible(false);
87 tree.setHeaderVisible(false);
88
89 createContent();
90 createDoubleClickListener();
91 createRightClickListener();
92 createQuickAdd();
93 createDragAndDrop();
94 createToolTips();
95 }
96
97 /**
98 * Tool tips are used to show warnings; there is no tooltip mechanism for
99 * individual tree items out-of-the-bo, so tipListener belows builds one
100 */
101 private void createToolTips() {
102 Tree tree = this.getTree();
103
104 // Disable native tooltip
105 tree.setToolTipText ("");
106
107 tree.addListener (SWT.Dispose, tipListener);
108 tree.addListener (SWT.KeyDown, tipListener);
109 tree.addListener (SWT.MouseMove, tipListener);
110 tree.addListener (SWT.MouseHover, tipListener);
111 }
112
113 /**
114 * Add drag and drop functionality
115 */
116 private void createDragAndDrop() {
117 final Tree tree = this.getTree();
118 Transfer[] types = new Transfer[] {TextTransfer.getInstance()};
119 int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
120
121 final DragSource source = new DragSource (tree, operations);
122 source.setTransfer(types);
123 final TreeItem[] dragSourceItem = new TreeItem[1];
124 source.addDragListener (new DragSourceListener () {
125 public void dragStart(DragSourceEvent event) {
126 TreeItem[] selection = tree.getSelection();
127 if (selection.length > 0) {
128 event.doit = true;
129 dragSourceItem[0] = selection[0];
130 } else {
131 event.doit = false;
132 }
133 }
134 public void dragSetData (DragSourceEvent event) {
135 event.data = dragSourceItem[0].getText();
136 }
137 public void dragFinished(DragSourceEvent event) {
138 if (event.detail == DND.DROP_MOVE)
139 dragSourceItem[0].dispose();
140 dragSourceItem[0] = null; // should this be put into above conditional clause?
141
142 }
143 });
144
145 DropTarget target = new DropTarget(tree, operations);
146 target.setTransfer(types);
147 target.addDropListener (new DropTargetAdapter() {
148 Display display = PlatformUI.getWorkbench().getDisplay();
149 public void dragOver(DropTargetEvent event) {
150 event.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
151 if (event.item != null) {
152 TreeItem item = (TreeItem)event.item;
153 Point pt = display.map(null, tree, event.x, event.y);
154 Rectangle bounds = item.getBounds();
155 if (pt.y < bounds.y + bounds.height/3) {
156 event.feedback |= DND.FEEDBACK_INSERT_BEFORE;
157 } else if (pt.y > bounds.y + 2*bounds.height/3) {
158 event.feedback |= DND.FEEDBACK_INSERT_AFTER;
159 } else {
160 event.feedback |= DND.FEEDBACK_SELECT;
161 }
162 }
163 }
164 public void drop(DropTargetEvent event) {
165
166 if (event.data == null) {
167 event.detail = DND.DROP_NONE;
168 return;
169 }
170 Taxon taxon = (Taxon) dragSourceItem[0].getData();
171 Taxon parentTaxon = (Taxon) event.item.getData();
172
173 new ActionMoveTaxon(taxon, parentTaxon).run();
174
175 // TODO choose appropriate parentTaxon depending on where in tree,
176 // esp. BETWEEN entries, taxon is dragged
177
178 // if (event.item == null) {
179 // // not sure which case this is ...
180 // TreeItem item = new TreeItem(tree, SWT.NONE);
181 // item.setText("unclear");
182 // } else {
183 // TreeItem item = (TreeItem)event.item;
184 // Point pt = display.map(null, tree, event.x, event.y);
185 // Rectangle bounds = item.getBounds();
186 // TreeItem parent = item.getParentItem();
187 // System.out.println(((Taxon) parent.getData()).getName().getNameCache());
188 // if (parent != null) {
189 // TreeItem[] items = parent.getItems();
190 // int index = 0;
191 // for (int i = 0; i < items.length; i++) {
192 // if (items[i] == item) {
193 // index = i;
194 // break;
195 // }
196 // }
197 // if (pt.y < bounds.y + bounds.height/3) {
198 // TreeItem newItem = new TreeItem(parent, SWT.NONE, index);
199 // newItem.setText(text);
200 // } else if (pt.y > bounds.y + 2*bounds.height/3) {
201 // TreeItem newItem = new TreeItem(parent, SWT.NONE, index+1);
202 // newItem.setText(text);
203 // } else {
204 // TreeItem newItem = new TreeItem(item, SWT.NONE);
205 // newItem.setText(text);
206 // }
207 //
208 // } else {
209 // System.out.println("y");
210 // TreeItem[] items = tree.getItems();
211 // int index = 0;
212 // for (int i = 0; i < items.length; i++) {
213 // if (items[i] == item) {
214 // index = i;
215 // break;
216 // }
217 // }
218 // if (pt.y < bounds.y + bounds.height/3) {
219 // TreeItem newItem = new TreeItem(tree, SWT.NONE, index);
220 // newItem.setText(text);
221 // } else if (pt.y > bounds.y + 2*bounds.height/3) {
222 // TreeItem newItem = new TreeItem(tree, SWT.NONE, index+1);
223 // newItem.setText(text);
224 // } else {
225 // TreeItem newItem = new TreeItem(item, SWT.NONE);
226 // newItem.setText(text);
227 // }
228 // }
229 // }
230 }
231 });
232 }
233
234 /**
235 * Taxa with no NameCache - i.e. added with quick name -
236 * can be edited in place in the tax. tree
237 */
238 private void createQuickAdd() {
239
240 final Tree tree = this.getTree();
241 final TextCellEditor taxTreeNodeEditor = new TextCellEditor(tree);
242 taxTreeNodeEditor.addListener(new ICellEditorListener() {
243 public void applyEditorValue() {}
244 public void cancelEditor() {
245 // user has pressed ESC - transfer focus to parent item,
246 // remove taxon from tree
247 Taxon taxon = (Taxon) tree.getSelection()[0].getData();
248 tree.setSelection(tree.getSelection()[0].getParentItem());
249 new ActionRemoveTaxonFromTree(taxon).run();
250 }
251 public void editorValueChanged(boolean oldValidState,
252 boolean newValidState) {}
253 });
254
255 this.setCellEditors(new CellEditor[] {taxTreeNodeEditor});
256 this.setColumnProperties(new String[] {"col1"});
257 this.setCellModifier(new ICellModifier() {
258 public boolean canModify(Object element, String property) {
259 // If name element has not been initialized,
260 // this is a taxon added with QuickAdd
261 if (((Taxon) element).getName().getTitleCache() == null)
262 return true;
263 return false;
264 }
265 public Object getValue(Object element, String property) {
266 // If this node is editable, TitleCache is by definition empty
267 return "";
268 }
269 public void modify(Object element, String property, Object value) {
270
271 // Get Taxon object from TreeItem
272 Taxon taxon = (Taxon)((Item) element).getData();
273
274 if (((String) value).equals("")) {
275 // Remove temporary Taxon from tree
276 tree.setSelection(tree.getSelection()[0].getParentItem());
277 new ActionRemoveTaxonFromTree(taxon).run();
278
279 } else {
280 // Set Taxon's TitleCache, save it to CDM
281 taxon.getName().setTitleCache((String) value);
282 new ActionSaveTaxon(taxon).run();
283 }
284 }
285 });
286
287 }
288
289 /**
290 * On double click, open name editor
291 */
292 private void createDoubleClickListener() {
293 this.addDoubleClickListener(new IDoubleClickListener(){
294
295 public void doubleClick(DoubleClickEvent event) {
296 Taxon taxon = null;
297 try {
298 taxon = (Taxon) ((StructuredSelection)event.getSelection()).getFirstElement();
299 }catch (Exception e){
300 e.printStackTrace();
301 taxon = null;
302 }
303 new ActionOpenNameEditor(taxon).run();
304 }
305 });
306 }
307
308 /**
309 * Make right-click menu for tree nodes
310 */
311 private void createRightClickListener() {
312
313 final Tree tree = this.getTree();
314 final Menu menu = new Menu(tree);
315 tree.setMenu(menu);
316 menu.addMenuListener(new MenuAdapter() {
317 public void menuShown(MenuEvent e) {
318
319 // Get rid of existing menu items -
320 // same menu is re-used for all nodes
321 MenuItem[] items = menu.getItems();
322 for (int i = 0; i < items.length; i++) {
323 ((MenuItem) items[i]).dispose();
324 }
325
326 // Get Taxon associated with this node
327 final Taxon taxon = (Taxon) tree.getSelection()[0].getData();
328 System.out.println("open " + taxon.toString());
329
330 // Add menu item to edit Taxon
331 MenuItem itemEditTaxon = new MenuItem(menu, SWT.NONE);
332 itemEditTaxon.setText("Edit taxon");
333 itemEditTaxon.addSelectionListener(new SelectionListener() {
334 public void widgetDefaultSelected(SelectionEvent e) {}
335 public void widgetSelected(SelectionEvent e) {
336 new ActionOpenNameEditor(taxon).run();
337 }
338 });
339
340 new MenuItem(menu, SWT.SEPARATOR);
341
342 // Add menu item to add a child node
343 MenuItem itemAddChildTaxon = new MenuItem(menu, SWT.NONE);
344 itemAddChildTaxon.setText("Add child taxon");
345 itemAddChildTaxon.addSelectionListener(new SelectionListener() {
346 public void widgetDefaultSelected(SelectionEvent e) {}
347 public void widgetSelected(SelectionEvent e) {
348 new ActionOpenNewChildNameEditor(taxon).run();
349 }
350 });
351
352 // Add menu item to add a child node with edit name in place
353 MenuItem itemQuickAdd = new MenuItem(menu, SWT.NONE);
354 itemQuickAdd.setText("Add child taxon with quick name");
355 itemQuickAdd.addSelectionListener(new SelectionListener() {
356 public void widgetDefaultSelected(SelectionEvent e) {}
357 public void widgetSelected(SelectionEvent e) {
358 new ActionAddQuickName(taxon).run();
359 }
360 });
361 }
362 });
363 }
364
365 /**
366 * Set up content providers and viewer input
367 */
368 private void createContent() {
369 // Use custom content provider extended to add tree node retrieval
370 ObservableListContentProvider viewerContentProviderList = new NameTreeContentProvider();
371 this.setContentProvider(viewerContentProviderList);
372
373 // Label provider that listens for changes to name cache
374 IObservableMap[] viewerLabelProviderMaps = BeansObservables.observeMaps(viewerContentProviderList.getKnownElements(),
375 Taxon.class, new String[]{"name"});
376 this.setLabelProvider(new ObservableMapLabelProvider(viewerLabelProviderMaps) {
377 /*
378 * TODO only show warning if there is something taxonomically amiss
379 *
380 * @see org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider#getColumnImage(java.lang.Object, int)
381 */
382 public Image getColumnImage(Object element, int columnIndex) {
383 if (((Taxon) element).getName().getHasProblem())
384 return ResourceManager.getPluginImage(Activator.getDefault(), "icons/warn_tsk.gif");
385 return null;
386 }
387 /*
388 * JFace databinding syntax makes it to difficult to retrieve
389 * Taxon.getName().getNameCache, so override function that returns
390 * label
391 *
392 * (non-Javadoc)
393 * @see org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider#getColumnText(java.lang.Object, int)
394 */
395 public String getColumnText(Object element, int columnIndex) {
396 return ((Taxon) element).getName().getTitleCache();
397 }
398 });
399
400 // TaxonTreeList added to every time a node is opened with its
401 // children, or when a new taxon is added
402 this.setInput(Activator.getDefault().getObservableTaxonTreeList());
403
404 // Sort according to "getColumnText" above, i.e. by NameCache
405 this.setComparator(new ViewerComparator());
406 }
407
408 /**
409 * If taxon node in the tree is hidden, open and select it
410 *
411 * @param taxon
412 */
413 public void revealTaxon(Taxon taxon) {
414 this.setSelection(new StructuredSelection(taxon), true);
415 this.reveal(taxon);
416 }
417
418 // Implement a "fake" tooltip
419 final Listener labelListener = new Listener () {
420 public void handleEvent (Event event) {
421 Label label = (Label)event.widget;
422 Shell shell = label.getShell ();
423 switch (event.type) {
424 case SWT.MouseDown:
425 Event e = new Event ();
426 e.item = (TableItem) label.getData ("_TABLEITEM");
427 // Assuming table is single select, set the selection as if
428 // the mouse down event went through to the table
429 Tree tree = TaxonomicTreeViewer.this.getTree();
430 tree.setSelection(new TreeItem [] {(TreeItem) e.item});
431 tree.notifyListeners (SWT.Selection, e);
432 shell.dispose ();
433 tree.setFocus();
434 break;
435 case SWT.MouseExit:
436 shell.dispose ();
437 break;
438 }
439 }
440 };
441
442 Listener tipListener = new Listener () {
443 Shell tip = null;
444 Label label = null;
445 public void handleEvent (Event event) {
446 switch (event.type) {
447 case SWT.Dispose:
448 case SWT.KeyDown:
449 case SWT.MouseMove: {
450 if (tip == null) break;
451 tip.dispose ();
452 tip = null;
453 label = null;
454 break;
455 }
456 case SWT.MouseHover: {
457 Item item = TaxonomicTreeViewer.this.getItemAt(new Point(event.x, event.y));
458 if (item != null) {
459 if (tip != null && !tip.isDisposed ()) tip.dispose ();
460 tip = new Shell (Display.getCurrent(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
461 tip.setBackground (Display.getCurrent().getSystemColor (SWT.COLOR_INFO_BACKGROUND));
462 FillLayout layout = new FillLayout ();
463 layout.marginWidth = 2;
464 tip.setLayout (layout);
465 label = new Label (tip, SWT.NONE);
466 label.setForeground (Display.getCurrent().getSystemColor (SWT.COLOR_INFO_FOREGROUND));
467 label.setBackground (Display.getCurrent().getSystemColor (SWT.COLOR_INFO_BACKGROUND));
468 label.setData ("_TABLEITEM", item);
469 // label.setText (item.getText ());
470 label.setText ("Joe mama");
471 label.addListener (SWT.MouseExit, labelListener);
472 label.addListener (SWT.MouseDown, labelListener);
473 Point size = tip.computeSize (SWT.DEFAULT, SWT.DEFAULT);
474 Rectangle rect = ((TreeItem) item).getBounds (0);
475 Point pt = TaxonomicTreeViewer.this.getTree().toDisplay (rect.x, rect.y);
476 tip.setBounds (pt.x, pt.y, size.x, size.y);
477 tip.setVisible (true);
478 }
479 }
480 }
481 }
482 };
483
484 }
485