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
.Collections
;
15 import java
.util
.HashMap
;
16 import java
.util
.LinkedList
;
17 import java
.util
.List
;
19 import java
.util
.Map
.Entry
;
20 import java
.util
.Properties
;
22 import java
.util
.TreeSet
;
23 import java
.util
.UUID
;
24 import java
.util
.stream
.Collectors
;
26 import javax
.inject
.Inject
;
28 import org
.apache
.commons
.collections4
.map
.LinkedMap
;
29 import org
.eclipse
.core
.runtime
.ICoreRunnable
;
30 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
31 import org
.eclipse
.core
.runtime
.SubMonitor
;
32 import org
.eclipse
.core
.runtime
.jobs
.IJobChangeEvent
;
33 import org
.eclipse
.core
.runtime
.jobs
.Job
;
34 import org
.eclipse
.core
.runtime
.jobs
.JobChangeAdapter
;
35 import org
.eclipse
.e4
.core
.di
.annotations
.Optional
;
36 import org
.eclipse
.e4
.ui
.di
.UIEventTopic
;
37 import org
.eclipse
.e4
.ui
.di
.UISynchronize
;
38 import org
.eclipse
.e4
.ui
.services
.EMenuService
;
39 import org
.eclipse
.jface
.layout
.GridDataFactory
;
40 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
41 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
42 import org
.eclipse
.nebula
.widgets
.nattable
.NatTable
;
43 import org
.eclipse
.nebula
.widgets
.nattable
.config
.AbstractUiBindingConfiguration
;
44 import org
.eclipse
.nebula
.widgets
.nattable
.config
.ConfigRegistry
;
45 import org
.eclipse
.nebula
.widgets
.nattable
.config
.DefaultNatTableStyleConfiguration
;
46 import org
.eclipse
.nebula
.widgets
.nattable
.coordinate
.PositionCoordinate
;
47 import org
.eclipse
.nebula
.widgets
.nattable
.coordinate
.Range
;
48 import org
.eclipse
.nebula
.widgets
.nattable
.copy
.command
.InternalCopyDataCommandHandler
;
49 import org
.eclipse
.nebula
.widgets
.nattable
.copy
.command
.InternalPasteDataCommandHandler
;
50 import org
.eclipse
.nebula
.widgets
.nattable
.data
.IDataProvider
;
51 import org
.eclipse
.nebula
.widgets
.nattable
.data
.ListDataProvider
;
52 import org
.eclipse
.nebula
.widgets
.nattable
.export
.command
.ExportCommandHandler
;
53 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.e4
.selection
.E4SelectionListener
;
54 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.GlazedListsEventLayer
;
55 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.GlazedListsSortModel
;
56 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.tree
.GlazedListTreeData
;
57 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.tree
.GlazedListTreeRowModel
;
58 import org
.eclipse
.nebula
.widgets
.nattable
.freeze
.CompositeFreezeLayer
;
59 import org
.eclipse
.nebula
.widgets
.nattable
.freeze
.FreezeHelper
;
60 import org
.eclipse
.nebula
.widgets
.nattable
.freeze
.FreezeLayer
;
61 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.command
.ClientAreaResizeCommand
;
62 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultColumnHeaderDataProvider
;
63 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultCornerDataProvider
;
64 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultRowHeaderDataProvider
;
65 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.layer
.ColumnHeaderLayer
;
66 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.layer
.CornerLayer
;
67 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.layer
.DefaultRowHeaderDataLayer
;
68 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.layer
.GridLayer
;
69 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.layer
.RowHeaderLayer
;
70 import org
.eclipse
.nebula
.widgets
.nattable
.group
.ColumnGroupGroupHeaderLayer
;
71 import org
.eclipse
.nebula
.widgets
.nattable
.group
.ColumnGroupHeaderLayer
;
72 import org
.eclipse
.nebula
.widgets
.nattable
.group
.ColumnGroupModel
;
73 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.AbstractLayer
;
74 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.DataLayer
;
75 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.ILayer
;
76 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.gui
.PersistenceDialog
;
77 import org
.eclipse
.nebula
.widgets
.nattable
.selection
.SelectionLayer
;
78 import org
.eclipse
.nebula
.widgets
.nattable
.sort
.SortHeaderLayer
;
79 import org
.eclipse
.nebula
.widgets
.nattable
.sort
.config
.SingleClickSortConfiguration
;
80 import org
.eclipse
.nebula
.widgets
.nattable
.style
.theme
.ModernNatTableThemeConfiguration
;
81 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.ITreeRowModel
;
82 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.TreeLayer
;
83 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.command
.TreeExpandToLevelCommand
;
84 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.binding
.UiBindingRegistry
;
85 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.matcher
.MouseEventMatcher
;
86 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.menu
.PopupMenuAction
;
87 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.menu
.PopupMenuBuilder
;
88 import org
.eclipse
.nebula
.widgets
.nattable
.util
.GUIHelper
;
89 import org
.eclipse
.swt
.SWT
;
90 import org
.eclipse
.swt
.layout
.GridData
;
91 import org
.eclipse
.swt
.layout
.GridLayout
;
92 import org
.eclipse
.swt
.widgets
.Button
;
93 import org
.eclipse
.swt
.widgets
.Composite
;
94 import org
.eclipse
.swt
.widgets
.Menu
;
96 import ca
.odell
.glazedlists
.BasicEventList
;
97 import ca
.odell
.glazedlists
.EventList
;
98 import ca
.odell
.glazedlists
.SortedList
;
99 import ca
.odell
.glazedlists
.TreeList
;
100 import eu
.etaxonomy
.cdm
.api
.application
.CdmApplicationState
;
101 import eu
.etaxonomy
.cdm
.api
.service
.IDescriptiveDataSetService
;
102 import eu
.etaxonomy
.cdm
.api
.service
.IOccurrenceService
;
103 import eu
.etaxonomy
.cdm
.api
.service
.UpdateResult
;
104 import eu
.etaxonomy
.cdm
.api
.service
.dto
.FieldUnitDTO
;
105 import eu
.etaxonomy
.cdm
.api
.service
.dto
.RowWrapperDTO
;
106 import eu
.etaxonomy
.cdm
.api
.service
.dto
.SpecimenRowWrapperDTO
;
107 import eu
.etaxonomy
.cdm
.common
.monitor
.IRemotingProgressMonitor
;
108 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
109 import eu
.etaxonomy
.cdm
.model
.description
.DescriptiveDataSet
;
110 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
111 import eu
.etaxonomy
.cdm
.model
.description
.MeasurementUnit
;
112 import eu
.etaxonomy
.cdm
.model
.occurrence
.FieldUnit
;
113 import eu
.etaxonomy
.cdm
.model
.term
.TermNode
;
114 import eu
.etaxonomy
.cdm
.model
.term
.TermTree
;
115 import eu
.etaxonomy
.cdm
.persistence
.dto
.SpecimenNodeWrapper
;
116 import eu
.etaxonomy
.cdm
.persistence
.dto
.TermDto
;
117 import eu
.etaxonomy
.cdm
.persistence
.dto
.UuidAndTitleCache
;
118 import eu
.etaxonomy
.taxeditor
.editor
.internal
.TaxeditorEditorPlugin
;
119 import eu
.etaxonomy
.taxeditor
.editor
.l10n
.Messages
;
120 import eu
.etaxonomy
.taxeditor
.event
.WorkbenchEventConstants
;
121 import eu
.etaxonomy
.taxeditor
.model
.MessagingUtils
;
122 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySession
;
123 import eu
.etaxonomy
.taxeditor
.store
.CdmStore
;
124 import eu
.etaxonomy
.taxeditor
.workbench
.WorkbenchUtility
;
127 * Character matrix editor for editing specimen/taxon descriptions in a table
129 * @since Nov 26, 2017
132 public class CharacterMatrix
extends Composite
{
134 private static final String CHARACTER_MATRIX_STATE_PROPERTIES
= "characterMatrixState.properties"; //$NON-NLS-1$
136 static final int LEADING_COLUMN_COUNT
= 4;
137 static final String TAXON_COLUMN
= "taxon_column"; //$NON-NLS-1$
138 static final String COLLECTOR_COLUMN
= "collector_column"; //$NON-NLS-1$
139 static final String IDENTIFIER_COLUMN
= "identifier_column"; //$NON-NLS-1$
140 static final String COUNTRY_COLUMN
= "country_column"; //$NON-NLS-1$
142 static final String LABEL_TAXON_ROW
= "TAXON_ROW"; //$NON-NLS-1$
143 static final String LABEL_TAXON_AGGREGATED_DESCRIPTION
= "TAXON_AGGREGATED_DESCRIPTION"; //$NON-NLS-1$
144 static final String LABEL_TAXON_AGGREGATED_DESCRIPTION_ICON
= "TAXON_AGGREGATED_DESCRIPTION_ICON"; //$NON-NLS-1$
145 static final String LABEL_TAXON_DEFAULT_DESCRIPTION
= "TAXON_DEFAULT_DESCRIPTION"; //$NON-NLS-1$
146 static final String LABEL_TAXON_DEFAULT_DESCRIPTION_ICON
= "TAXON_DEFAULT_DESCRIPTION_ICON"; //$NON-NLS-1$
147 static final String LABEL_TAXON_LITERATURE_DESCRIPTION
= "TAXON_LITERATURE_DESCRIPTION"; //$NON-NLS-1$
148 static final String LABEL_TAXON_LITERATURE_DESCRIPTION_ICON
= "TAXON_LITERATURE_DESCRIPTION_ICON"; //$NON-NLS-1$
149 static final String LABEL_TAXON_DESCRIPTION
= "LABEL_TAXON_DESCRIPTION"; //$NON-NLS-1$
150 static final String LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA
= "LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA"; //$NON-NLS-1$
153 private UISynchronize sync
;
156 private EMenuService menuService
;
158 private NatTable natTable
;
160 // private Collection<RowWrapperDTO<?>> rowsToSave = new HashSet<>();
161 private HashMap
<UUID
, RowWrapperDTO
<?
>> rowsToMerge
= new HashMap
<>();
163 private Map
<Integer
, Feature
> indexToFeatureMap
= new HashMap
<>();
165 private Map
<Feature
, List
<TermDto
>> categoricalFeatureToStateMap
= new HashMap
<>();
167 private LinkedMap
<String
, String
> propertyToLabelMap
= new LinkedMap
<>();
169 private EventList
<Object
> descriptions
;
171 private Collection
<SpecimenNodeWrapper
> specimenCache
= null;
173 private Map
<Feature
, CategoricalDataHistogram
> featureToHistogramMap
= new HashMap
<>();
175 private ListDataProvider
<Object
> bodyDataProvider
;
177 private FreezeLayer freezeLayer
;
179 private List
<Feature
> features
;
181 private CharacterMatrixPart part
;
183 private AbstractLayer topMostLayer
;
185 private ConfigRegistry configRegistry
;
187 private MatrixBodyLayerStack bodyLayer
;
189 private boolean isTreeView
= true;
191 private CharacterMatrixToolbar toolbar
;
193 private DescriptionTreeFormat treeFormat
;
194 private List
<UUID
> descriptionUuidsToDelete
;
196 private List
<SpecimenNodeWrapper
> specimenToAdd
;
198 private MouseEventMatcher mouseEventMatcher
= null;
200 public CharacterMatrix(Composite parent
, CharacterMatrixPart part
) {
201 super(parent
, SWT
.NONE
);
203 this.setLayout(new GridLayout());
207 natTable
= new NatTable(this, false);
209 createBottomToolbar();
213 private void createToolBar(){
214 toolbar
= new CharacterMatrixToolbar(this, SWT
.NONE
);
217 @SuppressWarnings("unused")
218 private void createBottomToolbar() {
219 new CharacterMatrixBottomToolbar(this, SWT
.NONE
);
223 private void applyStyles(){
224 ModernNatTableThemeConfiguration configuration
= new ModernNatTableThemeConfiguration();
225 // NOTE: Getting the colors and fonts from the GUIHelper ensures that
226 // they are disposed properly (required by SWT)
227 configuration
.cHeaderBgColor
= GUIHelper
.getColor(211, 211, 211);
228 configuration
.rHeaderBgColor
= GUIHelper
.getColor(211, 211, 211);
229 natTable
.addConfiguration(configuration
);
233 void toggleTreeFlat(boolean isTree
, Button btnToggleFlat
, Button btnToggleTree
, Button btnCollapseAll
, Button btnExpandAll
, Button btnFreezeSuppInfo
) {
235 createTable(isTree
, freezeLayer
.isFrozen(), true);
236 btnToggleFlat
.setEnabled(isTree
);
237 btnToggleTree
.setEnabled(!isTree
);
238 btnCollapseAll
.setEnabled(isTree
);
239 btnExpandAll
.setEnabled(isTree
);
242 public boolean isTreeView() {
246 public void createTable(boolean treeView
, boolean freezeSupplementalColumns
, boolean isInitialExpandToDeepestTaxonLevel
){
250 createLayers(treeView
);
256 configureNatTable(treeView
, configRegistry
, topMostLayer
);
259 * handlers and listeners
261 registerHandlersAndListeners();
264 GridDataFactory
.fillDefaults().grab(true, true).applyTo(natTable
);
266 //update label to current data set
267 toolbar
.getWsLabel().setText(getDescriptiveDataSet().getLabel());
268 toolbar
.getWsLabel().setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, true, false));
269 toolbar
.getWsLabel().getParent().layout();
271 //initial freeze of supplemental columns
272 freezeSupplementalColumns(freezeSupplementalColumns
);
275 //add tooltip to table
276 // new CategoricalChartTooltip(this);
277 // new QuantitativeChartTooltip(this);
281 natTable
.doCommand(new ClientAreaResizeCommand(natTable
));
284 if(isInitialExpandToDeepestTaxonLevel
){
285 Integer deepestTaxonLevel
= treeFormat
.getDeepestTaxonLevel();
286 if(deepestTaxonLevel
!=null){
287 natTable
.doCommand(new TreeExpandToLevelCommand(deepestTaxonLevel
-2));
291 // clean up table state
292 getNatTableState().remove(NatTable
.INITIAL_PAINT_COMPLETE_FLAG
);
293 getNatTableState().remove(PersistenceDialog
.ACTIVE_VIEW_CONFIGURATION_KEY
);
296 private List
<Feature
> initFeatureList(TermNode
<Feature
> node
){
297 List
<Feature
> features
= new ArrayList
<>();
298 List
<TermNode
<Feature
>> childNodes
= node
.getChildNodes();
299 for (TermNode
<Feature
> childNode
: childNodes
) {
300 features
.add(childNode
.getTerm());
301 features
.addAll(initFeatureList(childNode
));
306 public void initDescriptiveDataSet(){
307 //get features/columns stored in descriptive data set
308 TermTree
<Feature
> tree
= getDescriptiveDataSet().getDescriptiveSystem();
309 features
= initFeatureList(tree
.getRoot());
311 Set
<Feature
> duplicateFeatures
= features
.stream().filter(i
-> Collections
.frequency(features
, i
) >1)
312 .collect(Collectors
.toSet());
314 if (!duplicateFeatures
.isEmpty()) {
315 throw new IllegalArgumentException("Duplicate features found: "
316 + duplicateFeatures
.stream().map(feature
-> feature
.getLabel()).collect(Collectors
.joining(",")));
319 //init state data for categorical features
320 features
.forEach(feature
->fetchSupportedStates(feature
));
322 descriptions
= new BasicEventList
<>();
326 private void fetchSupportedStates(Feature feature
) {
327 List
<TermDto
> supportedStates
= CdmStore
.getService(IDescriptiveDataSetService
.class).getSupportedStatesForFeature(feature
.getUuid());
328 categoricalFeatureToStateMap
.put(feature
, supportedStates
);
333 private void createLayers(boolean treeView
) {
335 SortedList
<Object
> sortedList
= new SortedList
<>(descriptions
, new MatrixRowComparator());
336 // wrap the SortedList with the TreeList
337 treeFormat
= new DescriptionTreeFormat(getDescriptiveDataSet());
338 TreeList
<Object
> treeList
= new TreeList(sortedList
, treeFormat
, TreeList
.NODES_START_COLLAPSED
);
339 // wrap the SortedList with the TreeList
340 treeFormat
= new DescriptionTreeFormat(getDescriptiveDataSet());
345 SpecimenColumnPropertyAccessor columnPropertyAccessor
= new SpecimenColumnPropertyAccessor(this);
346 bodyDataProvider
= treeView?
new ListDataProvider
<>(treeList
, columnPropertyAccessor
):new ListDataProvider
<>(sortedList
, columnPropertyAccessor
);
348 DataLayer bodyDataLayer
= new DataLayer(bodyDataProvider
);
349 bodyDataLayer
.registerCommandHandler(new CopyPasteUpdateDataCommandHandler(bodyDataLayer
));
360 TreeLayer (default visible)
384 configRegistry
= new ConfigRegistry();
386 CharacterMatrixConfigLabelAccumulator labelAccumulator
= new CharacterMatrixConfigLabelAccumulator(this);
387 bodyDataLayer
.setConfigLabelAccumulator(labelAccumulator
);
390 propertyToLabelMap
.put(TAXON_COLUMN
, Messages
.CharacterMatrix_TAXON
);
391 propertyToLabelMap
.put(COLLECTOR_COLUMN
, Messages
.CharacterMatrix_COLLECTOR_NO
);
392 propertyToLabelMap
.put(IDENTIFIER_COLUMN
, Messages
.CharacterMatrix_IDENTIFIER
);
393 propertyToLabelMap
.put(COUNTRY_COLUMN
, Messages
.CharacterMatrix_COUNTRY
);
394 for(int i
=0;i
<features
.size();i
++){
395 Feature feature
= features
.get(i
);
396 initLabels(i
, feature
);
399 // layer for event handling of GlazedLists and PropertyChanges
400 GlazedListsEventLayer eventLayer
= new GlazedListsEventLayer
<>(bodyDataLayer
, (EventList
)bodyDataProvider
.getList());
402 GlazedListTreeData treeData
= new GlazedListTreeData
<>(treeList
);
403 ITreeRowModel treeRowModel
= new GlazedListTreeRowModel
<>(treeData
);
405 // assemble the column groups
406 LinkedList
<ColumnGroupWrapper
> columnGroups
= new LinkedList
<>();
407 List
<TermNode
<Feature
>> rootChildren
= getDescriptiveDataSet().getDescriptiveSystem().getRootChildren();
408 buildHeader(rootChildren
, columnGroups
);
410 bodyLayer
= new MatrixBodyLayerStack(eventLayer
, columnGroups
);
411 final SelectionLayer selectionLayer
= bodyLayer
.getSelectionLayer();
412 freezeLayer
= new FreezeLayer(selectionLayer
);
413 final CompositeFreezeLayer compositeFreezeLayer
= new CompositeFreezeLayer(
414 freezeLayer
, bodyLayer
.getViewportLayer(), selectionLayer
);
415 TreeLayer treeLayer
= null;
417 treeLayer
= new TreeLayer(compositeFreezeLayer
, treeRowModel
);
420 topMostLayer
= treeView?treeLayer
:compositeFreezeLayer
;
424 * column header layer
426 IDataProvider columnHeaderDataProvider
= new DefaultColumnHeaderDataProvider(
427 propertyToLabelMap
.values().toArray(new String
[] {}), propertyToLabelMap
);
428 DataLayer columnHeaderDataLayer
= new DataLayer(columnHeaderDataProvider
);
429 ColumnHeaderLayer columnHeaderLayer
= new ColumnHeaderLayer(columnHeaderDataLayer
, topMostLayer
, selectionLayer
);
431 ILayer topHeaderLayer
= columnHeaderLayer
;
433 if(!columnGroups
.isEmpty()){
435 ColumnGroupWrapper groupLevel1
= columnGroups
.get(0);
436 ColumnGroupHeaderLayer groupLayerLevel1
= null;
437 ColumnGroupGroupHeaderLayer groupLayerLevel2
= null;
438 groupLayerLevel1
= new ColumnGroupHeaderLayer(columnHeaderLayer
, selectionLayer
, groupLevel1
.getModel());
439 for (Entry
<TermNode
<Feature
>, TreeSet
<Integer
>> entry
: groupLevel1
.getColumnGroupToIndexMap().entrySet()) {
440 TermNode
<Feature
> group
= entry
.getKey();
441 TreeSet
<Integer
> indexList
= entry
.getValue();
442 int[] intArray
= indexList
.stream().mapToInt(Integer
::intValue
).toArray();
443 groupLayerLevel1
.addColumnsIndexesToGroup(group
.getTerm().getTitleCache(), intArray
);
444 groupLayerLevel1
.setGroupUnbreakable(indexList
.iterator().next());
446 topHeaderLayer
= groupLayerLevel1
;
448 // second group level
449 if(columnGroups
.size()>1){
450 ColumnGroupWrapper groupLevel2
= columnGroups
.get(1);
451 groupLayerLevel2
= new ColumnGroupGroupHeaderLayer(groupLayerLevel1
, selectionLayer
, groupLevel2
.getModel());
452 for (Entry
<TermNode
<Feature
>, TreeSet
<Integer
>> entry
: groupLevel2
.getColumnGroupToIndexMap().entrySet()) {
453 TermNode
<Feature
> group
= entry
.getKey();
454 TreeSet
<Integer
> indexList
= entry
.getValue();
455 int[] intArray
= indexList
.stream().mapToInt(Integer
::intValue
).toArray();
456 groupLayerLevel2
.addColumnsIndexesToGroup(group
.getTerm().getTitleCache(), intArray
);
457 groupLayerLevel2
.setGroupUnbreakable(indexList
.iterator().next());
459 topHeaderLayer
= groupLayerLevel2
;
463 // add the SortHeaderLayer to the column header layer stack
464 // as we use GlazedLists, we use the GlazedListsSortModel which
465 // delegates the sorting to the SortedList
466 final SortHeaderLayer
<DescriptionBase
> sortHeaderLayer
= new SortHeaderLayer
<>(
468 new GlazedListsSortModel
<>(
470 columnPropertyAccessor
,
472 columnHeaderDataLayer
));
479 IDataProvider rowHeaderDataProvider
= new DefaultRowHeaderDataProvider(bodyDataProvider
);
480 DefaultRowHeaderDataLayer rowHeaderDataLayer
= new DefaultRowHeaderDataLayer(rowHeaderDataProvider
);
481 RowHeaderLayer rowHeaderLayer
= new RowHeaderLayer(rowHeaderDataLayer
,
482 topMostLayer
, selectionLayer
);
488 ILayer cornerLayer
= new CornerLayer(
489 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider
, rowHeaderDataProvider
)),
490 rowHeaderLayer
, sortHeaderLayer
);
494 * GRID layer (composition of all other layers)
496 GridLayer gridLayer
= new GridLayer(topMostLayer
, sortHeaderLayer
, rowHeaderLayer
, cornerLayer
);
498 natTable
.setLayer(gridLayer
);
502 private TreeSet
<Integer
> recurseChildIndexes(TermNode
<Feature
> node
){
503 TreeSet
<Integer
> childIndexes
= new TreeSet
<>();
504 if(node
.getChildCount()>0){
505 List
<TermNode
<Feature
>> childNodes
= node
.getChildNodes();
506 for (TermNode
<Feature
> childNode
: childNodes
) {
507 childIndexes
.addAll(recurseChildIndexes(childNode
));
510 childIndexes
.add(features
.indexOf(node
.getTerm())+LEADING_COLUMN_COUNT
);
514 private void buildHeader(List
<TermNode
<Feature
>> nodes
, LinkedList
<ColumnGroupWrapper
> columnGroups
){
515 Map
<TermNode
<Feature
>, TreeSet
<Integer
>> columnGroupToIndexMap
= new HashMap
<>();
516 List
<TermNode
<Feature
>> childNodes
= new ArrayList
<>();
517 for (TermNode
<Feature
> node
: nodes
) {
518 TreeSet
<Integer
> childIndexes
= recurseChildIndexes(node
);
519 if(childIndexes
.size()>1){
520 // filter out groups that only have one member
521 columnGroupToIndexMap
.put(node
, childIndexes
);
523 childNodes
.addAll(node
.getChildNodes());
525 if(!columnGroupToIndexMap
.isEmpty()){
526 columnGroups
.addFirst(new ColumnGroupWrapper(new ColumnGroupModel(), columnGroupToIndexMap
));
528 if(!childNodes
.isEmpty()){
529 buildHeader(childNodes
, columnGroups
);
533 private void registerHandlersAndListeners() {
534 natTable
.registerCommandHandler(new ExportCommandHandler(natTable
));
537 E4SelectionListener selectionListener
= new CellSelectionListener(part
.getSelectionService(),
538 bodyLayer
.getSelectionLayer(), bodyDataProvider
, part
);
539 bodyLayer
.getSelectionLayer().addLayerListener(selectionListener
);
540 selectionListener
.setFullySelectedRowsOnly(false);
542 //register handler for view configuration menu
543 natTable
.registerCommandHandler(toolbar
.getDisplayPersistenceDialogCommandHandler());
545 //register handlers for copy&paste
546 natTable
.registerCommandHandler(
547 new InternalPasteDataCommandHandler(bodyLayer
.getSelectionLayer(), natTable
.getInternalCellClipboard()));
548 natTable
.registerCommandHandler(
549 new InternalCopyDataCommandHandler(bodyLayer
.getSelectionLayer(), natTable
.getInternalCellClipboard()));
552 private void configureNatTable(boolean treeView
,
553 ConfigRegistry configRegistry
,
554 AbstractLayer topMostLayer
) {
559 menuService
.registerContextMenu(natTable
, "eu.etaxonomy.taxeditor.editor.popupmenu.charactermatrix"); //$NON-NLS-1$
560 // get the menu registered by EMenuService
561 final Menu e4Menu
= natTable
.getMenu();
563 natTable
.setConfigRegistry(configRegistry
);
567 //add default configuration because autoconfigure is set to false in constructor
568 natTable
.addConfiguration(new DefaultNatTableStyleConfiguration());
570 // this is for DEBUG ONLY
571 // natTable.addConfiguration(new DebugMenuConfiguration(natTable));
573 // override the default sort configuration and change the mouse bindings
574 // to sort on a single click
576 natTable
.addConfiguration(new SingleClickSortConfiguration());
579 natTable
.addConfiguration(new CharacterMatrixLabelStyleConfiguration());
581 // add the header menu configuration for adding the column header menu
582 // with hide/show actions
583 // natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable));
585 // add custom configuration for data conversion and add column labels to viewport layer
586 topMostLayer
.addConfiguration(new CellEditorDataConversionConfiguration(this));
588 //copy&paste configuration
589 natTable
.addConfiguration(new CopyPasteEditBindings(bodyLayer
.getSelectionLayer(), natTable
.getInternalCellClipboard()));
592 // remove the menu reference from NatTable instance
593 natTable
.setMenu(null);
596 // natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable));
599 natTable
.configure();
602 natTable
.addConfiguration(
603 new AbstractUiBindingConfiguration() {
605 public void configureUiBindings(
606 UiBindingRegistry uiBindingRegistry
) {
607 // add e4 menu to NatTable
610 uiBindingRegistry
.unregisterMouseDownBinding(mouseEventMatcher
);
611 new PopupMenuBuilder(natTable
, e4Menu
)
612 .withHideColumnMenuItem()
613 .withShowAllColumnsMenuItem()
616 // register the UI binding for header, corner and body region
617 mouseEventMatcher
= new MouseEventMatcher(
620 MouseEventMatcher
.RIGHT_BUTTON
);
621 uiBindingRegistry
.registerMouseDownBinding(mouseEventMatcher
,
622 new PopupMenuAction(e4Menu
));
628 void freezeSupplementalColumns(boolean freeze
){
629 int rightMostFreezeColumIndex
= LEADING_COLUMN_COUNT
-1;
630 Collection
<Integer
> hiddenColumnIndexes
= bodyLayer
.getColumnHideShowLayer().getHiddenColumnIndexes();
631 for (Integer integer
: hiddenColumnIndexes
) {
632 if(integer
<LEADING_COLUMN_COUNT
){
633 rightMostFreezeColumIndex
--;
637 FreezeHelper
.freeze(freezeLayer
, bodyLayer
.getViewportLayer(),
638 new PositionCoordinate(bodyLayer
.getViewportLayer(), 0, 0),
639 new PositionCoordinate(bodyLayer
.getViewportLayer(), rightMostFreezeColumIndex
, -1));
642 FreezeHelper
.unfreeze(freezeLayer
, bodyLayer
.getViewportLayer());
646 private void initLabels(int index
, Feature feature
) {
647 indexToFeatureMap
.put(index
+LEADING_COLUMN_COUNT
, feature
);
649 String label
= feature
.getLabel();
650 String property
= feature
.getUuid().toString();
651 //show unit for quantitative data
652 if(feature
.isSupportsQuantitativeData()){
653 Set
<MeasurementUnit
> recommendedMeasurementUnits
= feature
.getRecommendedMeasurementUnits();
654 // if(recommendedMeasurementUnits.size()>1){
655 // MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class,
656 // String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel()));
658 if(recommendedMeasurementUnits
.size()==1){
659 MeasurementUnit unit
= recommendedMeasurementUnits
.iterator().next();
660 label
+= " ["+unit
.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
663 propertyToLabelMap
.put(property
, label
);
666 public void loadDescriptions(boolean isInitialExpandToDeepestTaxonLevel
, boolean initialLoading
) {
667 UUID monitorUuid
= CdmApplicationState
.getLongRunningTasksService().monitGetRowWrapper(this.getDescriptiveDataSet().getUuid());
669 final Collection
<RowWrapperDTO
> wrappers
= new ArrayList
<>();
670 String jobLabel
= Messages
.CharacterMatrix_LOAD_CHARACTER_DATA
;
671 Job job
= Job
.create(jobLabel
, (ICoreRunnable
) monitor
-> {
672 SubMonitor subMonitor
= SubMonitor
.convert(monitor
);
673 subMonitor
.beginTask(jobLabel
, IProgressMonitor
.UNKNOWN
);
674 IRemotingProgressMonitor remotingMonitor
;
676 remotingMonitor
= CdmStore
.getProgressMonitorClientManager()
677 .pollMonitor(jobLabel
,
683 } catch (InterruptedException e
) {
684 MessagingUtils
.informationDialog(Messages
.CharacterMatrix_LOADING_FAILED_TITLE
,
685 Messages
.CharacterMatrix_LOADING_FAILED_MESSAGE
);
688 Object result
= remotingMonitor
.getResult();
689 if(result
instanceof Collection
){
690 wrappers
.addAll((Collection
<RowWrapperDTO
>) result
);
692 if(result
instanceof Exception
){
693 MessagingUtils
.errorDialog("Exception during description loading", this.getClass(), "An exception occured during loading", TaxeditorEditorPlugin
.PLUGIN_ID
, (Throwable
) result
, true);
695 else if(wrappers
.isEmpty()){
696 MessagingUtils
.informationDialog(Messages
.CharacterMatrix_NO_DESCRIPTION_TITLE
,
697 Messages
.CharacterMatrix_NO_DESCRIPTION_MESSAGE
);
701 job
.addJobChangeListener(new JobChangeAdapter(){
703 public void done(IJobChangeEvent event
) {
705 List
<RowWrapperDTO
> rowsWithoutTaxonNode
= wrappers
.stream().filter(row
->row
.getTaxonNode()==null).collect(Collectors
.toList());
706 if(!rowsWithoutTaxonNode
.isEmpty()){
707 String collect
= rowsWithoutTaxonNode
.stream().
708 map(row
->row
.getDescription().toString())
709 .collect(Collectors
.joining("\n\n - ")); //$NON-NLS-1$
710 MessagingUtils
.warningDialog(
711 Messages
.CharacterMatrix_NO_NODE_FOUND_TITLE
,
713 String
.format(Messages
.CharacterMatrix_NO_NODE_FOUND_MESSAGE
, collect
)
716 descriptions
.clear();
717 wrappers
.stream().filter(row
->row
.getTaxonNode()!=null).forEach(wrapper
->CharacterMatrix
.this.descriptions
.add(wrapper
));
719 loadingDone(isInitialExpandToDeepestTaxonLevel
);
728 public IStructuredSelection
getSelection(){
729 Set
<Range
> selectedRowPositions
= bodyLayer
.getSelectionLayer().getSelectedRowPositions();
730 List
<Object
> selectedObjects
= new ArrayList
<>();
731 for (Range range
: selectedRowPositions
) {
732 for(int i
=range
.start
;i
<range
.end
;i
++){
733 selectedObjects
.add(bodyDataProvider
.getRowObject(i
));
736 return new StructuredSelection(selectedObjects
);
739 private void loadingDone(boolean isInitialExpandToDeepestTaxonLevel
) {
740 this.part
.loadingDone();
741 createTable(isTreeView
, freezeLayer
.isFrozen(), isInitialExpandToDeepestTaxonLevel
);
745 public List
<TermDto
> getSupportedStatesForCategoricalFeature(Feature feature
){
746 return categoricalFeatureToStateMap
.get(feature
);
749 public Map
<Integer
, Feature
> getIndexToFeatureMap() {
750 return indexToFeatureMap
;
753 public LinkedMap
<String
, String
> getPropertyToLabelMap() {
754 return propertyToLabelMap
;
757 public void setDirty() {
761 public List
<UUID
> getDescriptionsToDelete() {
762 return descriptionUuidsToDelete
;
765 public void addDescriptionToDelete(UUID descriptionToDelete
) {
766 if (descriptionUuidsToDelete
== null){
767 descriptionUuidsToDelete
= new ArrayList();
770 this.descriptionUuidsToDelete
.add(descriptionToDelete
);
774 public List
<SpecimenNodeWrapper
> getSpecimenToAdd() {
775 return specimenToAdd
;
778 public void addSpecimenToAdd(Collection
<SpecimenNodeWrapper
> specimenToAdd
) {
779 if (this.specimenToAdd
== null){
780 this.specimenToAdd
= new ArrayList();
783 this.specimenToAdd
.addAll(specimenToAdd
);
784 this.specimenCache
.removeAll(specimenToAdd
);
786 public void addSpecimenToAdd(SpecimenNodeWrapper specimenToAdd
) {
787 if (this.specimenToAdd
== null){
788 this.specimenToAdd
= new ArrayList();
791 this.specimenToAdd
.add(specimenToAdd
);
792 this.specimenCache
.remove(specimenToAdd
);
795 public CharacterMatrixPart
getPart() {
799 public NatTable
getNatTable() {
803 public EventList
<Object
> getDescriptions() {
807 public DescriptiveDataSet
getDescriptiveDataSet() {
808 return part
.getDescriptiveDataSet();
811 public void setDescriptiveDataSet(DescriptiveDataSet dataSet
) {
812 part
.setDescriptiveDataSet(dataSet
);
815 public Collection
<SpecimenNodeWrapper
> getSpecimenCache() {
816 return specimenCache
;
819 public void setSpecimenCache(Collection
<SpecimenNodeWrapper
> specimenCache
) {
820 this.specimenCache
= specimenCache
.stream()
822 //map descriptions on a list of uuids of the described specimen
823 !this.descriptions
.stream()
824 .filter(rowWrapper
->rowWrapper
instanceof SpecimenRowWrapperDTO
)
825 .map(specimenRowWrapper
->((SpecimenRowWrapperDTO
) specimenRowWrapper
).getSpecimen().getUuid())
826 .collect(Collectors
.toList())
827 //and check if the specimen to add is already contained
828 .contains(wrapper
.getUuidAndTitleCache().getUuid())
830 .collect(Collectors
.toList());
833 // public void addRowToSave(RowWrapperDTO<?> row){
834 // rowsToMerge.put(row.getDescription().getDescription().getUuid(), row);
837 // public HashMap<UUID,RowWrapperDTO<?>> getRowsToSave() {
838 // return rowsToMerge;
841 public HashMap
<UUID
, RowWrapperDTO
<?
>> getRowsToMerge() {
845 public void putRowToMerge(RowWrapperDTO rowToMerge
) {
846 if (this.rowsToMerge
== null){
847 this.rowsToMerge
= new HashMap
<>();
849 this.rowsToMerge
.put(rowToMerge
.getDescription().getDescription().getUuid(), rowToMerge
);
852 public Properties
getNatTableState() {
853 return toolbar
.getNatTableState();
856 public ListDataProvider
<Object
> getBodyDataProvider() {
857 return bodyDataProvider
;
860 MatrixBodyLayerStack
getBodyLayer() {
864 File
getStatePropertiesFile() {
865 return new File(WorkbenchUtility
.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES
);
868 public List
<Feature
> getFeatures() {
872 public Map
<Feature
, CategoricalDataHistogram
> getFeatureToHistogramMap() {
873 return featureToHistogramMap
;
878 public ICdmEntitySession
getCdmEntitiySession(){
879 return part
.getCdmEntitySession();
884 private void updateSpecimenList(@UIEventTopic(WorkbenchEventConstants
.REFRESH_DESCRIPTIVE_DATASET
)UUID uuid
){
885 if(uuid
!= null && uuid
.equals(part
.getDescriptiveDataSet().getUuid())){
886 this.specimenCache
= null;
891 public void addRowsToMatrix(Collection
<SpecimenNodeWrapper
> wrappers
){
892 addSpecimenToAdd(wrappers
);
893 List
<UUID
> specimenUuids
= new ArrayList();
894 wrappers
.forEach(wrapper
-> specimenUuids
.add(wrapper
.getUuidAndTitleCache().getUuid()));
895 // List<SpecimenOrObservationBase> specimens = CdmStore.getService(IOccurrenceService.class).load(specimenUuids, null);
896 Map
<UUID
, UuidAndTitleCache
<FieldUnit
>> specimenMap
= new HashMap();
898 for (UUID specimenUuid
: specimenUuids
){
900 FieldUnitDTO fieldUnitDto
= CdmStore
.getService(IOccurrenceService
.class).loadFieldUnitDTO(specimenUuid
);
901 if (fieldUnitDto
!= null){
902 UuidAndTitleCache
<FieldUnit
> fieldUnit
= new UuidAndTitleCache(FieldUnit
.class, fieldUnitDto
.getUuid(), null, fieldUnitDto
.getTitleCache());
903 specimenMap
.put(specimenUuid
, fieldUnit
);
910 for (SpecimenNodeWrapper wrapper
: wrappers
){
911 SpecimenRowWrapperDTO rowWrapper
= CdmStore
.getService(IDescriptiveDataSetService
.class).createSpecimenRowWrapper(wrapper
.getUuidAndTitleCache().getUuid(), wrapper
.getTaxonNode().getUuid(), getDescriptiveDataSet().getUuid());
912 // SpecimenRowWrapperDTO rowWrapper = new SpecimenRowWrapperDTO(wrapper.getUuidAndTitleCache(), wrapper.getType(), wrapper.getTaxonNode(), specimenMap.get(wrapper.getUuidAndTitleCache().getUuid()), null, null);
913 descriptions
.add(rowWrapper
);
920 public void addSpecimensToDescriptiveDataSet(){
921 if (specimenToAdd
== null || specimenToAdd
.isEmpty()){
924 UpdateResult result
= CdmStore
.getService(IDescriptiveDataSetService
.class).addRowWrapperToDataset(specimenToAdd
, getDescriptiveDataSet().getUuid());
925 if(!result
.getExceptions().isEmpty()){
926 MessagingUtils
.warningDialog(Messages
.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_TITLE
, this,
927 String
.format(Messages
.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_MESSAGE
, result
.getExceptions()
928 .stream().map(ex
->ex
.toString())
929 .collect(Collectors
.joining("\n"))));
931 DescriptiveDataSet dataSet
= (DescriptiveDataSet
) result
.getCdmEntity();
932 dataSet
= this.getCdmEntitiySession().load(dataSet
, true);
933 // update local dataset
934 this.setDescriptiveDataSet(dataSet
);
936 // UUID monitorUuid = CdmApplicationState.getLongRunningTasksService().addRowWrapperToDataset(specimenToAdd, getDescriptiveDataSet().getUuid());
937 // specimenToAdd = null;
938 // String jobLabel = "Add specimens to matrix";
939 // Job job = Job.create(jobLabel, (ICoreRunnable) monitor -> {
940 // SubMonitor subMonitor = SubMonitor.convert(monitor);
941 // subMonitor.beginTask(jobLabel, IProgressMonitor.UNKNOWN);
942 // IRemotingProgressMonitor remotingMonitor;
944 // remotingMonitor = CdmStore.getProgressMonitorClientManager()
945 // .pollMonitor(jobLabel,
951 // Object resultObject = remotingMonitor.getResult();
952 // if(resultObject instanceof Exception){
953 // MessagingUtils.errorDialog("Adding specimens failed", this, "Adding specimens was not successfull", TaxeditorEditorPlugin.PLUGIN_ID, (Exception)resultObject, true, true);
955 // else if(resultObject instanceof UpdateResult){
956 // UpdateResult result = (UpdateResult)resultObject;
957 // if(!result.getExceptions().isEmpty()){
958 // MessagingUtils.warningDialog(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_TITLE, this,
959 // String.format(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_MESSAGE, result.getExceptions()
960 // .stream().map(ex->ex.toString())
961 // .collect(Collectors.joining("\n"))));
963 // DescriptiveDataSet dataSet = (DescriptiveDataSet) result.getCdmEntity();
964 // dataSet = this.getCdmEntitiySession().load(dataSet, true);
965 // // update local dataset
966 // this.setDescriptiveDataSet(dataSet);
968 //// this.loadDescriptions(getDescriptiveDataSet().getUuid(), false);
972 // } catch (InterruptedException e) {