22206310d7c5788f9217295f2f085f60697b45b3
[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.IEditorPart;
47 import org.eclipse.ui.IMemento;
48
49 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
50 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
51 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
52 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
53 import eu.etaxonomy.cdm.model.common.CdmBase;
54 import eu.etaxonomy.cdm.model.molecular.SingleRead;
55 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
56 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
57 import eu.etaxonomy.cdm.model.taxon.Taxon;
58 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
59 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
60 import eu.etaxonomy.taxeditor.editor.EditorUtil;
61 import eu.etaxonomy.taxeditor.editor.MultiPageTaxonEditor;
62 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
63 import eu.etaxonomy.taxeditor.editor.view.derivate.searchFilter.DerivateSearchCompositeController;
64 import eu.etaxonomy.taxeditor.model.IContextListener;
65 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
66 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
67 import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
68 import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
69 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
70 import eu.etaxonomy.taxeditor.model.MessagingUtils;
71 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
72 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
73 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
74 import eu.etaxonomy.taxeditor.store.CdmStore;
75 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateContentProvider;
76 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
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 {
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 selectionChangedListener = new ISelectionChangedListener() {
219 @Override
220 public void selectionChanged(SelectionChangedEvent event) {
221 IStructuredSelection isel = (IStructuredSelection) event.getSelection();
222 selService.setSelection((isel.size() == 1 ? isel.getFirstElement() : isel.toArray()));
223 }
224 };
225 viewer.addSelectionChangedListener(selectionChangedListener);
226
227 //create context menu
228 menuService.registerContextMenu(viewer.getControl(), "eu.etaxonomy.taxeditor.editor.popupmenu.specimeneditor");
229
230 //add drag'n'drop support
231 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer(),};
232 viewer.addDragSupport(dndOperations, transfers, new DerivateDragListener(this));
233 viewer.addDropSupport(dndOperations, transfers, new DerivateDropListener(this));
234 }
235
236 public void updateRootEntities() {
237 updateRootEntities((Collection)null);
238 }
239
240 public void updateRootEntities(Collection<UUID> derivativeUuids) {
241 if(conversation!=null){
242 if (!conversation.isBound()) {
243 conversation.bind();
244 }
245 /*
246 * If the active session is not the session of the Derivative Editor
247 * then we will save the active session for later, bind temporarily
248 * to our session and rebind to the original session when we are
249 * done. This happens e.g. if a selection change happens in the
250 * taxon editor and "Link with editor" is enabled. The selection
251 * change event and thus the loading in updateRootEntities() happens
252 * in the session of the taxon editor.
253 */
254 ICdmEntitySession previousCdmEntitySession = CdmStore.getCurrentSessionManager().getActiveSession();
255 if(cdmEntitySession != null) {
256 cdmEntitySession.bind();
257 }
258
259 List<SpecimenOrObservationBase> derivates = null;
260 if(derivativeUuids!=null){
261 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
262 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
263 derivates = CdmStore.getService(IOccurrenceService.class).load(new ArrayList(derivativeUuids), SPECIMEN_INIT_STRATEGY);
264 }
265 updateRootEntities(derivates);
266 previousCdmEntitySession.bind();
267 }
268 }
269
270
271 public void updateRootEntities(List<SpecimenOrObservationBase> derivates) {
272 if(derivates!=null){
273 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
274 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
275 for (SpecimenOrObservationBase derivate : derivates) {
276
277 if(derivate instanceof FieldUnit){
278 derivateToRootEntityMap.put(derivate, derivate);
279 }
280 else {
281 SpecimenOrObservationBase<?> topMostDerivate = EditorUtil.getTopMostDerivate(derivate);
282 if(topMostDerivate!=null){
283 derivateToRootEntityMap.put(derivate, topMostDerivate);
284 }
285 else{
286 derivateToRootEntityMap.put(derivate, derivate);
287 }
288 }
289 }
290 for (SpecimenOrObservationBase<?> specimen : derivateToRootEntityMap.values()) {
291 rootElements.add(specimen);
292 }
293 }
294 labelProvider.updateLabelCache(rootElements);
295 viewer.setInput(rootElements);
296
297 //TODO e4
298 // getEditorSite().getActionBars().getStatusLineManager().setMessage(String.format(Messages.DerivateView_CNT_DERIVATIVES_FOUND, rootElements.size()));
299
300 //set selection to derivatives if the filter criteria
301 //taxon assignment or derivative type are set
302 if(derivates!=null && !derivateSearchCompositeController.isDefaultSearch()){
303 List<TreeNode> nodesToSelect = new ArrayList<TreeNode>();
304 for (SpecimenOrObservationBase specimenOrObservationBase : derivates) {
305 nodesToSelect.add(new TreeNode(specimenOrObservationBase));
306 }
307 setSelection(new StructuredSelection(nodesToSelect));
308 }
309 else{
310 setSelection(null);
311 }
312 }
313
314 private void setSelection(StructuredSelection selection){
315 viewer.removeSelectionChangedListener(selectionChangedListener);
316 viewer.setSelection(selection);
317 viewer.addSelectionChangedListener(selectionChangedListener);
318 }
319
320 public void updateLabelCache(){
321 labelProvider.updateLabelCache(rootElements);
322 }
323
324 @Persist
325 public void doSave(IProgressMonitor monitor) {
326 String taskName = Messages.DerivateView_SAVING_HIERARCHY;
327 monitor.beginTask(taskName, 3);
328 if (!conversation.isBound()) {
329 conversation.bind();
330 }
331 monitor.worked(1);
332
333 // commit the conversation and start a new transaction immediately
334 conversation.commit(true);
335
336 CdmStore.getService(IOccurrenceService.class).merge(new ArrayList<SpecimenOrObservationBase>(rootElements), true);
337
338 monitor.worked(1);
339
340 this.setDirty(false);
341 monitor.worked(1);
342 monitor.done();
343 dirty.setDirty(false);
344 refreshTree();
345 }
346
347 /**
348 * @param isDirty the isDirty to set
349 */
350 public void setDirty(boolean isDirty) {
351 dirty.setDirty(isDirty);
352 }
353
354 @Focus
355 public void setFocus() {
356 //make sure to bind again if maybe in another view the conversation was unbound
357 if(conversation!=null && !conversation.isBound()){
358 conversation.bind();
359 }
360 if(cdmEntitySession != null) {
361 cdmEntitySession.bind();
362 }
363 derivateSearchCompositeController.setFocusOnSearchField();
364 }
365
366 @Override
367 public void update(CdmDataChangeMap changeEvents) {
368 }
369
370 @Override
371 public ConversationHolder getConversationHolder() {
372 return conversation;
373 }
374
375 @Override
376 public void changed(Object element) {
377 setDirty(true);
378 //firePropertyChange(IEditorPart.PROP_DIRTY);
379 viewer.update(new TreeNode(element), null);
380 }
381
382 @Override
383 public void forceDirty() {
384 changed(null);
385 }
386
387 @Override
388 public Map<Object, List<String>> getPropertyPathsMap() {
389 List<String> specimenPropertyPaths = Arrays.asList(new String[] {
390 "descriptions", //$NON-NLS-1$
391 "derivationEvents.derivates", //$NON-NLS-1$
392 "annotations", //$NON-NLS-1$
393 "markers", //$NON-NLS-1$
394 "credits", //$NON-NLS-1$
395 "extensions", //$NON-NLS-1$
396 "rights", //$NON-NLS-1$
397 "sources" //$NON-NLS-1$
398 });
399 Map<Object, List<String>> specimenPropertyPathMap =
400 new HashMap<Object, List<String>>();
401 specimenPropertyPathMap.put(SpecimenOrObservationBase.class,specimenPropertyPaths);
402 return specimenPropertyPathMap;
403 }
404
405 /**
406 * Refreshes the derivate hierarchy tree and expands the tree
407 * to show and select the given object.
408 *
409 * @param expandTo the object to which the tree should be expanded
410 */
411 public void refreshTree(Object expandTo){
412 refreshTree();
413 TreeSelection selection = (TreeSelection) viewer.getSelection();
414 viewer.expandToLevel(selection.getFirstElement(), 1);
415 viewer.setSelection(new StructuredSelection(new TreeNode(expandTo)));
416 }
417
418 /**
419 * Refreshes the derivate hierarchy tree
420 */
421 public void refreshTree(){
422 if(!viewer.getTree().isDisposed()){
423 viewer.refresh();
424 }
425 }
426
427 //FIXME:Remoting hack to make this work for remoting
428 //This should actually be resolved using remoting post operations
429 public void remove(Object obj) {
430 if (obj instanceof TreeNode){
431 obj = ((TreeNode)obj).getValue();
432 }
433 rootElements.remove(obj);
434 Object o = this.derivateToRootEntityMap.remove(obj);
435 viewer.setInput(rootElements);
436 }
437
438 /**
439 * @return a set of {@link SingleRead}s that have multiple parents
440 */
441 public Set<SingleRead> getMultiLinkSingleReads() {
442 return DerivateLabelProvider.getMultiLinkSingleReads();
443 }
444
445 public Object getSelectionInput() {
446 return selectedTaxon;
447 }
448
449 public DerivateLabelProvider getLabelProvider() {
450 return labelProvider;
451 }
452
453 @Override
454 public boolean postOperation(CdmBase objectAffectedByOperation) {
455 refreshTree();
456 if(objectAffectedByOperation!=null){
457 changed(objectAffectedByOperation);
458 }
459 return true;
460 }
461
462 @Override
463 public boolean onComplete() {
464 return true;
465 }
466
467
468 @Override
469 public boolean canAttachMedia() {
470 return true;
471 }
472
473 public void addFieldUnit(FieldUnit fieldUnit) {
474 rootElements.add(fieldUnit);
475 derivateToRootEntityMap.put(fieldUnit, fieldUnit);
476 }
477
478 @Override
479 public ICdmEntitySession getCdmEntitySession() {
480 return cdmEntitySession;
481 }
482
483 @PreDestroy
484 public void dispose() {
485 if(conversation!=null){
486 conversation.close();
487 }
488 if(cdmEntitySession != null) {
489 cdmEntitySession.dispose();
490 }
491 }
492
493
494 @Inject
495 @Optional
496 public void selectionChanged(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection,
497 @Named(IServiceConstants.ACTIVE_PART) MPart activePart, MPart thisPart)
498 {
499 if(activePart == this || viewer==null){
500 return;
501 }
502 if(viewer.getTree().isDisposed()){
503 return;
504 }
505 if(listenToSelectionChange){
506 selectedTaxon = null;
507 if(activePart instanceof MultiPageTaxonEditor){
508 selectedTaxon = ((MultiPageTaxonEditor) activePart).getTaxon();
509 }
510 else if(selection instanceof IStructuredSelection){
511 Object selectedElement = ((IStructuredSelection) selection).getFirstElement();
512 if(selectedElement instanceof CdmBase){
513 if(((CdmBase) selectedElement).isInstanceOf(TaxonNode.class)){
514 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, TaxonNode.class).getTaxon();
515 }
516 else if(((CdmBase) selectedElement).isInstanceOf(Taxon.class)){
517 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, Taxon.class);
518 }
519 }
520 }
521 if(selectedTaxon!=null){
522 Collection<SpecimenOrObservationBase> fieldUnits = CdmStore.getService(IOccurrenceService.class).listFieldUnitsByAssociatedTaxon(selectedTaxon, null, null);
523 Collection<UUID> uuids = new HashSet<UUID>();
524 for (SpecimenOrObservationBase specimenOrObservationBase : fieldUnits) {
525 uuids.add(specimenOrObservationBase.getUuid());
526 }
527 checkWarnThreshold(uuids);
528 updateRootEntities(uuids);
529
530 thisPart.setLabel(SPECIMEN_EDITOR+": " + selectedTaxon.getName()); //$NON-NLS-1$
531 }
532 else{
533 updateRootEntities((Collection<UUID>)Collections.EMPTY_LIST);
534 }
535 }
536 }
537
538 private void checkWarnThreshold(Collection<UUID> uuids) {
539 if(uuids!=null && uuids.size()>WARN_THRESHOLD){
540 MessagingUtils.warningDialog(Messages.DerivateView_PERF_WARNING, this.getClass(), String.format(Messages.DerivateView_PERF_WARNING_MESSAGE, uuids.size()));
541 uuids.clear();
542 }
543 }
544
545 public TreeViewer getViewer() {
546 return viewer;
547 }
548
549 /**
550 * {@inheritDoc}
551 */
552 @Override
553 public List<SpecimenOrObservationBase<?>> getRootEntities() {
554 return new ArrayList<SpecimenOrObservationBase<?>>(rootElements);
555 }
556
557 public void toggleListenToSelectionChange(MPart part) {
558 listenToSelectionChange = !listenToSelectionChange;
559 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
560 if(!listenToSelectionChange){
561 selectedTaxon = null;
562 part.setLabel(SPECIMEN_EDITOR);
563 }
564 else if(selectedTaxon==null){
565 part.setLabel(SPECIMEN_EDITOR+Messages.DerivateView_NO_TAXON_SELECTED);
566 }
567 }
568
569 public boolean isListenToSelectionChange(){
570 return listenToSelectionChange;
571 }
572
573 /**
574 * {@inheritDoc}
575 */
576 @Override
577 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
578 }
579
580 /**
581 * {@inheritDoc}
582 */
583 @Override
584 public void contextStop(IMemento memento, IProgressMonitor monitor) {
585 derivateSearchCompositeController.setEnabled(false);
586 if(!viewer.getTree().isDisposed()) {
587 viewer.getTree().setEnabled(false);
588 viewer.setInput(null);
589 }
590 }
591
592 /**
593 * {@inheritDoc}
594 */
595 @Override
596 public void contextStart(IMemento memento, IProgressMonitor monitor) {
597 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
598 if(!viewer.getTree().isDisposed()){
599 viewer.getTree().setEnabled(true);
600 }
601 refreshTree();
602 }
603
604 /**
605 * {@inheritDoc}
606 */
607 @Override
608 public void contextRefresh(IProgressMonitor monitor) {
609 }
610
611 /**
612 * {@inheritDoc}
613 */
614 @Override
615 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
616 }
617
618
619 public boolean isDirty() {
620 return dirty.isDirty();
621 }
622
623 }