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