Project

General

Profile

Download (19.4 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.workingSet.matrix;
10

    
11
import java.util.ArrayList;
12
import java.util.Collection;
13
import java.util.Collections;
14
import java.util.HashMap;
15
import java.util.List;
16
import java.util.Map;
17
import java.util.Set;
18
import java.util.UUID;
19

    
20
import javax.annotation.PostConstruct;
21
import javax.annotation.PreDestroy;
22
import javax.inject.Inject;
23

    
24
import org.apache.commons.collections4.map.LinkedMap;
25
import org.eclipse.core.runtime.IProgressMonitor;
26
import org.eclipse.e4.ui.di.Focus;
27
import org.eclipse.e4.ui.di.Persist;
28
import org.eclipse.e4.ui.model.application.ui.MDirtyable;
29
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
30
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
31
import org.eclipse.jface.layout.GridDataFactory;
32
import org.eclipse.nebula.widgets.nattable.NatTable;
33
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
34
import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
35
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
36
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
37
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
38
import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
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.edit.editor.IComboBoxDataProvider;
43
import org.eclipse.nebula.widgets.nattable.export.command.ExportCommand;
44
import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
45
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
46
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
47
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
48
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
49
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
50
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
51
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
52
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
53
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
54
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
55
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
56
import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
57
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
58
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
59
import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
60
import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
61
import org.eclipse.nebula.widgets.nattable.reorder.RowReorderLayer;
62
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
63
import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
64
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
65
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
66
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
67
import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
68
import org.eclipse.swt.SWT;
69
import org.eclipse.swt.events.SelectionAdapter;
70
import org.eclipse.swt.events.SelectionEvent;
71
import org.eclipse.swt.layout.GridLayout;
72
import org.eclipse.swt.widgets.Button;
73
import org.eclipse.swt.widgets.Composite;
74

    
75
import ca.odell.glazedlists.EventList;
76
import ca.odell.glazedlists.GlazedLists;
77
import ca.odell.glazedlists.SortedList;
78
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
79
import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
80
import eu.etaxonomy.cdm.api.service.IWorkingSetService;
81
import eu.etaxonomy.cdm.model.common.TermVocabulary;
82
import eu.etaxonomy.cdm.model.description.DescriptionBase;
83
import eu.etaxonomy.cdm.model.description.Feature;
84
import eu.etaxonomy.cdm.model.description.FeatureTree;
85
import eu.etaxonomy.cdm.model.description.MeasurementUnit;
86
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
87
import eu.etaxonomy.cdm.model.description.State;
88
import eu.etaxonomy.cdm.model.description.WorkingSet;
89
import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
90
import eu.etaxonomy.taxeditor.editor.workingSet.matrix.categorical.CategoricalDataCellEditor;
91
import eu.etaxonomy.taxeditor.editor.workingSet.matrix.categorical.CategoricalDataDisplayConverter;
92
import eu.etaxonomy.taxeditor.editor.workingSet.matrix.quantitative.QuantitativeDataCellEditor;
93
import eu.etaxonomy.taxeditor.editor.workingSet.matrix.quantitative.QuantitativeDataDisplayConverter;
94
import eu.etaxonomy.taxeditor.editor.workingSet.matrix.supplementalInfo.SupplementalInfoDisplayConverter;
95
import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
96
import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
97
import eu.etaxonomy.taxeditor.model.MessagingUtils;
98
import eu.etaxonomy.taxeditor.store.CdmStore;
99
import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
100

    
101
/**
102
 * @author pplitzner
103
 * @since Nov 26, 2017
104
 *
105
 */
106
public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails, IConversationEnabled, IDirtyMarkable{
107

    
108
    private static final int LEADING_COLUMN_COUNT = 4;
109
    private static final String TAXON_COLUMN = "taxon_column";
110
    private static final String COLLECTOR_COLUMN = "collector_column";
111
    private static final String IDENTIFIER_COLUMN = "identifier_column";
112
    private static final String COUNTRY_COLUMN = "country_column";
113

    
114
    private WorkingSet workingSet;
115

    
116
    private Composite parent;
117

    
118
    private ConversationHolder conversation;
119

    
120
    @Inject
121
    private ESelectionService selService;
122

    
123
    @Inject
124
    private MDirtyable dirty;
125

    
126
    @Inject
127
    private MPart thisPart;
128

    
129
    private NatTable natTable;
130

    
131
    private Map<Integer, Feature> indexToFeatureMap = new HashMap<>();
132

    
133
    private LinkedMap<String, String> propertyToLabelMap = new LinkedMap<>();
134

    
135
    @PostConstruct
136
    public void create(Composite parent) {
137
        if(CdmStore.isActive() && conversation==null){
138
            conversation = CdmStore.createConversation();
139
        }
140
        else{
141
            return;
142
        }
143
        parent.setLayout(new GridLayout());
144
        this.parent = parent;
145
    }
146

    
147

    
148
    public void init(UUID workingSetUuid) {
149
        this.workingSet = CdmStore.getService(IWorkingSetService.class).load(workingSetUuid);
150
        thisPart.setLabel(workingSet.getLabel());
151

    
152
        //get features/columns stored in working set
153
        FeatureTree tree = workingSet.getDescriptiveSystem();
154
        List<Feature> features = new ArrayList<>(tree.getDistinctFeatures());
155
        Collections.sort(features);
156

    
157

    
158
        EventList<RowWrapper> descriptions = GlazedLists.eventList(getDescriptions(workingSet));
159
        SortedList<RowWrapper> sortedList = new SortedList<>(descriptions, null);
160

    
161
        /**
162
         * data provider
163
         */
164
        SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
165
        IDataProvider bodyDataProvider = new ListDataProvider<RowWrapper>(sortedList, columnPropertyAccessor);
166

    
167
        /**
168
         * BODY layer
169
         */
170
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
171

    
172
        //register labels for columns
173
        final ColumnOverrideLabelAccumulator columnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
174
        bodyDataLayer.setConfigLabelAccumulator(columnLabelAccumulator);
175
        propertyToLabelMap.put(TAXON_COLUMN, "Taxon");
176
        columnLabelAccumulator.registerColumnOverrides(0, TAXON_COLUMN);
177
        propertyToLabelMap.put(COLLECTOR_COLUMN, "Collector + No");
178
        columnLabelAccumulator.registerColumnOverrides(1, COLLECTOR_COLUMN);
179
        propertyToLabelMap.put(IDENTIFIER_COLUMN, "Identifier");
180
        columnLabelAccumulator.registerColumnOverrides(2, IDENTIFIER_COLUMN);
181
        propertyToLabelMap.put(COUNTRY_COLUMN, "Country");
182
        columnLabelAccumulator.registerColumnOverrides(3, COUNTRY_COLUMN);
183
        for(int i=0;i<features.size();i++){
184
            Feature feature = features.get(i);
185
            initLabels(columnLabelAccumulator, i, feature);
186
        }
187

    
188
        GlazedListsEventLayer<RowWrapper> eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, sortedList);
189

    
190
        RowReorderLayer rowReorderLayer = new RowReorderLayer(eventLayer);
191
        ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(rowReorderLayer);
192
        SelectionLayer selectionLayer = new SelectionLayer(columnReorderLayer);
193
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
194

    
195
        /**
196
         * column header layer
197
         */
198
        IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
199
                propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
200
        DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
201
        ILayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
202

    
203
        ConfigRegistry configRegistry = new ConfigRegistry();
204

    
205
        // add the SortHeaderLayer to the column header layer stack
206
        // as we use GlazedLists, we use the GlazedListsSortModel which
207
        // delegates the sorting to the SortedList
208
        final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
209
                        columnHeaderLayer,
210
                        new GlazedListsSortModel<>(
211
                                sortedList,
212
                                columnPropertyAccessor,
213
                                configRegistry,
214
                                columnHeaderDataLayer));
215

    
216

    
217
        /**
218
         * row header layer
219
         */
220
        IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
221
        DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider, 40, 20);
222
        ILayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
223

    
224

    
225
        /**
226
         * corner layer
227
         */
228
        ILayer cornerLayer = new CornerLayer(
229
                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
230
                rowHeaderLayer, sortHeaderLayer);
231

    
232

    
233
        /**
234
         * GRID layer (composition of all other layers)
235
         */
236
        GridLayer gridLayer = new GridLayer(viewportLayer, sortHeaderLayer, rowHeaderLayer, cornerLayer);
237

    
238

    
239
        natTable = new NatTable(parent, gridLayer, false);
240

    
241
        natTable.setConfigRegistry(configRegistry);
242

    
243

    
244
        //add default configuration because autoconfigure is set to false in constructor
245
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
246

    
247
        // override the default sort configuration and change the mouse bindings
248
        // to sort on a single click
249
        natTable.addConfiguration(new SingleClickSortConfiguration());
250

    
251

    
252
        // add custom configuration for data conversion and add column labels
253
        viewportLayer.addConfiguration(new AbstractRegistryConfiguration() {
254
            @Override
255
            public void configureRegistry(IConfigRegistry configRegistry) {
256
                //add display converter for string representation
257
                configRegistry.registerConfigAttribute(
258
                        CellConfigAttributes.DISPLAY_CONVERTER,
259
                        new SupplementalInfoDisplayConverter(),
260
                        DisplayMode.NORMAL,
261
                        TAXON_COLUMN);
262
                configRegistry.registerConfigAttribute(
263
                        CellConfigAttributes.DISPLAY_CONVERTER,
264
                        new SupplementalInfoDisplayConverter(),
265
                        DisplayMode.NORMAL,
266
                        COLLECTOR_COLUMN);
267
                configRegistry.registerConfigAttribute(
268
                        CellConfigAttributes.DISPLAY_CONVERTER,
269
                        new SupplementalInfoDisplayConverter(),
270
                        DisplayMode.NORMAL,
271
                        IDENTIFIER_COLUMN);
272
                configRegistry.registerConfigAttribute(
273
                        CellConfigAttributes.DISPLAY_CONVERTER,
274
                        new SupplementalInfoDisplayConverter(),
275
                        DisplayMode.NORMAL,
276
                        COUNTRY_COLUMN);
277
                features.forEach(feature->registerColumnConfiguration(feature, configRegistry));
278
            }
279

    
280
        });
281

    
282
        // add the ExportCommandHandler to the ViewportLayer in order to make
283
        // exporting work
284
        viewportLayer.registerCommandHandler(new ExportCommandHandler(viewportLayer));
285

    
286
        //propagate single cell selection
287
        natTable.addLayerListener(new ILayerListener() {
288
            @Override
289
            public void handleLayerEvent(ILayerEvent event) {
290
                if(event instanceof CellSelectionEvent){
291
                    CellSelectionEvent cellSelectionEvent = (CellSelectionEvent)event;
292
                    Collection<ILayerCell> selectedCells = cellSelectionEvent.getSelectionLayer().getSelectedCells();
293
                    if(selectedCells.size()==1){
294
                        ILayerCell cell = selectedCells.iterator().next();
295
                        selService.setSelection(cell.getDataValue());
296
                    }
297
                }
298
            }
299
        });
300

    
301
        natTable.configure();
302

    
303
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
304

    
305
        //excel export
306
        Button addColumnButton = new Button(parent, SWT.PUSH);
307
        addColumnButton.setText("Export");
308
        addColumnButton.addSelectionListener(new SelectionAdapter() {
309
            @Override
310
            public void widgetSelected(SelectionEvent e) {
311
                natTable.doCommand(
312
                        new ExportCommand(
313
                                natTable.getConfigRegistry(),
314
                                natTable.getShell()));
315
            }
316
        });
317

    
318
        parent.layout();
319
    }
320

    
321
    private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator,
322
            int index, Feature feature) {
323

    
324
        columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, getProperty(feature));
325
        indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
326

    
327
        String featureLabel = feature.getLabel();
328
        String property = featureLabel;
329
        //show unit for quantitative data
330
        if(feature.isSupportsQuantitativeData()){
331
            Set<MeasurementUnit> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
332
            if(recommendedMeasurementUnits.size()!=1){
333
                MessagingUtils.error(SpecimenColumnPropertyAccessor.class, "Only one unit is allowed for quantitative data", null);
334
                return;
335
            }
336
            MeasurementUnit unit = recommendedMeasurementUnits.iterator().next();
337
            featureLabel += " ["+unit.getLabel()+"]";
338
        }
339
        propertyToLabelMap.put(property, featureLabel);
340
    }
341

    
342
    private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) {
343
        //make cell editable
344
        configRegistry.registerConfigAttribute(
345
                EditConfigAttributes.CELL_EDITABLE_RULE,
346
                IEditableRule.ALWAYS_EDITABLE,
347
                DisplayMode.EDIT,
348
                getProperty(feature)
349
                );
350
        if(feature.isSupportsQuantitativeData()){
351
            //add display converter for string representation
352
            configRegistry.registerConfigAttribute(
353
                    CellConfigAttributes.DISPLAY_CONVERTER,
354
                    new QuantitativeDataDisplayConverter(),
355
                    DisplayMode.NORMAL,
356
                    getProperty(feature));
357
            //register quantitative editor
358
            configRegistry.registerConfigAttribute(
359
                    EditConfigAttributes.CELL_EDITOR,
360
                    new QuantitativeDataCellEditor(feature.getRecommendedStatisticalMeasures(), this),
361
                    DisplayMode.EDIT,
362
                    getProperty(feature));
363
        }
364
        else if(feature.isSupportsCategoricalData()){
365
            //add display converter for string representation
366
            configRegistry.registerConfigAttribute(
367
                    CellConfigAttributes.DISPLAY_CONVERTER,
368
                    new CategoricalDataDisplayConverter(),
369
                    DisplayMode.NORMAL,
370
                    getProperty(feature));
371

    
372
            //add combo box cell editor
373
            CategoricalDataCellEditor comboBoxCellEditor = new CategoricalDataCellEditor(new IComboBoxDataProvider() {
374

    
375
                @Override
376
                public List<?> getValues(int columnIndex, int rowIndex) {
377
                    List<State> states = new ArrayList<>();
378
                    Feature feature = indexToFeatureMap.get(columnIndex);
379
                    if(feature.isSupportsCategoricalData()){
380
                        Set<TermVocabulary<State>> stateVocs = feature.getSupportedCategoricalEnumerations();
381
                        for (TermVocabulary<State> voc : stateVocs) {
382
                            states.addAll(voc.getTerms());
383
                        }
384
                    }
385
                    return states;
386
                }
387
            }, 5, this);
388
            //register editor
389
            configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
390
                    comboBoxCellEditor,
391
                    DisplayMode.EDIT,
392
                    getProperty(feature));
393

    
394
        }
395

    
396
    }
397

    
398
    private List<RowWrapper> getDescriptions(WorkingSet workingSet) {
399
        List<RowWrapper> rowWrappers = new ArrayList<>();
400
        Set<DescriptionBase> wsDescriptions = workingSet.getDescriptions();
401
        for (DescriptionBase descriptionBase : wsDescriptions) {
402
            if(descriptionBase instanceof SpecimenDescription){
403
                rowWrappers.add(new RowWrapper((SpecimenDescription) descriptionBase));
404
            }
405
        }
406
        return rowWrappers;
407
    }
408

    
409
    private String getProperty(Feature feature){
410
        return feature.getLabel();
411
    }
412

    
413
    public Map<Integer, Feature> getIndexToFeatureMap() {
414
        return indexToFeatureMap;
415
    }
416

    
417
    public LinkedMap<String, String> getPropertyToLabelMap() {
418
        return propertyToLabelMap;
419
    }
420

    
421
    public void setDirty() {
422
        this.dirty.setDirty(true);
423
    }
424

    
425
    @Persist
426
    @Override
427
    public void save(IProgressMonitor monitor) {
428
        CdmStore.getService(IWorkingSetService.class).merge(workingSet, true);
429
        conversation.commit();
430
        dirty.setDirty(false);
431
    }
432

    
433
    @Focus
434
    public void setFocus(){
435
        if(conversation!=null){
436
            conversation.bind();
437
        }
438
    }
439

    
440
    @PreDestroy
441
    public void dispose(){
442
        if(conversation!=null){
443
            conversation.close();
444
            conversation = null;
445
        }
446
    }
447

    
448

    
449
    /**
450
     * {@inheritDoc}
451
     */
452
    @Override
453
    public void update(CdmDataChangeMap arg0) {
454
    }
455

    
456

    
457
    /**
458
     * {@inheritDoc}
459
     */
460
    @Override
461
    public ConversationHolder getConversationHolder() {
462
        return conversation;
463
    }
464

    
465

    
466
    /**
467
     * {@inheritDoc}
468
     */
469
    @Override
470
    public void changed(Object element) {
471
        dirty.setDirty(true);
472
        natTable.refresh();
473
    }
474

    
475

    
476
    /**
477
     * {@inheritDoc}
478
     */
479
    @Override
480
    public void forceDirty() {
481
        dirty.setDirty(true);
482
    }
483

    
484
}
(1-1/4)