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