Project

General

Profile

Download (44 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2017 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
package eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix;
10

    
11
import java.io.File;
12
import java.util.ArrayList;
13
import java.util.Collection;
14
import java.util.Collections;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.LinkedList;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Map.Entry;
21
import java.util.Properties;
22
import java.util.Set;
23
import java.util.TreeSet;
24
import java.util.UUID;
25
import java.util.stream.Collectors;
26

    
27
import javax.inject.Inject;
28

    
29
import org.apache.commons.collections4.map.LinkedMap;
30
import org.eclipse.core.runtime.ICoreRunnable;
31
import org.eclipse.core.runtime.IProgressMonitor;
32
import org.eclipse.core.runtime.SubMonitor;
33
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
34
import org.eclipse.core.runtime.jobs.Job;
35
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
36
import org.eclipse.e4.core.di.annotations.Optional;
37
import org.eclipse.e4.ui.di.UIEventTopic;
38
import org.eclipse.e4.ui.di.UISynchronize;
39
import org.eclipse.e4.ui.services.EMenuService;
40
import org.eclipse.jface.layout.GridDataFactory;
41
import org.eclipse.jface.viewers.IStructuredSelection;
42
import org.eclipse.jface.viewers.StructuredSelection;
43
import org.eclipse.nebula.widgets.nattable.NatTable;
44
import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
45
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
46
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
47
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
48
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
49
import org.eclipse.nebula.widgets.nattable.copy.command.InternalCopyDataCommandHandler;
50
import org.eclipse.nebula.widgets.nattable.copy.command.InternalPasteDataCommandHandler;
51
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
52
import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;
53
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
54
import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
55
import org.eclipse.nebula.widgets.nattable.extension.e4.selection.E4SelectionListener;
56
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
57
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
58
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
59
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
60
import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
61
import org.eclipse.nebula.widgets.nattable.freeze.FreezeHelper;
62
import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
63
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
64
import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand;
65
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
66
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
67
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
68
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
69
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
70
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
71
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
72
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
73
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupGroupHeaderLayer;
74
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer;
75
import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel;
76
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
77
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
78
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
79
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
80
import org.eclipse.nebula.widgets.nattable.persistence.gui.PersistenceDialog;
81
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
82
import org.eclipse.nebula.widgets.nattable.selection.command.SelectRowsCommand;
83
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
84
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
85
import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
86
import org.eclipse.nebula.widgets.nattable.tooltip.NatTableContentTooltip;
87
import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
88
import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
89
import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandToLevelCommand;
90
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
91
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
92
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction;
93
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
94
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
95
import org.eclipse.swt.SWT;
96
import org.eclipse.swt.layout.GridData;
97
import org.eclipse.swt.layout.GridLayout;
98
import org.eclipse.swt.widgets.Button;
99
import org.eclipse.swt.widgets.Composite;
100
import org.eclipse.swt.widgets.Event;
101
import org.eclipse.swt.widgets.Menu;
102

    
103
import ca.odell.glazedlists.BasicEventList;
104
import ca.odell.glazedlists.EventList;
105
import ca.odell.glazedlists.SortedList;
106
import ca.odell.glazedlists.TreeList;
107
import eu.etaxonomy.cdm.api.application.CdmApplicationState;
108
import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService;
109
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
110
import eu.etaxonomy.cdm.api.service.UpdateResult;
111
import eu.etaxonomy.cdm.api.service.config.RemoveDescriptionsFromDescriptiveDataSetConfigurator;
112
import eu.etaxonomy.cdm.api.service.dto.FieldUnitDTO;
113
import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
114
import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO;
115
import eu.etaxonomy.cdm.api.service.dto.TaxonRowWrapperDTO;
116
import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
117
import eu.etaxonomy.cdm.model.common.CdmBase;
118
import eu.etaxonomy.cdm.model.description.DescriptionBase;
119
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
120
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
121
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
122
import eu.etaxonomy.cdm.persistence.dto.DescriptiveDataSetBaseDto;
123
import eu.etaxonomy.cdm.persistence.dto.FeatureDto;
124
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
125
import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
126
import eu.etaxonomy.cdm.persistence.dto.TermDto;
127
import eu.etaxonomy.cdm.persistence.dto.TermNodeDto;
128
import eu.etaxonomy.cdm.persistence.dto.TermTreeDto;
129
import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
130
import eu.etaxonomy.taxeditor.editor.internal.TaxeditorEditorPlugin;
131
import eu.etaxonomy.taxeditor.editor.l10n.Messages;
132
import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
133
import eu.etaxonomy.taxeditor.model.MessagingUtils;
134
import eu.etaxonomy.taxeditor.operation.IFeedbackGenerator;
135
import eu.etaxonomy.taxeditor.session.ICdmEntitySession;
136
import eu.etaxonomy.taxeditor.store.CdmStore;
137
import eu.etaxonomy.taxeditor.workbench.WorkbenchUtility;
138

    
139
/**
140
 * Character matrix editor for editing specimen/taxon descriptions in a table
141
 * @author pplitzner
142
 * @since Nov 26, 2017
143
 */
144
public class CharacterMatrix extends Composite {
145

    
146
    private static final String CHARACTER_MATRIX_STATE_PROPERTIES = "characterMatrixState.properties"; //$NON-NLS-1$
147

    
148
    static final int LEADING_COLUMN_COUNT = 4;
149
    static final String TAXON_COLUMN = "taxon_column"; //$NON-NLS-1$
150
    static final String COLLECTOR_COLUMN = "collector_column"; //$NON-NLS-1$
151
    static final String IDENTIFIER_COLUMN = "identifier_column"; //$NON-NLS-1$
152
    static final String COUNTRY_COLUMN = "country_column"; //$NON-NLS-1$
153

    
154
    static final String LABEL_TAXON_ROW = "TAXON_ROW"; //$NON-NLS-1$
155
    static final String LABEL_TAXON_AGGREGATED_DESCRIPTION = "TAXON_AGGREGATED_DESCRIPTION"; //$NON-NLS-1$
156
    static final String LABEL_TAXON_AGGREGATED_DESCRIPTION_ICON = "TAXON_AGGREGATED_DESCRIPTION_ICON"; //$NON-NLS-1$
157
    static final String LABEL_TAXON_DEFAULT_DESCRIPTION = "TAXON_DEFAULT_DESCRIPTION"; //$NON-NLS-1$
158
    static final String LABEL_TAXON_DEFAULT_DESCRIPTION_ICON = "TAXON_DEFAULT_DESCRIPTION_ICON"; //$NON-NLS-1$
159
    static final String LABEL_TAXON_LITERATURE_DESCRIPTION = "TAXON_LITERATURE_DESCRIPTION"; //$NON-NLS-1$
160
    static final String LABEL_TAXON_LITERATURE_DESCRIPTION_ICON = "TAXON_LITERATURE_DESCRIPTION_ICON"; //$NON-NLS-1$
161
    static final String LABEL_TAXON_DESCRIPTION = "LABEL_TAXON_DESCRIPTION"; //$NON-NLS-1$
162
    static final String LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA = "LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA"; //$NON-NLS-1$
163

    
164
    boolean isFirstCall = true;
165
    @Inject
166
    private UISynchronize sync;
167

    
168
    @Inject
169
    private EMenuService menuService;
170

    
171
    private NatTable natTable;
172

    
173
    private HashMap<UUID, RowWrapperDTO<?>> rowsToMerge = new HashMap<>();
174

    
175
    private Map<Integer, FeatureDto> indexToFeatureMap = new HashMap<>();
176

    
177
    private Map<UUID, List<TermDto>> categoricalFeatureToStateMap = new HashMap<>();
178

    
179
    private LinkedMap<String, String> propertyToLabelMap = new LinkedMap<>();
180

    
181
    private EventList<Object> descriptions;
182

    
183
    private Collection<SpecimenNodeWrapper> specimenCache = null;
184

    
185
    private Map<FeatureDto, CategoricalDataHistogram> featureToHistogramMap = new HashMap<>();
186

    
187
    private ListDataProvider<Object> bodyDataProvider;
188

    
189
    private FreezeLayer freezeLayer;
190

    
191
    private List<FeatureDto> features;
192

    
193
    private CharacterMatrixPart part;
194

    
195
    private AbstractLayer topMostLayer;
196

    
197
    private ConfigRegistry configRegistry;
198

    
199
    private MatrixBodyLayerStack bodyLayer;
200

    
201
    private SelectionLayer selectionLayer;
202

    
203
    private boolean isTreeView = true;
204

    
205
    private CharacterMatrixToolbar toolbar;
206

    
207
    private DescriptionTreeFormat treeFormat;
208

    
209
    private Map<RemoveDescriptionsFromDescriptiveDataSetConfigurator, List<UUID>> descriptionUuidsToDelete;
210

    
211
    private List<SpecimenRowWrapperDTO> specimenToAdd;
212

    
213
    private MouseEventMatcher mouseEventMatcher = null;
214

    
215
    private Set<DescriptionBase<?>> descriptionsToSave = new HashSet<>();
216

    
217
    public CharacterMatrix(Composite parent, CharacterMatrixPart part) {
218
        super(parent, SWT.NONE);
219
        this.part = part;
220
        this.setLayout(new GridLayout());
221

    
222
        createToolBar();
223

    
224
        natTable = new NatTable(this, false);
225
    }
226

    
227
    private void createToolBar(){
228
        toolbar = new CharacterMatrixToolbar(this, SWT.NONE);
229
    }
230

    
231
    private void applyStyles(){
232
        ModernNatTableThemeConfiguration configuration = new ModernNatTableThemeConfiguration();
233
        // NOTE: Getting the colors and fonts from the GUIHelper ensures that
234
        // they are disposed properly (required by SWT)
235
        configuration.cHeaderBgColor = GUIHelper.getColor(211, 211, 211);
236
        configuration.rHeaderBgColor = GUIHelper.getColor(211, 211, 211);
237
        natTable.addConfiguration(configuration);
238
    }
239

    
240
    void toggleTreeFlat(boolean isTree, Button btnToggleFlat, Button btnToggleTree, Button btnCollapseAll, Button btnExpandAll, Button btnFreezeSuppInfo) {
241
        isTreeView = isTree;
242
        createTable(isTree, freezeLayer.isFrozen(), true);
243
        btnToggleFlat.setEnabled(isTree);
244
        btnToggleTree.setEnabled(!isTree);
245
        btnCollapseAll.setEnabled(isTree);
246
        btnExpandAll.setEnabled(isTree);
247
    }
248

    
249
    public boolean isTreeView() {
250
        return isTreeView;
251
    }
252

    
253
    public void createTable(boolean treeView, boolean freezeSupplementalColumns, boolean isInitialExpandToDeepestTaxonLevel){
254
        /**
255
         * layers
256
         */
257
        createLayers(treeView);
258

    
259
        /**
260
         * configuration
261
         */
262

    
263
        configureNatTable(treeView, configRegistry, topMostLayer);
264

    
265
        /**
266
         * handlers and listeners
267
         */
268
        registerHandlersAndListeners();
269

    
270
        //grab all space
271
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
272

    
273
        //update label to current dataset
274
        toolbar.getWsLabel().setText(getDescriptiveDataSet().getTitleCache());
275
        toolbar.getWsLabel().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
276
        toolbar.getWsLabel().getParent().layout();
277

    
278
        //initial freeze of supplemental columns
279
        freezeSupplementalColumns(freezeSupplementalColumns);
280

    
281

    
282
        //add tooltip to table
283
//        new CategoricalChartTooltip(this);
284
//        new QuantitativeChartTooltip(this);
285

    
286

    
287
        this.layout();
288
        natTable.doCommand(new ClientAreaResizeCommand(natTable));
289

    
290
        // expand all taxa
291
        if(isInitialExpandToDeepestTaxonLevel){
292
            Integer deepestTaxonLevel = treeFormat.getDeepestTaxonLevel();
293
            if(deepestTaxonLevel!=null){
294
                natTable.doCommand(new TreeExpandToLevelCommand(deepestTaxonLevel-2));
295
            }
296
        }
297
        new NatTableContentTooltip(natTable, GridRegion.BODY) {
298
    		protected String getText(Event event) {
299
    			int col = this.natTable.getColumnPositionByX(event.x);
300
    	        int row = this.natTable.getRowPositionByY(event.y);
301

    
302
    	        ILayerCell cell = this.natTable.getCellByPosition(col, row);
303
    	        if (cell.getConfigLabels().getLabels().contains(CharacterMatrixConfigLabelAccumulator.NOT_EDITABLE)){
304
    	        	Object o = bodyDataProvider.getRowObject(row);
305
    	        	String object = "";
306
    	        	if (o instanceof TaxonNodeDto || o instanceof TaxonRowWrapperDTO){
307
    	        		object = "taxon";
308
    	        	}else {
309
    	        		object = "specimen";
310
    	        	}
311
    	        		
312
    	        	return "Multiple data exist. Editing only possible in factual data view of " + object;
313
    	        }
314
    			return null;
315
    		}
316
    	};
317
    
318

    
319
        // clean up table state
320
        getNatTableState().remove(NatTable.INITIAL_PAINT_COMPLETE_FLAG);
321
        getNatTableState().remove(PersistenceDialog.ACTIVE_VIEW_CONFIGURATION_KEY);
322
    }
323

    
324
    private List<FeatureDto> initFeatureList(TermNodeDto node){
325
        List<FeatureDto> features = new ArrayList<>();
326
        List<TermNodeDto> childNodes = node.getChildren();
327
        for (TermNodeDto childNode : childNodes) {
328
            if (childNode != null){
329
                features.add((FeatureDto) childNode.getTerm());
330
                features.addAll(initFeatureList(childNode));
331
            }
332
        }
333
        return features;
334
    }
335

    
336
    public void initDescriptiveDataSet(){
337
        //get features/columns stored in descriptive dataset
338
        TermTreeDto tree = getDescriptiveDataSet().getDescriptiveSystem();
339
        features = initFeatureList(tree.getRoot());
340

    
341
        Set<FeatureDto> duplicateFeatures = features.stream().filter(i -> Collections.frequency(features, i) >1)
342
        .collect(Collectors.toSet());
343

    
344
        if (!duplicateFeatures.isEmpty()) {
345
            throw new IllegalArgumentException("Duplicate features found: "
346
                    + duplicateFeatures.stream().map(feature -> feature.getRepresentation_L10n()).collect(Collectors.joining(",")));
347
        }
348

    
349
        //init state data for categorical features
350
//        features.forEach(feature->fetchSupportedStates(feature));
351
        fetchSupportedStates(features);
352

    
353
        descriptions = new BasicEventList<>();
354

    
355
    }
356

    
357
    private void fetchSupportedStates(List<FeatureDto> features) {
358
        Set<UUID> featureUuids = new HashSet<>();
359
        features.forEach(i->featureUuids.add(i.getUuid()));
360
        categoricalFeatureToStateMap = CdmStore.getService(IDescriptiveDataSetService.class).getSupportedStatesForFeature(featureUuids);
361

    
362
    }
363

    
364

    
365

    
366
    private void createLayers(boolean treeView) {
367

    
368
        SortedList<Object> sortedList = new SortedList<>(descriptions, new MatrixRowComparator());
369
        // wrap the SortedList with the TreeList
370
        treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet());
371
        @SuppressWarnings("unchecked")
372
        TreeList<Object> treeList = new TreeList<Object>(sortedList, treeFormat, TreeList.NODES_START_COLLAPSED);
373
        // wrap the SortedList with the TreeList
374
//        treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet());
375

    
376
        /**
377
         * data provider
378
         */
379
        SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
380
        bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor);
381

    
382
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
383
        bodyDataLayer.registerCommandHandler(new CopyPasteUpdateDataCommandHandler(bodyDataLayer));
384

    
385

    
386
        /**
387
         * BODY layer
388
         *
389
         *
390

    
391
        ViewportLayer
392

    
393
             ^
394
        TreeLayer (default visible)
395

    
396
             ^
397
        CompositeFreezeLayer
398
         - viewportLayer
399
         - selectionLayer
400
         - freezeLayer
401

    
402
             ^
403
        FreezeLayer
404

    
405
             ^
406
        SelectionLayer
407

    
408
             ^
409
        ColumnHideShowLayer
410

    
411
             ^
412
        DataLayer
413

    
414
         *
415

    
416
         */
417

    
418
        configRegistry = new ConfigRegistry();
419
        //register labels
420
        CharacterMatrixConfigLabelAccumulator labelAccumulator = new CharacterMatrixConfigLabelAccumulator(this);
421
        bodyDataLayer.setConfigLabelAccumulator(labelAccumulator);
422

    
423

    
424
        propertyToLabelMap.put(TAXON_COLUMN, Messages.CharacterMatrix_TAXON);
425
        propertyToLabelMap.put(COLLECTOR_COLUMN, Messages.CharacterMatrix_COLLECTOR_NO);
426
        propertyToLabelMap.put(IDENTIFIER_COLUMN, Messages.CharacterMatrix_IDENTIFIER);
427
        propertyToLabelMap.put(COUNTRY_COLUMN, Messages.CharacterMatrix_COUNTRY);
428
        for(int i=0;i<features.size();i++){
429
            FeatureDto feature = features.get(i);
430
            initLabels(i, feature);
431
        }
432

    
433
        // layer for event handling of GlazedLists and PropertyChanges
434
        GlazedListsEventLayer<Object> eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, (EventList<Object>)bodyDataProvider.getList());
435

    
436
        GlazedListTreeData<?> treeData = new GlazedListTreeData<>(treeList);
437
        ITreeRowModel<?> treeRowModel = new GlazedListTreeRowModel<>(treeData);
438

    
439
        // assemble the column groups
440
        LinkedList<ColumnGroupWrapper> columnGroups = new LinkedList<>();
441
        List<TermNodeDto> rootChildren = getDescriptiveDataSet().getDescriptiveSystem().getRoot().getChildren();
442
        buildHeader(rootChildren, columnGroups);
443

    
444
        bodyLayer = new MatrixBodyLayerStack(eventLayer, columnGroups);
445
        selectionLayer = bodyLayer.getSelectionLayer();
446
        freezeLayer = new FreezeLayer(selectionLayer);
447
        final CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(
448
                freezeLayer, bodyLayer.getViewportLayer(), selectionLayer);
449
        TreeLayer treeLayer = null;
450
        if (treeView){
451
            treeLayer = new TreeLayer(compositeFreezeLayer, treeRowModel);
452
        }
453

    
454
        topMostLayer = treeView?treeLayer:compositeFreezeLayer;
455

    
456

    
457
        /**
458
         * column header layer
459
         */
460
        IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
461
                propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
462
        DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
463
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, topMostLayer, selectionLayer);
464

    
465
        ILayer topHeaderLayer = columnHeaderLayer;
466

    
467
        if(!columnGroups.isEmpty()){
468
            // first group level
469
            ColumnGroupWrapper groupLevel1 = columnGroups.get(0);
470
            ColumnGroupHeaderLayer groupLayerLevel1 = null;
471
            ColumnGroupGroupHeaderLayer groupLayerLevel2 = null;
472
            groupLayerLevel1 = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer, groupLevel1.getModel());
473
            for (Entry<TermNodeDto, TreeSet<Integer>> entry: groupLevel1.getColumnGroupToIndexMap().entrySet()) {
474
                TermNodeDto group = entry.getKey();
475
                TreeSet<Integer> indexList = entry.getValue();
476
                int[] intArray = indexList.stream().mapToInt(Integer::intValue).toArray();
477
                groupLayerLevel1.addColumnsIndexesToGroup(group.getTerm().getTitleCache(), intArray);
478
                groupLayerLevel1.setGroupUnbreakable(indexList.iterator().next());
479
            }
480
            topHeaderLayer = groupLayerLevel1;
481

    
482
            // second group level
483
            if(columnGroups.size()>1){
484
                ColumnGroupWrapper groupLevel2 = columnGroups.get(1);
485
                groupLayerLevel2 = new ColumnGroupGroupHeaderLayer(groupLayerLevel1, selectionLayer, groupLevel2.getModel());
486
                for (Entry<TermNodeDto, TreeSet<Integer>> entry: groupLevel2.getColumnGroupToIndexMap().entrySet()) {
487
                    TermNodeDto group = entry.getKey();
488
                    TreeSet<Integer> indexList = entry.getValue();
489
                    int[] intArray = indexList.stream().mapToInt(Integer::intValue).toArray();
490
                    groupLayerLevel2.addColumnsIndexesToGroup(group.getTerm().getTitleCache(), intArray);
491
                    groupLayerLevel2.setGroupUnbreakable(indexList.iterator().next());
492
                }
493
                topHeaderLayer = groupLayerLevel2;
494
            }
495
        }
496

    
497
        // add the SortHeaderLayer to the column header layer stack
498
        // as we use GlazedLists, we use the GlazedListsSortModel which
499
        // delegates the sorting to the SortedList
500
        final SortHeaderLayer<DescriptionBase<?>> sortHeaderLayer = new SortHeaderLayer<>(
501
                topHeaderLayer,
502
                new GlazedListsSortModel<>(
503
                        sortedList,
504
                        columnPropertyAccessor,
505
                        configRegistry,
506
                        columnHeaderDataLayer));
507

    
508
        /**
509
         * row header layer
510
         */
511
        IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
512
        DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
513
        RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer,
514
                topMostLayer, selectionLayer);
515

    
516
        /**
517
         * corner layer
518
         */
519
        ILayer cornerLayer = new CornerLayer(
520
                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
521
                rowHeaderLayer, sortHeaderLayer);
522

    
523
        /**
524
         * GRID layer (composition of all other layers)
525
         */
526
        GridLayer gridLayer = new GridLayer(topMostLayer, sortHeaderLayer, rowHeaderLayer, cornerLayer);
527
        natTable.setLayer(gridLayer);
528
    }
529

    
530
    private TreeSet<Integer> recurseChildIndexes(TermNodeDto node){
531
        TreeSet<Integer> childIndexes = new TreeSet<>();
532
        if(node.getChildren().size() >0){
533
            List<TermNodeDto> childNodes = node.getChildren();
534
            for (TermNodeDto childNode: childNodes) {
535
                if (childNode != null){
536
                    childIndexes.addAll(recurseChildIndexes(childNode));
537
                }
538
            }
539
        }
540
        childIndexes.add(features.indexOf(node.getTerm())+LEADING_COLUMN_COUNT);
541
        return childIndexes;
542
    }
543

    
544
    private void buildHeader(List<TermNodeDto> nodes, LinkedList<ColumnGroupWrapper> columnGroups){
545
        Map<TermNodeDto, TreeSet<Integer>> columnGroupToIndexMap = new HashMap<>();
546
        List<TermNodeDto> childNodes = new ArrayList<>();
547
        for (TermNodeDto node : nodes) {
548
            if (node != null){
549
                TreeSet<Integer> childIndexes = recurseChildIndexes(node);
550
                if(childIndexes.size()>1){
551
                    // filter out groups that only have one member
552
                    columnGroupToIndexMap.put(node, childIndexes);
553
                }
554
                childNodes.addAll(node.getChildren());
555
            }
556
        }
557
        if(!columnGroupToIndexMap.isEmpty()){
558
            columnGroups.addFirst(new ColumnGroupWrapper(new ColumnGroupModel(), columnGroupToIndexMap));
559
        }
560
        if(!childNodes.isEmpty()){
561
            buildHeader(childNodes, columnGroups);
562
        }
563
    }
564

    
565
    private void registerHandlersAndListeners() {
566
        natTable.registerCommandHandler(new ExportCommandHandler(natTable));
567

    
568
        //selection listener
569
        E4SelectionListener<Object> selectionListener = new CellSelectionListener(part.getSelectionService(),
570
                bodyLayer.getSelectionLayer(), bodyDataProvider, part);
571
        bodyLayer.getSelectionLayer().addLayerListener(selectionListener);
572
        selectionListener.setFullySelectedRowsOnly(false);
573

    
574
        //register handler for view configuration menu
575
        natTable.registerCommandHandler(toolbar.getDisplayPersistenceDialogCommandHandler());
576

    
577
        //register handlers for copy&paste
578
        natTable.registerCommandHandler(
579
                new InternalPasteDataCommandHandler(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard()));
580
        natTable.registerCommandHandler(
581
                new InternalCopyDataCommandHandler(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard()));
582
    }
583

    
584
    private void configureNatTable(boolean treeView,
585
            ConfigRegistry configRegistry,
586
            AbstractLayer topMostLayer) {
587
        /**
588
         * CONFIGURATION
589
         */
590
      //+++CONTEXT MENU+++
591
        menuService.registerContextMenu(natTable, "eu.etaxonomy.taxeditor.editor.popupmenu.charactermatrix"); //$NON-NLS-1$
592
        // get the menu registered by EMenuService
593
        final Menu e4Menu = natTable.getMenu();
594

    
595
        natTable.setConfigRegistry(configRegistry);
596

    
597
        applyStyles();
598

    
599
        //add default configuration because autoconfigure is set to false in constructor
600
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
601

    
602
        // this is for DEBUG ONLY
603
//                natTable.addConfiguration(new DebugMenuConfiguration(natTable));
604

    
605
        // override the default sort configuration and change the mouse bindings
606
        // to sort on a single click
607
        if(!treeView){
608
            natTable.addConfiguration(new SingleClickSortConfiguration());
609
        }
610

    
611
        natTable.addConfiguration(new CharacterMatrixLabelStyleConfiguration());
612

    
613
        // add the header menu configuration for adding the column header menu
614
        // with hide/show actions
615
//        natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable));
616

    
617
        // add custom configuration for data conversion and add column labels to viewport layer
618
        topMostLayer.addConfiguration(new CellEditorDataConversionConfiguration(this));
619

    
620
        //copy&paste configuration
621
        natTable.addConfiguration(new CopyPasteEditBindings(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard()));
622

    
623

    
624
        // remove the menu reference from NatTable instance
625
        natTable.setMenu(null);
626

    
627

    
628
//        natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable));
629

    
630

    
631
        natTable.configure();
632

    
633

    
634
        natTable.addConfiguration(
635
                    new AbstractUiBindingConfiguration() {
636
                @Override
637
                public void configureUiBindings(
638
                        UiBindingRegistry uiBindingRegistry) {
639
                    // add e4 menu to NatTable
640
                    uiBindingRegistry.unregisterMouseDownBinding(mouseEventMatcher);
641
                    if (isFirstCall){
642
	                    new PopupMenuBuilder(natTable, e4Menu)
643
	                    .withHideColumnMenuItem()
644
	                    .withShowAllColumnsMenuItem()
645
	                    .build();
646
	                    isFirstCall = false;
647
                    }else{
648
                    	new PopupMenuBuilder(natTable, e4Menu)
649
	                    .build();
650
                    }
651
                    // register the UI binding for header, corner and body region
652
                    mouseEventMatcher = new MouseEventMatcher(
653
                            SWT.NONE,
654
                            null,
655
                            MouseEventMatcher.RIGHT_BUTTON);
656
                    uiBindingRegistry.registerMouseDownBinding(mouseEventMatcher,
657
                            new PopupMenuAction(e4Menu));
658
                }
659
            });
660

    
661
    }
662

    
663
    void freezeSupplementalColumns(boolean freeze){
664
        int rightMostFreezeColumIndex = LEADING_COLUMN_COUNT-1;
665
        Collection<Integer> hiddenColumnIndexes = bodyLayer.getColumnHideShowLayer().getHiddenColumnIndexes();
666
        for (Integer integer : hiddenColumnIndexes) {
667
            if(integer<LEADING_COLUMN_COUNT){
668
                rightMostFreezeColumIndex--;
669
            }
670
        }
671
        if(freeze){
672
            FreezeHelper.freeze(freezeLayer, bodyLayer.getViewportLayer(),
673
                    new PositionCoordinate(bodyLayer.getViewportLayer(), 0, 0),
674
                    new PositionCoordinate(bodyLayer.getViewportLayer(), rightMostFreezeColumIndex, -1));
675
        }
676
        else{
677
            FreezeHelper.unfreeze(freezeLayer, bodyLayer.getViewportLayer());
678
        }
679
    }
680

    
681
    private void initLabels(int index, FeatureDto feature) {
682
        indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
683

    
684
        String label = feature.getTitleCache();
685
        String property = feature.getUuid().toString();
686
        //show unit for quantitative data
687
        if(feature.isSupportsQuantitativeData()){
688
            Set<TermDto> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
689
//            if(recommendedMeasurementUnits.size()>1){
690
//                MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class,
691
//                        String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel()));
692
//            }
693
            if(recommendedMeasurementUnits.size()==1){
694
                TermDto unit = recommendedMeasurementUnits.iterator().next();
695
                label += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
696
            }
697
        }
698
        propertyToLabelMap.put(property, label);
699
    }
700

    
701
    public void loadDescriptions(boolean isInitialExpandToDeepestTaxonLevel, boolean initialLoading) {
702
    	
703
        UUID monitorUuid =  CdmApplicationState.getLongRunningTasksService().monitGetRowWrapper(this.getDescriptiveDataSet().getUuid());
704

    
705
        final Collection<RowWrapperDTO<?>> wrappers = new ArrayList<>();
706
        String jobLabel = Messages.CharacterMatrix_LOAD_CHARACTER_DATA;
707
        Job job = Job.create(jobLabel, (ICoreRunnable) monitor -> {
708
            SubMonitor subMonitor = SubMonitor.convert(monitor);
709
            subMonitor.beginTask(jobLabel, IProgressMonitor.UNKNOWN);
710
            IRemotingProgressMonitor remotingMonitor;
711
            try {
712
                 remotingMonitor = CdmStore.getProgressMonitorClientManager()
713
                .pollMonitor(jobLabel,
714
                        monitorUuid,
715
                        50,
716
                        null,
717
                        (List<IFeedbackGenerator>)null,
718
                        subMonitor);
719
            } catch (InterruptedException e) {
720
                MessagingUtils.informationDialog(Messages.CharacterMatrix_LOADING_FAILED_TITLE,
721
                        Messages.CharacterMatrix_LOADING_FAILED_MESSAGE);
722
                return;
723
            }
724
            Object result = remotingMonitor.getResult();
725
            if(result instanceof Collection){
726
                wrappers.addAll((Collection<RowWrapperDTO<?>>) result);
727
            }
728
            if(result instanceof Exception){
729
                MessagingUtils.errorDialog("Exception during description loading", this.getClass(), "An exception occured during loading", TaxeditorEditorPlugin.PLUGIN_ID, (Throwable) result, true);
730
            }
731
            else if(wrappers.isEmpty()){
732
                MessagingUtils.informationDialog(Messages.CharacterMatrix_NO_DESCRIPTION_TITLE,
733
                        Messages.CharacterMatrix_NO_DESCRIPTION_MESSAGE);
734
            }
735
            monitor.done();
736
        });
737
        job.addJobChangeListener(new JobChangeAdapter(){
738
            @Override
739
            public void done(IJobChangeEvent event) {
740
                sync.syncExec(()->{
741
                    List<RowWrapperDTO> rowsWithoutTaxonNode = wrappers.stream().filter(row->row.getTaxonNode()==null).collect(Collectors.toList());
742
                    if(!rowsWithoutTaxonNode.isEmpty()){
743
                        String collect = rowsWithoutTaxonNode.stream().
744
                        map(row->row.getDescription().toString())
745
                        .collect(Collectors.joining("\n\n - ")); //$NON-NLS-1$
746
                        MessagingUtils.warningDialog(
747
                                Messages.CharacterMatrix_NO_NODE_FOUND_TITLE,
748
                                this.getClass(),
749
                                String.format(Messages.CharacterMatrix_NO_NODE_FOUND_MESSAGE, collect)
750
                                );
751
                    }
752
                    descriptions.clear();
753
//                    part.setDescriptiveDataSet(CdmStore.getService(IDescriptiveDataSetService.class).getDescriptiveDataSetDtoByUuid(part.getDescriptiveDataSet().getUuid()));
754
                    wrappers.stream().filter(row->row.getTaxonNode()!=null).forEach(wrapper->CharacterMatrix.this.descriptions.add(wrapper));
755
                    if(initialLoading){
756
                        loadingDone(isInitialExpandToDeepestTaxonLevel);
757
                    }
758
                });
759
            }
760
        });
761
        job.schedule();
762
    }
763

    
764
    public IStructuredSelection getSelection(){
765
        Set<Range> selectedRowPositions = bodyLayer.getSelectionLayer().getSelectedRowPositions();
766
        List<Object> selectedObjects = new ArrayList<>();
767
        for (Range range : selectedRowPositions) {
768
            for(int i=range.start;i<range.end;i++){
769
                selectedObjects.add(bodyDataProvider.getRowObject(i));
770
            }
771
        }
772
        return new StructuredSelection(selectedObjects);
773
    }
774

    
775
    private void loadingDone(boolean isInitialExpandToDeepestTaxonLevel) {
776
        this.part.loadingDone();
777
        createTable(isTreeView, freezeLayer.isFrozen(), isInitialExpandToDeepestTaxonLevel);
778

    
779
    }
780

    
781
    public List<TermDto> getSupportedStatesForCategoricalFeature(UUID featureUuid){
782
        return categoricalFeatureToStateMap.get(featureUuid);
783
    }
784

    
785
    public Set<DescriptionBase<?>> getDescriptionsToSave() {
786
        return descriptionsToSave;
787
    }
788

    
789
    public void addDescriptionToSave(DescriptionBase<?> descriptionToSave) {
790
        this.descriptionsToSave.add(descriptionToSave);
791
    }
792

    
793
    public Map<Integer, FeatureDto> getIndexToFeatureMap() {
794
        return indexToFeatureMap;
795
    }
796

    
797
    public LinkedMap<String, String> getPropertyToLabelMap() {
798
        return propertyToLabelMap;
799
    }
800

    
801
    public void setDirty() {
802
        part.setDirty();
803
    }
804

    
805
    public Map<RemoveDescriptionsFromDescriptiveDataSetConfigurator, List<UUID>> getDescriptionsToDelete() {
806
        return descriptionUuidsToDelete;
807
    }
808

    
809
    public void addDescriptionToDelete(UUID descriptionToDelete, RemoveDescriptionsFromDescriptiveDataSetConfigurator config) {
810
        if (descriptionUuidsToDelete == null){
811
            descriptionUuidsToDelete = new HashMap<>();
812
        }
813
        if (descriptionUuidsToDelete.get(config) != null){
814
            descriptionUuidsToDelete.get(config).add(descriptionToDelete);
815
        }else{
816
            List<UUID> uuidList = new ArrayList<>();
817
            uuidList.add(descriptionToDelete);
818
            this.descriptionUuidsToDelete.put(config, uuidList);
819
        }
820

    
821

    
822
    }
823

    
824
    public List<SpecimenRowWrapperDTO> getSpecimenToAdd() {
825
        return specimenToAdd;
826
    }
827

    
828
    public void addSpecimenToAdd(Collection<SpecimenRowWrapperDTO> specimenToAdd) {
829
        if (this.specimenToAdd == null){
830
            this.specimenToAdd = new ArrayList<>();
831
        }
832

    
833
        this.specimenToAdd.addAll(specimenToAdd);
834
        this.specimenCache.removeAll(specimenToAdd);
835
    }
836
    public void addSpecimenToAdd(SpecimenRowWrapperDTO specimenToAdd) {
837
        if (this.specimenToAdd == null){
838
            this.specimenToAdd = new ArrayList<>();
839
        }
840

    
841
        this.specimenToAdd.add(specimenToAdd);
842
        this.specimenCache.remove(specimenToAdd);
843
    }
844

    
845
    public CharacterMatrixPart getPart() {
846
        return part;
847
    }
848

    
849
    public NatTable getNatTable() {
850
        return natTable;
851
    }
852

    
853
    public EventList<Object> getDescriptions() {
854
        return descriptions;
855
    }
856

    
857
    public DescriptiveDataSetBaseDto getDescriptiveDataSet() {
858
        return part.getDescriptiveDataSet();
859
    }
860

    
861
    public void setDescriptiveDataSet(DescriptiveDataSetBaseDto dataSet) {
862
        part.setDescriptiveDataSet(dataSet);
863
    }
864

    
865
    public Collection<SpecimenNodeWrapper> getSpecimenCache() {
866
             return specimenCache;
867
    }
868

    
869
    public void setSpecimenCache(Collection<SpecimenNodeWrapper> specimenCache) {
870
        this.specimenCache = specimenCache.stream()
871
                .filter(wrapper ->
872
        //map descriptions on a list of uuids of the described specimen
873
        !this.descriptions.stream()
874
        .filter(rowWrapper->rowWrapper instanceof SpecimenRowWrapperDTO)
875
        .map(specimenRowWrapper->((SpecimenRowWrapperDTO) specimenRowWrapper).getSpecimenDto().getUuid())
876
        .collect(Collectors.toList())
877
        //and check if the specimen to add is already contained
878
        .contains(wrapper.getUuidAndTitleCache().getUuid())
879
        )
880
        .collect(Collectors.toList());
881
    }
882

    
883
//    public void addRowToSave(RowWrapperDTO<?> row){
884
//        rowsToMerge.put(row.getDescription().getDescription().getUuid(), row);
885
//    }
886
//
887
//    public HashMap<UUID,RowWrapperDTO<?>> getRowsToSave() {
888
//        return rowsToMerge;
889
//    }
890

    
891
    public HashMap<UUID, RowWrapperDTO<?>> getRowsToMerge() {
892
        return rowsToMerge;
893
    }
894

    
895
    public void putRowToMerge(RowWrapperDTO<?> rowToMerge) {
896
        if (this.rowsToMerge == null){
897
            this.rowsToMerge = new HashMap<>();
898
        }
899
        this.rowsToMerge.put(rowToMerge.getDescription().getDescriptionUuid(), rowToMerge);
900
    }
901

    
902
    public Properties getNatTableState() {
903
        return toolbar.getNatTableState();
904
    }
905

    
906
    public ListDataProvider<Object> getBodyDataProvider() {
907
        return bodyDataProvider;
908
    }
909

    
910
    MatrixBodyLayerStack getBodyLayer() {
911
        return bodyLayer;
912
    }
913

    
914
    File getStatePropertiesFile() {
915
        return new File(WorkbenchUtility.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES);
916
    }
917

    
918
    public List<FeatureDto> getFeatures() {
919
        return features;
920
    }
921

    
922
    public Map<FeatureDto, CategoricalDataHistogram> getFeatureToHistogramMap() {
923
        return featureToHistogramMap;
924
    }
925

    
926

    
927

    
928
    public ICdmEntitySession getCdmEntitySession(){
929
        return part.getCdmEntitySession();
930
    }
931

    
932
    @Inject
933
    @Optional
934
    private void updateSpecimenList(@UIEventTopic(WorkbenchEventConstants.REFRESH_DESCRIPTIVE_DATASET)UUID uuid){
935
        if(uuid!= null && uuid.equals(part.getDescriptiveDataSet().getUuid())){
936
           this.specimenCache = null;
937

    
938
        }
939
    }
940

    
941
    public void addRowsToMatrix(Collection<SpecimenNodeWrapper> wrappers){
942

    
943
        List<UUID> specimenUuids = new ArrayList<>();
944
        wrappers.forEach(wrapper -> specimenUuids.add(wrapper.getUuidAndTitleCache().getUuid()));
945
//        List<SpecimenOrObservationBase> specimens = CdmStore.getService(IOccurrenceService.class).load(specimenUuids, null);
946
        Map<UUID, UuidAndTitleCache<FieldUnit>> specimenMap = new HashMap<>();
947

    
948
        for (UUID specimenUuid: specimenUuids ){
949
            try{
950
                FieldUnitDTO fieldUnitDto = CdmStore.getService(IOccurrenceService.class).loadFieldUnitDTO(specimenUuid);
951
                if (fieldUnitDto != null){
952
                    UuidAndTitleCache<FieldUnit> fieldUnit = new UuidAndTitleCache<>(FieldUnit.class, fieldUnitDto.getUuid(), null, fieldUnitDto.getLabel());
953
                    specimenMap.put(specimenUuid, fieldUnit);
954
                }
955
            }catch(Exception e){
956
                e.printStackTrace();
957
            }
958

    
959
        }
960
        for (SpecimenNodeWrapper wrapper: wrappers){
961
            SpecimenRowWrapperDTO rowWrapper = CdmStore.getService(IDescriptiveDataSetService.class).createSpecimenRowWrapper(wrapper.getUuidAndTitleCache().getUuid(), wrapper.getTaxonNode().getUuid(), getDescriptiveDataSet().getUuid());
962
            addSpecimenToAdd(rowWrapper);
963
//            SpecimenRowWrapperDTO rowWrapper = new SpecimenRowWrapperDTO(wrapper.getUuidAndTitleCache(), wrapper.getType(), wrapper.getTaxonNode(), specimenMap.get(wrapper.getUuidAndTitleCache().getUuid()), null, null);
964
            descriptions.add(rowWrapper);
965
        }
966

    
967

    
968
        setDirty();
969
    }
970

    
971
    public HashMap<UUID, DescriptionBase<?>> addSpecimensToDescriptiveDataSet(){
972
        if (specimenToAdd == null || specimenToAdd.isEmpty()){
973
            return new HashMap<>();
974
        }
975
        UpdateResult result = CdmStore.getService(IDescriptiveDataSetService.class).addRowWrapperToDataset(specimenToAdd, getDescriptiveDataSet().getUuid());
976
        if(!result.getExceptions().isEmpty()){
977
            MessagingUtils.warningDialog(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_TITLE, this,
978
                    String.format(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_MESSAGE, result.getExceptions()
979
                            .stream().map(ex->ex.toString())
980
                            .collect(Collectors.joining("\n"))));
981
        }
982
        DescriptiveDataSet dataSet = (DescriptiveDataSet) result.getCdmEntity();
983
        HashMap<UUID, DescriptionBase<?>> resultMap = new HashMap<>();
984
        for (CdmBase updated: result.getUpdatedObjects()){
985
            if (updated instanceof SpecimenDescription){
986
                resultMap.put(updated.getUuid(), (DescriptionBase<?>)updated);
987
            }
988

    
989

    
990
        }
991
        dataSet = this.getCdmEntitySession().load(dataSet, true);
992
        // update local dataset
993
        DescriptiveDataSetBaseDto dto = DescriptiveDataSetBaseDto.fromDescriptiveDataSet(dataSet);
994
        this.setDescriptiveDataSet(dto);
995

    
996
        //these descriptions are already updated
997
        for (SpecimenRowWrapperDTO row: specimenToAdd){
998
            this.rowsToMerge.remove(row.getDescription().getDescriptionUuid());
999
        }
1000
        specimenToAdd.clear();
1001
        return resultMap;
1002

    
1003
    }
1004
    public void setSelectedObject(Object selectedObject){
1005

    
1006
        int rowIndex = ((IRowDataProvider<Object>)bodyDataProvider).indexOfRowObject(selectedObject);
1007
    	int rowPosition = selectionLayer.getRowPositionByIndex(rowIndex);
1008
    	natTable.doCommand( new SelectRowsCommand(natTable, 1, rowPosition, false, false));
1009
    }
1010

    
1011
    public TreeList<Object> updateDescriptions(boolean treeView){
1012
    	SortedList<Object> sortedList = new SortedList<>(descriptions, new MatrixRowComparator());
1013
        // wrap the SortedList with the TreeList
1014
        treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet());
1015
        TreeList<Object> treeList = new TreeList<Object>(sortedList, treeFormat, TreeList.NODES_START_COLLAPSED);
1016
        // wrap the SortedList with the TreeList
1017
//        treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet());
1018

    
1019
        /**
1020
         * data provider
1021
         */
1022
        SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
1023
        bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor);
1024
        return treeList;
1025

    
1026
    }
1027

    
1028
    @Override
1029
    public void dispose () {
1030
        super.dispose();
1031
        if (descriptions != null){
1032
        	descriptions.dispose();
1033
        }
1034
        descriptions = null;
1035
        this.natTable.dispose();
1036
        this.natTable = null;
1037
        categoricalFeatureToStateMap = null;
1038
        rowsToMerge.clear();
1039
        rowsToMerge = null;
1040
        indexToFeatureMap = null;
1041
        propertyToLabelMap = null;
1042
        specimenCache = null;
1043
        featureToHistogramMap = null;
1044
        bodyDataProvider = null;
1045
        freezeLayer = null;
1046
        features = null;
1047
        topMostLayer.dispose();
1048
        topMostLayer = null;
1049
        configRegistry = null;
1050
        bodyLayer.dispose();
1051
        bodyLayer = null;
1052
        toolbar.dispose();
1053
        toolbar = null;
1054
        treeFormat = null;
1055
        descriptionUuidsToDelete = null;
1056
        specimenToAdd = null;
1057
        mouseEventMatcher = null;
1058
        descriptionsToSave = null;
1059
        menuService = null;
1060
        sync = null;
1061
        part = null;
1062

    
1063
    }
1064

    
1065
}
(4-4/18)