ba2b3168117fa00724603b78ea2823dda7670a34
[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.HashMap;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.UUID;
12
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.Menu;
31 import org.eclipse.swt.widgets.Tree;
32 import org.eclipse.ui.IEditorInput;
33 import org.eclipse.ui.IEditorPart;
34 import org.eclipse.ui.IEditorSite;
35 import org.eclipse.ui.IMemento;
36 import org.eclipse.ui.ISelectionListener;
37 import org.eclipse.ui.ISelectionService;
38 import org.eclipse.ui.IWorkbenchPart;
39 import org.eclipse.ui.PartInitException;
40 import org.eclipse.ui.part.EditorPart;
41
42 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
43 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
44 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
45 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
46 import eu.etaxonomy.cdm.model.common.CdmBase;
47 import eu.etaxonomy.cdm.model.molecular.SingleRead;
48 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
49 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
50 import eu.etaxonomy.cdm.model.taxon.Taxon;
51 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
52 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
53 import eu.etaxonomy.taxeditor.editor.EditorUtil;
54 import eu.etaxonomy.taxeditor.editor.Messages;
55 import eu.etaxonomy.taxeditor.editor.view.derivate.searchFilter.DerivateSearchCompositeController;
56 import eu.etaxonomy.taxeditor.model.IContextListener;
57 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
58 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
59 import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
60 import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
61 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
62 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
63 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
64 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
65 import eu.etaxonomy.taxeditor.store.CdmStore;
66 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateContentProvider;
67 import eu.etaxonomy.taxeditor.view.derivateSearch.DerivateLabelProvider;
68
69 /**
70 * Displays the derivate hierarchy of the specimen specified in the editor input.
71 *
72 */
73 public class DerivateView extends EditorPart implements IPartContentHasFactualData, IConversationEnabled,
74 ICdmEntitySessionEnabled, IDirtyMarkable, IPostOperationEnabled, IPartContentHasDetails, IPartContentHasSupplementalData, IPartContentHasMedia,
75 IContextListener, ISelectionListener {
76 public static final String ID = "eu.etaxonomy.taxeditor.editor.view.derivate.DerivateView"; //$NON-NLS-1$
77
78 public static final String YOU_NEED_TO_SAVE_BEFORE_PERFORMING_THIS_ACTION = Messages.DerivateView_YOU_NEED_TO_SAVE;
79 public static final String VIEW_HAS_UNSAVED_CHANGES = Messages.DerivateView_UNSAVED_CHANGES;
80
81 private static final List<String> SPECIMEN_INIT_STRATEGY = Arrays.asList(new String[] {
82 "descriptions",
83 "annotations",
84 "markers",
85 "credits",
86 "extensions",
87 "rights",
88 "sources",
89 "derivationEvents.derivatives.annotations",
90 "derivationEvents.derivatives.markers",
91 "derivationEvents.derivatives.credits",
92 "derivationEvents.derivatives.extensions",
93 "derivationEvents.derivatives.rights",
94 "derivationEvents.derivatives.sources"
95 });
96
97 private ConversationHolder conversation;
98
99 private TreeViewer viewer;
100
101 private boolean isDirty;
102
103 private final int dndOperations = DND.DROP_MOVE;
104
105 private DerivateLabelProvider labelProvider;
106
107 private DerivateContentProvider contentProvider;
108
109 private DerivateSearchCompositeController derivateSearchCompositeController;
110
111 /**
112 * A map with keys being the derivative entities belonging to the {@link UUID}s passed to the constructor
113 * and values being the root elements of the hierarchy (may be the same objects as the derivative entities)
114 */
115 private Map<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>> derivateToRootEntityMap;
116
117 /**
118 * The set of root elements
119 */
120 private Set<SpecimenOrObservationBase<?>> rootElements;
121
122 private ICdmEntitySession cdmEntitySession;
123
124 /**
125 * <code>true</code> if this view is listening to selection changes
126 */
127 private boolean listenToSelectionChange;
128
129 private Taxon selectedTaxon;
130
131 private ISelectionService selectionService;
132
133 /**
134 * Default constructor
135 */
136 public DerivateView() {
137 }
138
139
140 /**
141 * {@inheritDoc}
142 */
143 @Override
144 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
145 this.setSite(site);
146 this.setInput(input);
147 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
148 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
149
150 if (CdmStore.isActive() && conversation == null) {
151 conversation = CdmStore.createConversation();
152 }
153 if (CdmStore.isActive()) {
154 cdmEntitySession = CdmStore.getCurrentSessionManager().newSession(this, true);
155 }
156 //listen to context changes
157 CdmStore.getContextManager().addContextListener(this);
158 }
159
160 @Override
161 public void createPartControl(Composite parent) {
162
163 parent.setLayout(new GridLayout());
164
165 //---search and filter---
166 derivateSearchCompositeController = new DerivateSearchCompositeController(parent, this);
167 GridData gridDataSearchBar = new GridData();
168 gridDataSearchBar.horizontalAlignment = GridData.FILL;
169 gridDataSearchBar.grabExcessHorizontalSpace = true;
170 derivateSearchCompositeController.setLayoutData(gridDataSearchBar);
171 derivateSearchCompositeController.setEnabled(CdmStore.isActive());
172
173 //---tree viewer---
174 viewer = new TreeViewer(new Tree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION));
175 GridData gridDataTree = new GridData();
176 gridDataTree.horizontalAlignment = GridData.FILL;
177 gridDataTree.verticalAlignment = GridData.FILL;
178 gridDataTree.grabExcessVerticalSpace = true;
179 gridDataTree.grabExcessHorizontalSpace = true;
180 viewer.getTree().setLayoutData(gridDataTree);
181 contentProvider = new DerivateContentProvider();
182 viewer.setContentProvider(contentProvider);
183 labelProvider = new DerivateLabelProvider();
184 labelProvider.setConversation(conversation);
185 viewer.setLabelProvider(labelProvider);
186 viewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
187 viewer.getTree().setEnabled(CdmStore.isActive());
188 // Propagate selection from viewer
189 getSite().setSelectionProvider(viewer);
190
191 //listen to selection changes
192 selectionService = getSite().getWorkbenchWindow().getSelectionService();
193 selectionService.addSelectionListener(this);
194
195 //create context menu
196 MenuManager menuManager = new MenuManager();
197 getSite().registerContextMenu(menuManager, viewer);
198 Control control = viewer.getControl();
199 Menu menu = menuManager.createContextMenu(control);
200 control.setMenu(menu);
201
202 //init tree
203 updateRootEntities();
204
205 //add drag'n'drop support
206 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer(),};
207 viewer.addDragSupport(dndOperations, transfers, new DerivateDragListener(this));
208 viewer.addDropSupport(dndOperations, transfers, new DerivateDropListener(this));
209 }
210
211 public void updateRootEntities() {
212 updateRootEntities(null);
213 }
214
215 public void updateRootEntities(Collection<UUID> derivativeUuids) {
216 if(conversation!=null){
217 if (!conversation.isBound()) {
218 conversation.bind();
219 }
220 /*
221 * If the active session is not the session of the Derivate Editor then we will
222 * save it, bind temporarily to our session and rebind to the original session.
223 * This happens e.g. if a selection change happens in the taxon editor and
224 * "Link with editor" is enabled. The selection change event and thus the
225 * loading in updateRootEntities() happens in the session of the taxon
226 * editor.
227 */
228 ICdmEntitySession previousCdmEntitySession = CdmStore.getCurrentSessionManager().getActiveSession();
229 if(cdmEntitySession != null) {
230 cdmEntitySession.bind();
231 }
232
233 if(derivativeUuids!=null){
234 this.derivateToRootEntityMap = new HashMap<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>>();
235 this.rootElements = new HashSet<SpecimenOrObservationBase<?>>();
236 for (UUID uuid : derivativeUuids) {
237 SpecimenOrObservationBase<?> derivate = CdmStore.getService(IOccurrenceService.class).load(uuid, SPECIMEN_INIT_STRATEGY);
238 if(derivate instanceof FieldUnit){
239 derivateToRootEntityMap.put(derivate, derivate);
240 }
241 else {
242 SpecimenOrObservationBase<?> topMostDerivate = EditorUtil.getTopMostDerivate(derivate);
243 if(topMostDerivate!=null){
244 derivateToRootEntityMap.put(derivate, topMostDerivate);
245 }
246 else{
247 derivateToRootEntityMap.put(derivate, derivate);
248 }
249 }
250 }
251 for (SpecimenOrObservationBase<?> specimen : derivateToRootEntityMap.values()) {
252 rootElements.add(specimen);
253 }
254 }
255 viewer.setInput(rootElements);
256 refreshTree(false);
257 previousCdmEntitySession.bind();
258 }
259 }
260
261 @Override
262 public void doSave(IProgressMonitor monitor) {
263 String taskName = Messages.DerivateView_SAVING_HIERARCHY;
264 monitor.beginTask(taskName, 3);
265 if (!conversation.isBound()) {
266 conversation.bind();
267 }
268 monitor.worked(1);
269
270 // commit the conversation and start a new transaction immediately
271 conversation.commit(true);
272
273 if(CdmStore.getCurrentSessionManager().isRemoting()) {
274 CdmStore.getService(IOccurrenceService.class).merge(new ArrayList<SpecimenOrObservationBase>(rootElements), true);
275 }
276 monitor.worked(1);
277
278 this.setDirty(false);
279 monitor.worked(1);
280 monitor.done();
281 firePropertyChange(PROP_DIRTY);
282 refreshTree();
283 }
284
285 @Override
286 public void doSaveAs() {
287 }
288
289 @Override
290 public String getTitleToolTip() {
291 return Messages.DerivateView_DERIVATIVE_EDITOR;
292 }
293
294 @Override
295 public boolean isDirty() {
296 return isDirty;
297 }
298
299 /**
300 * @param isDirty the isDirty to set
301 */
302 public void setDirty(boolean isDirty) {
303 this.isDirty = isDirty;
304 }
305
306 @Override
307 public boolean isSaveAsAllowed() {
308 return false;
309 }
310
311 @Override
312 public void setFocus() {
313 viewer.getControl().setFocus();
314 //make sure to bind again if maybe in another view the conversation was unbound
315 if(conversation!=null && !conversation.isBound()){
316 conversation.bind();
317 }
318 if(cdmEntitySession != null) {
319 cdmEntitySession.bind();
320 }
321 }
322
323 @Override
324 public void update(CdmDataChangeMap changeEvents) {
325 }
326
327 @Override
328 public ConversationHolder getConversationHolder() {
329 return conversation;
330 }
331
332 @Override
333 public void changed(Object element) {
334 setDirty(true);
335 firePropertyChange(IEditorPart.PROP_DIRTY);
336 viewer.refresh();
337 }
338
339 @Override
340 public void forceDirty() {
341 changed(null);
342 }
343
344 @Override
345 public Map<Object, List<String>> getPropertyPathsMap() {
346 List<String> specimenPropertyPaths = Arrays.asList(new String[] {
347 "descriptions",
348 "derivationEvents.derivates",
349 "annotations",
350 "markers",
351 "credits",
352 "extensions",
353 "rights",
354 "sources"
355 });
356 Map<Object, List<String>> specimenPropertyPathMap =
357 new HashMap<Object, List<String>>();
358 specimenPropertyPathMap.put(SpecimenOrObservationBase.class,specimenPropertyPaths);
359 return specimenPropertyPathMap;
360 }
361
362 /**
363 * Refreshes the derivate hierarchy tree and expands the tree
364 * to show and select the given object.
365 *
366 * @param expandTo the object to which the tree should be expanded
367 */
368 public void refreshTree(Object expandTo){
369 refreshTree();
370 TreeSelection selection = (TreeSelection) viewer.getSelection();
371 viewer.expandToLevel(selection.getFirstElement(), 1);
372 viewer.setSelection(new StructuredSelection(new TreeNode(expandTo)));
373 }
374
375 /**
376 * Refreshes the derivate hierarchy tree
377 */
378 public void refreshTree(){
379 refreshTree(true);
380 }
381
382 /**
383 * Refreshes the derivate hierarchy tree
384 * @param refreshViewer if <code>true</code> then also the
385 * viewer will be refreshed. This was implemented due to
386 * performance reasons. If passing <code>false</code>
387 * does what was expected use <code>false</code> preferably.
388 */
389 public void refreshTree(boolean refreshViewer){
390 //refresh typedesignations
391 labelProvider.refresh();
392 if(refreshViewer){
393 viewer.refresh();
394 }
395 }
396
397 //FIXME:Remoting hack to make this work for remoting
398 //This should actually be resolved using remoting post operations
399 public void remove(Object obj) {
400 rootElements.remove(obj);
401 viewer.setInput(rootElements);
402 }
403
404 /**
405 * @return a set of {@link SingleRead}s that have multiple parents
406 */
407 public Set<SingleRead> getMultiLinkSingleReads() {
408 return DerivateLabelProvider.getMultiLinkSingleReads();
409 }
410
411 public Object getSelectionInput() {
412 return selectedTaxon;
413 }
414
415 public DerivateLabelProvider getLabelProvider() {
416 return labelProvider;
417 }
418
419 @Override
420 public boolean postOperation(CdmBase objectAffectedByOperation) {
421 refreshTree();
422 if(objectAffectedByOperation!=null){
423 changed(objectAffectedByOperation);
424 }
425 return true;
426 }
427
428 @Override
429 public boolean onComplete() {
430 return true;
431 }
432
433
434 @Override
435 public boolean canAttachMedia() {
436 return true;
437 }
438
439 public void addFieldUnit(FieldUnit fieldUnit) {
440 rootElements.add(fieldUnit);
441 derivateToRootEntityMap.put(fieldUnit, fieldUnit);
442 }
443
444 @Override
445 public ICdmEntitySession getCdmEntitySession() {
446 return cdmEntitySession;
447 }
448
449 @Override
450 public void dispose() {
451 super.dispose();
452 if(conversation!=null){
453 conversation.close();
454 }
455 if(cdmEntitySession != null) {
456 cdmEntitySession.dispose();
457 }
458 }
459
460 @Override
461 public void selectionChanged(IWorkbenchPart part, ISelection selection) {
462 if(viewer.getTree().isDisposed()){
463 return;
464 }
465 if(listenToSelectionChange && selection instanceof IStructuredSelection){
466 Object selectedElement = ((IStructuredSelection) selection).getFirstElement();
467 if(selectedElement instanceof CdmBase){
468 if(((CdmBase) selectedElement).isInstanceOf(TaxonNode.class)){
469 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, TaxonNode.class).getTaxon();
470 }
471 else if(((CdmBase) selectedElement).isInstanceOf(Taxon.class)){
472 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, Taxon.class);
473 }
474 Collection<SpecimenOrObservationBase> fieldUnits = CdmStore.getService(IOccurrenceService.class).listFieldUnitsByAssociatedTaxon(selectedTaxon, null, null);
475 Collection<UUID> uuids = new HashSet<UUID>();
476 for (SpecimenOrObservationBase specimenOrObservationBase : fieldUnits) {
477 uuids.add(specimenOrObservationBase.getUuid());
478 }
479 updateRootEntities(uuids);
480 setPartName("Derivative Editor: " + selectedTaxon.getName());
481 }
482 }
483 }
484
485 public TreeViewer getViewer() {
486 return viewer;
487 }
488
489 /**
490 * {@inheritDoc}
491 */
492 @Override
493 public List<SpecimenOrObservationBase<?>> getRootEntities() {
494 return new ArrayList<SpecimenOrObservationBase<?>>(rootElements);
495 }
496
497 public void toggleListenToSelectionChange() {
498 listenToSelectionChange = !listenToSelectionChange;
499 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
500 if(!listenToSelectionChange){
501 selectedTaxon = null;
502 setPartName("Derivative Editor");
503 }
504 else if(selectedTaxon==null){
505 setPartName("Derivative Editor [no taxon selected]");
506 }
507 }
508
509 public boolean isListenToSelectionChange(){
510 return listenToSelectionChange;
511 }
512
513 /**
514 * {@inheritDoc}
515 */
516 @Override
517 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
518 }
519
520 /**
521 * {@inheritDoc}
522 */
523 @Override
524 public void contextStop(IMemento memento, IProgressMonitor monitor) {
525 derivateSearchCompositeController.setEnabled(false);
526 viewer.getTree().setEnabled(false);
527 viewer.setInput(null);
528 }
529
530 /**
531 * {@inheritDoc}
532 */
533 @Override
534 public void contextStart(IMemento memento, IProgressMonitor monitor) {
535 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
536 viewer.getTree().setEnabled(true);
537 refreshTree();
538 }
539
540 /**
541 * {@inheritDoc}
542 */
543 @Override
544 public void contextRefresh(IProgressMonitor monitor) {
545 }
546
547 /**
548 * {@inheritDoc}
549 */
550 @Override
551 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
552 }
553
554 }