1 package eu
.etaxonomy
.taxeditor
.editor
.view
.derivate
;
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
;
12 import java
.util
.UUID
;
14 import javax
.annotation
.PostConstruct
;
15 import javax
.annotation
.PreDestroy
;
16 import javax
.inject
.Inject
;
17 import javax
.inject
.Named
;
19 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
20 import org
.eclipse
.e4
.core
.contexts
.ContextInjectionFactory
;
21 import org
.eclipse
.e4
.core
.contexts
.IEclipseContext
;
22 import org
.eclipse
.e4
.core
.di
.annotations
.Optional
;
23 import org
.eclipse
.e4
.ui
.di
.Focus
;
24 import org
.eclipse
.e4
.ui
.di
.Persist
;
25 import org
.eclipse
.e4
.ui
.model
.application
.ui
.MDirtyable
;
26 import org
.eclipse
.e4
.ui
.model
.application
.ui
.basic
.MPart
;
27 import org
.eclipse
.e4
.ui
.services
.EMenuService
;
28 import org
.eclipse
.e4
.ui
.services
.IServiceConstants
;
29 import org
.eclipse
.e4
.ui
.workbench
.modeling
.EPartService
;
30 import org
.eclipse
.e4
.ui
.workbench
.modeling
.ESelectionService
;
31 import org
.eclipse
.jface
.util
.LocalSelectionTransfer
;
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
;
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
.api
.service
.ITaxonService
;
51 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
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
.TaxonBase
;
57 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
58 import eu
.etaxonomy
.cdm
.persistence
.dto
.TaxonNodeDto
;
59 import eu
.etaxonomy
.cdm
.persistence
.hibernate
.CdmDataChangeMap
;
60 import eu
.etaxonomy
.taxeditor
.editor
.EditorUtil
;
61 import eu
.etaxonomy
.taxeditor
.editor
.l10n
.Messages
;
62 import eu
.etaxonomy
.taxeditor
.editor
.name
.e4
.TaxonNameEditorE4
;
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
.search
.derivative
.DerivateContentProvider
;
76 import eu
.etaxonomy
.taxeditor
.view
.search
.derivative
.DerivateLabelProvider
;
77 import eu
.etaxonomy
.taxeditor
.workbench
.part
.ICollapsableExpandable
;
78 import eu
.etaxonomy
.taxeditor
.workbench
.part
.IE4SavablePart
;
81 * Displays the derivate hierarchy of the specimen specified in the editor input.
84 public class DerivateView
implements IPartContentHasFactualData
, IConversationEnabled
,
85 ICdmEntitySessionEnabled
, IDirtyMarkable
, IPostOperationEnabled
, IPartContentHasDetails
, IPartContentHasSupplementalData
, IPartContentHasMedia
,
86 IContextListener
, IE4SavablePart
, ICollapsableExpandable
{
88 private static final String SPECIMEN_EDITOR
= Messages
.DerivateView_SPECIMEN_EDITOR
;
90 public static final String ID
= "eu.etaxonomy.taxeditor.editor.view.derivate.DerivateView"; //$NON-NLS-1$
91 public static final String INPUT_ID
= ID
+".editorInput"; //$NON-NLS-1$
93 public static final String YOU_NEED_TO_SAVE_BEFORE_PERFORMING_THIS_ACTION
= Messages
.DerivateView_YOU_NEED_TO_SAVE
;
94 public static final String VIEW_HAS_UNSAVED_CHANGES
= Messages
.DerivateView_UNSAVED_CHANGES
;
96 private static final List
<String
> SPECIMEN_INIT_STRATEGY
= Arrays
.asList(new String
[] {
97 "descriptions", //$NON-NLS-1$
98 "annotations", //$NON-NLS-1$
99 "markers", //$NON-NLS-1$
100 "credits", //$NON-NLS-1$
101 "extensions", //$NON-NLS-1$
102 "rights", //$NON-NLS-1$
103 "sources", //$NON-NLS-1$
104 "derivationEvents.derivatives.annotations", //$NON-NLS-1$
105 "derivationEvents.derivatives.markers", //$NON-NLS-1$
106 "derivationEvents.derivatives.credits", //$NON-NLS-1$
107 "derivationEvents.derivatives.extensions", //$NON-NLS-1$
108 "derivationEvents.derivatives.rights", //$NON-NLS-1$
109 "derivationEvents.derivatives.sources" //$NON-NLS-1$
112 private static final int WARN_THRESHOLD
= 200;
115 private ConversationHolder conversation
;
117 private TreeViewer viewer
;
119 private final int dndOperations
= DND
.DROP_MOVE
;
121 private DerivateLabelProvider labelProvider
;
123 private DerivateContentProvider contentProvider
;
125 private DerivateSearchCompositeController derivateSearchCompositeController
;
128 * A map with keys being the derivative entities belonging to the {@link UUID}s passed to the constructor
129 * and values being the root elements of the hierarchy (may be the same objects as the derivative entities)
131 private Map
<SpecimenOrObservationBase
<?
>, SpecimenOrObservationBase
<?
>> derivateToRootEntityMap
;
134 * The set of root elements
136 private Set
<SpecimenOrObservationBase
<?
>> rootElements
;
138 private ICdmEntitySession cdmEntitySession
;
141 * <code>true</code> if this view is listening to selection changes
143 private boolean listenToSelectionChange
;
145 private Taxon selectedTaxon
;
148 private ESelectionService selService
;
151 private MDirtyable dirty
;
153 private ISelectionChangedListener selectionChangedListener
;
156 private MPart thisPart
;
159 * Default constructor
162 public DerivateView() {
168 public void init(DerivateViewEditorInput editorInput
){
169 this.derivateToRootEntityMap
= new HashMap
<>();
170 this.rootElements
= new HashSet
<>();
173 Collection
<UUID
> derivativeUuids
= editorInput
.getDerivativeUuids();
174 checkWarnThreshold(derivativeUuids
);
175 updateRootEntities(derivativeUuids
);
177 derivateSearchCompositeController
.setTaxonFilter(editorInput
.getTaxonUuid());
180 // getEditorSite().getActionBars().getStatusLineManager().setMessage(""); //$NON-NLS-1$
184 public void createPartControl(Composite parent
, EMenuService menuService
,
185 IEclipseContext context
) {
186 if (CdmStore
.isActive()){
187 if(conversation
== null){
188 conversation
= CdmStore
.createConversation();
190 if(cdmEntitySession
== null){
191 cdmEntitySession
= CdmStore
.getCurrentSessionManager().newSession(this, true);
197 //listen to context changes
198 CdmStore
.getContextManager().addContextListener(this);
200 parent
.setLayout(new GridLayout());
202 //---search and filter---
203 derivateSearchCompositeController
= new DerivateSearchCompositeController(parent
, this);
204 GridData gridDataSearchBar
= new GridData();
205 gridDataSearchBar
.horizontalAlignment
= GridData
.FILL
;
206 gridDataSearchBar
.grabExcessHorizontalSpace
= true;
207 derivateSearchCompositeController
.setLayoutData(gridDataSearchBar
);
208 derivateSearchCompositeController
.setEnabled(CdmStore
.isActive());
211 viewer
= new TreeViewer(new Tree(parent
, SWT
.MULTI
| SWT
.H_SCROLL
| SWT
.V_SCROLL
| SWT
.FULL_SELECTION
));
212 GridData gridDataTree
= new GridData();
213 gridDataTree
.horizontalAlignment
= GridData
.FILL
;
214 gridDataTree
.verticalAlignment
= GridData
.FILL
;
215 gridDataTree
.grabExcessVerticalSpace
= true;
216 gridDataTree
.grabExcessHorizontalSpace
= true;
217 viewer
.getTree().setLayoutData(gridDataTree
);
218 contentProvider
= new DerivateContentProvider();
219 viewer
.setContentProvider(contentProvider
);
220 labelProvider
= new DerivateLabelProvider();
221 labelProvider
.setConversation(conversation
);
222 viewer
.setLabelProvider(labelProvider
);
223 viewer
.getTree().setEnabled(CdmStore
.isActive());
225 //propagate selection
226 selectionChangedListener
= (event
-> selService
.setSelection(event
.getSelection()));
227 viewer
.addSelectionChangedListener(selectionChangedListener
);
229 //create context menu
230 menuService
.registerContextMenu(viewer
.getControl(), "eu.etaxonomy.taxeditor.editor.popupmenu.specimeneditor");
232 //add drag'n'drop support
233 Transfer
[] transfers
= new Transfer
[] {LocalSelectionTransfer
.getTransfer(),};
234 viewer
.addDragSupport(dndOperations
, transfers
, new DerivateDragListener(this));
235 DerivateDropListener dropListener
= new DerivateDropListener(this);
236 ContextInjectionFactory
.inject(dropListener
, context
);
237 viewer
.addDropSupport(dndOperations
, transfers
, dropListener
);
240 public void updateRootEntities() {
241 updateRootEntities((Collection
)null);
244 public void updateRootEntities(Collection
<UUID
> derivativeUuids
) {
245 if(conversation
!=null){
246 if (!conversation
.isBound()) {
250 * If the active session is not the session of the Derivative Editor
251 * then we will save the active session for later, bind temporarily
252 * to our session and rebind to the original session when we are
253 * done. This happens e.g. if a selection change happens in the
254 * taxon editor and "Link with editor" is enabled. The selection
255 * change event and thus the loading in updateRootEntities() happens
256 * in the session of the taxon editor.
258 ICdmEntitySession previousCdmEntitySession
= CdmStore
.getCurrentSessionManager().getActiveSession();
259 if(cdmEntitySession
!= null) {
260 cdmEntitySession
.bind();
263 List
<SpecimenOrObservationBase
> derivates
= null;
264 if(derivativeUuids
!=null){
265 this.derivateToRootEntityMap
= new HashMap
<>();
266 this.rootElements
= new HashSet
<>();
267 derivates
= CdmStore
.getService(IOccurrenceService
.class).load(new ArrayList(derivativeUuids
), SPECIMEN_INIT_STRATEGY
);
269 updateRootEntities(derivates
);
270 if(previousCdmEntitySession
!=null){
271 previousCdmEntitySession
.bind();
277 public void updateRootEntities(List
<SpecimenOrObservationBase
> derivates
) {
279 this.derivateToRootEntityMap
= new HashMap
<>();
280 this.rootElements
= new HashSet
<>();
281 for (SpecimenOrObservationBase derivate
: derivates
) {
283 if(derivate
instanceof FieldUnit
){
284 derivateToRootEntityMap
.put(derivate
, derivate
);
287 SpecimenOrObservationBase
<?
> topMostDerivate
= EditorUtil
.getTopMostDerivate(derivate
);
288 if(topMostDerivate
!=null){
289 derivateToRootEntityMap
.put(derivate
, topMostDerivate
);
292 derivateToRootEntityMap
.put(derivate
, derivate
);
296 for (SpecimenOrObservationBase
<?
> specimen
: derivateToRootEntityMap
.values()) {
297 rootElements
.add(specimen
);
300 labelProvider
.updateLabelCache(rootElements
);
301 viewer
.setInput(rootElements
);
304 // getEditorSite().getActionBars().getStatusLineManager().setMessage(String.format(Messages.DerivateView_CNT_DERIVATIVES_FOUND, rootElements.size()));
306 //set selection to derivatives if the filter criteria
307 //taxon assignment or derivative type are set
308 if(derivates
!=null && !derivateSearchCompositeController
.isDefaultSearch()){
309 List
<TreeNode
> nodesToSelect
= new ArrayList
<>();
310 for (SpecimenOrObservationBase specimenOrObservationBase
: derivates
) {
311 nodesToSelect
.add(new TreeNode(specimenOrObservationBase
));
313 setSelection(new StructuredSelection(nodesToSelect
));
320 private void setSelection(StructuredSelection selection
){
321 viewer
.removeSelectionChangedListener(selectionChangedListener
);
322 viewer
.setSelection(selection
);
323 viewer
.addSelectionChangedListener(selectionChangedListener
);
326 public void updateLabelCache(){
327 labelProvider
.updateLabelCache(rootElements
);
332 public void save(IProgressMonitor monitor
) {
333 String taskName
= Messages
.DerivateView_SAVING_HIERARCHY
;
334 monitor
.beginTask(taskName
, 3);
335 if (!conversation
.isBound()) {
337 if (!cdmEntitySession
.isActive()){
338 cdmEntitySession
.bind();
343 // commit the conversation and start a new transaction immediately
344 conversation
.commit(true);
346 CdmStore
.getService(IOccurrenceService
.class).merge(new ArrayList
<>(rootElements
), true);
350 this.setDirty(false);
353 dirty
.setDirty(false);
358 * @param isDirty the isDirty to set
360 public void setDirty(boolean isDirty
) {
361 dirty
.setDirty(isDirty
);
365 public void setFocus() {
366 //make sure to bind again if maybe in another view the conversation was unbound
367 if(conversation
!=null && !conversation
.isBound()){
370 if(cdmEntitySession
!= null) {
371 cdmEntitySession
.bind();
373 if(viewer
!=null && !viewer
.getControl().isDisposed()) {
374 viewer
.getControl().setFocus();
375 selService
.setSelection(viewer
.getSelection());
380 public void update(CdmDataChangeMap changeEvents
) {
384 public ConversationHolder
getConversationHolder() {
389 public void changed(Object element
) {
391 //firePropertyChange(IEditorPart.PROP_DIRTY);
392 viewer
.update(new TreeNode(element
), null);
397 public void forceDirty() {
402 public Map
<Object
, List
<String
>> getPropertyPathsMap() {
403 List
<String
> specimenPropertyPaths
= Arrays
.asList(new String
[] {
404 "descriptions", //$NON-NLS-1$
405 "derivationEvents.derivates", //$NON-NLS-1$
406 "annotations", //$NON-NLS-1$
407 "markers", //$NON-NLS-1$
408 "credits", //$NON-NLS-1$
409 "extensions", //$NON-NLS-1$
410 "rights", //$NON-NLS-1$
411 "sources" //$NON-NLS-1$
413 Map
<Object
, List
<String
>> specimenPropertyPathMap
=
415 specimenPropertyPathMap
.put(SpecimenOrObservationBase
.class,specimenPropertyPaths
);
416 return specimenPropertyPathMap
;
420 * Refreshes the derivate hierarchy tree and expands the tree
421 * to show and select the given object.
423 * @param expandTo the object to which the tree should be expanded
425 public void refreshTree(Object expandTo
){
427 TreeSelection selection
= (TreeSelection
) viewer
.getSelection();
428 viewer
.expandToLevel(selection
.getFirstElement(), 1);
429 viewer
.setSelection(new StructuredSelection(new TreeNode(expandTo
)));
433 * Refreshes the derivate hierarchy tree
435 public void refreshTree(){
436 if(!viewer
.getTree().isDisposed()){
441 //FIXME:Remoting hack to make this work for remoting
442 //This should actually be resolved using remoting post operations
443 public void remove(Object obj
) {
444 if (obj
instanceof TreeNode
){
445 obj
= ((TreeNode
)obj
).getValue();
447 rootElements
.remove(obj
);
448 Object o
= this.derivateToRootEntityMap
.remove(obj
);
449 viewer
.setInput(rootElements
);
453 * @return a set of {@link SingleRead}s that have multiple parents
455 public Set
<SingleRead
> getMultiLinkSingleReads() {
456 return DerivateLabelProvider
.getMultiLinkSingleReads();
459 public Object
getSelectionInput() {
460 return selectedTaxon
;
463 public DerivateLabelProvider
getLabelProvider() {
464 return labelProvider
;
468 public boolean postOperation(Object objectAffectedByOperation
) {
470 if(objectAffectedByOperation
!=null){
471 changed(objectAffectedByOperation
);
477 public boolean onComplete() {
483 public boolean canAttachMedia() {
487 public void addFieldUnit(FieldUnit fieldUnit
) {
488 rootElements
.add(fieldUnit
);
489 derivateToRootEntityMap
.put(fieldUnit
, fieldUnit
);
493 public ICdmEntitySession
getCdmEntitySession() {
494 return cdmEntitySession
;
498 public void dispose() {
499 if(conversation
!=null){
500 conversation
.close();
503 if(cdmEntitySession
!= null) {
504 cdmEntitySession
.dispose();
505 cdmEntitySession
= null;
507 dirty
.setDirty(false);
513 public void selectionChanged(@Optional @Named(IServiceConstants
.ACTIVE_SELECTION
) IStructuredSelection selection
,
514 @Named(IServiceConstants
.ACTIVE_PART
) MPart activePart
, MPart thisPart
){
515 if(activePart
== thisPart
|| viewer
==null){
518 if(viewer
.getTree().isDisposed()){
521 if(listenToSelectionChange
){
522 selectedTaxon
= null;
523 if(activePart
.getObject() instanceof TaxonNameEditorE4
){
524 selectedTaxon
= ((TaxonNameEditorE4
) activePart
.getObject()).getTaxon();
526 else if(selection
!= null){
527 Object selectedElement
= selection
.getFirstElement();
528 if(selectedElement
instanceof TaxonNodeDto
){
529 TaxonBase taxonBase
= CdmStore
.getService(ITaxonService
.class).load(((TaxonNodeDto
)selectedElement
).getTaxonUuid());
530 if(HibernateProxyHelper
.isInstanceOf(taxonBase
, Taxon
.class)){
531 selectedTaxon
= HibernateProxyHelper
.deproxy(taxonBase
, Taxon
.class);
533 else if(selectedElement
instanceof TaxonNode
){
534 selectedTaxon
= HibernateProxyHelper
.deproxy(selectedElement
, TaxonNode
.class).getTaxon();
536 else if(selectedElement
instanceof Taxon
){
537 selectedTaxon
= HibernateProxyHelper
.deproxy(selectedElement
, Taxon
.class);
541 if(selectedTaxon
!=null){
542 Collection
<SpecimenOrObservationBase
> fieldUnits
= CdmStore
.getService(IOccurrenceService
.class).listFieldUnitsByAssociatedTaxon(selectedTaxon
, null, null);
543 Collection
<UUID
> uuids
= new HashSet
<>();
544 for (SpecimenOrObservationBase specimenOrObservationBase
: fieldUnits
) {
545 uuids
.add(specimenOrObservationBase
.getUuid());
547 checkWarnThreshold(uuids
);
548 updateRootEntities(uuids
);
550 thisPart
.setLabel(SPECIMEN_EDITOR
+": " + selectedTaxon
.getName()); //$NON-NLS-1$
553 updateRootEntities((Collection
<UUID
>)Collections
.EMPTY_LIST
);
558 private void checkWarnThreshold(Collection
<UUID
> uuids
) {
559 if(uuids
!=null && uuids
.size()>WARN_THRESHOLD
){
560 MessagingUtils
.warningDialog(Messages
.DerivateView_PERF_WARNING
, this.getClass(), String
.format(Messages
.DerivateView_PERF_WARNING_MESSAGE
, uuids
.size()));
565 public TreeViewer
getViewer() {
573 public List
<SpecimenOrObservationBase
<?
>> getRootEntities() {
574 return new ArrayList
<>(rootElements
);
577 public void toggleListenToSelectionChange(MPart part
) {
578 listenToSelectionChange
= !listenToSelectionChange
;
579 derivateSearchCompositeController
.setEnabled(!listenToSelectionChange
);
580 if(!listenToSelectionChange
){
581 selectedTaxon
= null;
582 part
.setLabel(SPECIMEN_EDITOR
);
584 else if(selectedTaxon
==null){
585 part
.setLabel(SPECIMEN_EDITOR
+Messages
.DerivateView_NO_TAXON_SELECTED
);
589 public boolean isListenToSelectionChange(){
590 return listenToSelectionChange
;
597 public void contextAboutToStop(IMemento memento
, IProgressMonitor monitor
) {
604 public void contextStop(IMemento memento
, IProgressMonitor monitor
) {
605 //close view when workbench closes
607 thisPart
.getContext().get(EPartService
.class).hidePart(thisPart
);
618 public void contextStart(IMemento memento
, IProgressMonitor monitor
) {
625 public void contextRefresh(IProgressMonitor monitor
) {
632 public void workbenchShutdown(IMemento memento
, IProgressMonitor monitor
) {
636 public boolean isDirty() {
637 return dirty
.isDirty();
641 public void collapse() {
642 viewer
.collapseAll();
646 public void expand() {