Set focus to search field for specimen editor
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / view / derivate / DerivateView.java
1 package eu.etaxonomy.taxeditor.editor.view.derivate;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.UUID;
12
13 import org.eclipse.core.runtime.IProgressMonitor;
14 import org.eclipse.jface.action.MenuManager;
15 import org.eclipse.jface.util.LocalSelectionTransfer;
16 import org.eclipse.jface.viewers.AbstractTreeViewer;
17 import org.eclipse.jface.viewers.ISelection;
18 import org.eclipse.jface.viewers.IStructuredSelection;
19 import org.eclipse.jface.viewers.StructuredSelection;
20 import org.eclipse.jface.viewers.TreeNode;
21 import org.eclipse.jface.viewers.TreeSelection;
22 import org.eclipse.jface.viewers.TreeViewer;
23 import org.eclipse.swt.SWT;
24 import org.eclipse.swt.dnd.DND;
25 import org.eclipse.swt.dnd.Transfer;
26 import org.eclipse.swt.layout.GridData;
27 import org.eclipse.swt.layout.GridLayout;
28 import org.eclipse.swt.widgets.Composite;
29 import org.eclipse.swt.widgets.Control;
30 import org.eclipse.swt.widgets.Display;
31 import org.eclipse.swt.widgets.Menu;
32 import org.eclipse.swt.widgets.Tree;
33 import org.eclipse.ui.IEditorInput;
34 import org.eclipse.ui.IEditorPart;
35 import org.eclipse.ui.IEditorSite;
36 import org.eclipse.ui.IMemento;
37 import org.eclipse.ui.ISelectionListener;
38 import org.eclipse.ui.ISelectionService;
39 import org.eclipse.ui.IWorkbenchPart;
40 import org.eclipse.ui.PartInitException;
41 import org.eclipse.ui.part.EditorPart;
42
43 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
44 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
45 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
46 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
47 import eu.etaxonomy.cdm.model.common.CdmBase;
48 import eu.etaxonomy.cdm.model.molecular.SingleRead;
49 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
50 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
51 import eu.etaxonomy.cdm.model.taxon.Taxon;
52 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
53 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
54 import eu.etaxonomy.taxeditor.editor.EditorUtil;
55 import eu.etaxonomy.taxeditor.editor.Messages;
56 import eu.etaxonomy.taxeditor.editor.MultiPageTaxonEditor;
57 import eu.etaxonomy.taxeditor.editor.view.derivate.searchFilter.DerivateSearchCompositeController;
58 import eu.etaxonomy.taxeditor.model.IContextListener;
59 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
60 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
61 import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
62 import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
63 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
64 import eu.etaxonomy.taxeditor.model.MessagingUtils;
65 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
66 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
67 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
68 import eu.etaxonomy.taxeditor.store.CdmStore;
69 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateContentProvider;
70 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
71
72 /**
73 * Displays the derivate hierarchy of the specimen specified in the editor input.
74 *
75 */
76 public class DerivateView extends EditorPart implements IPartContentHasFactualData, IConversationEnabled,
77 ICdmEntitySessionEnabled, IDirtyMarkable, IPostOperationEnabled, IPartContentHasDetails, IPartContentHasSupplementalData, IPartContentHasMedia,
78 IContextListener, ISelectionListener {
79 public static final String ID = "eu.etaxonomy.taxeditor.editor.view.derivate.DerivateView"; //$NON-NLS-1$
80
81 public static final String YOU_NEED_TO_SAVE_BEFORE_PERFORMING_THIS_ACTION = Messages.DerivateView_YOU_NEED_TO_SAVE;
82 public static final String VIEW_HAS_UNSAVED_CHANGES = Messages.DerivateView_UNSAVED_CHANGES;
83
84 private static final List<String> SPECIMEN_INIT_STRATEGY = Arrays.asList(new String[] {
85 "descriptions",
86 "annotations",
87 "markers",
88 "credits",
89 "extensions",
90 "rights",
91 "sources",
92 "derivationEvents.derivatives.annotations",
93 "derivationEvents.derivatives.markers",
94 "derivationEvents.derivatives.credits",
95 "derivationEvents.derivatives.extensions",
96 "derivationEvents.derivatives.rights",
97 "derivationEvents.derivatives.sources"
98 });
99
100 private static final int WARN_THRESHOLD = 200;
101
102 private DelaySelection delaySelection = null;
103 /**
104 * This is the monitor for the DelaySelection runnable.
105 * If it is <code>true</code> then it is currently delaying a selection.
106 */
107 private boolean isInDelay;
108
109
110 /**
111 * This class invokes internal_selectionChanged() in a separate thread.
112 * This allows an asynchronous and/or delayed handling of selection changes
113 */
114 private class DelaySelection implements Runnable{
115 private IWorkbenchPart part;
116 private ISelection selection;
117
118 public DelaySelection(IWorkbenchPart part, ISelection selection) {
119 super();
120 this.part = part;
121 this.selection = selection;
122 }
123
124 @Override
125 public void run() {
126 try{
127 selectionChanged_internal(part, selection);
128 }
129 finally{
130 isInDelay = false;
131 }
132 }
133
134 public synchronized void setSelection(ISelection selection) {
135 this.selection = selection;
136 }
137
138 public synchronized void setPart(IWorkbenchPart part) {
139 this.part = part;
140 }
141
142 }
143
144 private ConversationHolder conversation;
145
146 private TreeViewer viewer;
147
148 private boolean isDirty;
149
150 private final int dndOperations = DND.DROP_MOVE;
151
152 private DerivateLabelProvider labelProvider;
153
154 private DerivateContentProvider contentProvider;
155
156 private DerivateSearchCompositeController derivateSearchCompositeController;
157
158 /**
159 * A map with keys being the derivative entities belonging to the {@link UUID}s passed to the constructor
160 * and values being the root elements of the hierarchy (may be the same objects as the derivative entities)
161 */
162 private Map<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>> derivateToRootEntityMap;
163
164 /**
165 * The set of root elements
166 */
167 private Set<SpecimenOrObservationBase<?>> rootElements;
168
169 private ICdmEntitySession cdmEntitySession;
170
171 /**
172 * <code>true</code> if this view is listening to selection changes
173 */
174 private boolean listenToSelectionChange;
175
176 private Taxon selectedTaxon;
177
178 private ISelectionService selectionService;
179
180 /**
181 * Default constructor
182 */
183 public DerivateView() {
184 }
185
186
187 /**
188 * {@inheritDoc}
189 */
190 @Override
191 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
192 this.setSite(site);
193 this.setInput(input);
194 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
195 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
196
197 if (CdmStore.isActive() && conversation == null) {
198 conversation = CdmStore.createConversation();
199 }
200 if (CdmStore.isActive()) {
201 cdmEntitySession = CdmStore.getCurrentSessionManager().newSession(this, true);
202 }
203 //listen to context changes
204 CdmStore.getContextManager().addContextListener(this);
205 }
206
207 @Override
208 public void createPartControl(Composite parent) {
209
210 parent.setLayout(new GridLayout());
211
212 //---search and filter---
213 derivateSearchCompositeController = new DerivateSearchCompositeController(parent, this);
214 GridData gridDataSearchBar = new GridData();
215 gridDataSearchBar.horizontalAlignment = GridData.FILL;
216 gridDataSearchBar.grabExcessHorizontalSpace = true;
217 derivateSearchCompositeController.setLayoutData(gridDataSearchBar);
218 derivateSearchCompositeController.setEnabled(CdmStore.isActive());
219
220 //---tree viewer---
221 viewer = new TreeViewer(new Tree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION));
222 GridData gridDataTree = new GridData();
223 gridDataTree.horizontalAlignment = GridData.FILL;
224 gridDataTree.verticalAlignment = GridData.FILL;
225 gridDataTree.grabExcessVerticalSpace = true;
226 gridDataTree.grabExcessHorizontalSpace = true;
227 viewer.getTree().setLayoutData(gridDataTree);
228 contentProvider = new DerivateContentProvider();
229 viewer.setContentProvider(contentProvider);
230 labelProvider = new DerivateLabelProvider();
231 labelProvider.setConversation(conversation);
232 viewer.setLabelProvider(labelProvider);
233 viewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
234 viewer.getTree().setEnabled(CdmStore.isActive());
235 // Propagate selection from viewer
236 getSite().setSelectionProvider(viewer);
237
238 //listen to selection changes
239 selectionService = getSite().getWorkbenchWindow().getSelectionService();
240 selectionService.addSelectionListener(this);
241
242 //create context menu
243 MenuManager menuManager = new MenuManager();
244 menuManager.setRemoveAllWhenShown(true);
245 getSite().registerContextMenu(menuManager, viewer);
246 Control control = viewer.getControl();
247 Menu menu = menuManager.createContextMenu(control);
248 control.setMenu(menu);
249
250 //init tree
251 Collection<UUID> derivativeUuids = ((DerivateViewEditorInput)getEditorInput()).getDerivativeUuids();
252 checkWarnThreshold(derivativeUuids);
253 updateRootEntities(derivativeUuids);
254 //set taxon filter
255 derivateSearchCompositeController.setTaxonFilter(((DerivateViewEditorInput) getEditorInput()).getTaxonUuid());
256 //reset status bar
257 getEditorSite().getActionBars().getStatusLineManager().setMessage("");
258
259 //add drag'n'drop support
260 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer(),};
261 viewer.addDragSupport(dndOperations, transfers, new DerivateDragListener(this));
262 viewer.addDropSupport(dndOperations, transfers, new DerivateDropListener(this));
263 }
264
265 public void updateRootEntities() {
266 updateRootEntities((Collection)null);
267 }
268
269 public void updateRootEntities(Collection<UUID> derivativeUuids) {
270 if(conversation!=null){
271 if (!conversation.isBound()) {
272 conversation.bind();
273 }
274 /*
275 * If the active session is not the session of the Derivative Editor
276 * then we will save the active session for later, bind temporarily
277 * to our session and rebind to the original session when we are
278 * done. This happens e.g. if a selection change happens in the
279 * taxon editor and "Link with editor" is enabled. The selection
280 * change event and thus the loading in updateRootEntities() happens
281 * in the session of the taxon editor.
282 */
283 ICdmEntitySession previousCdmEntitySession = CdmStore.getCurrentSessionManager().getActiveSession();
284 if(cdmEntitySession != null) {
285 cdmEntitySession.bind();
286 }
287
288 List<SpecimenOrObservationBase> derivates = null;
289 if(derivativeUuids!=null){
290 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
291 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
292 derivates = CdmStore.getService(IOccurrenceService.class).load(new ArrayList(derivativeUuids), SPECIMEN_INIT_STRATEGY);
293 }
294 updateRootEntities(derivates);
295 previousCdmEntitySession.bind();
296 }
297 }
298
299
300 public void updateRootEntities(List<SpecimenOrObservationBase> derivates) {
301 if(derivates!=null){
302 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
303 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
304 for (SpecimenOrObservationBase derivate : derivates) {
305
306 if(derivate instanceof FieldUnit){
307 derivateToRootEntityMap.put(derivate, derivate);
308 }
309 else {
310 SpecimenOrObservationBase<?> topMostDerivate = EditorUtil.getTopMostDerivate(derivate);
311 if(topMostDerivate!=null){
312 derivateToRootEntityMap.put(derivate, topMostDerivate);
313 }
314 else{
315 derivateToRootEntityMap.put(derivate, derivate);
316 }
317 }
318 }
319 for (SpecimenOrObservationBase<?> specimen : derivateToRootEntityMap.values()) {
320 rootElements.add(specimen);
321 }
322 }
323 labelProvider.updateLabelCache(rootElements);
324 viewer.setInput(rootElements);
325
326 getEditorSite().getActionBars().getStatusLineManager().setMessage(rootElements.size() +" derivative hierarchies found");
327
328 //set selection to derivatives if the filter criteria
329 //taxon assignment or derivative type are set
330 if(derivates!=null && !derivateSearchCompositeController.isDefaultSearch()){
331 List<TreeNode> nodesToSelect = new ArrayList<TreeNode>();
332 for (SpecimenOrObservationBase specimenOrObservationBase : derivates) {
333 nodesToSelect.add(new TreeNode(specimenOrObservationBase));
334 }
335 viewer.setSelection(new StructuredSelection(nodesToSelect));
336 }
337 else{
338 viewer.setSelection(null);
339 }
340
341 }
342
343 public void updateLabelCache(){
344 labelProvider.updateLabelCache(rootElements);
345 }
346
347 @Override
348 public void doSave(IProgressMonitor monitor) {
349 String taskName = Messages.DerivateView_SAVING_HIERARCHY;
350 monitor.beginTask(taskName, 3);
351 if (!conversation.isBound()) {
352 conversation.bind();
353 }
354 monitor.worked(1);
355
356 // commit the conversation and start a new transaction immediately
357 conversation.commit(true);
358
359 if(CdmStore.getCurrentSessionManager().isRemoting()) {
360 CdmStore.getService(IOccurrenceService.class).merge(new ArrayList<SpecimenOrObservationBase>(rootElements), true);
361 }
362 monitor.worked(1);
363
364 this.setDirty(false);
365 monitor.worked(1);
366 monitor.done();
367 firePropertyChange(PROP_DIRTY);
368 refreshTree();
369 }
370
371 @Override
372 public void doSaveAs() {
373 }
374
375 @Override
376 public String getTitleToolTip() {
377 return Messages.DerivateView_DERIVATIVE_EDITOR;
378 }
379
380 @Override
381 public boolean isDirty() {
382 return isDirty;
383 }
384
385 /**
386 * @param isDirty the isDirty to set
387 */
388 public void setDirty(boolean isDirty) {
389 this.isDirty = isDirty;
390 }
391
392 @Override
393 public boolean isSaveAsAllowed() {
394 return false;
395 }
396
397 @Override
398 public void setFocus() {
399 //make sure to bind again if maybe in another view the conversation was unbound
400 if(conversation!=null && !conversation.isBound()){
401 conversation.bind();
402 }
403 if(cdmEntitySession != null) {
404 cdmEntitySession.bind();
405 }
406 derivateSearchCompositeController.setFocusOnSearchField();
407 }
408
409 @Override
410 public void update(CdmDataChangeMap changeEvents) {
411 }
412
413 @Override
414 public ConversationHolder getConversationHolder() {
415 return conversation;
416 }
417
418 @Override
419 public void changed(Object element) {
420 setDirty(true);
421 firePropertyChange(IEditorPart.PROP_DIRTY);
422 viewer.update(new TreeNode(element), null);
423 }
424
425 @Override
426 public void forceDirty() {
427 changed(null);
428 }
429
430 @Override
431 public Map<Object, List<String>> getPropertyPathsMap() {
432 List<String> specimenPropertyPaths = Arrays.asList(new String[] {
433 "descriptions",
434 "derivationEvents.derivates",
435 "annotations",
436 "markers",
437 "credits",
438 "extensions",
439 "rights",
440 "sources"
441 });
442 Map<Object, List<String>> specimenPropertyPathMap =
443 new HashMap<Object, List<String>>();
444 specimenPropertyPathMap.put(SpecimenOrObservationBase.class,specimenPropertyPaths);
445 return specimenPropertyPathMap;
446 }
447
448 /**
449 * Refreshes the derivate hierarchy tree and expands the tree
450 * to show and select the given object.
451 *
452 * @param expandTo the object to which the tree should be expanded
453 */
454 public void refreshTree(Object expandTo){
455 refreshTree();
456 TreeSelection selection = (TreeSelection) viewer.getSelection();
457 viewer.expandToLevel(selection.getFirstElement(), 1);
458 viewer.setSelection(new StructuredSelection(new TreeNode(expandTo)));
459 }
460
461 /**
462 * Refreshes the derivate hierarchy tree
463 */
464 public void refreshTree(){
465 if(!viewer.getTree().isDisposed()){
466 viewer.refresh();
467 }
468 }
469
470 //FIXME:Remoting hack to make this work for remoting
471 //This should actually be resolved using remoting post operations
472 public void remove(Object obj) {
473 rootElements.remove(obj);
474 viewer.setInput(rootElements);
475 }
476
477 /**
478 * @return a set of {@link SingleRead}s that have multiple parents
479 */
480 public Set<SingleRead> getMultiLinkSingleReads() {
481 return DerivateLabelProvider.getMultiLinkSingleReads();
482 }
483
484 public Object getSelectionInput() {
485 return selectedTaxon;
486 }
487
488 public DerivateLabelProvider getLabelProvider() {
489 return labelProvider;
490 }
491
492 @Override
493 public boolean postOperation(CdmBase objectAffectedByOperation) {
494 refreshTree();
495 if(objectAffectedByOperation!=null){
496 changed(objectAffectedByOperation);
497 }
498 return true;
499 }
500
501 @Override
502 public boolean onComplete() {
503 return true;
504 }
505
506
507 @Override
508 public boolean canAttachMedia() {
509 return true;
510 }
511
512 public void addFieldUnit(FieldUnit fieldUnit) {
513 rootElements.add(fieldUnit);
514 derivateToRootEntityMap.put(fieldUnit, fieldUnit);
515 }
516
517 @Override
518 public ICdmEntitySession getCdmEntitySession() {
519 return cdmEntitySession;
520 }
521
522 @Override
523 public void dispose() {
524 super.dispose();
525 if(conversation!=null){
526 conversation.close();
527 }
528 if(cdmEntitySession != null) {
529 cdmEntitySession.dispose();
530 }
531 }
532 public void selectionChanged_internal(IWorkbenchPart part, ISelection selection) {
533 if(part == this){
534 return;
535 }
536 if(viewer.getTree().isDisposed()){
537 return;
538 }
539 if(listenToSelectionChange){
540 if(part instanceof MultiPageTaxonEditor){
541 selectedTaxon = ((MultiPageTaxonEditor) part).getTaxon();
542 }
543 else if(selection instanceof IStructuredSelection){
544 Object selectedElement = ((IStructuredSelection) selection).getFirstElement();
545 if(selectedElement instanceof CdmBase){
546 if(((CdmBase) selectedElement).isInstanceOf(TaxonNode.class)){
547 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, TaxonNode.class).getTaxon();
548 }
549 else if(((CdmBase) selectedElement).isInstanceOf(Taxon.class)){
550 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, Taxon.class);
551 }
552 }
553 }
554 if(selectedTaxon!=null){
555 Collection<SpecimenOrObservationBase> fieldUnits = CdmStore.getService(IOccurrenceService.class).listFieldUnitsByAssociatedTaxon(selectedTaxon, null, null);
556 Collection<UUID> uuids = new HashSet<UUID>();
557 for (SpecimenOrObservationBase specimenOrObservationBase : fieldUnits) {
558 uuids.add(specimenOrObservationBase.getUuid());
559 }
560 checkWarnThreshold(uuids);
561 updateRootEntities(uuids);
562 setPartName("Derivative Editor: " + selectedTaxon.getName());
563 }
564 }
565 }
566
567 @Override
568 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
569 if(delaySelection==null){
570 delaySelection = new DelaySelection(part, selection);
571 }
572 delaySelection.setPart(part);
573 delaySelection.setSelection(selection);
574 if(!isInDelay){
575 isInDelay = true;
576 Display.getCurrent().asyncExec(delaySelection);
577 }
578 }
579
580
581 private void checkWarnThreshold(Collection<UUID> uuids) {
582 if(uuids!=null && uuids.size()>WARN_THRESHOLD){
583 MessagingUtils.warningDialog("Performance warning", this.getClass(), String.format("Specimens will not be loaded!\n"
584 + "There are %d specimens associated with the current "
585 + "selection. If you really want to show all of them in the specimen editor please "
586 + "use the taxon filter in the search bar.", uuids.size()));
587 uuids.clear();
588 }
589 }
590
591 public TreeViewer getViewer() {
592 return viewer;
593 }
594
595 /**
596 * {@inheritDoc}
597 */
598 @Override
599 public List<SpecimenOrObservationBase<?>> getRootEntities() {
600 return new ArrayList<SpecimenOrObservationBase<?>>(rootElements);
601 }
602
603 public void toggleListenToSelectionChange() {
604 listenToSelectionChange = !listenToSelectionChange;
605 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
606 if(!listenToSelectionChange){
607 selectedTaxon = null;
608 setPartName("Derivative Editor");
609 }
610 else if(selectedTaxon==null){
611 setPartName("Derivative Editor [no taxon selected]");
612 }
613 }
614
615 public boolean isListenToSelectionChange(){
616 return listenToSelectionChange;
617 }
618
619 /**
620 * {@inheritDoc}
621 */
622 @Override
623 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
624 }
625
626 /**
627 * {@inheritDoc}
628 */
629 @Override
630 public void contextStop(IMemento memento, IProgressMonitor monitor) {
631 derivateSearchCompositeController.setEnabled(false);
632 if(!viewer.getTree().isDisposed()) {
633 viewer.getTree().setEnabled(false);
634 viewer.setInput(null);
635 }
636 }
637
638 /**
639 * {@inheritDoc}
640 */
641 @Override
642 public void contextStart(IMemento memento, IProgressMonitor monitor) {
643 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
644 if(!viewer.getTree().isDisposed()){
645 viewer.getTree().setEnabled(true);
646 }
647 refreshTree();
648 }
649
650 /**
651 * {@inheritDoc}
652 */
653 @Override
654 public void contextRefresh(IProgressMonitor monitor) {
655 }
656
657 /**
658 * {@inheritDoc}
659 */
660 @Override
661 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
662 }
663
664 }