Project

General

Profile

Download (42.1 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.HashMap;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Properties;
19
import java.util.Set;
20
import java.util.UUID;
21
import java.util.stream.Collectors;
22

    
23
import org.apache.commons.collections4.map.LinkedMap;
24
import org.eclipse.core.runtime.ICoreRunnable;
25
import org.eclipse.core.runtime.IProgressMonitor;
26
import org.eclipse.core.runtime.jobs.Job;
27
import org.eclipse.jface.layout.GridDataFactory;
28
import org.eclipse.jface.viewers.ComboViewer;
29
import org.eclipse.jface.viewers.StructuredSelection;
30
import org.eclipse.jface.window.Window;
31
import org.eclipse.nebula.widgets.nattable.NatTable;
32
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
33
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
34
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
35
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
36
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
37
import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
38
import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
39
import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
40
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
41
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
42
import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
43
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
44
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
45
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
46
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
47
import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
48
import org.eclipse.nebula.widgets.nattable.freeze.FreezeHelper;
49
import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
50
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
51
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
52
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
53
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
54
import org.eclipse.nebula.widgets.nattable.grid.data.FixedSummaryRowHeaderLayer;
55
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
56
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
57
import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
58
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
59
import org.eclipse.nebula.widgets.nattable.grid.layer.config.DefaultRowStyleConfiguration;
60
import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
61
import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
62
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
63
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
64
import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
65
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
66
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
67
import org.eclipse.nebula.widgets.nattable.layer.config.DefaultColumnHeaderStyleConfiguration;
68
import org.eclipse.nebula.widgets.nattable.layer.config.DefaultRowHeaderStyleConfiguration;
69
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
70
import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
71
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
72
import org.eclipse.nebula.widgets.nattable.selection.config.DefaultSelectionStyleConfiguration;
73
import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
74
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
75
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
76
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
77
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
78
import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
79
import org.eclipse.nebula.widgets.nattable.style.Style;
80
import org.eclipse.nebula.widgets.nattable.style.VerticalAlignmentEnum;
81
import org.eclipse.nebula.widgets.nattable.summaryrow.DefaultSummaryRowConfiguration;
82
import org.eclipse.nebula.widgets.nattable.summaryrow.FixedSummaryRowLayer;
83
import org.eclipse.nebula.widgets.nattable.summaryrow.ISummaryProvider;
84
import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowConfigAttributes;
85
import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer;
86
import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
87
import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
88
import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
89
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
90
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
91
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
92
import org.eclipse.swt.SWT;
93
import org.eclipse.swt.events.SelectionAdapter;
94
import org.eclipse.swt.events.SelectionEvent;
95
import org.eclipse.swt.graphics.Color;
96
import org.eclipse.swt.graphics.FontData;
97
import org.eclipse.swt.layout.GridData;
98
import org.eclipse.swt.layout.GridLayout;
99
import org.eclipse.swt.layout.RowLayout;
100
import org.eclipse.swt.widgets.Button;
101
import org.eclipse.swt.widgets.Composite;
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.IDescriptionService;
109
import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService;
110
import eu.etaxonomy.cdm.api.service.IOccurrenceService;
111
import eu.etaxonomy.cdm.api.service.IProgressMonitorService;
112
import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
113
import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
114
import eu.etaxonomy.cdm.model.description.CategoricalData;
115
import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
116
import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
117
import eu.etaxonomy.cdm.model.description.Feature;
118
import eu.etaxonomy.cdm.model.description.FeatureNode;
119
import eu.etaxonomy.cdm.model.description.FeatureTree;
120
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
121
import eu.etaxonomy.cdm.model.description.QuantitativeData;
122
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
123
import eu.etaxonomy.cdm.model.description.State;
124
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
125
import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
126
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataCellEditor;
127
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataDisplayConverter;
128
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataCellEditor;
129
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataDisplayConverter;
130
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.supplementalInfo.SupplementalInfoDisplayConverter;
131
import eu.etaxonomy.taxeditor.editor.l10n.Messages;
132
import eu.etaxonomy.taxeditor.model.ColorResources;
133
import eu.etaxonomy.taxeditor.model.DescriptionHelper;
134
import eu.etaxonomy.taxeditor.model.ImageResources;
135
import eu.etaxonomy.taxeditor.model.MessagingUtils;
136
import eu.etaxonomy.taxeditor.preference.Resources;
137
import eu.etaxonomy.taxeditor.store.CdmStore;
138
import eu.etaxonomy.taxeditor.workbench.WorkbenchUtility;
139

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

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

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

    
156
    private DescriptiveDataSet descriptiveDataSet;
157

    
158
    private NatTable natTable;
159

    
160
    private Map<Integer, Feature> indexToFeatureMap = new HashMap<>();
161

    
162
    private Map<Feature, List<State>> categoricalFeatureToStateMap = new HashMap<>();
163

    
164
    private LinkedMap<String, String> propertyToLabelMap = new LinkedMap<>();
165

    
166
    private EventList<Object> descriptions;
167

    
168
    private Collection<SpecimenNodeWrapper> specimenCache = null;
169

    
170
    private ListDataProvider<Object> bodyDataProvider;
171

    
172
    private FreezeLayer freezeLayer;
173

    
174
    private ViewportLayer viewportLayer;
175

    
176
    private List<Feature> features;
177

    
178
    private CharacterMatrixPart part;
179

    
180
    private AbstractLayer topMostLayer;
181

    
182
    private FixedSummaryRowLayer summaryRowLayer;
183

    
184
    private ConfigRegistry configRegistry;
185

    
186
    private DefaultBodyLayerStack bodyLayer;
187

    
188
    private boolean isTreeView = true;
189

    
190
    private CharacterMatrixToolbar toolbar;
191

    
192

    
193
    public CharacterMatrix(Composite parent, CharacterMatrixPart part) {
194
        super(parent, SWT.NONE);
195
        this.part = part;
196
        this.setLayout(new GridLayout());
197

    
198
        createToolBar();
199

    
200
        natTable = new NatTable(this, false);
201

    
202
        applyStyles();
203

    
204
        createBottomToolbar();
205

    
206
    }
207

    
208
    private void createToolBar(){
209
        toolbar = new CharacterMatrixToolbar(this, SWT.NONE);
210
    }
211

    
212
    private void createBottomToolbar() {
213
        Composite buttonPanel = new Composite(this, SWT.NONE);
214

    
215
        buttonPanel.setLayout(new RowLayout());
216
        GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonPanel);
217

    
218
        /**
219
         * Add description button
220
         */
221
        Button btnAddDescription = new Button(buttonPanel, SWT.PUSH);
222
        btnAddDescription.setImage(ImageResources.getImage(ImageResources.ADD_ICON_GREEN));
223
        btnAddDescription.addSelectionListener(new SelectionAdapter() {
224
            @Override
225
            public void widgetSelected(SelectionEvent e) {
226
                SpecimenSelectionDialog dialog = new SpecimenSelectionDialog(natTable.getShell(), CharacterMatrix.this);
227
                if(dialog.open()==Window.OK){
228
                    Collection<SpecimenNodeWrapper> wrappers = dialog.getSpecimen();
229
                    for (SpecimenNodeWrapper wrapper : wrappers) {
230
                        SpecimenOrObservationBase specimen = CdmStore.getService(IOccurrenceService.class).load(wrapper.getUuidAndTitleCache().getUuid());
231
                        SpecimenDescription description = getDescriptionForDescriptiveDataSet(specimen);
232
                        //description elements
233
                        Map<Feature, DescriptionElementBase> featureToElementMap = new HashMap<>();
234
                        Set<DescriptionElementBase> elements = description.getElements();
235
                        for (DescriptionElementBase descriptionElementBase : elements) {
236
                            Feature feature = descriptionElementBase.getFeature();
237
                            featureToElementMap.put(feature, descriptionElementBase);
238
                        }
239
                        RowWrapperDTO rowWrapper = CdmStore.getService(IDescriptiveDataSetService.class).createRowWrapper(description, descriptiveDataSet);
240
                        CharacterMatrix.this.descriptions.add(rowWrapper);
241
                        descriptiveDataSet.addDescription(description);
242
                        setDirty();
243
                        specimenCache.remove(wrapper);
244
                    }
245
                }
246
            }
247
        });
248
        /**
249
         * Remove description button
250
         */
251
        Button btnRemoveDescription = new Button(buttonPanel, SWT.PUSH);
252
        btnRemoveDescription.setImage(ImageResources.getImage(ImageResources.ACTIVE_DELETE_ICON));
253
        btnRemoveDescription.addSelectionListener(new SelectionAdapter() {
254
            @Override
255
            public void widgetSelected(SelectionEvent e) {
256
                int[] fullySelectedRowPositions = bodyLayer.getSelectionLayer().getFullySelectedRowPositions();
257
                List<RowWrapperDTO> toRemove = new ArrayList<>();
258
                for (int i : fullySelectedRowPositions) {
259
                    Object rowObject = bodyDataProvider.getRowObject(i);
260
                    if(rowObject instanceof RowWrapperDTO){
261
                        toRemove.add((RowWrapperDTO) rowObject);
262
                    }
263
                }
264
                toRemove.forEach(rowToRemove->{
265
                    CharacterMatrix.this.descriptions.remove(rowToRemove);
266
                    descriptiveDataSet.removeDescription(rowToRemove.getSpecimenDescription());
267
                    setDirty();
268
                });
269
            }
270
        });
271
    }
272

    
273
    private void applyStyles(){
274
        // NOTE: Getting the colors and fonts from the GUIHelper ensures that
275
        // they are disposed properly (required by SWT)
276
        DefaultNatTableStyleConfiguration natTableConfiguration = new DefaultNatTableStyleConfiguration();
277
        natTableConfiguration.bgColor = GUIHelper.getColor(249, 172, 7);
278
        natTableConfiguration.fgColor = GUIHelper.getColor(30, 76, 19);
279
        natTableConfiguration.hAlign = HorizontalAlignmentEnum.LEFT;
280
        natTableConfiguration.vAlign = VerticalAlignmentEnum.TOP;
281
//        natTableConfiguration.borderStyle = new BorderStyle(1, GUIHelper.getColor(249, 172, 7), LineStyleEnum.SOLID);
282

    
283
        // Setup even odd row colors - row colors override the NatTable default
284
        // colors
285
        DefaultRowStyleConfiguration rowStyleConfiguration = new DefaultRowStyleConfiguration();
286
        rowStyleConfiguration.oddRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_ODD);
287
        rowStyleConfiguration.evenRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_EVEN);
288

    
289
        // Setup selection styling
290
        DefaultSelectionStyleConfiguration selectionStyle = new DefaultSelectionStyleConfiguration();
291
//        selectionStyle.selectionFont = GUIHelper.getFont(new FontData("Verdana", 8, SWT.NORMAL));
292
//        selectionStyle.selectionBgColor = GUIHelper.getColor(217, 232, 251);
293
//        selectionStyle.selectionFgColor = GUIHelper.COLOR_BLACK;
294
//        selectionStyle.anchorBorderStyle = new BorderStyle(1, GUIHelper.COLOR_DARK_GRAY, LineStyleEnum.SOLID);
295
//        selectionStyle.anchorBgColor = GUIHelper.getColor(65, 113, 43);
296
        selectionStyle.selectedHeaderBgColor = GUIHelper.getColor(156, 209, 103);
297

    
298
        // Add all style configurations to NatTable
299
        natTable.addConfiguration(natTableConfiguration);
300
        natTable.addConfiguration(rowStyleConfiguration);
301
        natTable.addConfiguration(selectionStyle);
302

    
303
        // Column/Row header style and custom painters
304
        DefaultRowHeaderStyleConfiguration rowHeaderConfig = new DefaultRowHeaderStyleConfiguration();
305
        Color rowColumnColor = GUIHelper.getColor(230, 255, 255);
306
        rowHeaderConfig.bgColor = rowColumnColor;
307
        natTable.addConfiguration(rowHeaderConfig);
308
        DefaultColumnHeaderStyleConfiguration columnHeaderStyle = new DefaultColumnHeaderStyleConfiguration();
309
        columnHeaderStyle.bgColor = rowColumnColor;
310
        columnHeaderStyle.font = GUIHelper.getFont(new FontData("Verdana", 9, SWT.BOLD)); //$NON-NLS-1$
311
        natTable.addConfiguration(columnHeaderStyle);
312

    
313
    }
314

    
315
    void toggleTreeFlat(boolean isTree, Button btnToggleFlat, Button btnToggleTree, Button btnCollapseAll, Button btnExpandAll, Button btnFreezeSuppInfo) {
316
        isTreeView = isTree;
317
        createTable(isTree);
318
        btnToggleFlat.setEnabled(isTree);
319
        btnToggleTree.setEnabled(!isTree);
320
        btnCollapseAll.setEnabled(isTree);
321
        btnExpandAll.setEnabled(isTree);
322
    }
323

    
324
    public boolean isTreeView() {
325
        return isTreeView;
326
    }
327

    
328
    public void createTable(boolean treeView){
329
        /**
330
         * layers
331
         */
332
        createLayers(treeView);
333

    
334
        /**
335
         * configuration
336
         */
337
        configureNatTable(treeView, configRegistry, topMostLayer, summaryRowLayer);
338

    
339
        /**
340
         * handlers and listeners
341
         */
342
        registerHandlersAndListeners(topMostLayer);
343

    
344
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
345

    
346
        toolbar.getWsLabel().setText(descriptiveDataSet.getLabel());
347
        toolbar.getWsLabel().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
348
        toolbar.getWsLabel().getParent().layout();
349

    
350
        freezeSupplementalColumns(true);
351

    
352
        this.layout();
353
    }
354

    
355
    private List<Feature> initFeatureList(FeatureNode node){
356
        List<Feature> features = new ArrayList<>();
357
        node.getChildNodes().forEach(childNode->
358
                {
359
                    features.add(childNode.getFeature());
360
                    features.addAll(initFeatureList(childNode));
361
                });
362
        return features;
363
    }
364

    
365
    public void initDescriptiveDataSet(DescriptiveDataSet descriptiveDataSet){
366
        this.descriptiveDataSet = descriptiveDataSet;
367
        //get features/columns stored in descriptive data set
368
        FeatureTree tree = descriptiveDataSet.getDescriptiveSystem();
369
        features = initFeatureList(tree.getRoot());
370

    
371
        //init state data for categorical features
372
        features.forEach(feature->
373
        {
374
            if(feature.isSupportsCategoricalData()){
375
                List<State> supportedStates = new ArrayList<>();
376
                feature.getSupportedCategoricalEnumerations().forEach(voc->supportedStates.addAll(voc.getTerms()));
377
                categoricalFeatureToStateMap.put(feature, supportedStates);
378
            }
379
        });
380
        descriptions = new BasicEventList<>();
381

    
382
    }
383

    
384
    private void createLayers(boolean treeView) {
385
        // use the SortedList constructor with 'null' for the Comparator
386
        // because the Comparator will be set by configuration
387
        SortedList<Object> sortedList = new SortedList<>(descriptions, new MatrixRowComparator());
388
        // wrap the SortedList with the TreeList
389
        TreeList<Object> treeList = new TreeList(sortedList, new DescriptionTreeFormat(descriptiveDataSet), TreeList.NODES_START_EXPANDED);
390
        /**
391
         * data provider
392
         */
393
        SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
394
        bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor);
395

    
396
        configRegistry = new ConfigRegistry();
397

    
398

    
399
        /**
400
         * BODY layer
401
         *
402
         *
403

    
404
        CompositeLayer
405
         - (top) SummaryRowLayer
406
         - (bottom) ViewportLayer
407

    
408
             ^
409
        ViewportLayer
410

    
411
             ^
412
        TreeLayer (default visible)
413

    
414
             ^
415
        CompositeFreezeLayer
416
         - viewportLayer
417
         - selectionLayer
418
         - freezeLayer
419

    
420
             ^
421
        FreezeLayer
422

    
423
             ^
424
        SelectionLayer
425

    
426
             ^
427
        ColumnHideShowLayer
428

    
429
             ^
430
        ColumnReorderLayer
431

    
432
             ^
433
        DataLayer
434

    
435
         *
436

    
437
         */
438
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
439

    
440
        //register labels for columns
441
        ColumnOverrideLabelAccumulator bodyColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
442
        bodyDataLayer.setConfigLabelAccumulator(bodyColumnLabelAccumulator);
443
        propertyToLabelMap.put(TAXON_COLUMN, Messages.CharacterMatrix_TAXON);
444
        bodyColumnLabelAccumulator.registerColumnOverrides(0, TAXON_COLUMN);
445
        propertyToLabelMap.put(COLLECTOR_COLUMN, Messages.CharacterMatrix_COLLECTOR_NO);
446
        bodyColumnLabelAccumulator.registerColumnOverrides(1, COLLECTOR_COLUMN);
447
        propertyToLabelMap.put(IDENTIFIER_COLUMN, Messages.CharacterMatrix_IDENTIFIER);
448
        bodyColumnLabelAccumulator.registerColumnOverrides(2, IDENTIFIER_COLUMN);
449
        propertyToLabelMap.put(COUNTRY_COLUMN, Messages.CharacterMatrix_COUNTRY);
450
        bodyColumnLabelAccumulator.registerColumnOverrides(3, COUNTRY_COLUMN);
451
        for(int i=0;i<features.size();i++){
452
            Feature feature = features.get(i);
453
            initLabels(bodyColumnLabelAccumulator, i, feature);
454
        }
455

    
456
        // layer for event handling of GlazedLists and PropertyChanges
457
        GlazedListsEventLayer eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, treeList);
458
        GlazedListTreeData treeData = new GlazedListTreeData<>(treeList);
459
        ITreeRowModel treeRowModel = new GlazedListTreeRowModel<>(treeData);
460

    
461
        bodyLayer = new DefaultBodyLayerStack(
462
                eventLayer);
463
        viewportLayer = bodyLayer.getViewportLayer();
464
        final SelectionLayer selectionLayer = bodyLayer.getSelectionLayer();
465
        freezeLayer = new FreezeLayer(selectionLayer);
466
        final CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(
467
                freezeLayer, bodyLayer.getViewportLayer(), selectionLayer);
468
        TreeLayer treeLayer = new TreeLayer(compositeFreezeLayer, treeRowModel);
469

    
470
        topMostLayer = treeView?treeLayer:compositeFreezeLayer;
471

    
472
        summaryRowLayer = new FixedSummaryRowLayer(bodyDataLayer, topMostLayer, configRegistry, false);
473
        //regoster labels with summary prefix for summary layer
474
        ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
475
        summaryRowLayer.setConfigLabelAccumulator(summaryColumnLabelAccumulator);
476
        for(int i=0;i<features.size();i++){
477
            Feature feature = features.get(i);
478
            summaryColumnLabelAccumulator.registerColumnOverrides(
479
                    i+LEADING_COLUMN_COUNT,
480
                    SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+MatrixUtility.getProperty(feature));
481
        }
482
        // because the horizontal dependency is the ViewportLayer
483
        // we need to set the composite dependency to false
484
        summaryRowLayer.setHorizontalCompositeDependency(false);
485

    
486
        CompositeLayer composite = new CompositeLayer(1, 2);
487
        composite.setChildLayer("SUMMARY", summaryRowLayer, 0, 0); //$NON-NLS-1$
488
        composite.setChildLayer(GridRegion.BODY, topMostLayer, 0, 1);
489

    
490

    
491
        /**
492
         * column header layer
493
         */
494
        IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
495
                propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
496
        DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
497
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, topMostLayer, selectionLayer);
498

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

    
510

    
511
        /**
512
         * row header layer
513
         */
514
        IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
515
        DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
516
        FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer = new FixedSummaryRowHeaderLayer(rowHeaderDataLayer,
517
                composite, selectionLayer);
518
        fixedSummaryRowHeaderLayer.setSummaryRowLabel("\u2211"); //$NON-NLS-1$
519

    
520

    
521
        /**
522
         * corner layer
523
         */
524
        ILayer cornerLayer = new CornerLayer(
525
                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
526
                fixedSummaryRowHeaderLayer, sortHeaderLayer);
527

    
528

    
529
        /**
530
         * GRID layer (composition of all other layers)
531
         */
532
        GridLayer gridLayer = new GridLayer(composite, sortHeaderLayer, fixedSummaryRowHeaderLayer, cornerLayer);
533

    
534
        natTable.setLayer(gridLayer);
535

    
536
    }
537

    
538
    private void registerHandlersAndListeners(AbstractLayer topMostLayer) {
539
        // add the ExportCommandHandler to the ViewportLayer in order to make
540
        // exporting work
541
        topMostLayer.registerCommandHandler(new ExportCommandHandler(topMostLayer));
542

    
543
        //propagate single cell selection
544
        natTable.addLayerListener(new ILayerListener() {
545
            @Override
546
            public void handleLayerEvent(ILayerEvent event) {
547
                if(event instanceof CellSelectionEvent){
548
                    CellSelectionEvent cellSelectionEvent = (CellSelectionEvent)event;
549
                    int columnPosition = cellSelectionEvent.getColumnPosition();
550
                    if(columnPosition>LEADING_COLUMN_COUNT){
551
                        Collection<ILayerCell> selectedCells = cellSelectionEvent.getSelectionLayer().getSelectedCells();
552
                        StructuredSelection selection = new StructuredSelection();
553
                        if(selectedCells.size()==1){
554
                            ILayerCell cell = selectedCells.iterator().next();
555
                            Object dataValue = cell.getDataValue();
556
                            if(dataValue!=null){
557
                                selection = new StructuredSelection(dataValue);
558
                            }
559
                        }
560
                        part.getSelectionService().setSelection(selection);
561
                    }
562
                }
563
            }
564
        });
565

    
566
        //register handler for view configuration menu
567
        natTable.registerCommandHandler(toolbar.getDisplayPersistenceDialogCommandHandler());
568
    }
569

    
570
    private void configureNatTable(boolean treeView, ConfigRegistry configRegistry, AbstractLayer topMostLayer,
571
            FixedSummaryRowLayer summaryRowLayer) {
572
        /**
573
         * CONFIGURATION
574
         */
575
        natTable.setConfigRegistry(configRegistry);
576

    
577
        //add default configuration because autoconfigure is set to false in constructor
578
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
579

    
580
        //FIXME: this is for DEBUG ONLY
581
        //        natTable.addConfiguration(new DebugMenuConfiguration(natTable));
582

    
583
        // override the default sort configuration and change the mouse bindings
584
        // to sort on a single click
585
        if(!treeView){
586
            natTable.addConfiguration(new SingleClickSortConfiguration());
587
        }
588

    
589
        // add the header menu configuration for adding the column header menu
590
        // with hide/show actions
591
        natTable.addConfiguration(new AbstractHeaderMenuConfiguration(natTable) {
592

    
593
            @Override
594
            protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
595
                return super.createColumnHeaderMenu(natTable)
596
                        .withHideColumnMenuItem()
597
                        .withShowAllColumnsMenuItem();
598
            }
599

    
600
        });
601

    
602
        Style cellStyle = new Style();
603
        cellStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT);
604

    
605
        // add custom configuration for data conversion and add column labels to viewport layer
606
        topMostLayer.addConfiguration(new AbstractRegistryConfiguration() {
607
            @Override
608
            public void configureRegistry(IConfigRegistry configRegistry) {
609
                //add display converter for string representation
610
                configRegistry.registerConfigAttribute(
611
                        CellConfigAttributes.DISPLAY_CONVERTER,
612
                        new SupplementalInfoDisplayConverter(),
613
                        DisplayMode.NORMAL,
614
                        TAXON_COLUMN);
615
                configRegistry.registerConfigAttribute(
616
                        CellConfigAttributes.CELL_STYLE,
617
                        cellStyle,
618
                        DisplayMode.NORMAL,
619
                        TAXON_COLUMN);
620
                configRegistry.registerConfigAttribute(
621
                        CellConfigAttributes.DISPLAY_CONVERTER,
622
                        new SupplementalInfoDisplayConverter(),
623
                        DisplayMode.NORMAL,
624
                        COLLECTOR_COLUMN);
625
                configRegistry.registerConfigAttribute(
626
                        CellConfigAttributes.CELL_STYLE,
627
                        cellStyle,
628
                        DisplayMode.NORMAL,
629
                        COLLECTOR_COLUMN);
630
                configRegistry.registerConfigAttribute(
631
                        CellConfigAttributes.DISPLAY_CONVERTER,
632
                        new SupplementalInfoDisplayConverter(),
633
                        DisplayMode.NORMAL,
634
                        IDENTIFIER_COLUMN);
635
                configRegistry.registerConfigAttribute(
636
                        CellConfigAttributes.CELL_STYLE,
637
                        cellStyle,
638
                        DisplayMode.NORMAL,
639
                        IDENTIFIER_COLUMN);
640
                configRegistry.registerConfigAttribute(
641
                        CellConfigAttributes.DISPLAY_CONVERTER,
642
                        new SupplementalInfoDisplayConverter(),
643
                        DisplayMode.NORMAL,
644
                        COUNTRY_COLUMN);
645
                configRegistry.registerConfigAttribute(
646
                        CellConfigAttributes.CELL_STYLE,
647
                        cellStyle,
648
                        DisplayMode.NORMAL,
649
                        COUNTRY_COLUMN);
650
                features.forEach(feature->registerColumnConfiguration(feature, configRegistry));
651
            }
652

    
653
        });
654

    
655
        //no summary for the supplemental columns
656
        for(int i=0;i<LEADING_COLUMN_COUNT;i++){
657
            int index = i;
658
            summaryRowLayer.addConfiguration(new DefaultSummaryRowConfiguration() {
659
                @Override
660
                public void addSummaryProviderConfig(IConfigRegistry configRegistry) {
661
                    configRegistry.registerConfigAttribute(
662
                            SummaryRowConfigAttributes.SUMMARY_PROVIDER,
663
                            new ISummaryProvider() {
664

    
665
                                @Override
666
                                public Object summarize(int columnIndex) {
667
                                    return "";
668
                                }
669
                            },
670
                            DisplayMode.NORMAL,
671
                            SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+index);
672
                }
673
            });
674
        }
675
        //register aggregation configuration for each feature
676
        features.forEach(feature->summaryRowLayer.addConfiguration(new AggregationConfiguration(bodyDataProvider, feature)));
677

    
678
        natTable.configure();
679
    }
680

    
681
    void freezeSupplementalColumns(boolean freeze){
682
        if(freeze){
683
            FreezeHelper.freeze(freezeLayer, viewportLayer,
684
                    new PositionCoordinate(viewportLayer, 0, 0),
685
                    new PositionCoordinate(viewportLayer, LEADING_COLUMN_COUNT-1, -1));
686
        }
687
        else{
688
            FreezeHelper.unfreeze(freezeLayer, viewportLayer);
689
        }
690
    }
691

    
692
    void selectStateItem(ComboViewer comboStates, String stateName){
693
        String[] items = comboStates.getCombo().getItems();
694
        for(int i=0;i<items.length;i++){
695
            if(items[i].equals(stateName)){
696
                comboStates.getCombo().select(i);
697
                break;
698
            }
699
        }
700
    }
701

    
702
    private SpecimenDescription getDescriptionForDescriptiveDataSet(SpecimenOrObservationBase specimen){
703
        Set<Feature> wsFeatures = descriptiveDataSet.getDescriptiveSystem().getDistinctFeatures();
704
        List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
705

    
706
        for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
707
            specimenDescription = (SpecimenDescription) CdmStore.getService(IDescriptionService.class).load(specimenDescription.getUuid());
708
            Set<Feature> specimenDescriptionFeatures = new HashSet<>();
709
            //gather specimen description features and check for match with WS features
710
            for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
711
                Feature feature = specimenDescriptionElement.getFeature();
712
                specimenDescriptionFeatures.add(feature);
713
                if(wsFeatures.contains(feature)){
714
                    matchingDescriptionElements.add(specimenDescriptionElement);
715
                }
716
            }
717
            //if description with the exact same features is found return the description
718
            if(specimenDescriptionFeatures.equals(wsFeatures)){
719
                return specimenDescription;
720
            }
721
        }
722
        //Create new specimen description if no match was found
723
        setDirty();
724
        SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
725
        newDesription.setTitleCache(Messages.CharacterMatrix_DESCRIPTIVE_DATA_SET+descriptiveDataSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
726

    
727
        //check for equals description element (same feature and same values)
728
        Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
729
        for(DescriptionElementBase element:matchingDescriptionElements){
730
            List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
731
            if(list==null){
732
                list = new ArrayList<>();
733
            }
734
            list.add(element);
735
            featureToElementMap.put(element.getFeature(), list);
736
        }
737
        Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
738
        for(Feature feature:featureToElementMap.keySet()){
739
            List<DescriptionElementBase> elements = featureToElementMap.get(feature);
740
            //no duplicate description elements found for this feature
741
            if(elements.size()==1){
742
                descriptionElementsToClone.add(elements.get(0));
743
            }
744
            //duplicates found -> check if all are equal
745
            else{
746
                DescriptionElementBase match = null;
747
                for (DescriptionElementBase descriptionElementBase : elements) {
748
                    if(match==null){
749
                        match = descriptionElementBase;
750
                    }
751
                    else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
752
                        match = null;
753
                        MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
754
                                String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
755
                        break;
756
                    }
757
                }
758
                if(match!=null){
759
                    descriptionElementsToClone.add(match);
760
                }
761
            }
762
        }
763
        //clone matching descriptionElements
764
        for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
765
            DescriptionElementBase clone;
766
            try {
767
                clone = descriptionElementBase.clone(newDesription);
768
                clone.getSources().forEach(source -> source.setOriginalNameString(DescriptionHelper.getLabel(descriptionElementBase)));
769
            } catch (CloneNotSupportedException e) {
770
                MessagingUtils.error(CharacterMatrix.class, e);
771
            }
772
        }
773

    
774
        //add all remaining description elements to the new description
775
        for(Feature wsFeature:wsFeatures){
776
            boolean featureFound = false;
777
            for(DescriptionElementBase element:newDesription.getElements()){
778
                if(element.getFeature().equals(wsFeature)){
779
                    featureFound = true;
780
                    break;
781
                }
782
            }
783
            if(!featureFound){
784
                if(wsFeature.isSupportsCategoricalData()){
785
                    newDesription.addElement(CategoricalData.NewInstance(wsFeature));
786
                }
787
                else if(wsFeature.isSupportsQuantitativeData()){
788
                    newDesription.addElement(QuantitativeData.NewInstance(wsFeature));
789
                }
790
            }
791
        }
792
        return newDesription;
793

    
794
    }
795

    
796
    private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator,
797
            int index, Feature feature) {
798

    
799
        columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, MatrixUtility.getProperty(feature));
800
        indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
801

    
802
        String featureLabel = feature.getLabel();
803
        String property = featureLabel;
804
        //show unit for quantitative data
805
        if(feature.isSupportsQuantitativeData()){
806
            Set<MeasurementUnit> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
807
            if(recommendedMeasurementUnits.size()>1){
808
                MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class,
809
                        String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel()));
810
            }
811
            if(recommendedMeasurementUnits.size()==1){
812
                MeasurementUnit unit = recommendedMeasurementUnits.iterator().next();
813
                featureLabel += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
814
            }
815
        }
816
        propertyToLabelMap.put(property, featureLabel);
817
    }
818

    
819
    private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) {
820
        //make cell editable
821
        configRegistry.registerConfigAttribute(
822
                EditConfigAttributes.CELL_EDITABLE_RULE,
823
                IEditableRule.ALWAYS_EDITABLE,
824
                DisplayMode.EDIT,
825
                MatrixUtility.getProperty(feature)
826
                );
827
        if(feature.isSupportsQuantitativeData()){
828
            //add display converter for string representation
829
            configRegistry.registerConfigAttribute(
830
                    CellConfigAttributes.DISPLAY_CONVERTER,
831
                    new QuantitativeDataDisplayConverter(),
832
                    DisplayMode.NORMAL,
833
                    MatrixUtility.getProperty(feature));
834
            //register quantitative editor
835
            configRegistry.registerConfigAttribute(
836
                    EditConfigAttributes.CELL_EDITOR,
837
                    new QuantitativeDataCellEditor(feature, this),
838
                    DisplayMode.EDIT,
839
                    MatrixUtility.getProperty(feature));
840
        }
841
        else if(feature.isSupportsCategoricalData()){
842
            //add display converter for string representation
843
            configRegistry.registerConfigAttribute(
844
                    CellConfigAttributes.DISPLAY_CONVERTER,
845
                    new CategoricalDataDisplayConverter(),
846
                    DisplayMode.NORMAL,
847
                    MatrixUtility.getProperty(feature));
848

    
849
            //add combo box cell editor
850
            //register editor
851
            configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
852
                    new CategoricalDataCellEditor(getSupportedStatesForCategoricalFeature(feature), this, feature),
853
                    DisplayMode.EDIT,
854
                    MatrixUtility.getProperty(feature));
855

    
856
        }
857

    
858
    }
859

    
860

    
861
    public void loadDescriptions(DescriptiveDataSet descriptiveDataSet) {
862
        UUID monitorUuid = CdmStore.getService(IDescriptiveDataSetService.class).monitGetRowWrapper(descriptiveDataSet);
863
        IProgressMonitorService progressMonitorService = CdmApplicationState.getCurrentAppConfig().getProgressMonitorService();
864

    
865

    
866
        String jobLabel = "Load character data";
867
        Job job = Job.create(jobLabel, (ICoreRunnable) monitor -> {
868
            monitor.beginTask(jobLabel, IProgressMonitor.UNKNOWN);
869
            while(progressMonitorService.isMonitorThreadRunning(monitorUuid)){
870
                if(monitor.isCanceled()){
871
                    progressMonitorService.interrupt(monitorUuid);
872
                }
873
            }
874
            IRemotingProgressMonitor remotingMonitor = progressMonitorService.getRemotingMonitor(monitorUuid);
875
            Collection<RowWrapperDTO> wrappers = (Collection<RowWrapperDTO>) remotingMonitor.getResult();
876
            if(wrappers!=null){
877
                wrappers.forEach(wrapper->CharacterMatrix.this.descriptions.add(wrapper));
878
            }
879
            monitor.done();
880
        });
881
        job.schedule();
882
    }
883

    
884
    public List<State> getSupportedStatesForCategoricalFeature(Feature feature){
885
        return categoricalFeatureToStateMap.get(feature);
886
    }
887

    
888
    public Map<Integer, Feature> getIndexToFeatureMap() {
889
        return indexToFeatureMap;
890
    }
891

    
892
    public LinkedMap<String, String> getPropertyToLabelMap() {
893
        return propertyToLabelMap;
894
    }
895

    
896
    public void setDirty() {
897
        part.setDirty();
898
    }
899

    
900
    public NatTable getNatTable() {
901
        return natTable;
902
    }
903

    
904
    public EventList<Object> getDescriptions() {
905
        return descriptions;
906
    }
907

    
908
    public DescriptiveDataSet getDescriptiveDataSet() {
909
        return descriptiveDataSet;
910
    }
911

    
912
    public Collection<SpecimenNodeWrapper> getSpecimenCache() {
913
        return specimenCache;
914
    }
915

    
916
    public void setSpecimenCache(Collection<SpecimenNodeWrapper> specimenCache) {
917
        this.specimenCache = specimenCache.stream()
918
                .filter(wrapper ->
919
        //map descriptions on a list of uuids of the described specimen
920
        !this.descriptions.stream()
921
        .map(o->((RowWrapperDTO)o).getSpecimen().getUuid())
922
        .collect(Collectors.toList())
923
        //an check if the specimen to add is already contained
924
        .contains(wrapper.getUuidAndTitleCache().getUuid())
925
        )
926
        .collect(Collectors.toList());
927
    }
928

    
929
    public Properties getNatTableState() {
930
        return toolbar.getNatTableState();
931
    }
932

    
933
    public ListDataProvider<Object> getBodyDataProvider() {
934
        return bodyDataProvider;
935
    }
936

    
937
    File getStatePropertiesFile() {
938
        return new File(WorkbenchUtility.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES);
939
    }
940

    
941
}
(2-2/10)