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