2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
9 package eu
.etaxonomy
.taxeditor
.editor
.descriptiveDataSet
.matrix
;
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
;
18 import java
.util
.Properties
;
20 import java
.util
.UUID
;
21 import java
.util
.stream
.Collectors
;
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
;
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
;
141 * Character matrix editor for editing specimen/taxon descriptions in a table
143 * @since Nov 26, 2017
146 public class CharacterMatrix
extends Composite
{
148 private static final String CHARACTER_MATRIX_STATE_PROPERTIES
= "characterMatrixState.properties"; //$NON-NLS-1$
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$
156 private DescriptiveDataSet descriptiveDataSet
;
158 private NatTable natTable
;
160 private Map
<Integer
, Feature
> indexToFeatureMap
= new HashMap
<>();
162 private Map
<Feature
, List
<State
>> categoricalFeatureToStateMap
= new HashMap
<>();
164 private LinkedMap
<String
, String
> propertyToLabelMap
= new LinkedMap
<>();
166 private EventList
<Object
> descriptions
;
168 private Collection
<SpecimenNodeWrapper
> specimenCache
= null;
170 private ListDataProvider
<Object
> bodyDataProvider
;
172 private FreezeLayer freezeLayer
;
174 private ViewportLayer viewportLayer
;
176 private List
<Feature
> features
;
178 private CharacterMatrixPart part
;
180 private AbstractLayer topMostLayer
;
182 private FixedSummaryRowLayer summaryRowLayer
;
184 private ConfigRegistry configRegistry
;
186 private DefaultBodyLayerStack bodyLayer
;
188 private boolean isTreeView
= true;
190 private CharacterMatrixToolbar toolbar
;
193 public CharacterMatrix(Composite parent
, CharacterMatrixPart part
) {
194 super(parent
, SWT
.NONE
);
196 this.setLayout(new GridLayout());
200 natTable
= new NatTable(this, false);
204 createBottomToolbar();
208 private void createToolBar(){
209 toolbar
= new CharacterMatrixToolbar(this, SWT
.NONE
);
212 private void createBottomToolbar() {
213 Composite buttonPanel
= new Composite(this, SWT
.NONE
);
215 buttonPanel
.setLayout(new RowLayout());
216 GridDataFactory
.fillDefaults().grab(true, false).applyTo(buttonPanel
);
219 * Add description button
221 Button btnAddDescription
= new Button(buttonPanel
, SWT
.PUSH
);
222 btnAddDescription
.setImage(ImageResources
.getImage(ImageResources
.ADD_ICON_GREEN
));
223 btnAddDescription
.addSelectionListener(new SelectionAdapter() {
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
);
239 RowWrapperDTO rowWrapper
= CdmStore
.getService(IDescriptiveDataSetService
.class).createRowWrapper(description
, descriptiveDataSet
);
240 CharacterMatrix
.this.descriptions
.add(rowWrapper
);
241 descriptiveDataSet
.addDescription(description
);
243 specimenCache
.remove(wrapper
);
249 * Remove description button
251 Button btnRemoveDescription
= new Button(buttonPanel
, SWT
.PUSH
);
252 btnRemoveDescription
.setImage(ImageResources
.getImage(ImageResources
.ACTIVE_DELETE_ICON
));
253 btnRemoveDescription
.addSelectionListener(new SelectionAdapter() {
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
);
264 toRemove
.forEach(rowToRemove
->{
265 CharacterMatrix
.this.descriptions
.remove(rowToRemove
);
266 descriptiveDataSet
.removeDescription(rowToRemove
.getSpecimenDescription());
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);
283 // Setup even odd row colors - row colors override the NatTable default
285 DefaultRowStyleConfiguration rowStyleConfiguration
= new DefaultRowStyleConfiguration();
286 rowStyleConfiguration
.oddRowBgColor
= ColorResources
.getColor(Resources
.COLOR_LIST_ODD
);
287 rowStyleConfiguration
.evenRowBgColor
= ColorResources
.getColor(Resources
.COLOR_LIST_EVEN
);
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);
298 // Add all style configurations to NatTable
299 natTable
.addConfiguration(natTableConfiguration
);
300 natTable
.addConfiguration(rowStyleConfiguration
);
301 natTable
.addConfiguration(selectionStyle
);
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
);
315 void toggleTreeFlat(boolean isTree
, Button btnToggleFlat
, Button btnToggleTree
, Button btnCollapseAll
, Button btnExpandAll
, Button btnFreezeSuppInfo
) {
318 btnToggleFlat
.setEnabled(isTree
);
319 btnToggleTree
.setEnabled(!isTree
);
320 btnCollapseAll
.setEnabled(isTree
);
321 btnExpandAll
.setEnabled(isTree
);
324 public boolean isTreeView() {
328 public void createTable(boolean treeView
){
332 createLayers(treeView
);
337 configureNatTable(treeView
, configRegistry
, topMostLayer
, summaryRowLayer
);
340 * handlers and listeners
342 registerHandlersAndListeners(topMostLayer
);
344 GridDataFactory
.fillDefaults().grab(true, true).applyTo(natTable
);
346 toolbar
.getWsLabel().setText(descriptiveDataSet
.getLabel());
347 toolbar
.getWsLabel().setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, true, false));
348 toolbar
.getWsLabel().getParent().layout();
350 freezeSupplementalColumns(true);
355 private List
<Feature
> initFeatureList(FeatureNode node
){
356 List
<Feature
> features
= new ArrayList
<>();
357 node
.getChildNodes().forEach(childNode
->
359 features
.add(childNode
.getFeature());
360 features
.addAll(initFeatureList(childNode
));
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());
371 //init state data for categorical features
372 features
.forEach(feature
->
374 if(feature
.isSupportsCategoricalData()){
375 List
<State
> supportedStates
= new ArrayList
<>();
376 feature
.getSupportedCategoricalEnumerations().forEach(voc
->supportedStates
.addAll(voc
.getTerms()));
377 categoricalFeatureToStateMap
.put(feature
, supportedStates
);
380 descriptions
= new BasicEventList
<>();
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
);
393 SpecimenColumnPropertyAccessor columnPropertyAccessor
= new SpecimenColumnPropertyAccessor(this);
394 bodyDataProvider
= treeView?
new ListDataProvider
<>(treeList
, columnPropertyAccessor
):new ListDataProvider
<>(sortedList
, columnPropertyAccessor
);
396 configRegistry
= new ConfigRegistry();
405 - (top) SummaryRowLayer
406 - (bottom) ViewportLayer
412 TreeLayer (default visible)
438 DataLayer bodyDataLayer
= new DataLayer(bodyDataProvider
);
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
);
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
);
461 bodyLayer
= new DefaultBodyLayerStack(
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
);
470 topMostLayer
= treeView?treeLayer
:compositeFreezeLayer
;
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
));
482 // because the horizontal dependency is the ViewportLayer
483 // we need to set the composite dependency to false
484 summaryRowLayer
.setHorizontalCompositeDependency(false);
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);
492 * column header layer
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
);
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
<>(
504 new GlazedListsSortModel
<>(
506 columnPropertyAccessor
,
508 columnHeaderDataLayer
));
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$
524 ILayer cornerLayer
= new CornerLayer(
525 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider
, rowHeaderDataProvider
)),
526 fixedSummaryRowHeaderLayer
, sortHeaderLayer
);
530 * GRID layer (composition of all other layers)
532 GridLayer gridLayer
= new GridLayer(composite
, sortHeaderLayer
, fixedSummaryRowHeaderLayer
, cornerLayer
);
534 natTable
.setLayer(gridLayer
);
538 private void registerHandlersAndListeners(AbstractLayer topMostLayer
) {
539 // add the ExportCommandHandler to the ViewportLayer in order to make
541 topMostLayer
.registerCommandHandler(new ExportCommandHandler(topMostLayer
));
543 //propagate single cell selection
544 natTable
.addLayerListener(new ILayerListener() {
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();
557 selection
= new StructuredSelection(dataValue
);
560 part
.getSelectionService().setSelection(selection
);
566 //register handler for view configuration menu
567 natTable
.registerCommandHandler(toolbar
.getDisplayPersistenceDialogCommandHandler());
570 private void configureNatTable(boolean treeView
, ConfigRegistry configRegistry
, AbstractLayer topMostLayer
,
571 FixedSummaryRowLayer summaryRowLayer
) {
575 natTable
.setConfigRegistry(configRegistry
);
577 //add default configuration because autoconfigure is set to false in constructor
578 natTable
.addConfiguration(new DefaultNatTableStyleConfiguration());
580 //FIXME: this is for DEBUG ONLY
581 // natTable.addConfiguration(new DebugMenuConfiguration(natTable));
583 // override the default sort configuration and change the mouse bindings
584 // to sort on a single click
586 natTable
.addConfiguration(new SingleClickSortConfiguration());
589 // add the header menu configuration for adding the column header menu
590 // with hide/show actions
591 natTable
.addConfiguration(new AbstractHeaderMenuConfiguration(natTable
) {
594 protected PopupMenuBuilder
createColumnHeaderMenu(NatTable natTable
) {
595 return super.createColumnHeaderMenu(natTable
)
596 .withHideColumnMenuItem()
597 .withShowAllColumnsMenuItem();
602 Style cellStyle
= new Style();
603 cellStyle
.setAttributeValue(CellStyleAttributes
.HORIZONTAL_ALIGNMENT
, HorizontalAlignmentEnum
.LEFT
);
605 // add custom configuration for data conversion and add column labels to viewport layer
606 topMostLayer
.addConfiguration(new AbstractRegistryConfiguration() {
608 public void configureRegistry(IConfigRegistry configRegistry
) {
609 //add display converter for string representation
610 configRegistry
.registerConfigAttribute(
611 CellConfigAttributes
.DISPLAY_CONVERTER
,
612 new SupplementalInfoDisplayConverter(),
615 configRegistry
.registerConfigAttribute(
616 CellConfigAttributes
.CELL_STYLE
,
620 configRegistry
.registerConfigAttribute(
621 CellConfigAttributes
.DISPLAY_CONVERTER
,
622 new SupplementalInfoDisplayConverter(),
625 configRegistry
.registerConfigAttribute(
626 CellConfigAttributes
.CELL_STYLE
,
630 configRegistry
.registerConfigAttribute(
631 CellConfigAttributes
.DISPLAY_CONVERTER
,
632 new SupplementalInfoDisplayConverter(),
635 configRegistry
.registerConfigAttribute(
636 CellConfigAttributes
.CELL_STYLE
,
640 configRegistry
.registerConfigAttribute(
641 CellConfigAttributes
.DISPLAY_CONVERTER
,
642 new SupplementalInfoDisplayConverter(),
645 configRegistry
.registerConfigAttribute(
646 CellConfigAttributes
.CELL_STYLE
,
650 features
.forEach(feature
->registerColumnConfiguration(feature
, configRegistry
));
655 //no summary for the supplemental columns
656 for(int i
=0;i
<LEADING_COLUMN_COUNT
;i
++){
658 summaryRowLayer
.addConfiguration(new DefaultSummaryRowConfiguration() {
660 public void addSummaryProviderConfig(IConfigRegistry configRegistry
) {
661 configRegistry
.registerConfigAttribute(
662 SummaryRowConfigAttributes
.SUMMARY_PROVIDER
,
663 new ISummaryProvider() {
666 public Object
summarize(int columnIndex
) {
671 SummaryRowLayer
.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX
+index
);
675 //register aggregation configuration for each feature
676 features
.forEach(feature
->summaryRowLayer
.addConfiguration(new AggregationConfiguration(bodyDataProvider
, feature
)));
678 natTable
.configure();
681 void freezeSupplementalColumns(boolean freeze
){
683 FreezeHelper
.freeze(freezeLayer
, viewportLayer
,
684 new PositionCoordinate(viewportLayer
, 0, 0),
685 new PositionCoordinate(viewportLayer
, LEADING_COLUMN_COUNT
-1, -1));
688 FreezeHelper
.unfreeze(freezeLayer
, viewportLayer
);
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
);
702 private SpecimenDescription
getDescriptionForDescriptiveDataSet(SpecimenOrObservationBase specimen
){
703 Set
<Feature
> wsFeatures
= descriptiveDataSet
.getDescriptiveSystem().getDistinctFeatures();
704 List
<DescriptionElementBase
> matchingDescriptionElements
= new ArrayList
<>();
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
);
717 //if description with the exact same features is found return the description
718 if(specimenDescriptionFeatures
.equals(wsFeatures
)){
719 return specimenDescription
;
722 //Create new specimen description if no match was found
724 SpecimenDescription newDesription
= SpecimenDescription
.NewInstance(specimen
);
725 newDesription
.setTitleCache(Messages
.CharacterMatrix_DESCRIPTIVE_DATA_SET
+descriptiveDataSet
.getLabel()+": "+newDesription
.generateTitle(), true); //$NON-NLS-2$
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());
732 list
= new ArrayList
<>();
735 featureToElementMap
.put(element
.getFeature(), list
);
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));
744 //duplicates found -> check if all are equal
746 DescriptionElementBase match
= null;
747 for (DescriptionElementBase descriptionElementBase
: elements
) {
749 match
= descriptionElementBase
;
751 else if(!new DescriptionElementCompareWrapper(match
).equals(new DescriptionElementCompareWrapper(descriptionElementBase
))){
753 MessagingUtils
.informationDialog(Messages
.CharacterMatrix_MULTIPLE_DATA
,
754 String
.format(Messages
.CharacterMatrix_MULTIPLE_DATA_MESSAGE
, feature
.getLabel()));
759 descriptionElementsToClone
.add(match
);
763 //clone matching descriptionElements
764 for (DescriptionElementBase descriptionElementBase
: descriptionElementsToClone
) {
765 DescriptionElementBase clone
;
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
);
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
)){
784 if(wsFeature
.isSupportsCategoricalData()){
785 newDesription
.addElement(CategoricalData
.NewInstance(wsFeature
));
787 else if(wsFeature
.isSupportsQuantitativeData()){
788 newDesription
.addElement(QuantitativeData
.NewInstance(wsFeature
));
792 return newDesription
;
796 private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator
,
797 int index
, Feature feature
) {
799 columnLabelAccumulator
.registerColumnOverrides(index
+LEADING_COLUMN_COUNT
, MatrixUtility
.getProperty(feature
));
800 indexToFeatureMap
.put(index
+LEADING_COLUMN_COUNT
, feature
);
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()));
811 if(recommendedMeasurementUnits
.size()==1){
812 MeasurementUnit unit
= recommendedMeasurementUnits
.iterator().next();
813 featureLabel
+= " ["+unit
.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
816 propertyToLabelMap
.put(property
, featureLabel
);
819 private void registerColumnConfiguration(Feature feature
, IConfigRegistry configRegistry
) {
821 configRegistry
.registerConfigAttribute(
822 EditConfigAttributes
.CELL_EDITABLE_RULE
,
823 IEditableRule
.ALWAYS_EDITABLE
,
825 MatrixUtility
.getProperty(feature
)
827 if(feature
.isSupportsQuantitativeData()){
828 //add display converter for string representation
829 configRegistry
.registerConfigAttribute(
830 CellConfigAttributes
.DISPLAY_CONVERTER
,
831 new QuantitativeDataDisplayConverter(),
833 MatrixUtility
.getProperty(feature
));
834 //register quantitative editor
835 configRegistry
.registerConfigAttribute(
836 EditConfigAttributes
.CELL_EDITOR
,
837 new QuantitativeDataCellEditor(feature
, this),
839 MatrixUtility
.getProperty(feature
));
841 else if(feature
.isSupportsCategoricalData()){
842 //add display converter for string representation
843 configRegistry
.registerConfigAttribute(
844 CellConfigAttributes
.DISPLAY_CONVERTER
,
845 new CategoricalDataDisplayConverter(),
847 MatrixUtility
.getProperty(feature
));
849 //add combo box cell editor
851 configRegistry
.registerConfigAttribute(EditConfigAttributes
.CELL_EDITOR
,
852 new CategoricalDataCellEditor(getSupportedStatesForCategoricalFeature(feature
), this, feature
),
854 MatrixUtility
.getProperty(feature
));
861 public void loadDescriptions(DescriptiveDataSet descriptiveDataSet
) {
862 UUID monitorUuid
= CdmStore
.getService(IDescriptiveDataSetService
.class).monitGetRowWrapper(descriptiveDataSet
);
863 IProgressMonitorService progressMonitorService
= CdmApplicationState
.getCurrentAppConfig().getProgressMonitorService();
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
);
874 IRemotingProgressMonitor remotingMonitor
= progressMonitorService
.getRemotingMonitor(monitorUuid
);
875 Collection
<RowWrapperDTO
> wrappers
= (Collection
<RowWrapperDTO
>) remotingMonitor
.getResult();
877 wrappers
.forEach(wrapper
->CharacterMatrix
.this.descriptions
.add(wrapper
));
884 public List
<State
> getSupportedStatesForCategoricalFeature(Feature feature
){
885 return categoricalFeatureToStateMap
.get(feature
);
888 public Map
<Integer
, Feature
> getIndexToFeatureMap() {
889 return indexToFeatureMap
;
892 public LinkedMap
<String
, String
> getPropertyToLabelMap() {
893 return propertyToLabelMap
;
896 public void setDirty() {
900 public NatTable
getNatTable() {
904 public EventList
<Object
> getDescriptions() {
908 public DescriptiveDataSet
getDescriptiveDataSet() {
909 return descriptiveDataSet
;
912 public Collection
<SpecimenNodeWrapper
> getSpecimenCache() {
913 return specimenCache
;
916 public void setSpecimenCache(Collection
<SpecimenNodeWrapper
> specimenCache
) {
917 this.specimenCache
= specimenCache
.stream()
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())
926 .collect(Collectors
.toList());
929 public Properties
getNatTableState() {
930 return toolbar
.getNatTableState();
933 public ListDataProvider
<Object
> getBodyDataProvider() {
934 return bodyDataProvider
;
937 File
getStatePropertiesFile() {
938 return new File(WorkbenchUtility
.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES
);