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
.HashMap
;
7 import java
.util
.HashSet
;
11 import java
.util
.UUID
;
13 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
14 import org
.eclipse
.jface
.action
.MenuManager
;
15 import org
.eclipse
.jface
.util
.LocalSelectionTransfer
;
16 import org
.eclipse
.jface
.viewers
.AbstractTreeViewer
;
17 import org
.eclipse
.jface
.viewers
.ISelection
;
18 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
19 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
20 import org
.eclipse
.jface
.viewers
.TreeNode
;
21 import org
.eclipse
.jface
.viewers
.TreeSelection
;
22 import org
.eclipse
.jface
.viewers
.TreeViewer
;
23 import org
.eclipse
.swt
.SWT
;
24 import org
.eclipse
.swt
.dnd
.DND
;
25 import org
.eclipse
.swt
.dnd
.Transfer
;
26 import org
.eclipse
.swt
.layout
.GridData
;
27 import org
.eclipse
.swt
.layout
.GridLayout
;
28 import org
.eclipse
.swt
.widgets
.Composite
;
29 import org
.eclipse
.swt
.widgets
.Control
;
30 import org
.eclipse
.swt
.widgets
.Display
;
31 import org
.eclipse
.swt
.widgets
.Menu
;
32 import org
.eclipse
.swt
.widgets
.Tree
;
33 import org
.eclipse
.ui
.IEditorInput
;
34 import org
.eclipse
.ui
.IEditorPart
;
35 import org
.eclipse
.ui
.IEditorSite
;
36 import org
.eclipse
.ui
.IMemento
;
37 import org
.eclipse
.ui
.ISelectionListener
;
38 import org
.eclipse
.ui
.ISelectionService
;
39 import org
.eclipse
.ui
.IWorkbenchPart
;
40 import org
.eclipse
.ui
.PartInitException
;
41 import org
.eclipse
.ui
.part
.EditorPart
;
43 import eu
.etaxonomy
.cdm
.api
.conversation
.ConversationHolder
;
44 import eu
.etaxonomy
.cdm
.api
.conversation
.IConversationEnabled
;
45 import eu
.etaxonomy
.cdm
.api
.service
.IOccurrenceService
;
46 import eu
.etaxonomy
.cdm
.hibernate
.HibernateProxyHelper
;
47 import eu
.etaxonomy
.cdm
.model
.common
.CdmBase
;
48 import eu
.etaxonomy
.cdm
.model
.molecular
.SingleRead
;
49 import eu
.etaxonomy
.cdm
.model
.occurrence
.FieldUnit
;
50 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
51 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
52 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
53 import eu
.etaxonomy
.cdm
.persistence
.hibernate
.CdmDataChangeMap
;
54 import eu
.etaxonomy
.taxeditor
.editor
.EditorUtil
;
55 import eu
.etaxonomy
.taxeditor
.editor
.MultiPageTaxonEditor
;
56 import eu
.etaxonomy
.taxeditor
.editor
.l10n
.Messages
;
57 import eu
.etaxonomy
.taxeditor
.editor
.view
.derivate
.searchFilter
.DerivateSearchCompositeController
;
58 import eu
.etaxonomy
.taxeditor
.model
.IContextListener
;
59 import eu
.etaxonomy
.taxeditor
.model
.IDirtyMarkable
;
60 import eu
.etaxonomy
.taxeditor
.model
.IPartContentHasDetails
;
61 import eu
.etaxonomy
.taxeditor
.model
.IPartContentHasFactualData
;
62 import eu
.etaxonomy
.taxeditor
.model
.IPartContentHasMedia
;
63 import eu
.etaxonomy
.taxeditor
.model
.IPartContentHasSupplementalData
;
64 import eu
.etaxonomy
.taxeditor
.model
.MessagingUtils
;
65 import eu
.etaxonomy
.taxeditor
.operation
.IPostOperationEnabled
;
66 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySession
;
67 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySessionEnabled
;
68 import eu
.etaxonomy
.taxeditor
.store
.CdmStore
;
69 import eu
.etaxonomy
.taxeditor
.view
.derivateSearch
.DerivateContentProvider
;
70 import eu
.etaxonomy
.taxeditor
.view
.derivateSearch
.DerivateLabelProvider
;
73 * Displays the derivate hierarchy of the specimen specified in the editor input.
76 public class DerivateView
extends EditorPart
implements IPartContentHasFactualData
, IConversationEnabled
,
77 ICdmEntitySessionEnabled
, IDirtyMarkable
, IPostOperationEnabled
, IPartContentHasDetails
, IPartContentHasSupplementalData
, IPartContentHasMedia
,
78 IContextListener
, ISelectionListener
{
79 private static final String SPECIMEN_EDITOR
= Messages
.DerivateView_SPECIMEN_EDITOR
;
81 public static final String ID
= "eu.etaxonomy.taxeditor.editor.view.derivate.DerivateView"; //$NON-NLS-1$
83 public static final String YOU_NEED_TO_SAVE_BEFORE_PERFORMING_THIS_ACTION
= Messages
.DerivateView_YOU_NEED_TO_SAVE
;
84 public static final String VIEW_HAS_UNSAVED_CHANGES
= Messages
.DerivateView_UNSAVED_CHANGES
;
86 private static final List
<String
> SPECIMEN_INIT_STRATEGY
= Arrays
.asList(new String
[] {
87 "descriptions", //$NON-NLS-1$
88 "annotations", //$NON-NLS-1$
89 "markers", //$NON-NLS-1$
90 "credits", //$NON-NLS-1$
91 "extensions", //$NON-NLS-1$
92 "rights", //$NON-NLS-1$
93 "sources", //$NON-NLS-1$
94 "derivationEvents.derivatives.annotations", //$NON-NLS-1$
95 "derivationEvents.derivatives.markers", //$NON-NLS-1$
96 "derivationEvents.derivatives.credits", //$NON-NLS-1$
97 "derivationEvents.derivatives.extensions", //$NON-NLS-1$
98 "derivationEvents.derivatives.rights", //$NON-NLS-1$
99 "derivationEvents.derivatives.sources" //$NON-NLS-1$
102 private static final int WARN_THRESHOLD
= 200;
104 private DelaySelection delaySelection
= null;
106 * This is the monitor for the DelaySelection runnable.
107 * If it is <code>true</code> then it is currently delaying a selection.
109 private boolean isInDelay
;
113 * This class invokes internal_selectionChanged() in a separate thread.
114 * This allows an asynchronous and/or delayed handling of selection changes
116 private class DelaySelection
implements Runnable
{
117 private IWorkbenchPart part
;
118 private ISelection selection
;
120 public DelaySelection(IWorkbenchPart part
, ISelection selection
) {
123 this.selection
= selection
;
129 selectionChanged_internal(part
, selection
);
136 public synchronized void setSelection(ISelection selection
) {
137 this.selection
= selection
;
140 public synchronized void setPart(IWorkbenchPart part
) {
146 private ConversationHolder conversation
;
148 private TreeViewer viewer
;
150 private boolean isDirty
;
152 private final int dndOperations
= DND
.DROP_MOVE
;
154 private DerivateLabelProvider labelProvider
;
156 private DerivateContentProvider contentProvider
;
158 private DerivateSearchCompositeController derivateSearchCompositeController
;
161 * A map with keys being the derivative entities belonging to the {@link UUID}s passed to the constructor
162 * and values being the root elements of the hierarchy (may be the same objects as the derivative entities)
164 private Map
<SpecimenOrObservationBase
<?
>, SpecimenOrObservationBase
<?
>> derivateToRootEntityMap
;
167 * The set of root elements
169 private Set
<SpecimenOrObservationBase
<?
>> rootElements
;
171 private ICdmEntitySession cdmEntitySession
;
174 * <code>true</code> if this view is listening to selection changes
176 private boolean listenToSelectionChange
;
178 private Taxon selectedTaxon
;
180 private ISelectionService selectionService
;
183 * Default constructor
185 public DerivateView() {
193 public void init(IEditorSite site
, IEditorInput input
) throws PartInitException
{
195 this.setInput(input
);
196 this.derivateToRootEntityMap
= new HashMap
<SpecimenOrObservationBase
<?
>, SpecimenOrObservationBase
<?
>>();
197 this.rootElements
= new HashSet
<SpecimenOrObservationBase
<?
>>();
199 if (CdmStore
.isActive() && conversation
== null) {
200 conversation
= CdmStore
.createConversation();
202 if (CdmStore
.isActive()) {
203 cdmEntitySession
= CdmStore
.getCurrentSessionManager().newSession(this, true);
205 //listen to context changes
206 CdmStore
.getContextManager().addContextListener(this);
210 public void createPartControl(Composite parent
) {
212 parent
.setLayout(new GridLayout());
214 //---search and filter---
215 derivateSearchCompositeController
= new DerivateSearchCompositeController(parent
, this);
216 GridData gridDataSearchBar
= new GridData();
217 gridDataSearchBar
.horizontalAlignment
= GridData
.FILL
;
218 gridDataSearchBar
.grabExcessHorizontalSpace
= true;
219 derivateSearchCompositeController
.setLayoutData(gridDataSearchBar
);
220 derivateSearchCompositeController
.setEnabled(CdmStore
.isActive());
223 viewer
= new TreeViewer(new Tree(parent
, SWT
.MULTI
| SWT
.H_SCROLL
| SWT
.V_SCROLL
| SWT
.FULL_SELECTION
));
224 GridData gridDataTree
= new GridData();
225 gridDataTree
.horizontalAlignment
= GridData
.FILL
;
226 gridDataTree
.verticalAlignment
= GridData
.FILL
;
227 gridDataTree
.grabExcessVerticalSpace
= true;
228 gridDataTree
.grabExcessHorizontalSpace
= true;
229 viewer
.getTree().setLayoutData(gridDataTree
);
230 contentProvider
= new DerivateContentProvider();
231 viewer
.setContentProvider(contentProvider
);
232 labelProvider
= new DerivateLabelProvider();
233 labelProvider
.setConversation(conversation
);
234 viewer
.setLabelProvider(labelProvider
);
235 viewer
.setAutoExpandLevel(AbstractTreeViewer
.ALL_LEVELS
);
236 viewer
.getTree().setEnabled(CdmStore
.isActive());
237 // Propagate selection from viewer
238 getSite().setSelectionProvider(viewer
);
240 //listen to selection changes
241 selectionService
= getSite().getWorkbenchWindow().getSelectionService();
242 selectionService
.addSelectionListener(this);
244 //create context menu
245 MenuManager menuManager
= new MenuManager();
246 menuManager
.setRemoveAllWhenShown(true);
247 getSite().registerContextMenu(menuManager
, viewer
);
248 Control control
= viewer
.getControl();
249 Menu menu
= menuManager
.createContextMenu(control
);
250 control
.setMenu(menu
);
253 Collection
<UUID
> derivativeUuids
= ((DerivateViewEditorInput
)getEditorInput()).getDerivativeUuids();
254 checkWarnThreshold(derivativeUuids
);
255 updateRootEntities(derivativeUuids
);
257 derivateSearchCompositeController
.setTaxonFilter(((DerivateViewEditorInput
) getEditorInput()).getTaxonUuid());
259 getEditorSite().getActionBars().getStatusLineManager().setMessage(""); //$NON-NLS-1$
261 //add drag'n'drop support
262 Transfer
[] transfers
= new Transfer
[] {LocalSelectionTransfer
.getTransfer(),};
263 viewer
.addDragSupport(dndOperations
, transfers
, new DerivateDragListener(this));
264 viewer
.addDropSupport(dndOperations
, transfers
, new DerivateDropListener(this));
267 public void updateRootEntities() {
268 updateRootEntities((Collection
)null);
271 public void updateRootEntities(Collection
<UUID
> derivativeUuids
) {
272 if(conversation
!=null){
273 if (!conversation
.isBound()) {
277 * If the active session is not the session of the Derivative Editor
278 * then we will save the active session for later, bind temporarily
279 * to our session and rebind to the original session when we are
280 * done. This happens e.g. if a selection change happens in the
281 * taxon editor and "Link with editor" is enabled. The selection
282 * change event and thus the loading in updateRootEntities() happens
283 * in the session of the taxon editor.
285 ICdmEntitySession previousCdmEntitySession
= CdmStore
.getCurrentSessionManager().getActiveSession();
286 if(cdmEntitySession
!= null) {
287 cdmEntitySession
.bind();
290 List
<SpecimenOrObservationBase
> derivates
= null;
291 if(derivativeUuids
!=null){
292 this.derivateToRootEntityMap
= new HashMap
<SpecimenOrObservationBase
<?
>, SpecimenOrObservationBase
<?
>>();
293 this.rootElements
= new HashSet
<SpecimenOrObservationBase
<?
>>();
294 derivates
= CdmStore
.getService(IOccurrenceService
.class).load(new ArrayList(derivativeUuids
), SPECIMEN_INIT_STRATEGY
);
296 updateRootEntities(derivates
);
297 previousCdmEntitySession
.bind();
302 public void updateRootEntities(List
<SpecimenOrObservationBase
> derivates
) {
304 this.derivateToRootEntityMap
= new HashMap
<SpecimenOrObservationBase
<?
>, SpecimenOrObservationBase
<?
>>();
305 this.rootElements
= new HashSet
<SpecimenOrObservationBase
<?
>>();
306 for (SpecimenOrObservationBase derivate
: derivates
) {
308 if(derivate
instanceof FieldUnit
){
309 derivateToRootEntityMap
.put(derivate
, derivate
);
312 SpecimenOrObservationBase
<?
> topMostDerivate
= EditorUtil
.getTopMostDerivate(derivate
);
313 if(topMostDerivate
!=null){
314 derivateToRootEntityMap
.put(derivate
, topMostDerivate
);
317 derivateToRootEntityMap
.put(derivate
, derivate
);
321 for (SpecimenOrObservationBase
<?
> specimen
: derivateToRootEntityMap
.values()) {
322 rootElements
.add(specimen
);
325 labelProvider
.updateLabelCache(rootElements
);
326 viewer
.setInput(rootElements
);
328 getEditorSite().getActionBars().getStatusLineManager().setMessage(String
.format(Messages
.DerivateView_CNT_DERIVATIVES_FOUND
, rootElements
.size()));
330 //set selection to derivatives if the filter criteria
331 //taxon assignment or derivative type are set
332 if(derivates
!=null && !derivateSearchCompositeController
.isDefaultSearch()){
333 List
<TreeNode
> nodesToSelect
= new ArrayList
<TreeNode
>();
334 for (SpecimenOrObservationBase specimenOrObservationBase
: derivates
) {
335 nodesToSelect
.add(new TreeNode(specimenOrObservationBase
));
337 viewer
.setSelection(new StructuredSelection(nodesToSelect
));
340 viewer
.setSelection(null);
345 public void updateLabelCache(){
346 labelProvider
.updateLabelCache(rootElements
);
350 public void doSave(IProgressMonitor monitor
) {
351 String taskName
= Messages
.DerivateView_SAVING_HIERARCHY
;
352 monitor
.beginTask(taskName
, 3);
353 if (!conversation
.isBound()) {
358 // commit the conversation and start a new transaction immediately
359 conversation
.commit(true);
361 CdmStore
.getService(IOccurrenceService
.class).merge(new ArrayList
<SpecimenOrObservationBase
>(rootElements
), true);
365 this.setDirty(false);
368 firePropertyChange(PROP_DIRTY
);
373 public void doSaveAs() {
377 public String
getTitleToolTip() {
378 return Messages
.DerivateView_DERIVATIVE_EDITOR
;
382 public boolean isDirty() {
387 * @param isDirty the isDirty to set
389 public void setDirty(boolean isDirty
) {
390 this.isDirty
= isDirty
;
394 public boolean isSaveAsAllowed() {
399 public void setFocus() {
400 //make sure to bind again if maybe in another view the conversation was unbound
401 if(conversation
!=null && !conversation
.isBound()){
404 if(cdmEntitySession
!= null) {
405 cdmEntitySession
.bind();
407 derivateSearchCompositeController
.setFocusOnSearchField();
411 public void update(CdmDataChangeMap changeEvents
) {
415 public ConversationHolder
getConversationHolder() {
420 public void changed(Object element
) {
422 firePropertyChange(IEditorPart
.PROP_DIRTY
);
423 viewer
.update(new TreeNode(element
), null);
427 public void forceDirty() {
432 public Map
<Object
, List
<String
>> getPropertyPathsMap() {
433 List
<String
> specimenPropertyPaths
= Arrays
.asList(new String
[] {
434 "descriptions", //$NON-NLS-1$
435 "derivationEvents.derivates", //$NON-NLS-1$
436 "annotations", //$NON-NLS-1$
437 "markers", //$NON-NLS-1$
438 "credits", //$NON-NLS-1$
439 "extensions", //$NON-NLS-1$
440 "rights", //$NON-NLS-1$
441 "sources" //$NON-NLS-1$
443 Map
<Object
, List
<String
>> specimenPropertyPathMap
=
444 new HashMap
<Object
, List
<String
>>();
445 specimenPropertyPathMap
.put(SpecimenOrObservationBase
.class,specimenPropertyPaths
);
446 return specimenPropertyPathMap
;
450 * Refreshes the derivate hierarchy tree and expands the tree
451 * to show and select the given object.
453 * @param expandTo the object to which the tree should be expanded
455 public void refreshTree(Object expandTo
){
457 TreeSelection selection
= (TreeSelection
) viewer
.getSelection();
458 viewer
.expandToLevel(selection
.getFirstElement(), 1);
459 viewer
.setSelection(new StructuredSelection(new TreeNode(expandTo
)));
463 * Refreshes the derivate hierarchy tree
465 public void refreshTree(){
466 if(!viewer
.getTree().isDisposed()){
471 //FIXME:Remoting hack to make this work for remoting
472 //This should actually be resolved using remoting post operations
473 public void remove(Object obj
) {
474 if (obj
instanceof TreeNode
){
475 obj
= ((TreeNode
)obj
).getValue();
477 rootElements
.remove(obj
);
478 Object o
= this.derivateToRootEntityMap
.remove(obj
);
479 viewer
.setInput(rootElements
);
483 * @return a set of {@link SingleRead}s that have multiple parents
485 public Set
<SingleRead
> getMultiLinkSingleReads() {
486 return DerivateLabelProvider
.getMultiLinkSingleReads();
489 public Object
getSelectionInput() {
490 return selectedTaxon
;
493 public DerivateLabelProvider
getLabelProvider() {
494 return labelProvider
;
498 public boolean postOperation(CdmBase objectAffectedByOperation
) {
500 if(objectAffectedByOperation
!=null){
501 changed(objectAffectedByOperation
);
507 public boolean onComplete() {
513 public boolean canAttachMedia() {
517 public void addFieldUnit(FieldUnit fieldUnit
) {
518 rootElements
.add(fieldUnit
);
519 derivateToRootEntityMap
.put(fieldUnit
, fieldUnit
);
523 public ICdmEntitySession
getCdmEntitySession() {
524 return cdmEntitySession
;
528 public void dispose() {
530 if(conversation
!=null){
531 conversation
.close();
533 if(cdmEntitySession
!= null) {
534 cdmEntitySession
.dispose();
537 public void selectionChanged_internal(IWorkbenchPart part
, ISelection selection
) {
541 if(viewer
.getTree().isDisposed()){
544 if(listenToSelectionChange
){
545 if(part
instanceof MultiPageTaxonEditor
){
546 selectedTaxon
= ((MultiPageTaxonEditor
) part
).getTaxon();
548 else if(selection
instanceof IStructuredSelection
){
549 Object selectedElement
= ((IStructuredSelection
) selection
).getFirstElement();
550 if(selectedElement
instanceof CdmBase
){
551 if(((CdmBase
) selectedElement
).isInstanceOf(TaxonNode
.class)){
552 selectedTaxon
= HibernateProxyHelper
.deproxy(selectedElement
, TaxonNode
.class).getTaxon();
554 else if(((CdmBase
) selectedElement
).isInstanceOf(Taxon
.class)){
555 selectedTaxon
= HibernateProxyHelper
.deproxy(selectedElement
, Taxon
.class);
559 if(selectedTaxon
!=null){
560 Collection
<SpecimenOrObservationBase
> fieldUnits
= CdmStore
.getService(IOccurrenceService
.class).listFieldUnitsByAssociatedTaxon(selectedTaxon
, null, null);
561 Collection
<UUID
> uuids
= new HashSet
<UUID
>();
562 for (SpecimenOrObservationBase specimenOrObservationBase
: fieldUnits
) {
563 uuids
.add(specimenOrObservationBase
.getUuid());
565 checkWarnThreshold(uuids
);
566 updateRootEntities(uuids
);
567 setPartName(SPECIMEN_EDITOR
+": " + selectedTaxon
.getName()); //$NON-NLS-1$
573 public void selectionChanged(IWorkbenchPart part
, ISelection selection
) {
574 if(delaySelection
==null){
575 delaySelection
= new DelaySelection(part
, selection
);
577 delaySelection
.setPart(part
);
578 delaySelection
.setSelection(selection
);
581 Display
.getCurrent().asyncExec(delaySelection
);
586 private void checkWarnThreshold(Collection
<UUID
> uuids
) {
587 if(uuids
!=null && uuids
.size()>WARN_THRESHOLD
){
588 MessagingUtils
.warningDialog(Messages
.DerivateView_PERF_WARNING
, this.getClass(), String
.format(Messages
.DerivateView_PERF_WARNING_MESSAGE
, uuids
.size()));
593 public TreeViewer
getViewer() {
601 public List
<SpecimenOrObservationBase
<?
>> getRootEntities() {
602 return new ArrayList
<SpecimenOrObservationBase
<?
>>(rootElements
);
605 public void toggleListenToSelectionChange() {
606 listenToSelectionChange
= !listenToSelectionChange
;
607 derivateSearchCompositeController
.setEnabled(!listenToSelectionChange
);
608 if(!listenToSelectionChange
){
609 selectedTaxon
= null;
610 setPartName(SPECIMEN_EDITOR
);
612 else if(selectedTaxon
==null){
613 setPartName(SPECIMEN_EDITOR
+Messages
.DerivateView_NO_TAXON_SELECTED
);
617 public boolean isListenToSelectionChange(){
618 return listenToSelectionChange
;
625 public void contextAboutToStop(IMemento memento
, IProgressMonitor monitor
) {
632 public void contextStop(IMemento memento
, IProgressMonitor monitor
) {
633 derivateSearchCompositeController
.setEnabled(false);
634 if(!viewer
.getTree().isDisposed()) {
635 viewer
.getTree().setEnabled(false);
636 viewer
.setInput(null);
644 public void contextStart(IMemento memento
, IProgressMonitor monitor
) {
645 derivateSearchCompositeController
.setEnabled(!listenToSelectionChange
);
646 if(!viewer
.getTree().isDisposed()){
647 viewer
.getTree().setEnabled(true);
656 public void contextRefresh(IProgressMonitor monitor
) {
663 public void workbenchShutdown(IMemento memento
, IProgressMonitor monitor
) {