ref #2380: implement usage of Dto for taxon navigator
[taxeditor.git] / eu.etaxonomy.taxeditor.navigation / src / main / java / eu / etaxonomy / taxeditor / navigation / navigator / e4 / TaxonNavigatorE4.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.taxeditor.navigation.navigator.e4;
11
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Observable;
20 import java.util.Observer;
21 import java.util.Set;
22 import java.util.UUID;
23
24 import javax.annotation.PostConstruct;
25 import javax.annotation.PreDestroy;
26 import javax.inject.Inject;
27
28 import org.eclipse.core.commands.operations.UndoContext;
29 import org.eclipse.core.runtime.IAdaptable;
30 import org.eclipse.core.runtime.IProgressMonitor;
31 import org.eclipse.e4.core.di.annotations.Optional;
32 import org.eclipse.e4.ui.di.Focus;
33 import org.eclipse.e4.ui.di.UIEventTopic;
34 import org.eclipse.e4.ui.di.UISynchronize;
35 import org.eclipse.e4.ui.model.application.MApplication;
36 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
37 import org.eclipse.e4.ui.model.application.ui.menu.MToolBar;
38 import org.eclipse.e4.ui.model.application.ui.menu.MToolBarElement;
39 import org.eclipse.e4.ui.model.application.ui.menu.impl.HandledToolItemImpl;
40 import org.eclipse.e4.ui.services.EMenuService;
41 import org.eclipse.e4.ui.workbench.modeling.EModelService;
42 import org.eclipse.e4.ui.workbench.modeling.EPartService;
43 import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
44 import org.eclipse.jface.util.LocalSelectionTransfer;
45 import org.eclipse.jface.viewers.IElementComparer;
46 import org.eclipse.jface.viewers.ISelection;
47 import org.eclipse.jface.viewers.ISelectionChangedListener;
48 import org.eclipse.jface.viewers.IStructuredSelection;
49 import org.eclipse.jface.viewers.StructuredSelection;
50 import org.eclipse.jface.viewers.TreePath;
51 import org.eclipse.jface.viewers.TreeViewer;
52 import org.eclipse.swt.SWT;
53 import org.eclipse.swt.dnd.DND;
54 import org.eclipse.swt.dnd.Transfer;
55 import org.eclipse.swt.layout.FillLayout;
56 import org.eclipse.swt.widgets.Composite;
57 import org.eclipse.swt.widgets.Tree;
58 import org.eclipse.ui.IMemento;
59
60 import eu.etaxonomy.cdm.api.application.CdmApplicationState;
61 import eu.etaxonomy.cdm.api.application.CdmChangeEvent;
62 import eu.etaxonomy.cdm.api.application.ICdmChangeListener;
63 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
64 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
65 import eu.etaxonomy.cdm.api.service.IClassificationService;
66 import eu.etaxonomy.cdm.api.service.TaxonNodeDtoByNameComparator;
67 import eu.etaxonomy.cdm.api.service.TaxonNodeDtoByRankAndNameComparator;
68 import eu.etaxonomy.cdm.api.service.TaxonNodeDtoNaturalComparator;
69 import eu.etaxonomy.cdm.model.common.CdmBase;
70 import eu.etaxonomy.cdm.model.common.ICdmBase;
71 import eu.etaxonomy.cdm.model.taxon.Classification;
72 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
73 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
74 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
75 import eu.etaxonomy.taxeditor.editor.ITaxonEditor;
76 import eu.etaxonomy.taxeditor.editor.name.e4.TaxonNameEditorE4;
77 import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
78 import eu.etaxonomy.taxeditor.model.AbstractUtility;
79 import eu.etaxonomy.taxeditor.model.DataChangeBridge;
80 import eu.etaxonomy.taxeditor.model.IContextListener;
81 import eu.etaxonomy.taxeditor.model.IDataChangeBehavior;
82 import eu.etaxonomy.taxeditor.navigation.AppModelId;
83 import eu.etaxonomy.taxeditor.navigation.NavigationUtil;
84 import eu.etaxonomy.taxeditor.navigation.l10n.Messages;
85 import eu.etaxonomy.taxeditor.navigation.navigator.EmptyRoot;
86 import eu.etaxonomy.taxeditor.navigation.navigator.Root;
87 import eu.etaxonomy.taxeditor.navigation.navigator.TaxonNodeNavigatorComparator;
88 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
89 import eu.etaxonomy.taxeditor.preference.PreferencesUtil;
90 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
91 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
92 import eu.etaxonomy.taxeditor.store.CdmStore;
93 import eu.etaxonomy.taxeditor.store.LoginManager;
94 import eu.etaxonomy.taxeditor.ui.element.LayoutConstants;
95 import eu.etaxonomy.taxeditor.workbench.part.ICollapsableExpandable;
96
97 /**
98 *
99 * @author pplitzner
100 * @since Sep 7, 2017
101 *
102 */
103 public class TaxonNavigatorE4 implements
104 IPostOperationEnabled, IConversationEnabled, Observer,
105 ICdmEntitySessionEnabled, ICdmChangeListener, IContextListener,
106 ICollapsableExpandable {
107
108 private static final String RESTORING_TAXON_NAVIGATOR = Messages.TaxonNavigator_RESTORE;
109
110 private static final String TREE_PATH = "treepath"; //$NON-NLS-1$
111
112 private static final String TREE_PATHS = "treepaths"; //$NON-NLS-1$
113
114 private final int dndOperations = DND.DROP_MOVE;
115
116 private ConversationHolder conversation;
117
118 private ICdmEntitySession cdmEntitySession;
119
120 private IDataChangeBehavior dataChangeBehavior;
121
122 private Root root;
123
124 private TreeViewer viewer;
125
126 @Inject
127 private ESelectionService selService;
128
129 @Inject
130 private UISynchronize sync;
131
132 private ISelectionChangedListener selectionChangedListener;
133
134 private UndoContext undoContext;
135
136 @Inject
137 private MApplication application;
138
139 @Inject
140 private EModelService modelService;
141
142 @Inject
143 private EPartService partService;
144
145 private boolean linkWithTaxon = false;
146
147 @Inject
148 public TaxonNavigatorE4() {
149 undoContext = new UndoContext();
150 CdmStore.getContextManager().addContextListener(this);
151 }
152
153 @PostConstruct
154 private void create(Composite parent, EMenuService menuService){
155 FillLayout layout = new FillLayout();
156 layout.marginHeight = 0;
157 layout.marginWidth = 0;
158 layout.type = SWT.VERTICAL;
159
160 parent.setLayout(layout);
161 viewer = new TreeViewer(new Tree(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.MULTI));
162 viewer.getControl().setLayoutData(LayoutConstants.FILL());
163
164 viewer.setContentProvider(new TaxonNavigatorContentProviderE4());
165 viewer.setLabelProvider(new TaxonNavigatorLabelProviderE4());
166 viewer.addDoubleClickListener(event->{
167 ISelection selection = event.getSelection();
168 if(selection instanceof IStructuredSelection){
169 Object firstElement = ((IStructuredSelection) selection).getFirstElement();
170 if(firstElement instanceof ICdmBase){
171 NavigationUtil.openEditor((ICdmBase) firstElement, viewer.getControl().getShell(), modelService, partService, application);
172 }
173 if(firstElement instanceof TaxonNodeDto){
174 NavigationUtil.openEditor((TaxonNodeDto) firstElement, viewer.getControl().getShell(), modelService, partService, application);
175 }
176 }
177 });
178
179 //propagate selection
180 selectionChangedListener = (event -> selService.setSelection(event.getSelection()));
181 viewer.addSelectionChangedListener(selectionChangedListener);
182
183 //create context menu
184 menuService.registerContextMenu(viewer.getControl(), AppModelId.POPUPMENU_EU_ETAXONOMY_TAXEDITOR_NAVIGATOR_POPUPMENU_TAXONNAVIGATOR );
185
186 //add drag'n'drop support
187 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer()};
188 viewer.addDragSupport(dndOperations, transfers, new TreeNodeDragListenerE4(viewer));
189 viewer.addDropSupport(dndOperations, transfers, new TreeNodeDropAdapterE4(this));
190
191 updateSyncButton();
192
193
194
195 init();
196 }
197
198 protected void updateSyncButton() {
199 MPart viewPart = partService.findPart(AppModelId.PARTDESCRIPTOR_EU_ETAXONOMY_TAXEDITOR_NAVIGATION_NAVIGATOR);
200 if(viewPart!=null){
201 MToolBar toolBar = viewPart.getToolbar();
202
203 List<MToolBarElement> toolBarElements = toolBar.getChildren();
204
205 MToolBarElement upperHandledMenuItem = toolBarElements.get(1);
206 if (upperHandledMenuItem instanceof HandledToolItemImpl){
207 ((HandledToolItemImpl)upperHandledMenuItem).setSelected(linkWithTaxon);
208 }
209 }
210 }
211
212 /** {@inheritDoc} */
213 protected IAdaptable getInitialInput() {
214 Comparator<TaxonNodeDto> comparator;
215 if (PreferencesUtil.getSortNodesNaturally()){
216 comparator = new TaxonNodeDtoNaturalComparator();
217 } else if (PreferencesUtil.getSortNodesStrictlyAlphabetically()){
218 comparator = new TaxonNodeDtoByNameComparator();
219 }else {
220 comparator = new TaxonNodeDtoByRankAndNameComparator();
221 }
222
223 TaxonNodeNavigatorComparator viewerComparator = new TaxonNodeNavigatorComparator(comparator);
224 viewer.setComparator(viewerComparator);
225 viewer.setComparer(new IElementComparer() {
226
227 @Override
228 public int hashCode(Object element) {
229 if (element instanceof TaxonNodeDto){
230 TaxonNodeDto nodeDto = (TaxonNodeDto)element;
231
232 String s = nodeDto.getUuid().toString();
233 if (s != null) {
234 return s.hashCode();
235 }
236 return element.hashCode();
237 }else{
238 return element.toString().hashCode();
239 }
240 }
241
242 @Override
243 public boolean equals(Object element1, Object element2) {
244 if (element1 instanceof TaxonNodeDto && element2 instanceof TaxonNodeDto){
245 TaxonNodeDto node1 = (TaxonNodeDto)element1;
246 TaxonNodeDto node2 = (TaxonNodeDto)element2;
247 return (node1.getUuid().equals(node2.getUuid()));
248 }else {
249 return element1.equals(element2);
250 }
251 }
252 }
253 );
254
255 if (CdmStore.isActive()) {
256
257 // TODO when closing and reopening the taxon navigator
258 // we do not preserve state. Closing the view, in contrary to
259 // closing the whole application
260 // should be handled by the state manager too
261 root = new Root(conversation);
262
263 return root;
264 }
265 return new EmptyRoot();
266 }
267
268 public void init() {
269 if (CdmStore.isActive() && conversation == null) {
270 conversation = CdmStore.createConversation();
271 conversation.registerForDataStoreChanges(TaxonNavigatorE4.this);
272 }
273 if (CdmStore.isActive()) {
274 cdmEntitySession = CdmStore.getCurrentSessionManager().newSession(this, true);
275 CdmApplicationState.getCurrentDataChangeService().register(this);
276 }
277 CdmStore.getLoginManager().addObserver(this);
278 viewer.setInput(getInitialInput());
279 }
280
281 //Link with taxon selection
282 @Inject
283 @Optional
284 public void updateCurrentTaxon(@UIEventTopic(WorkbenchEventConstants.CURRENT_ACTIVE_EDITOR)ITaxonEditor editor){
285 if(linkWithTaxon && editor!=null){
286 viewer.refresh();
287 TaxonNodeDto taxonNode = null;
288 if(editor.getTaxon()!=null && editor.getTaxon().getTaxonNodes()!=null){
289 if (editor instanceof TaxonNameEditorE4){
290 taxonNode = new TaxonNodeDto( ((TaxonNameEditorE4)editor).getEditorInput().getTaxonNode());
291 }else{
292 taxonNode = new TaxonNodeDto(editor.getTaxon().getTaxonNodes().iterator().next());
293 }
294 viewer.reveal(taxonNode);
295 viewer.setSelection(new StructuredSelection(taxonNode));
296 }
297 }
298 }
299
300 public void setLinkWithTaxon(boolean linkWithTaxon) {
301 this.linkWithTaxon = linkWithTaxon;
302 }
303
304 public boolean isLinkWithTaxon() {
305 return linkWithTaxon;
306 }
307
308 /**
309 * Refresh this navigators viewer
310 */
311 public void refresh() {
312 if(getConversationHolder() != null){
313 getConversationHolder().bind();
314 //FIXME : Need to make sure this is a stable fix (ticket 3822)
315 if(!getConversationHolder().isCompleted()){
316 getConversationHolder().commit();
317 }
318 }
319 if(!viewer.getTree().isDisposed()){
320 viewer.refresh();
321 }
322 updateSyncButton();
323 }
324
325 /**
326 * Refresh this navigators viewer
327 */
328 public void refresh(Set<?> objects) {
329 for(Object obj : objects) {
330 viewer.refresh(obj);
331 }
332 updateSyncButton();
333 }
334
335 /**
336 * Refresh this navigators viewer
337 */
338 public void refresh(Object object) {
339 viewer.refresh(object);
340 updateSyncButton();
341 }
342
343 /**
344 * Removes all content
345 */
346 public void clear() {
347 viewer.setInput(new EmptyRoot());
348 }
349
350 private void restore(IMemento memento, IProgressMonitor monitor) {
351 root = new Root(conversation);
352 if (memento == null) {
353 viewer.setInput(root);
354 return;
355 }
356 int mementoWork = 0;
357 Set<TreePath> treePaths = new HashSet<TreePath>();
358 IMemento[] treePathMementos = null;
359
360 IMemento treePathsMemento = memento.getChild(TREE_PATHS);
361
362 if (treePathsMemento != null) {
363 treePathMementos = treePathsMemento.getChildren(TREE_PATH);
364 mementoWork = treePathMementos.length;
365 }
366 // begin the monitor with steps for all tree paths and steps for
367 // creating
368 // conversation s.o., refreshing the tree and setting the paths
369 IProgressMonitor subProgressMonitor = AbstractUtility
370 .getSubProgressMonitor(monitor, 1);
371
372 subProgressMonitor.beginTask(RESTORING_TAXON_NAVIGATOR,
373 1 + mementoWork + 5);
374 subProgressMonitor.subTask(RESTORING_TAXON_NAVIGATOR);
375 subProgressMonitor.worked(1);
376
377 conversation = CdmStore.createConversation();
378 subProgressMonitor.worked(1);
379 conversation.registerForDataStoreChanges(TaxonNavigatorE4.this);
380 subProgressMonitor.worked(1);
381 viewer.setInput(root);
382 subProgressMonitor.worked(1);
383 viewer.refresh();
384 subProgressMonitor.worked(1);
385
386 if (treePathMementos != null && treePathMementos.length > 0) {
387 for (IMemento treePathMemento : treePathMementos) {
388 TreePath treePath = createTreePathFromString(treePathMemento
389 .getID());
390 if (!subProgressMonitor.isCanceled() && treePath != null) {
391 treePaths.add(treePath);
392 subProgressMonitor.worked(1);
393 }
394 }
395 }
396 if (treePaths.size() > 0) {
397 TaxonNavigatorE4.this.viewer.setExpandedTreePaths(
398 treePaths.toArray(new TreePath[0]));
399 subProgressMonitor.worked(1);
400 }
401 subProgressMonitor.done();
402 }
403
404 private TreePath createTreePathFromString(String string) {
405
406 List<CdmBase> pathList = new ArrayList<CdmBase>();
407
408 if (string.length() == 0) {
409 return null;
410 }
411
412 for (String uuid : string.split(" ")) { //$NON-NLS-1$
413 CdmBase cdmBaseObject = CdmStore.getService(
414 IClassificationService.class).getTaxonNodeByUuid(
415 UUID.fromString(uuid));
416 if (cdmBaseObject == null) {
417 // is this a tree uuid?
418 cdmBaseObject = CdmStore.getService(
419 IClassificationService.class).load(
420 UUID.fromString(uuid));
421
422 if (cdmBaseObject == null) {
423 return null;
424 }
425 }
426 pathList.add(cdmBaseObject);
427 }
428 return new TreePath(pathList.toArray());
429 }
430
431 /**
432 * {@inheritDoc}
433 */
434 @Override
435 public void collapse() {
436 viewer.collapseAll();
437 }
438
439 /**
440 * {@inheritDoc}
441 */
442 @Override
443 public void expand() {
444 viewer.expandAll();
445 }
446
447 @Override
448 public ConversationHolder getConversationHolder() {
449 return conversation;
450 }
451
452 /** {@inheritDoc} */
453 @PreDestroy
454 public void dispose() {
455 dataChangeBehavior = null;
456 if (conversation != null) {
457 conversation.unregisterForDataStoreChanges(this);
458 conversation.close();
459 }
460 if(cdmEntitySession != null) {
461 cdmEntitySession.dispose();
462 cdmEntitySession = null;
463 }
464 if(CdmApplicationState.getCurrentDataChangeService() != null) {
465 CdmApplicationState.getCurrentDataChangeService().unregister(this);
466 }
467 }
468
469 /** {@inheritDoc} */
470 @Focus
471 public void setFocus() {
472 if (getConversationHolder() != null) {
473 getConversationHolder().bind();
474 }
475 if(cdmEntitySession != null) {
476 cdmEntitySession.bind();
477 }
478 }
479
480 public UISynchronize getSync() {
481 return sync;
482 }
483
484 public TreeViewer getViewer() {
485 return viewer;
486 }
487
488 public UndoContext getUndoContext() {
489 return undoContext;
490 }
491
492 /** {@inheritDoc} */
493 @Override
494 public boolean postOperation(CdmBase objectAffectedByOperation) {
495 viewer.refresh();
496 return true;
497 }
498
499 @Override
500 public boolean onComplete() {
501 return true;
502 }
503
504 @Override
505 public void update(Observable o, Object arg) {
506 if(o instanceof LoginManager){
507 refresh();
508 }
509
510 }
511 /** {@inheritDoc} */
512 @Override
513 public void update(CdmDataChangeMap changeEvents) {
514 if (dataChangeBehavior == null) {
515 dataChangeBehavior = new TaxonNavigatorDataChangeBehaviorE4(this);
516 }
517
518 DataChangeBridge.handleDataChange(changeEvents, dataChangeBehavior);
519 updateSyncButton();
520
521 }
522
523 @Override
524 public ICdmEntitySession getCdmEntitySession() {
525 return cdmEntitySession;
526 }
527
528 @Override
529 public List<TaxonNodeDto> getRootEntities() {
530 if(root != null) {
531 return root.getParentBeans();
532 }
533 return null;
534 }
535
536 @Override
537 public void onChange(CdmChangeEvent event) {
538 refresh();
539 for(CdmBase cb : event.getChangedObjects()) {
540 if(cb instanceof TaxonNode) {
541 TaxonNode tn = (TaxonNode)cb;
542 if(tn.getTaxon() == null) {
543 viewer.refresh(tn.getClassification());
544 } else {
545 viewer.refresh(cb);
546 }
547 } else if (cb instanceof Classification) {
548 viewer.refresh();
549 }
550 }
551 }
552
553 @Override
554 public Map<Object, List<String>> getPropertyPathsMap() {
555 Map<Object, List<String>> propertyPathsMap = new HashMap<Object, List<String>>();
556 List<String> taxonNodePropertyPaths = Arrays.asList(new String[] {
557 "taxon.name" //$NON-NLS-1$
558 });
559 propertyPathsMap.put("childNodes", taxonNodePropertyPaths); //$NON-NLS-1$
560 return propertyPathsMap;
561 }
562
563 /**
564 * {@inheritDoc}
565 */
566 @Override
567 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
568 // TODO Auto-generated method stub
569
570 }
571
572 /**
573 * {@inheritDoc}
574 */
575 @Override
576 public void contextStop(IMemento memento, IProgressMonitor monitor) {
577 }
578
579 /**
580 * {@inheritDoc}
581 */
582 @Override
583 public void contextStart(IMemento memento, IProgressMonitor monitor) {
584 if(viewer!=null && viewer.getControl()!=null && !viewer.getControl().isDisposed()){
585 init();
586 }
587 }
588
589 /**
590 * {@inheritDoc}
591 */
592 @Override
593 public void contextRefresh(IProgressMonitor monitor) {
594 }
595
596 /**
597 * {@inheritDoc}
598 */
599 @Override
600 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
601 }
602
603 @Inject
604 @Optional
605 private void updateView(@UIEventTopic(WorkbenchEventConstants.REFRESH_NAVIGATOR)boolean refresh){
606 if(refresh){
607 refresh();
608 }
609 }
610 }