Merge branch 'hotfix/5.7.1'
[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.Collections;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12 import java.util.UUID;
13
14 import javax.annotation.PostConstruct;
15 import javax.annotation.PreDestroy;
16 import javax.inject.Inject;
17 import javax.inject.Named;
18
19 import org.eclipse.core.runtime.IProgressMonitor;
20 import org.eclipse.e4.core.contexts.ContextInjectionFactory;
21 import org.eclipse.e4.core.contexts.IEclipseContext;
22 import org.eclipse.e4.core.di.annotations.Optional;
23 import org.eclipse.e4.ui.di.Focus;
24 import org.eclipse.e4.ui.di.Persist;
25 import org.eclipse.e4.ui.model.application.ui.MDirtyable;
26 import org.eclipse.e4.ui.model.application.ui.basic.MPart;
27 import org.eclipse.e4.ui.services.EMenuService;
28 import org.eclipse.e4.ui.services.IServiceConstants;
29 import org.eclipse.e4.ui.workbench.modeling.EPartService;
30 import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
31 import org.eclipse.jface.util.LocalSelectionTransfer;
32 import org.eclipse.jface.viewers.ISelectionChangedListener;
33 import org.eclipse.jface.viewers.IStructuredSelection;
34 import org.eclipse.jface.viewers.StructuredSelection;
35 import org.eclipse.jface.viewers.TreeNode;
36 import org.eclipse.jface.viewers.TreeSelection;
37 import org.eclipse.jface.viewers.TreeViewer;
38 import org.eclipse.swt.SWT;
39 import org.eclipse.swt.dnd.DND;
40 import org.eclipse.swt.dnd.Transfer;
41 import org.eclipse.swt.layout.GridData;
42 import org.eclipse.swt.layout.GridLayout;
43 import org.eclipse.swt.widgets.Composite;
44 import org.eclipse.swt.widgets.Tree;
45 import org.eclipse.ui.IMemento;
46
47 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
48 import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
49 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
50 import eu.etaxonomy.cdm.api.service.ITaxonService;
51 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
52 import eu.etaxonomy.cdm.model.molecular.SingleRead;
53 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
54 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
55 import eu.etaxonomy.cdm.model.taxon.Taxon;
56 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
57 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
58 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
59 import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
60 import eu.etaxonomy.taxeditor.editor.EditorUtil;
61 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
62 import eu.etaxonomy.taxeditor.editor.name.e4.TaxonNameEditorE4;
63 import eu.etaxonomy.taxeditor.editor.view.derivate.searchFilter.DerivateSearchCompositeController;
64 import eu.etaxonomy.taxeditor.model.IContextListener;
65 import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
66 import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
67 import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
68 import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
69 import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
70 import eu.etaxonomy.taxeditor.model.MessagingUtils;
71 import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
72 import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
73 import eu.etaxonomy.taxeditor.session.ICdmEntitySessionEnabled;
74 import eu.etaxonomy.taxeditor.store.CdmStore;
75 import eu.etaxonomy.taxeditor.view.search.derivative.DerivateContentProvider;
76 import eu.etaxonomy.taxeditor.view.search.derivative.DerivateLabelProvider;
77 import eu.etaxonomy.taxeditor.workbench.part.ICollapsableExpandable;
78 import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
79
80 /**
81 * Displays the derivate hierarchy of the specimen specified in the editor input.
82 *
83 */
84 public class DerivateView implements IPartContentHasFactualData, IConversationEnabled,
85 ICdmEntitySessionEnabled, IDirtyMarkable, IPostOperationEnabled, IPartContentHasDetails, IPartContentHasSupplementalData, IPartContentHasMedia,
86 IContextListener, IE4SavablePart, ICollapsableExpandable {
87
88 private static final String SPECIMEN_EDITOR = Messages.DerivateView_SPECIMEN_EDITOR;
89
90 public static final String ID = "eu.etaxonomy.taxeditor.editor.view.derivate.DerivateView"; //$NON-NLS-1$
91 public static final String INPUT_ID = ID+".editorInput"; //$NON-NLS-1$
92
93 public static final String YOU_NEED_TO_SAVE_BEFORE_PERFORMING_THIS_ACTION = Messages.DerivateView_YOU_NEED_TO_SAVE;
94 public static final String VIEW_HAS_UNSAVED_CHANGES = Messages.DerivateView_UNSAVED_CHANGES;
95
96 private static final List<String> SPECIMEN_INIT_STRATEGY = Arrays.asList(new String[] {
97 "descriptions", //$NON-NLS-1$
98 "annotations", //$NON-NLS-1$
99 "markers", //$NON-NLS-1$
100 "credits", //$NON-NLS-1$
101 "extensions", //$NON-NLS-1$
102 "rights", //$NON-NLS-1$
103 "sources", //$NON-NLS-1$
104 "derivationEvents.derivatives.annotations", //$NON-NLS-1$
105 "derivationEvents.derivatives.markers", //$NON-NLS-1$
106 "derivationEvents.derivatives.credits", //$NON-NLS-1$
107 "derivationEvents.derivatives.extensions", //$NON-NLS-1$
108 "derivationEvents.derivatives.rights", //$NON-NLS-1$
109 "derivationEvents.derivatives.sources" //$NON-NLS-1$
110 });
111
112 private static final int WARN_THRESHOLD = 200;
113
114
115 private ConversationHolder conversation;
116
117 private TreeViewer viewer;
118
119 private final int dndOperations = DND.DROP_MOVE;
120
121 private DerivateLabelProvider labelProvider;
122
123 private DerivateContentProvider contentProvider;
124
125 private DerivateSearchCompositeController derivateSearchCompositeController;
126
127 /**
128 * A map with keys being the derivative entities belonging to the {@link UUID}s passed to the constructor
129 * and values being the root elements of the hierarchy (may be the same objects as the derivative entities)
130 */
131 private Map<SpecimenOrObservationBase<?>, SpecimenOrObservationBase<?>> derivateToRootEntityMap;
132
133 /**
134 * The set of root elements
135 */
136 private Set<SpecimenOrObservationBase<?>> rootElements;
137
138 private ICdmEntitySession cdmEntitySession;
139
140 /**
141 * <code>true</code> if this view is listening to selection changes
142 */
143 private boolean listenToSelectionChange;
144
145 private Taxon selectedTaxon;
146
147 @Inject
148 private ESelectionService selService;
149
150 @Inject
151 private MDirtyable dirty;
152
153 private ISelectionChangedListener selectionChangedListener;
154
155 @Inject
156 private MPart thisPart;
157
158 /**
159 * Default constructor
160 */
161 @Inject
162 public DerivateView() {
163 }
164
165 /**
166 * {@inheritDoc}
167 */
168 public void init(DerivateViewEditorInput editorInput){
169 this.derivateToRootEntityMap = new HashMap<>();
170 this.rootElements = new HashSet<>();
171
172 //init tree
173 Collection<UUID> derivativeUuids = editorInput.getDerivativeUuids();
174 checkWarnThreshold(derivativeUuids);
175 updateRootEntities(derivativeUuids);
176 //set taxon filter
177 derivateSearchCompositeController.setTaxonFilter(editorInput.getTaxonUuid());
178 //reset status bar
179 //TODO e4
180 // getEditorSite().getActionBars().getStatusLineManager().setMessage(""); //$NON-NLS-1$
181 }
182
183 @PostConstruct
184 public void createPartControl(Composite parent, EMenuService menuService,
185 IEclipseContext context) {
186 if (CdmStore.isActive()){
187 if(conversation == null){
188 conversation = CdmStore.createConversation();
189 }
190 if(cdmEntitySession == null){
191 cdmEntitySession = CdmStore.getCurrentSessionManager().newSession(this, true);
192 }
193 }
194 else{
195 return;
196 }
197 //listen to context changes
198 CdmStore.getContextManager().addContextListener(this);
199
200 parent.setLayout(new GridLayout());
201
202 //---search and filter---
203 derivateSearchCompositeController = new DerivateSearchCompositeController(parent, this);
204 GridData gridDataSearchBar = new GridData();
205 gridDataSearchBar.horizontalAlignment = GridData.FILL;
206 gridDataSearchBar.grabExcessHorizontalSpace = true;
207 derivateSearchCompositeController.setLayoutData(gridDataSearchBar);
208 derivateSearchCompositeController.setEnabled(CdmStore.isActive());
209
210 //---tree viewer---
211 viewer = new TreeViewer(new Tree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION));
212 GridData gridDataTree = new GridData();
213 gridDataTree.horizontalAlignment = GridData.FILL;
214 gridDataTree.verticalAlignment = GridData.FILL;
215 gridDataTree.grabExcessVerticalSpace = true;
216 gridDataTree.grabExcessHorizontalSpace = true;
217 viewer.getTree().setLayoutData(gridDataTree);
218 contentProvider = new DerivateContentProvider();
219 viewer.setContentProvider(contentProvider);
220 labelProvider = new DerivateLabelProvider();
221 labelProvider.setConversation(conversation);
222 viewer.setLabelProvider(labelProvider);
223 viewer.getTree().setEnabled(CdmStore.isActive());
224
225 //propagate selection
226 selectionChangedListener = (event -> selService.setSelection(event.getSelection()));
227 viewer.addSelectionChangedListener(selectionChangedListener);
228
229 //create context menu
230 menuService.registerContextMenu(viewer.getControl(), "eu.etaxonomy.taxeditor.editor.popupmenu.specimeneditor");
231
232 //add drag'n'drop support
233 Transfer[] transfers = new Transfer[] {LocalSelectionTransfer.getTransfer(),};
234 viewer.addDragSupport(dndOperations, transfers, new DerivateDragListener(this));
235 DerivateDropListener dropListener = new DerivateDropListener(this);
236 ContextInjectionFactory.inject(dropListener, context);
237 viewer.addDropSupport(dndOperations, transfers, dropListener);
238 }
239
240 public void updateRootEntities() {
241 updateRootEntities((Collection)null);
242 }
243
244 public void updateRootEntities(Collection<UUID> derivativeUuids) {
245 if(conversation!=null){
246 if (!conversation.isBound()) {
247 conversation.bind();
248 }
249 /*
250 * If the active session is not the session of the Derivative Editor
251 * then we will save the active session for later, bind temporarily
252 * to our session and rebind to the original session when we are
253 * done. This happens e.g. if a selection change happens in the
254 * taxon editor and "Link with editor" is enabled. The selection
255 * change event and thus the loading in updateRootEntities() happens
256 * in the session of the taxon editor.
257 */
258 ICdmEntitySession previousCdmEntitySession = CdmStore.getCurrentSessionManager().getActiveSession();
259 if(cdmEntitySession != null) {
260 cdmEntitySession.bind();
261 }
262
263 List<SpecimenOrObservationBase> derivates = null;
264 if(derivativeUuids!=null){
265 this.derivateToRootEntityMap = new HashMap<>();
266 this.rootElements = new HashSet<>();
267 derivates = CdmStore.getService(IOccurrenceService.class).load(new ArrayList(derivativeUuids), SPECIMEN_INIT_STRATEGY);
268 }
269 updateRootEntities(derivates);
270 if(previousCdmEntitySession!=null){
271 previousCdmEntitySession.bind();
272 }
273 }
274 }
275
276
277 public void updateRootEntities(List<SpecimenOrObservationBase> derivates) {
278 if(derivates!=null){
279 this.derivateToRootEntityMap = new HashMap<>();
280 this.rootElements = new HashSet<>();
281 for (SpecimenOrObservationBase derivate : derivates) {
282
283 if(derivate instanceof FieldUnit){
284 derivateToRootEntityMap.put(derivate, derivate);
285 }
286 else {
287 SpecimenOrObservationBase<?> topMostDerivate = EditorUtil.getTopMostDerivate(derivate);
288 if(topMostDerivate!=null){
289 derivateToRootEntityMap.put(derivate, topMostDerivate);
290 }
291 else{
292 derivateToRootEntityMap.put(derivate, derivate);
293 }
294 }
295 }
296 for (SpecimenOrObservationBase<?> specimen : derivateToRootEntityMap.values()) {
297 rootElements.add(specimen);
298 }
299 }
300 labelProvider.updateLabelCache(rootElements);
301 viewer.setInput(rootElements);
302
303 //TODO e4
304 // getEditorSite().getActionBars().getStatusLineManager().setMessage(String.format(Messages.DerivateView_CNT_DERIVATIVES_FOUND, rootElements.size()));
305
306 //set selection to derivatives if the filter criteria
307 //taxon assignment or derivative type are set
308 if(derivates!=null && !derivateSearchCompositeController.isDefaultSearch()){
309 List<TreeNode> nodesToSelect = new ArrayList<>();
310 for (SpecimenOrObservationBase specimenOrObservationBase : derivates) {
311 nodesToSelect.add(new TreeNode(specimenOrObservationBase));
312 }
313 setSelection(new StructuredSelection(nodesToSelect));
314 }
315 else{
316 setSelection(null);
317 }
318 }
319
320 private void setSelection(StructuredSelection selection){
321 viewer.removeSelectionChangedListener(selectionChangedListener);
322 viewer.setSelection(selection);
323 viewer.addSelectionChangedListener(selectionChangedListener);
324 }
325
326 public void updateLabelCache(){
327 labelProvider.updateLabelCache(rootElements);
328 }
329
330 @Persist
331 @Override
332 public void save(IProgressMonitor monitor) {
333 String taskName = Messages.DerivateView_SAVING_HIERARCHY;
334 monitor.beginTask(taskName, 3);
335 if (!conversation.isBound()) {
336 conversation.bind();
337 if (!cdmEntitySession.isActive()){
338 cdmEntitySession.bind();
339 }
340 }
341 monitor.worked(1);
342
343 // commit the conversation and start a new transaction immediately
344 conversation.commit(true);
345
346 CdmStore.getService(IOccurrenceService.class).merge(new ArrayList<>(rootElements), true);
347
348 monitor.worked(1);
349
350 this.setDirty(false);
351 monitor.worked(1);
352 monitor.done();
353 dirty.setDirty(false);
354 refreshTree();
355 }
356
357 /**
358 * @param isDirty the isDirty to set
359 */
360 public void setDirty(boolean isDirty) {
361 dirty.setDirty(isDirty);
362 }
363
364 @Focus
365 public void setFocus() {
366 //make sure to bind again if maybe in another view the conversation was unbound
367 if(conversation!=null && !conversation.isBound()){
368 conversation.bind();
369 }
370 if(cdmEntitySession != null) {
371 cdmEntitySession.bind();
372 }
373 if(viewer!=null && !viewer.getControl().isDisposed()) {
374 viewer.getControl().setFocus();
375 selService.setSelection(viewer.getSelection());
376 }
377 }
378
379 @Override
380 public void update(CdmDataChangeMap changeEvents) {
381 }
382
383 @Override
384 public ConversationHolder getConversationHolder() {
385 return conversation;
386 }
387
388 @Override
389 public void changed(Object element) {
390 setDirty(true);
391 //firePropertyChange(IEditorPart.PROP_DIRTY);
392 viewer.update(new TreeNode(element), null);
393 viewer.refresh();
394 }
395
396 @Override
397 public void forceDirty() {
398 changed(null);
399 }
400
401 @Override
402 public Map<Object, List<String>> getPropertyPathsMap() {
403 List<String> specimenPropertyPaths = Arrays.asList(new String[] {
404 "descriptions", //$NON-NLS-1$
405 "derivationEvents.derivates", //$NON-NLS-1$
406 "annotations", //$NON-NLS-1$
407 "markers", //$NON-NLS-1$
408 "credits", //$NON-NLS-1$
409 "extensions", //$NON-NLS-1$
410 "rights", //$NON-NLS-1$
411 "sources" //$NON-NLS-1$
412 });
413 Map<Object, List<String>> specimenPropertyPathMap =
414 new HashMap<>();
415 specimenPropertyPathMap.put(SpecimenOrObservationBase.class,specimenPropertyPaths);
416 return specimenPropertyPathMap;
417 }
418
419 /**
420 * Refreshes the derivate hierarchy tree and expands the tree
421 * to show and select the given object.
422 *
423 * @param expandTo the object to which the tree should be expanded
424 */
425 public void refreshTree(Object expandTo){
426 refreshTree();
427 TreeSelection selection = (TreeSelection) viewer.getSelection();
428 viewer.expandToLevel(selection.getFirstElement(), 1);
429 viewer.setSelection(new StructuredSelection(new TreeNode(expandTo)));
430 }
431
432 /**
433 * Refreshes the derivate hierarchy tree
434 */
435 public void refreshTree(){
436 if(!viewer.getTree().isDisposed()){
437 viewer.refresh();
438 }
439 }
440
441 //FIXME:Remoting hack to make this work for remoting
442 //This should actually be resolved using remoting post operations
443 public void remove(Object obj) {
444 if (obj instanceof TreeNode){
445 obj = ((TreeNode)obj).getValue();
446 }
447 rootElements.remove(obj);
448 Object o = this.derivateToRootEntityMap.remove(obj);
449 viewer.setInput(rootElements);
450 }
451
452 /**
453 * @return a set of {@link SingleRead}s that have multiple parents
454 */
455 public Set<SingleRead> getMultiLinkSingleReads() {
456 return DerivateLabelProvider.getMultiLinkSingleReads();
457 }
458
459 public Object getSelectionInput() {
460 return selectedTaxon;
461 }
462
463 public DerivateLabelProvider getLabelProvider() {
464 return labelProvider;
465 }
466
467 @Override
468 public boolean postOperation(Object objectAffectedByOperation) {
469 refreshTree();
470 if(objectAffectedByOperation!=null){
471 changed(objectAffectedByOperation);
472 }
473 return true;
474 }
475
476 @Override
477 public boolean onComplete() {
478 return true;
479 }
480
481
482 @Override
483 public boolean canAttachMedia() {
484 return true;
485 }
486
487 public void addFieldUnit(FieldUnit fieldUnit) {
488 rootElements.add(fieldUnit);
489 derivateToRootEntityMap.put(fieldUnit, fieldUnit);
490 }
491
492 @Override
493 public ICdmEntitySession getCdmEntitySession() {
494 return cdmEntitySession;
495 }
496
497 @PreDestroy
498 public void dispose() {
499 if(conversation!=null){
500 conversation.close();
501 conversation = null;
502 }
503 if(cdmEntitySession != null) {
504 cdmEntitySession.dispose();
505 cdmEntitySession = null;
506 }
507 dirty.setDirty(false);
508 }
509
510
511 @Inject
512 @Optional
513 public void selectionChanged(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) IStructuredSelection selection,
514 @Named(IServiceConstants.ACTIVE_PART) MPart activePart, MPart thisPart){
515 if(activePart == thisPart || viewer==null){
516 return;
517 }
518 if(viewer.getTree().isDisposed()){
519 return;
520 }
521 if(listenToSelectionChange){
522 selectedTaxon = null;
523 if(activePart.getObject() instanceof TaxonNameEditorE4){
524 selectedTaxon = ((TaxonNameEditorE4) activePart.getObject()).getTaxon();
525 }
526 else if(selection != null){
527 Object selectedElement = selection.getFirstElement();
528 if(selectedElement instanceof TaxonNodeDto){
529 TaxonBase taxonBase = CdmStore.getService(ITaxonService.class).load(((TaxonNodeDto)selectedElement).getTaxonUuid());
530 if(HibernateProxyHelper.isInstanceOf(taxonBase, Taxon.class)){
531 selectedTaxon = HibernateProxyHelper.deproxy(taxonBase, Taxon.class);
532 }
533 else if(selectedElement instanceof TaxonNode){
534 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, TaxonNode.class).getTaxon();
535 }
536 else if(selectedElement instanceof Taxon){
537 selectedTaxon = HibernateProxyHelper.deproxy(selectedElement, Taxon.class);
538 }
539 }
540 }
541 if(selectedTaxon!=null){
542 Collection<SpecimenOrObservationBase> fieldUnits = CdmStore.getService(IOccurrenceService.class).listFieldUnitsByAssociatedTaxon(selectedTaxon, null, null);
543 Collection<UUID> uuids = new HashSet<>();
544 for (SpecimenOrObservationBase specimenOrObservationBase : fieldUnits) {
545 uuids.add(specimenOrObservationBase.getUuid());
546 }
547 checkWarnThreshold(uuids);
548 updateRootEntities(uuids);
549
550 thisPart.setLabel(SPECIMEN_EDITOR+": " + selectedTaxon.getName()); //$NON-NLS-1$
551 }
552 else{
553 updateRootEntities((Collection<UUID>)Collections.EMPTY_LIST);
554 }
555 }
556 }
557
558 private void checkWarnThreshold(Collection<UUID> uuids) {
559 if(uuids!=null && uuids.size()>WARN_THRESHOLD){
560 MessagingUtils.warningDialog(Messages.DerivateView_PERF_WARNING, this.getClass(), String.format(Messages.DerivateView_PERF_WARNING_MESSAGE, uuids.size()));
561 uuids.clear();
562 }
563 }
564
565 public TreeViewer getViewer() {
566 return viewer;
567 }
568
569 /**
570 * {@inheritDoc}
571 */
572 @Override
573 public List<SpecimenOrObservationBase<?>> getRootEntities() {
574 return new ArrayList<>(rootElements);
575 }
576
577 public void toggleListenToSelectionChange(MPart part) {
578 listenToSelectionChange = !listenToSelectionChange;
579 derivateSearchCompositeController.setEnabled(!listenToSelectionChange);
580 if(!listenToSelectionChange){
581 selectedTaxon = null;
582 part.setLabel(SPECIMEN_EDITOR);
583 }
584 else if(selectedTaxon==null){
585 part.setLabel(SPECIMEN_EDITOR+Messages.DerivateView_NO_TAXON_SELECTED);
586 }
587 }
588
589 public boolean isListenToSelectionChange(){
590 return listenToSelectionChange;
591 }
592
593 /**
594 * {@inheritDoc}
595 */
596 @Override
597 public void contextAboutToStop(IMemento memento, IProgressMonitor monitor) {
598 }
599
600 /**
601 * {@inheritDoc}
602 */
603 @Override
604 public void contextStop(IMemento memento, IProgressMonitor monitor) {
605 //close view when workbench closes
606 try{
607 thisPart.getContext().get(EPartService.class).hidePart(thisPart);
608 }
609 catch(Exception e){
610 //nothing
611 }
612 }
613
614 /**
615 * {@inheritDoc}
616 */
617 @Override
618 public void contextStart(IMemento memento, IProgressMonitor monitor) {
619 }
620
621 /**
622 * {@inheritDoc}
623 */
624 @Override
625 public void contextRefresh(IProgressMonitor monitor) {
626 }
627
628 /**
629 * {@inheritDoc}
630 */
631 @Override
632 public void workbenchShutdown(IMemento memento, IProgressMonitor monitor) {
633 }
634
635
636 public boolean isDirty() {
637 return dirty.isDirty();
638 }
639
640 @Override
641 public void collapse() {
642 viewer.collapseAll();
643 }
644
645 @Override
646 public void expand() {
647 viewer.expandAll();
648 }
649
650 }