fix #8582: fix refresh in 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.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 if (editor.getTaxon().getTaxonNodes() != null && !editor.getTaxon().getTaxonNodes().isEmpty()){
302 taxonNode = new TaxonNodeDto(editor.getTaxon().getTaxonNodes().iterator().next());
303 }
304 }
305 if (taxonNode != null){
306 viewer.reveal(taxonNode);
307 viewer.setSelection(new StructuredSelection(taxonNode));
308 }else{
309 //TODO: show message in status bar
310 }
311
312 }
313 }
314 }
315
316 public void setLinkWithTaxon(boolean linkWithTaxon) {
317 this.linkWithTaxon = linkWithTaxon;
318 }
319
320 public boolean isLinkWithTaxon() {
321 return linkWithTaxon;
322 }
323
324 /**
325 * Refresh this navigators viewer
326 */
327 public void refresh() {
328 if(getConversationHolder() != null){
329 getConversationHolder().bind();
330 //FIXME : Need to make sure this is a stable fix (ticket 3822)
331 if(!getConversationHolder().isCompleted()){
332 getConversationHolder().commit();
333 }
334 }
335 if(!viewer.getTree().isDisposed()){
336 viewer.setInput(getInitialInput());
337 viewer.refresh();
338 }
339
340 updateSyncButton();
341 }
342
343 /**
344 * Refresh this navigators viewer
345 */
346 public void refresh(Set<?> objects) {
347 for(Object obj : objects) {
348 viewer.refresh(obj);
349 }
350 updateSyncButton();
351 }
352
353 /**
354 * Refresh this navigators viewer
355 */
356 public void refresh(Object object) {
357 viewer.refresh(object);
358 updateSyncButton();
359 }
360
361 /**
362 * Removes all content
363 */
364 public void clear() {
365 viewer.setInput(new EmptyRoot());
366 }
367
368 private void restore(IMemento memento, IProgressMonitor monitor) {
369 root = new Root(conversation);
370 if (memento == null) {
371 viewer.setInput(root);
372 return;
373 }
374 int mementoWork = 0;
375 Set<TreePath> treePaths = new HashSet<TreePath>();
376 IMemento[] treePathMementos = null;
377
378 IMemento treePathsMemento = memento.getChild(TREE_PATHS);
379
380 if (treePathsMemento != null) {
381 treePathMementos = treePathsMemento.getChildren(TREE_PATH);
382 mementoWork = treePathMementos.length;
383 }
384 // begin the monitor with steps for all tree paths and steps for
385 // creating
386 // conversation s.o., refreshing the tree and setting the paths
387 IProgressMonitor subProgressMonitor = AbstractUtility
388 .getSubProgressMonitor(monitor, 1);
389
390 subProgressMonitor.beginTask(RESTORING_TAXON_NAVIGATOR,
391 1 + mementoWork + 5);
392 subProgressMonitor.subTask(RESTORING_TAXON_NAVIGATOR);
393 subProgressMonitor.worked(1);
394
395 conversation = CdmStore.createConversation();
396 subProgressMonitor.worked(1);
397 conversation.registerForDataStoreChanges(TaxonNavigatorE4.this);
398 subProgressMonitor.worked(1);
399 viewer.setInput(root);
400 subProgressMonitor.worked(1);
401 viewer.refresh();
402 subProgressMonitor.worked(1);
403
404 if (treePathMementos != null && treePathMementos.length > 0) {
405 for (IMemento treePathMemento : treePathMementos) {
406 TreePath treePath = createTreePathFromString(treePathMemento
407 .getID());
408 if (!subProgressMonitor.isCanceled() && treePath != null) {
409 treePaths.add(treePath);
410 subProgressMonitor.worked(1);
411 }
412 }
413 }
414 if (treePaths.size() > 0) {
415 TaxonNavigatorE4.this.viewer.setExpandedTreePaths(
416 treePaths.toArray(new TreePath[0]));
417 subProgressMonitor.worked(1);
418 }
419 subProgressMonitor.done();
420 }
421
422 private TreePath createTreePathFromString(String string) {
423
424 List<CdmBase> pathList = new ArrayList<CdmBase>();
425
426 if (string.length() == 0) {
427 return null;
428 }
429
430 for (String uuid : string.split(" ")) { //$NON-NLS-1$
431 CdmBase cdmBaseObject = CdmStore.getService(
432 ITaxonNodeService.class).find(
433 UUID.fromString(uuid));
434 if (cdmBaseObject == null) {
435 // is this a tree uuid?
436 cdmBaseObject = CdmStore.getService(
437 IClassificationService.class).load(
438 UUID.fromString(uuid));
439
440 if (cdmBaseObject == null) {
441 return null;
442 }
443 }
444 pathList.add(cdmBaseObject);
445 }
446 return new TreePath(pathList.toArray());
447 }
448
449 /**
450 * {@inheritDoc}
451 */
452 @Override
453 public void collapse() {
454 viewer.collapseAll();
455 }
456
457 /**
458 * {@inheritDoc}
459 */
460 @Override
461 public void expand() {
462 viewer.expandAll();
463 }
464
465 @Override
466 public ConversationHolder getConversationHolder() {
467 return conversation;
468 }
469
470 /** {@inheritDoc} */
471 @PreDestroy
472 public void dispose() {
473 dataChangeBehavior = null;
474 if (conversation != null) {
475 conversation.unregisterForDataStoreChanges(this);
476 conversation.close();
477 }
478 if(cdmEntitySession != null) {
479 cdmEntitySession.dispose();
480 cdmEntitySession = null;
481 }
482 if(CdmApplicationState.getCurrentDataChangeService() != null) {
483 CdmApplicationState.getCurrentDataChangeService().unregister(this);
484 }
485 }
486
487 /** {@inheritDoc} */
488 @Focus
489 public void setFocus() {
490 if (getConversationHolder() != null) {
491 getConversationHolder().bind();
492 }
493 if(cdmEntitySession != null) {
494 cdmEntitySession.bind();
495 }
496 }
497
498 public UISynchronize getSync() {
499 return sync;
500 }
501
502 public TreeViewer getViewer() {
503 return viewer;
504 }
505
506 public UndoContext getUndoContext() {
507 return undoContext;
508 }
509
510 /** {@inheritDoc} */
511 @Override
512 public boolean postOperation(Object objectAffectedByOperation) {
513 viewer.refresh();
514 return true;
515 }
516
517 @Override
518 public boolean onComplete() {
519 return true;
520 }
521
522 @Override
523 public void update(Observable o, Object arg) {
524 if(o instanceof LoginManager){
525 refresh();
526 }
527
528 }
529 /** {@inheritDoc} */
530 @Override
531 public void update(CdmDataChangeMap changeEvents) {
532 if (dataChangeBehavior == null) {
533 dataChangeBehavior = new TaxonNavigatorDataChangeBehaviorE4(this);
534 }
535
536 DataChangeBridge.handleDataChange(changeEvents, dataChangeBehavior);
537 updateSyncButton();
538
539 }
540
541 @Override
542 public ICdmEntitySession getCdmEntitySession() {
543 return cdmEntitySession;
544 }
545
546 @Override
547 public List<TaxonNodeDto> getRootEntities() {
548 if(root != null) {
549 return root.getParentBeans();
550 }
551 return null;
552 }
553
554 @Override
555 public void onChange(CdmChangeEvent event) {
556 refresh();
557 for(CdmBase cb : event.getChangedObjects()) {
558 if(cb instanceof TaxonNode) {
559 TaxonNode tn = (TaxonNode)cb;
560 if(tn.getTaxon() == null) {
561 viewer.refresh(tn.getClassification());
562 } else {
563 viewer.refresh(cb);
564 }
565 } else if (cb instanceof Classification) {
566 if ( event.getAction().equals(Action.Create)){
567 root.addRootNode((Classification)cb);
568 } else if ( event.getAction().equals(Action.Delete)){
569 root.removeRootNode((Classification)cb);
570 }
571 viewer.refresh();
572 }
573 }
574 }
575
576 @Override
577 public Map<Object, List<String>> getPropertyPathsMap() {
578 Map<Object, List<String>> propertyPathsMap = new HashMap<Object, List<String>>();
579 List<String> taxonNodePropertyPaths = Arrays.asList(new String[] {
580 "taxon.name" //$NON-NLS-1$
581 });
582 propertyPathsMap.put("childNodes", taxonNodePropertyPaths); //$NON-NLS-1$
583 return propertyPathsMap;
584 }
585
586 /**
587 * {@inheritDoc}
588 */
589 @Override
590 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
591 // TODO Auto-generated method stub
592
593 }
594
595 /**
596 * {@inheritDoc}
597 */
598 @Override
599 public void contextStop(IMemento memento, IProgressMonitor monitor) {
600 }
601
602 /**
603 * {@inheritDoc}
604 */
605 @Override
606 public void contextStart(IMemento memento, IProgressMonitor monitor) {
607 if(viewer!=null && viewer.getControl()!=null && !viewer.getControl().isDisposed()){
608 init();
609 }
610 }
611
612 /**
613 * {@inheritDoc}
614 */
615 @Override
616 public void contextRefresh(IProgressMonitor monitor) {
617 }
618
619 /**
620 * {@inheritDoc}
621 */
622 @Override
623 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
624 }
625
626 @Inject
627 @Optional
628 private void updateView(@UIEventTopic(WorkbenchEventConstants.REFRESH_NAVIGATOR)boolean refresh){
629 if(refresh){
630 refresh();
631 }
632 }
633 }