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