Project

General

Profile

Download (18 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.model.IDirtyMarkable;
95
import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
96
import eu.etaxonomy.taxeditor.model.MessagingUtils;
97
import eu.etaxonomy.taxeditor.store.CdmStore;
98
import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
99

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

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

    
113
    private WorkingSet workingSet;
114

    
115
    private Composite parent;
116

    
117
    private ConversationHolder conversation;
118

    
119
    @Inject
120
    private ESelectionService selService;
121

    
122
    @Inject
123
    private MDirtyable dirty;
124

    
125
    @Inject
126
    private MPart thisPart;
127

    
128
    private NatTable natTable;
129

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

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

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

    
146

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

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

    
156

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

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

    
166
        /**
167
         * BODY layer
168
         */
169
        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
170
        final ColumnOverrideLabelAccumulator columnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
171
        bodyDataLayer.setConfigLabelAccumulator(columnLabelAccumulator);
172
        for(int i=0;i<features.size();i++){
173
            Feature feature = features.get(i);
174
            initLabels(features, columnLabelAccumulator, i, feature);
175
        }
176
        GlazedListsEventLayer<SpecimenDescription> eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, sortedList);
177

    
178
        RowReorderLayer rowReorderLayer = new RowReorderLayer(eventLayer);
179
        ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(rowReorderLayer);
180
        SelectionLayer selectionLayer = new SelectionLayer(columnReorderLayer);
181
        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
182

    
183
        /**
184
         * column header layer
185
         */
186
        IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
187
                propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
188
        DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
189
        ILayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
190

    
191
        ConfigRegistry configRegistry = new ConfigRegistry();
192

    
193
        // add the SortHeaderLayer to the column header layer stack
194
        // as we use GlazedLists, we use the GlazedListsSortModel which
195
        // delegates the sorting to the SortedList
196
        final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
197
                        columnHeaderLayer,
198
                        new GlazedListsSortModel<>(
199
                                sortedList,
200
                                columnPropertyAccessor,
201
                                configRegistry,
202
                                columnHeaderDataLayer));
203

    
204

    
205
        /**
206
         * row header layer
207
         */
208
        IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
209
        DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider, 40, 20);
210
        ILayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
211

    
212

    
213
        /**
214
         * corner layer
215
         */
216
        ILayer cornerLayer = new CornerLayer(
217
                new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
218
                rowHeaderLayer, sortHeaderLayer);
219

    
220

    
221
        /**
222
         * GRID layer (composition of all other layers)
223
         */
224
        GridLayer gridLayer = new GridLayer(viewportLayer, sortHeaderLayer, rowHeaderLayer, cornerLayer);
225

    
226

    
227
        natTable = new NatTable(parent, gridLayer, false);
228

    
229
        natTable.setConfigRegistry(configRegistry);
230

    
231

    
232
        //add default configuration because autoconfigure is set to false in constructor
233
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
234

    
235
        // override the default sort configuration and change the mouse bindings
236
        // to sort on a single click
237
        natTable.addConfiguration(new SingleClickSortConfiguration());
238

    
239

    
240
        // add custom configuration for data conversion and add column labels for each feature
241
        viewportLayer.addConfiguration(new AbstractRegistryConfiguration() {
242
            @Override
243
            public void configureRegistry(IConfigRegistry configRegistry) {
244
                features.forEach(feature->registerColumnConfiguration(feature, configRegistry));
245
            }
246

    
247
        });
248

    
249
        // add the ExportCommandHandler to the ViewportLayer in order to make
250
        // exporting work
251
        viewportLayer.registerCommandHandler(new ExportCommandHandler(viewportLayer));
252

    
253
        //propagate single cell selection
254
        natTable.addLayerListener(new ILayerListener() {
255
            @Override
256
            public void handleLayerEvent(ILayerEvent event) {
257
                if(event instanceof CellSelectionEvent){
258
                    CellSelectionEvent cellSelectionEvent = (CellSelectionEvent)event;
259
                    Collection<ILayerCell> selectedCells = cellSelectionEvent.getSelectionLayer().getSelectedCells();
260
                    if(selectedCells.size()==1){
261
                        ILayerCell cell = selectedCells.iterator().next();
262
                        selService.setSelection(cell.getDataValue());
263
                    }
264
                }
265
            }
266
        });
267

    
268
        natTable.configure();
269

    
270
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
271

    
272
        //excel export
273
        Button addColumnButton = new Button(parent, SWT.PUSH);
274
        addColumnButton.setText("Export");
275
        addColumnButton.addSelectionListener(new SelectionAdapter() {
276
            @Override
277
            public void widgetSelected(SelectionEvent e) {
278
                natTable.doCommand(
279
                        new ExportCommand(
280
                                natTable.getConfigRegistry(),
281
                                natTable.getShell()));
282
            }
283
        });
284

    
285
        parent.layout();
286
    }
287

    
288
    private void initLabels(List<Feature> features, final ColumnOverrideLabelAccumulator columnLabelAccumulator,
289
            int index, Feature feature) {
290

    
291
        propertyToLabelMap.put(TAXON_COLUMN, "Taxon");
292
        propertyToLabelMap.put(COLLECTOR_COLUMN, "Collector + No");
293
        propertyToLabelMap.put(IDENTIFIER_COLUMN, "Identifier");
294
        propertyToLabelMap.put(COUNTRY_COLUMN, "Country");
295

    
296
        columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, getProperty(features.get(index)));
297
        indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
298

    
299
        String featureLabel = feature.getLabel();
300
        String property = featureLabel;
301
        //show unit for quantitative data
302
        if(feature.isSupportsQuantitativeData()){
303
            Set<MeasurementUnit> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
304
            if(recommendedMeasurementUnits.size()!=1){
305
                MessagingUtils.error(SpecimenColumnPropertyAccessor.class, "Only one unit is allowed for quantitative data", null);
306
                return;
307
            }
308
            MeasurementUnit unit = recommendedMeasurementUnits.iterator().next();
309
            featureLabel += " ["+unit.getLabel()+"]";
310
        }
311
        propertyToLabelMap.put(property, featureLabel);
312
    }
313

    
314
    private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) {
315
        //make cell editable
316
        configRegistry.registerConfigAttribute(
317
                EditConfigAttributes.CELL_EDITABLE_RULE,
318
                IEditableRule.ALWAYS_EDITABLE,
319
                DisplayMode.EDIT,
320
                getProperty(feature)
321
                );
322
        if(feature.isSupportsQuantitativeData()){
323
            //add display converter for string representation
324
            configRegistry.registerConfigAttribute(
325
                    CellConfigAttributes.DISPLAY_CONVERTER,
326
                    new QuantitativeDataDisplayConverter(),
327
                    DisplayMode.NORMAL,
328
                    getProperty(feature));
329
            //register quantitative editor
330
            configRegistry.registerConfigAttribute(
331
                    EditConfigAttributes.CELL_EDITOR,
332
                    new QuantitativeDataCellEditor(feature.getRecommendedStatisticalMeasures(), this),
333
                    DisplayMode.EDIT,
334
                    getProperty(feature));
335
        }
336
        else if(feature.isSupportsCategoricalData()){
337
            //add display converter for string representation
338
            configRegistry.registerConfigAttribute(
339
                    CellConfigAttributes.DISPLAY_CONVERTER,
340
                    new CategoricalDataDisplayConverter(),
341
                    DisplayMode.NORMAL,
342
                    getProperty(feature));
343

    
344
            //add combo box cell editor
345
            CategoricalDataCellEditor comboBoxCellEditor = new CategoricalDataCellEditor(new IComboBoxDataProvider() {
346

    
347
                @Override
348
                public List<?> getValues(int columnIndex, int rowIndex) {
349
                    List<State> states = new ArrayList<>();
350
                    Feature feature = indexToFeatureMap.get(columnIndex);
351
                    if(feature.isSupportsCategoricalData()){
352
                        Set<TermVocabulary<State>> stateVocs = feature.getSupportedCategoricalEnumerations();
353
                        for (TermVocabulary<State> voc : stateVocs) {
354
                            states.addAll(voc.getTerms());
355
                        }
356
                    }
357
                    return states;
358
                }
359
            }, 5, this);
360
            //register editor
361
            configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
362
                    comboBoxCellEditor,
363
                    DisplayMode.EDIT,
364
                    getProperty(feature));
365

    
366
        }
367

    
368
    }
369

    
370
    private List<SpecimenDescription> getDescriptions(WorkingSet workingSet) {
371
        List<SpecimenDescription> descriptions = new ArrayList<>();
372
        Set<DescriptionBase> wsDescriptions = workingSet.getDescriptions();
373
        for (DescriptionBase descriptionBase : wsDescriptions) {
374
            if(descriptionBase instanceof SpecimenDescription){
375
                descriptions.add((SpecimenDescription) descriptionBase);
376
            }
377
        }
378
        return descriptions;
379
    }
380

    
381
    private String getProperty(Feature feature){
382
        return feature.getLabel();
383
    }
384

    
385
    public Map<Integer, Feature> getIndexToFeatureMap() {
386
        return indexToFeatureMap;
387
    }
388

    
389
    public LinkedMap<String, String> getPropertyToLabelMap() {
390
        return propertyToLabelMap;
391
    }
392

    
393
    public void setDirty() {
394
        this.dirty.setDirty(true);
395
    }
396

    
397
    @Persist
398
    @Override
399
    public void save(IProgressMonitor monitor) {
400
        CdmStore.getService(IWorkingSetService.class).merge(workingSet, true);
401
        conversation.commit();
402
        dirty.setDirty(false);
403
    }
404

    
405
    @Focus
406
    public void setFocus(){
407
        if(conversation!=null){
408
            conversation.bind();
409
        }
410
    }
411

    
412
    @PreDestroy
413
    public void dispose(){
414
        if(conversation!=null){
415
            conversation.close();
416
            conversation = null;
417
        }
418
    }
419

    
420

    
421
    /**
422
     * {@inheritDoc}
423
     */
424
    @Override
425
    public void update(CdmDataChangeMap arg0) {
426
    }
427

    
428

    
429
    /**
430
     * {@inheritDoc}
431
     */
432
    @Override
433
    public ConversationHolder getConversationHolder() {
434
        return conversation;
435
    }
436

    
437

    
438
    /**
439
     * {@inheritDoc}
440
     */
441
    @Override
442
    public void changed(Object element) {
443
        dirty.setDirty(true);
444
        natTable.refresh();
445
    }
446

    
447

    
448
    /**
449
     * {@inheritDoc}
450
     */
451
    @Override
452
    public void forceDirty() {
453
        dirty.setDirty(true);
454
    }
455

    
456
}
(1-1/3)