Refactor character matrix
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / descriptiveDataSet / matrix / CharacterMatrix.java
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.List;
16 import java.util.Map;
17 import java.util.Properties;
18 import java.util.Set;
19 import java.util.UUID;
20 import java.util.stream.Collectors;
21
22 import org.apache.commons.collections4.map.LinkedMap;
23 import org.eclipse.core.runtime.ICoreRunnable;
24 import org.eclipse.core.runtime.IProgressMonitor;
25 import org.eclipse.core.runtime.jobs.Job;
26 import org.eclipse.jface.layout.GridDataFactory;
27 import org.eclipse.jface.viewers.ComboViewer;
28 import org.eclipse.jface.viewers.StructuredSelection;
29 import org.eclipse.nebula.widgets.nattable.NatTable;
30 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
31 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
32 import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
33 import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
34 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
35 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
36 import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
37 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
38 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
39 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
40 import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
41 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
42 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
43 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
44 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
45 import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
46 import org.eclipse.nebula.widgets.nattable.freeze.FreezeHelper;
47 import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
48 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
49 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
50 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
51 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
52 import org.eclipse.nebula.widgets.nattable.grid.data.FixedSummaryRowHeaderLayer;
53 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
54 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
55 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
56 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
57 import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
58 import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
59 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
60 import org.eclipse.nebula.widgets.nattable.layer.ILayer;
61 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
62 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
63 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
64 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
65 import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
66 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
67 import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
68 import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
69 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
70 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
71 import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
72 import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
73 import org.eclipse.nebula.widgets.nattable.summaryrow.DefaultSummaryRowConfiguration;
74 import org.eclipse.nebula.widgets.nattable.summaryrow.FixedSummaryRowLayer;
75 import org.eclipse.nebula.widgets.nattable.summaryrow.ISummaryProvider;
76 import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowConfigAttributes;
77 import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer;
78 import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
79 import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
80 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
81 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
82 import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
83 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
84 import org.eclipse.swt.SWT;
85 import org.eclipse.swt.layout.GridData;
86 import org.eclipse.swt.layout.GridLayout;
87 import org.eclipse.swt.widgets.Button;
88 import org.eclipse.swt.widgets.Composite;
89
90 import ca.odell.glazedlists.BasicEventList;
91 import ca.odell.glazedlists.EventList;
92 import ca.odell.glazedlists.SortedList;
93 import ca.odell.glazedlists.TreeList;
94 import eu.etaxonomy.cdm.api.application.CdmApplicationState;
95 import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService;
96 import eu.etaxonomy.cdm.api.service.IProgressMonitorService;
97 import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO;
98 import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor;
99 import eu.etaxonomy.cdm.model.description.DescriptiveDataSet;
100 import eu.etaxonomy.cdm.model.description.Feature;
101 import eu.etaxonomy.cdm.model.description.FeatureNode;
102 import eu.etaxonomy.cdm.model.description.FeatureTree;
103 import eu.etaxonomy.cdm.model.description.MeasurementUnit;
104 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
105 import eu.etaxonomy.cdm.model.description.State;
106 import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper;
107 import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataCellEditor;
108 import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataDisplayConverter;
109 import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataCellEditor;
110 import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataDisplayConverter;
111 import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.supplementalInfo.SupplementalInfoDisplayConverter;
112 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
113 import eu.etaxonomy.taxeditor.model.MessagingUtils;
114 import eu.etaxonomy.taxeditor.store.CdmStore;
115 import eu.etaxonomy.taxeditor.workbench.WorkbenchUtility;
116
117 /**
118 * Character matrix editor for editing specimen/taxon descriptions in a table
119 * @author pplitzner
120 * @since Nov 26, 2017
121 *
122 */
123 public class CharacterMatrix extends Composite {
124
125 private static final String CHARACTER_MATRIX_STATE_PROPERTIES = "characterMatrixState.properties"; //$NON-NLS-1$
126
127 static final int LEADING_COLUMN_COUNT = 4;
128 static final String TAXON_COLUMN = "taxon_column"; //$NON-NLS-1$
129 static final String COLLECTOR_COLUMN = "collector_column"; //$NON-NLS-1$
130 static final String IDENTIFIER_COLUMN = "identifier_column"; //$NON-NLS-1$
131 static final String COUNTRY_COLUMN = "country_column"; //$NON-NLS-1$
132
133 private DescriptiveDataSet descriptiveDataSet;
134
135 private NatTable natTable;
136
137 private Map<Integer, Feature> indexToFeatureMap = new HashMap<>();
138
139 private Map<Feature, List<State>> categoricalFeatureToStateMap = new HashMap<>();
140
141 private LinkedMap<String, String> propertyToLabelMap = new LinkedMap<>();
142
143 private EventList<Object> descriptions;
144
145 private Collection<SpecimenNodeWrapper> specimenCache = null;
146
147 private ListDataProvider<Object> bodyDataProvider;
148
149 private FreezeLayer freezeLayer;
150
151 private ViewportLayer viewportLayer;
152
153 private List<Feature> features;
154
155 private CharacterMatrixPart part;
156
157 private AbstractLayer topMostLayer;
158
159 private FixedSummaryRowLayer summaryRowLayer;
160
161 private ConfigRegistry configRegistry;
162
163 private DefaultBodyLayerStack bodyLayer;
164
165 private boolean isTreeView = true;
166
167 private CharacterMatrixToolbar toolbar;
168
169
170 public CharacterMatrix(Composite parent, CharacterMatrixPart part) {
171 super(parent, SWT.NONE);
172 this.part = part;
173 this.setLayout(new GridLayout());
174
175 createToolBar();
176
177 natTable = new NatTable(this, false);
178
179 createBottomToolbar();
180
181 }
182
183 private void createToolBar(){
184 toolbar = new CharacterMatrixToolbar(this, SWT.NONE);
185 }
186
187 @SuppressWarnings("unused")
188 private void createBottomToolbar() {
189 new CharacterMatrixBottomToolbar(this, SWT.NONE);
190 }
191
192
193 private void applyStyles(){
194 ModernNatTableThemeConfiguration configuration = new ModernNatTableThemeConfiguration();
195 configuration.summaryRowHAlign = HorizontalAlignmentEnum.CENTER;
196 configuration.summaryRowBgColor = GUIHelper.getColor(255, 255, 153);
197 configuration.cHeaderBgColor = GUIHelper.getColor(211, 211, 211);
198 configuration.rHeaderBgColor = GUIHelper.getColor(211, 211, 211);
199 natTable.addConfiguration(configuration);
200 // // NOTE: Getting the colors and fonts from the GUIHelper ensures that
201 // // they are disposed properly (required by SWT)
202 // DefaultNatTableStyleConfiguration natTableConfiguration = new DefaultNatTableStyleConfiguration();
203 // natTableConfiguration.bgColor = GUIHelper.getColor(249, 172, 7);
204 // natTableConfiguration.fgColor = GUIHelper.getColor(30, 76, 19);
205 // natTableConfiguration.hAlign = HorizontalAlignmentEnum.LEFT;
206 // natTableConfiguration.vAlign = VerticalAlignmentEnum.TOP;
207 //// natTableConfiguration.borderStyle = new BorderStyle(1, GUIHelper.getColor(249, 172, 7), LineStyleEnum.SOLID);
208 //
209 // // Setup even odd row colors - row colors override the NatTable default
210 // // colors
211 // DefaultRowStyleConfiguration rowStyleConfiguration = new DefaultRowStyleConfiguration();
212 // rowStyleConfiguration.oddRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_ODD);
213 // rowStyleConfiguration.evenRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_EVEN);
214 //
215 // // Setup selection styling
216 // DefaultSelectionStyleConfiguration selectionStyle = new DefaultSelectionStyleConfiguration();
217 //// selectionStyle.selectionFont = GUIHelper.getFont(new FontData("Verdana", 8, SWT.NORMAL));
218 //// selectionStyle.selectionBgColor = GUIHelper.getColor(217, 232, 251);
219 //// selectionStyle.selectionFgColor = GUIHelper.COLOR_BLACK;
220 //// selectionStyle.anchorBorderStyle = new BorderStyle(1, GUIHelper.COLOR_DARK_GRAY, LineStyleEnum.SOLID);
221 //// selectionStyle.anchorBgColor = GUIHelper.getColor(65, 113, 43);
222 // selectionStyle.selectedHeaderBgColor = GUIHelper.getColor(156, 209, 103);
223 //
224 // // Add all style configurations to NatTable
225 // natTable.addConfiguration(natTableConfiguration);
226 // natTable.addConfiguration(rowStyleConfiguration);
227 // natTable.addConfiguration(selectionStyle);
228 //
229 // // Column/Row header style and custom painters
230 // DefaultRowHeaderStyleConfiguration rowHeaderConfig = new DefaultRowHeaderStyleConfiguration();
231 // Color rowColumnColor = GUIHelper.getColor(230, 255, 255);
232 // rowHeaderConfig.bgColor = rowColumnColor;
233 // natTable.addConfiguration(rowHeaderConfig);
234 // DefaultColumnHeaderStyleConfiguration columnHeaderStyle = new DefaultColumnHeaderStyleConfiguration();
235 // columnHeaderStyle.bgColor = rowColumnColor;
236 // columnHeaderStyle.font = GUIHelper.getFont(new FontData("Verdana", 9, SWT.BOLD)); //$NON-NLS-1$
237 // natTable.addConfiguration(columnHeaderStyle);
238
239 }
240
241 void toggleTreeFlat(boolean isTree, Button btnToggleFlat, Button btnToggleTree, Button btnCollapseAll, Button btnExpandAll, Button btnFreezeSuppInfo) {
242 isTreeView = isTree;
243 createTable(isTree);
244 btnToggleFlat.setEnabled(isTree);
245 btnToggleTree.setEnabled(!isTree);
246 btnCollapseAll.setEnabled(isTree);
247 btnExpandAll.setEnabled(isTree);
248 }
249
250 public boolean isTreeView() {
251 return isTreeView;
252 }
253
254 public void createTable(boolean treeView){
255 /**
256 * layers
257 */
258 createLayers(treeView);
259
260 /**
261 * configuration
262 */
263 configureNatTable(treeView, configRegistry, topMostLayer, summaryRowLayer);
264
265 /**
266 * handlers and listeners
267 */
268 registerHandlersAndListeners(topMostLayer);
269
270 GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
271
272 toolbar.getWsLabel().setText(descriptiveDataSet.getLabel());
273 toolbar.getWsLabel().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
274 toolbar.getWsLabel().getParent().layout();
275
276 freezeSupplementalColumns(true);
277
278 this.layout();
279 }
280
281 private List<Feature> initFeatureList(FeatureNode node){
282 List<Feature> features = new ArrayList<>();
283 node.getChildNodes().forEach(childNode->
284 {
285 features.add(childNode.getFeature());
286 features.addAll(initFeatureList(childNode));
287 });
288 return features;
289 }
290
291 public void initDescriptiveDataSet(DescriptiveDataSet descriptiveDataSet){
292 this.descriptiveDataSet = descriptiveDataSet;
293 //get features/columns stored in descriptive data set
294 FeatureTree tree = descriptiveDataSet.getDescriptiveSystem();
295 features = initFeatureList(tree.getRoot());
296
297 //init state data for categorical features
298 features.forEach(feature->
299 {
300 if(feature.isSupportsCategoricalData()){
301 List<State> supportedStates = new ArrayList<>();
302 feature.getSupportedCategoricalEnumerations().forEach(voc->supportedStates.addAll(voc.getTerms()));
303 categoricalFeatureToStateMap.put(feature, supportedStates);
304 }
305 });
306 descriptions = new BasicEventList<>();
307
308 }
309
310 private void createLayers(boolean treeView) {
311 // use the SortedList constructor with 'null' for the Comparator
312 // because the Comparator will be set by configuration
313 SortedList<Object> sortedList = new SortedList<>(descriptions, new MatrixRowComparator());
314 // wrap the SortedList with the TreeList
315 TreeList<Object> treeList = new TreeList(sortedList, new DescriptionTreeFormat(descriptiveDataSet), TreeList.NODES_START_EXPANDED);
316 /**
317 * data provider
318 */
319 SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
320 bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor);
321
322 configRegistry = new ConfigRegistry();
323
324
325 /**
326 * BODY layer
327 *
328 *
329
330 CompositeLayer
331 - (top) SummaryRowLayer
332 - (bottom) ViewportLayer
333
334 ^
335 ViewportLayer
336
337 ^
338 TreeLayer (default visible)
339
340 ^
341 CompositeFreezeLayer
342 - viewportLayer
343 - selectionLayer
344 - freezeLayer
345
346 ^
347 FreezeLayer
348
349 ^
350 SelectionLayer
351
352 ^
353 ColumnHideShowLayer
354
355 ^
356 ColumnReorderLayer
357
358 ^
359 DataLayer
360
361 *
362
363 */
364 DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
365
366 //register labels
367 CharacterMatrixConfigLabelAccumulator labelAccumulator = new CharacterMatrixConfigLabelAccumulator(this);
368 bodyDataLayer.setConfigLabelAccumulator(labelAccumulator);
369
370
371 propertyToLabelMap.put(TAXON_COLUMN, Messages.CharacterMatrix_TAXON);
372 propertyToLabelMap.put(COLLECTOR_COLUMN, Messages.CharacterMatrix_COLLECTOR_NO);
373 propertyToLabelMap.put(IDENTIFIER_COLUMN, Messages.CharacterMatrix_IDENTIFIER);
374 propertyToLabelMap.put(COUNTRY_COLUMN, Messages.CharacterMatrix_COUNTRY);
375 for(int i=0;i<features.size();i++){
376 Feature feature = features.get(i);
377 initLabels(i, feature);
378 }
379
380 // layer for event handling of GlazedLists and PropertyChanges
381 GlazedListsEventLayer eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, treeList);
382 GlazedListTreeData treeData = new GlazedListTreeData<>(treeList);
383 ITreeRowModel treeRowModel = new GlazedListTreeRowModel<>(treeData);
384
385 bodyLayer = new DefaultBodyLayerStack(
386 eventLayer);
387 viewportLayer = bodyLayer.getViewportLayer();
388 final SelectionLayer selectionLayer = bodyLayer.getSelectionLayer();
389 freezeLayer = new FreezeLayer(selectionLayer);
390 final CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(
391 freezeLayer, bodyLayer.getViewportLayer(), selectionLayer);
392 TreeLayer treeLayer = new TreeLayer(compositeFreezeLayer, treeRowModel);
393
394 topMostLayer = treeView?treeLayer:compositeFreezeLayer;
395
396 summaryRowLayer = new FixedSummaryRowLayer(bodyDataLayer, topMostLayer, configRegistry, false);
397 //regoster labels with summary prefix for summary layer
398 ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
399 summaryRowLayer.setConfigLabelAccumulator(summaryColumnLabelAccumulator);
400 for(int i=0;i<features.size();i++){
401 Feature feature = features.get(i);
402 summaryColumnLabelAccumulator.registerColumnOverrides(
403 i+LEADING_COLUMN_COUNT,
404 SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+MatrixUtility.getProperty(feature));
405 }
406 // because the horizontal dependency is the ViewportLayer
407 // we need to set the composite dependency to false
408 summaryRowLayer.setHorizontalCompositeDependency(false);
409
410 CompositeLayer composite = new CompositeLayer(1, 2);
411 composite.setChildLayer("SUMMARY", summaryRowLayer, 0, 0); //$NON-NLS-1$
412 composite.setChildLayer(GridRegion.BODY, topMostLayer, 0, 1);
413
414
415 /**
416 * column header layer
417 */
418 IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
419 propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
420 DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
421 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, topMostLayer, selectionLayer);
422
423 // add the SortHeaderLayer to the column header layer stack
424 // as we use GlazedLists, we use the GlazedListsSortModel which
425 // delegates the sorting to the SortedList
426 final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
427 columnHeaderLayer,
428 new GlazedListsSortModel<>(
429 sortedList,
430 columnPropertyAccessor,
431 configRegistry,
432 columnHeaderDataLayer));
433
434
435 /**
436 * row header layer
437 */
438 IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
439 DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
440 FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer = new FixedSummaryRowHeaderLayer(rowHeaderDataLayer,
441 composite, selectionLayer);
442 fixedSummaryRowHeaderLayer.setSummaryRowLabel("\u2211"); //$NON-NLS-1$
443
444
445 /**
446 * corner layer
447 */
448 ILayer cornerLayer = new CornerLayer(
449 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
450 fixedSummaryRowHeaderLayer, sortHeaderLayer);
451
452
453 /**
454 * GRID layer (composition of all other layers)
455 */
456 GridLayer gridLayer = new GridLayer(composite, sortHeaderLayer, fixedSummaryRowHeaderLayer, cornerLayer);
457
458 natTable.setLayer(gridLayer);
459
460 }
461
462 private void registerHandlersAndListeners(AbstractLayer topMostLayer) {
463 // add the ExportCommandHandler to the ViewportLayer in order to make
464 // exporting work
465 topMostLayer.registerCommandHandler(new ExportCommandHandler(topMostLayer));
466
467 //propagate single cell selection
468 natTable.addLayerListener(new ILayerListener() {
469 @Override
470 public void handleLayerEvent(ILayerEvent event) {
471 if(event instanceof CellSelectionEvent){
472 CellSelectionEvent cellSelectionEvent = (CellSelectionEvent)event;
473 int columnPosition = cellSelectionEvent.getColumnPosition();
474 if(columnPosition>LEADING_COLUMN_COUNT){
475 Collection<ILayerCell> selectedCells = cellSelectionEvent.getSelectionLayer().getSelectedCells();
476 StructuredSelection selection = new StructuredSelection();
477 if(selectedCells.size()==1){
478 ILayerCell cell = selectedCells.iterator().next();
479 Object dataValue = cell.getDataValue();
480 if(dataValue!=null){
481 selection = new StructuredSelection(dataValue);
482 }
483 }
484 part.getSelectionService().setSelection(selection);
485 }
486 }
487 }
488 });
489
490 //register handler for view configuration menu
491 natTable.registerCommandHandler(toolbar.getDisplayPersistenceDialogCommandHandler());
492 }
493
494 private void configureNatTable(boolean treeView, ConfigRegistry configRegistry, AbstractLayer topMostLayer,
495 FixedSummaryRowLayer summaryRowLayer) {
496 /**
497 * CONFIGURATION
498 */
499 natTable.setConfigRegistry(configRegistry);
500
501 applyStyles();
502
503 //add default configuration because autoconfigure is set to false in constructor
504 natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
505
506 //FIXME: this is for DEBUG ONLY
507 // natTable.addConfiguration(new DebugMenuConfiguration(natTable));
508
509 // override the default sort configuration and change the mouse bindings
510 // to sort on a single click
511 if(!treeView){
512 natTable.addConfiguration(new SingleClickSortConfiguration());
513 }
514
515 // add the header menu configuration for adding the column header menu
516 // with hide/show actions
517 natTable.addConfiguration(new AbstractHeaderMenuConfiguration(natTable) {
518
519 @Override
520 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
521 return super.createColumnHeaderMenu(natTable)
522 .withHideColumnMenuItem()
523 .withShowAllColumnsMenuItem();
524 }
525
526 });
527
528 // add custom configuration for data conversion and add column labels to viewport layer
529 topMostLayer.addConfiguration(new AbstractRegistryConfiguration() {
530 @Override
531 public void configureRegistry(IConfigRegistry configRegistry) {
532 //add display converter for string representation
533 configRegistry.registerConfigAttribute(
534 CellConfigAttributes.DISPLAY_CONVERTER,
535 new SupplementalInfoDisplayConverter(),
536 DisplayMode.NORMAL,
537 TAXON_COLUMN);
538 configRegistry.registerConfigAttribute(
539 CellConfigAttributes.DISPLAY_CONVERTER,
540 new SupplementalInfoDisplayConverter(),
541 DisplayMode.NORMAL,
542 COLLECTOR_COLUMN);
543 configRegistry.registerConfigAttribute(
544 CellConfigAttributes.DISPLAY_CONVERTER,
545 new SupplementalInfoDisplayConverter(),
546 DisplayMode.NORMAL,
547 IDENTIFIER_COLUMN);
548 configRegistry.registerConfigAttribute(
549 CellConfigAttributes.DISPLAY_CONVERTER,
550 new SupplementalInfoDisplayConverter(),
551 DisplayMode.NORMAL,
552 COUNTRY_COLUMN);
553 features.forEach(feature->registerColumnConfiguration(feature, configRegistry));
554 }
555
556 });
557
558 //no summary for the supplemental columns
559 for(int i=0;i<LEADING_COLUMN_COUNT;i++){
560 int index = i;
561 summaryRowLayer.addConfiguration(new DefaultSummaryRowConfiguration() {
562 @Override
563 public void addSummaryProviderConfig(IConfigRegistry configRegistry) {
564 configRegistry.registerConfigAttribute(
565 SummaryRowConfigAttributes.SUMMARY_PROVIDER,
566 new ISummaryProvider() {
567
568 @Override
569 public Object summarize(int columnIndex) {
570 return "";
571 }
572 },
573 DisplayMode.NORMAL,
574 SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+index);
575 }
576 });
577 }
578 //register aggregation configuration for each feature
579 features.forEach(feature->summaryRowLayer.addConfiguration(new AggregationConfiguration(bodyDataProvider, feature)));
580
581 natTable.configure();
582 }
583
584 void freezeSupplementalColumns(boolean freeze){
585 if(freeze){
586 FreezeHelper.freeze(freezeLayer, viewportLayer,
587 new PositionCoordinate(viewportLayer, 0, 0),
588 new PositionCoordinate(viewportLayer, LEADING_COLUMN_COUNT-1, -1));
589 }
590 else{
591 FreezeHelper.unfreeze(freezeLayer, viewportLayer);
592 }
593 }
594
595 void selectStateItem(ComboViewer comboStates, String stateName){
596 String[] items = comboStates.getCombo().getItems();
597 for(int i=0;i<items.length;i++){
598 if(items[i].equals(stateName)){
599 comboStates.getCombo().select(i);
600 break;
601 }
602 }
603 }
604
605 private void initLabels(int index, Feature feature) {
606 indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
607
608 String featureLabel = feature.getLabel();
609 String property = featureLabel;
610 //show unit for quantitative data
611 if(feature.isSupportsQuantitativeData()){
612 Set<MeasurementUnit> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
613 if(recommendedMeasurementUnits.size()>1){
614 MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class,
615 String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel()));
616 }
617 if(recommendedMeasurementUnits.size()==1){
618 MeasurementUnit unit = recommendedMeasurementUnits.iterator().next();
619 featureLabel += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
620 }
621 }
622 propertyToLabelMap.put(property, featureLabel);
623 }
624
625 private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) {
626 //make cell editable
627 configRegistry.registerConfigAttribute(
628 EditConfigAttributes.CELL_EDITABLE_RULE,
629 IEditableRule.ALWAYS_EDITABLE,
630 DisplayMode.EDIT,
631 MatrixUtility.getProperty(feature)
632 );
633 if(feature.isSupportsQuantitativeData()){
634 //add display converter for string representation
635 configRegistry.registerConfigAttribute(
636 CellConfigAttributes.DISPLAY_CONVERTER,
637 new QuantitativeDataDisplayConverter(),
638 DisplayMode.NORMAL,
639 MatrixUtility.getProperty(feature));
640 //register quantitative editor
641 configRegistry.registerConfigAttribute(
642 EditConfigAttributes.CELL_EDITOR,
643 new QuantitativeDataCellEditor(feature, this),
644 DisplayMode.EDIT,
645 MatrixUtility.getProperty(feature));
646 }
647 else if(feature.isSupportsCategoricalData()){
648 //add display converter for string representation
649 configRegistry.registerConfigAttribute(
650 CellConfigAttributes.DISPLAY_CONVERTER,
651 new CategoricalDataDisplayConverter(),
652 DisplayMode.NORMAL,
653 MatrixUtility.getProperty(feature));
654
655 //add combo box cell editor
656 //register editor
657 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
658 new CategoricalDataCellEditor(getSupportedStatesForCategoricalFeature(feature), this, feature),
659 DisplayMode.EDIT,
660 MatrixUtility.getProperty(feature));
661
662 }
663
664 }
665
666
667 public void loadDescriptions(DescriptiveDataSet descriptiveDataSet) {
668 UUID monitorUuid = CdmStore.getService(IDescriptiveDataSetService.class).monitGetRowWrapper(descriptiveDataSet);
669 IProgressMonitorService progressMonitorService = CdmApplicationState.getCurrentAppConfig().getProgressMonitorService();
670
671
672 String jobLabel = "Load character data";
673 Job job = Job.create(jobLabel, (ICoreRunnable) monitor -> {
674 monitor.beginTask(jobLabel, IProgressMonitor.UNKNOWN);
675 while(progressMonitorService.isMonitorThreadRunning(monitorUuid)){
676 if(monitor.isCanceled()){
677 progressMonitorService.interrupt(monitorUuid);
678 }
679 }
680 IRemotingProgressMonitor remotingMonitor = progressMonitorService.getRemotingMonitor(monitorUuid);
681 Collection<RowWrapperDTO> wrappers = (Collection<RowWrapperDTO>) remotingMonitor.getResult();
682 if(wrappers!=null){
683 wrappers.forEach(wrapper->CharacterMatrix.this.descriptions.add(wrapper));
684 }
685 monitor.done();
686 });
687 job.schedule();
688 }
689
690 public List<State> getSupportedStatesForCategoricalFeature(Feature feature){
691 return categoricalFeatureToStateMap.get(feature);
692 }
693
694 public Map<Integer, Feature> getIndexToFeatureMap() {
695 return indexToFeatureMap;
696 }
697
698 public LinkedMap<String, String> getPropertyToLabelMap() {
699 return propertyToLabelMap;
700 }
701
702 public void setDirty() {
703 part.setDirty();
704 }
705
706 public NatTable getNatTable() {
707 return natTable;
708 }
709
710 public EventList<Object> getDescriptions() {
711 return descriptions;
712 }
713
714 public DescriptiveDataSet getDescriptiveDataSet() {
715 return descriptiveDataSet;
716 }
717
718 public Collection<SpecimenNodeWrapper> getSpecimenCache() {
719 return specimenCache;
720 }
721
722 public void setSpecimenCache(Collection<SpecimenNodeWrapper> specimenCache) {
723 this.specimenCache = specimenCache.stream()
724 .filter(wrapper ->
725 //map descriptions on a list of uuids of the described specimen
726 !this.descriptions.stream()
727 .map(o->((RowWrapperDTO)o).getSpecimen().getUuid())
728 .collect(Collectors.toList())
729 //an check if the specimen to add is already contained
730 .contains(wrapper.getUuidAndTitleCache().getUuid())
731 )
732 .collect(Collectors.toList());
733 }
734
735 public Properties getNatTableState() {
736 return toolbar.getNatTableState();
737 }
738
739 public ListDataProvider<Object> getBodyDataProvider() {
740 return bodyDataProvider;
741 }
742
743 DefaultBodyLayerStack getBodyLayer() {
744 return bodyLayer;
745 }
746
747 File getStatePropertiesFile() {
748 return new File(WorkbenchUtility.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES);
749 }
750
751 public List<Feature> getFeatures() {
752 return features;
753 }
754
755 }