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