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