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
.workingSet
.matrix
;
12 import java
.io
.FileInputStream
;
13 import java
.io
.FileOutputStream
;
14 import java
.io
.IOException
;
15 import java
.util
.ArrayList
;
16 import java
.util
.Arrays
;
17 import java
.util
.Collection
;
18 import java
.util
.Collections
;
19 import java
.util
.HashMap
;
20 import java
.util
.HashSet
;
21 import java
.util
.List
;
23 import java
.util
.Properties
;
25 import java
.util
.UUID
;
27 import javax
.annotation
.PostConstruct
;
28 import javax
.annotation
.PreDestroy
;
29 import javax
.inject
.Inject
;
31 import org
.apache
.commons
.collections4
.map
.LinkedMap
;
32 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
33 import org
.eclipse
.e4
.ui
.di
.Focus
;
34 import org
.eclipse
.e4
.ui
.di
.Persist
;
35 import org
.eclipse
.e4
.ui
.model
.application
.ui
.MDirtyable
;
36 import org
.eclipse
.e4
.ui
.model
.application
.ui
.basic
.MPart
;
37 import org
.eclipse
.e4
.ui
.workbench
.modeling
.ESelectionService
;
38 import org
.eclipse
.jface
.layout
.GridDataFactory
;
39 import org
.eclipse
.jface
.viewers
.ArrayContentProvider
;
40 import org
.eclipse
.jface
.viewers
.ComboViewer
;
41 import org
.eclipse
.jface
.viewers
.LabelProvider
;
42 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
43 import org
.eclipse
.jface
.window
.Window
;
44 import org
.eclipse
.nebula
.widgets
.nattable
.NatTable
;
45 import org
.eclipse
.nebula
.widgets
.nattable
.config
.AbstractRegistryConfiguration
;
46 import org
.eclipse
.nebula
.widgets
.nattable
.config
.CellConfigAttributes
;
47 import org
.eclipse
.nebula
.widgets
.nattable
.config
.ConfigRegistry
;
48 import org
.eclipse
.nebula
.widgets
.nattable
.config
.DefaultNatTableStyleConfiguration
;
49 import org
.eclipse
.nebula
.widgets
.nattable
.config
.IConfigRegistry
;
50 import org
.eclipse
.nebula
.widgets
.nattable
.config
.IEditableRule
;
51 import org
.eclipse
.nebula
.widgets
.nattable
.data
.IDataProvider
;
52 import org
.eclipse
.nebula
.widgets
.nattable
.data
.ListDataProvider
;
53 import org
.eclipse
.nebula
.widgets
.nattable
.edit
.EditConfigAttributes
;
54 import org
.eclipse
.nebula
.widgets
.nattable
.edit
.editor
.IComboBoxDataProvider
;
55 import org
.eclipse
.nebula
.widgets
.nattable
.export
.command
.ExportCommand
;
56 import org
.eclipse
.nebula
.widgets
.nattable
.export
.command
.ExportCommandHandler
;
57 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.GlazedListsEventLayer
;
58 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.tree
.GlazedListTreeData
;
59 import org
.eclipse
.nebula
.widgets
.nattable
.extension
.glazedlists
.tree
.GlazedListTreeRowModel
;
60 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.GridRegion
;
61 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultColumnHeaderDataProvider
;
62 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultCornerDataProvider
;
63 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.DefaultRowHeaderDataProvider
;
64 import org
.eclipse
.nebula
.widgets
.nattable
.grid
.data
.FixedSummaryRowHeaderLayer
;
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
.hideshow
.ColumnHideShowLayer
;
70 import org
.eclipse
.nebula
.widgets
.nattable
.hideshow
.RowHideShowLayer
;
71 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.CompositeLayer
;
72 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.DataLayer
;
73 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.ILayer
;
74 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.ILayerListener
;
75 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.cell
.ColumnOverrideLabelAccumulator
;
76 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.cell
.ILayerCell
;
77 import org
.eclipse
.nebula
.widgets
.nattable
.layer
.event
.ILayerEvent
;
78 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.PersistenceHelper
;
79 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.command
.DisplayPersistenceDialogCommand
;
80 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.command
.DisplayPersistenceDialogCommandHandler
;
81 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.command
.IStateChangedListener
;
82 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.command
.StateChangeEvent
;
83 import org
.eclipse
.nebula
.widgets
.nattable
.persistence
.gui
.PersistenceDialog
;
84 import org
.eclipse
.nebula
.widgets
.nattable
.reorder
.ColumnReorderLayer
;
85 import org
.eclipse
.nebula
.widgets
.nattable
.reorder
.RowReorderLayer
;
86 import org
.eclipse
.nebula
.widgets
.nattable
.selection
.SelectionLayer
;
87 import org
.eclipse
.nebula
.widgets
.nattable
.selection
.event
.CellSelectionEvent
;
88 import org
.eclipse
.nebula
.widgets
.nattable
.style
.DisplayMode
;
89 import org
.eclipse
.nebula
.widgets
.nattable
.summaryrow
.FixedSummaryRowLayer
;
90 import org
.eclipse
.nebula
.widgets
.nattable
.summaryrow
.SummaryRowLayer
;
91 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.ITreeRowModel
;
92 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.TreeLayer
;
93 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.command
.TreeCollapseAllCommand
;
94 import org
.eclipse
.nebula
.widgets
.nattable
.tree
.command
.TreeExpandAllCommand
;
95 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.menu
.AbstractHeaderMenuConfiguration
;
96 import org
.eclipse
.nebula
.widgets
.nattable
.ui
.menu
.PopupMenuBuilder
;
97 import org
.eclipse
.nebula
.widgets
.nattable
.viewport
.ViewportLayer
;
98 import org
.eclipse
.swt
.SWT
;
99 import org
.eclipse
.swt
.events
.SelectionAdapter
;
100 import org
.eclipse
.swt
.events
.SelectionEvent
;
101 import org
.eclipse
.swt
.layout
.GridData
;
102 import org
.eclipse
.swt
.layout
.GridLayout
;
103 import org
.eclipse
.swt
.layout
.RowLayout
;
104 import org
.eclipse
.swt
.widgets
.Button
;
105 import org
.eclipse
.swt
.widgets
.Composite
;
106 import org
.eclipse
.swt
.widgets
.Label
;
108 import ca
.odell
.glazedlists
.EventList
;
109 import ca
.odell
.glazedlists
.GlazedLists
;
110 import ca
.odell
.glazedlists
.SortedList
;
111 import ca
.odell
.glazedlists
.TreeList
;
112 import eu
.etaxonomy
.cdm
.api
.conversation
.ConversationHolder
;
113 import eu
.etaxonomy
.cdm
.api
.conversation
.IConversationEnabled
;
114 import eu
.etaxonomy
.cdm
.api
.service
.IWorkingSetService
;
115 import eu
.etaxonomy
.cdm
.model
.common
.TermVocabulary
;
116 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionBase
;
117 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
118 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
119 import eu
.etaxonomy
.cdm
.model
.description
.FeatureTree
;
120 import eu
.etaxonomy
.cdm
.model
.description
.MeasurementUnit
;
121 import eu
.etaxonomy
.cdm
.model
.description
.SpecimenDescription
;
122 import eu
.etaxonomy
.cdm
.model
.description
.State
;
123 import eu
.etaxonomy
.cdm
.model
.description
.WorkingSet
;
124 import eu
.etaxonomy
.cdm
.model
.occurrence
.SpecimenOrObservationBase
;
125 import eu
.etaxonomy
.cdm
.persistence
.hibernate
.CdmDataChangeMap
;
126 import eu
.etaxonomy
.taxeditor
.editor
.workingSet
.matrix
.categorical
.CategoricalDataCellEditor
;
127 import eu
.etaxonomy
.taxeditor
.editor
.workingSet
.matrix
.categorical
.CategoricalDataDisplayConverter
;
128 import eu
.etaxonomy
.taxeditor
.editor
.workingSet
.matrix
.quantitative
.QuantitativeDataCellEditor
;
129 import eu
.etaxonomy
.taxeditor
.editor
.workingSet
.matrix
.quantitative
.QuantitativeDataDisplayConverter
;
130 import eu
.etaxonomy
.taxeditor
.editor
.workingSet
.matrix
.supplementalInfo
.SupplementalInfoDisplayConverter
;
131 import eu
.etaxonomy
.taxeditor
.model
.DescriptionHelper
;
132 import eu
.etaxonomy
.taxeditor
.model
.IDirtyMarkable
;
133 import eu
.etaxonomy
.taxeditor
.model
.IPartContentHasDetails
;
134 import eu
.etaxonomy
.taxeditor
.model
.ImageResources
;
135 import eu
.etaxonomy
.taxeditor
.model
.MessagingUtils
;
136 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySession
;
137 import eu
.etaxonomy
.taxeditor
.session
.ICdmEntitySessionEnabled
;
138 import eu
.etaxonomy
.taxeditor
.store
.CdmStore
;
139 import eu
.etaxonomy
.taxeditor
.workbench
.WorkbenchUtility
;
140 import eu
.etaxonomy
.taxeditor
.workbench
.part
.IE4SavablePart
;
144 * @since Nov 26, 2017
147 public class CharacterMatrix
implements IE4SavablePart
, IPartContentHasDetails
, IConversationEnabled
, IDirtyMarkable
,
148 ICdmEntitySessionEnabled
{
150 private static final List
<String
> WS_PROPERTY_PATH
= Arrays
.asList(new String
[] {
151 "descriptions", //$NON-NLS-1$
152 "descriptions.descriptionElements", //$NON-NLS-1$
153 "descriptions.descriptionElements.inDescription", //$NON-NLS-1$
154 "descriptions.descriptionElements.inDescription.descriptionElements", //$NON-NLS-1$
155 "descriptions.descriptionElements.feature", //$NON-NLS-1$
158 private static final String CHARACTER_MATRIX_STATE_PROPERTIES
= "characterMatrixState.properties";
160 private static final int LEADING_COLUMN_COUNT
= 4;
161 private static final String TAXON_COLUMN
= "taxon_column";
162 private static final String COLLECTOR_COLUMN
= "collector_column";
163 private static final String IDENTIFIER_COLUMN
= "identifier_column";
164 private static final String COUNTRY_COLUMN
= "country_column";
166 private WorkingSet workingSet
;
168 private Composite parent
;
170 private ConversationHolder conversation
;
172 private ICdmEntitySession cdmEntitySession
;
175 private ESelectionService selService
;
178 private MDirtyable dirty
;
181 private MPart thisPart
;
183 private NatTable natTable
;
185 private Map
<Integer
, Feature
> indexToFeatureMap
= new HashMap
<>();
187 private LinkedMap
<String
, String
> propertyToLabelMap
= new LinkedMap
<>();
189 private Properties natTableState
;
191 private EventList
<Object
> descriptions
;
193 private Collection
<SpecimenOrObservationBase
> specimenCache
= null;
195 private ListDataProvider
<Object
> bodyDataProvider
;
198 public void create(Composite parent
) {
199 if(CdmStore
.isActive() && conversation
==null){
200 conversation
= CdmStore
.createConversation();
202 if(cdmEntitySession
== null){
203 cdmEntitySession
= CdmStore
.getCurrentSessionManager().newSession(this, true);
208 parent
.setLayout(new GridLayout());
209 this.parent
= parent
;
213 public void init(UUID workingSetUuid
) {
214 this.workingSet
= CdmStore
.getService(IWorkingSetService
.class).load(workingSetUuid
, WS_PROPERTY_PATH
);
215 if(workingSet
.getDescriptiveSystem()==null){
216 MessagingUtils
.informationDialog("Editor could not be opened", "The working set has no feature tree selected.");
219 thisPart
.setLabel(workingSet
.getLabel());
221 Composite toolbarComposite
= new Composite(parent
, SWT
.NONE
);
222 toolbarComposite
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, true, false));
223 toolbarComposite
.setLayout(new GridLayout(6, false));
225 //get features/columns stored in working set
226 FeatureTree tree
= workingSet
.getDescriptiveSystem();
227 List
<Feature
> features
= new ArrayList
<>(tree
.getDistinctFeatures());
228 Collections
.sort(features
);
230 descriptions
= GlazedLists
.eventList(getDescriptions(workingSet
));
231 // use the SortedList constructor with 'null' for the Comparator
232 // because the Comparator will be set by configuration
233 SortedList
<Object
> sortedList
= new SortedList
<>(descriptions
, null);
234 // wrap the SortedList with the TreeList
235 TreeList treeList
= new TreeList(sortedList
, new DescriptionTreeFormat(workingSet
.getMaxRank()), TreeList
.NODES_START_EXPANDED
);
237 ConfigRegistry configRegistry
= new ConfigRegistry();
242 SpecimenColumnPropertyAccessor columnPropertyAccessor
= new SpecimenColumnPropertyAccessor(this);
243 bodyDataProvider
= new ListDataProvider
<Object
>(treeList
, columnPropertyAccessor
);
251 - (top) SummaryRowLayer
252 - (bottom) ViewportLayer
281 DataLayer bodyDataLayer
= new DataLayer(bodyDataProvider
);
283 //register labels for columns
284 ColumnOverrideLabelAccumulator bodyColumnLabelAccumulator
=new ColumnOverrideLabelAccumulator(bodyDataLayer
);
285 bodyDataLayer
.setConfigLabelAccumulator(bodyColumnLabelAccumulator
);
286 propertyToLabelMap
.put(TAXON_COLUMN
, "Taxon");
287 bodyColumnLabelAccumulator
.registerColumnOverrides(0, TAXON_COLUMN
);
288 propertyToLabelMap
.put(COLLECTOR_COLUMN
, "Collector + No");
289 bodyColumnLabelAccumulator
.registerColumnOverrides(1, COLLECTOR_COLUMN
);
290 propertyToLabelMap
.put(IDENTIFIER_COLUMN
, "Identifier");
291 bodyColumnLabelAccumulator
.registerColumnOverrides(2, IDENTIFIER_COLUMN
);
292 propertyToLabelMap
.put(COUNTRY_COLUMN
, "Country");
293 bodyColumnLabelAccumulator
.registerColumnOverrides(3, COUNTRY_COLUMN
);
294 for(int i
=0;i
<features
.size();i
++){
295 Feature feature
= features
.get(i
);
296 initLabels(bodyColumnLabelAccumulator
, i
, feature
);
299 // layer for event handling of GlazedLists and PropertyChanges
300 GlazedListsEventLayer eventLayer
= new GlazedListsEventLayer
<>(bodyDataLayer
, treeList
);
301 GlazedListTreeData treeData
= new GlazedListTreeData
<>(treeList
);
302 ITreeRowModel treeRowModel
= new GlazedListTreeRowModel
<>(treeData
);
304 RowReorderLayer rowReorderLayer
= new RowReorderLayer(eventLayer
);
305 ColumnReorderLayer columnReorderLayer
= new ColumnReorderLayer(rowReorderLayer
);
306 ColumnHideShowLayer columnHideShowLayer
= new ColumnHideShowLayer(columnReorderLayer
);
307 RowHideShowLayer rowHideShowLayer
= new RowHideShowLayer(columnHideShowLayer
);
308 SelectionLayer selectionLayer
= new SelectionLayer(rowHideShowLayer
);
309 TreeLayer treeLayer
= new TreeLayer(selectionLayer
, treeRowModel
);
310 ViewportLayer viewportLayer
= new ViewportLayer(treeLayer
);
312 // create a standalone FixedSummaryRowLayer
313 // since the summary row should be fixed at the top of the body
314 // region the horizontal dependency of the FixedSummaryRowLayer
315 // is the ViewportLayer
316 FixedSummaryRowLayer summaryRowLayer
=
317 new FixedSummaryRowLayer(bodyDataLayer
, viewportLayer
, configRegistry
, false);
318 //register labels with summary prefix for summary layer
319 ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator
=new ColumnOverrideLabelAccumulator(bodyDataLayer
);
320 summaryRowLayer
.setConfigLabelAccumulator(summaryColumnLabelAccumulator
);
321 for(int i
=0;i
<features
.size();i
++){
322 Feature feature
= features
.get(i
);
323 summaryColumnLabelAccumulator
.registerColumnOverrides(
324 i
+LEADING_COLUMN_COUNT
,
325 SummaryRowLayer
.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX
+MatrixUtility
.getProperty(feature
));
327 // because the horizontal dependency is the ViewportLayer
328 // we need to set the composite dependency to false
329 summaryRowLayer
.setHorizontalCompositeDependency(false);
331 CompositeLayer composite
= new CompositeLayer(1, 2);
332 composite
.setChildLayer("SUMMARY", summaryRowLayer
, 0, 0);
333 composite
.setChildLayer(GridRegion
.BODY
, viewportLayer
, 0, 1);
337 * column header layer
339 IDataProvider columnHeaderDataProvider
= new DefaultColumnHeaderDataProvider(
340 propertyToLabelMap
.values().toArray(new String
[] {}), propertyToLabelMap
);
341 DataLayer columnHeaderDataLayer
= new DataLayer(columnHeaderDataProvider
);
342 ColumnHeaderLayer columnHeaderLayer
= new ColumnHeaderLayer(columnHeaderDataLayer
, viewportLayer
, selectionLayer
);
344 // add the SortHeaderLayer to the column header layer stack
345 // as we use GlazedLists, we use the GlazedListsSortModel which
346 // delegates the sorting to the SortedList
347 //TODO currently disabled sorting because of tree list
348 // final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
349 // columnHeaderLayer,
350 // new GlazedListsSortModel<>(
352 // columnPropertyAccessor,
354 // columnHeaderDataLayer));
360 IDataProvider rowHeaderDataProvider
= new DefaultRowHeaderDataProvider(bodyDataProvider
);
361 DefaultRowHeaderDataLayer rowHeaderDataLayer
= new DefaultRowHeaderDataLayer(rowHeaderDataProvider
);
362 FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer
= new FixedSummaryRowHeaderLayer(rowHeaderDataLayer
,
363 composite
, selectionLayer
);
364 fixedSummaryRowHeaderLayer
.setSummaryRowLabel("\u2211");
370 ILayer cornerLayer
= new CornerLayer(
371 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider
, rowHeaderDataProvider
)),
372 fixedSummaryRowHeaderLayer
, columnHeaderLayer
);
376 * GRID layer (composition of all other layers)
378 GridLayer gridLayer
= new GridLayer(composite
, columnHeaderLayer
, fixedSummaryRowHeaderLayer
, cornerLayer
);
382 natTable
= new NatTable(parent
, gridLayer
, false);
388 natTable
.setConfigRegistry(configRegistry
);
391 //add default configuration because autoconfigure is set to false in constructor
392 natTable
.addConfiguration(new DefaultNatTableStyleConfiguration());
394 //FIXME: this is for DEBUG ONLY
395 // natTable.addConfiguration(new DebugMenuConfiguration(natTable));
397 // override the default sort configuration and change the mouse bindings
398 // to sort on a single click
399 //currently removed sorting because of using the TreeConfiguration
400 // natTable.addConfiguration(new SingleClickSortConfiguration());
403 // add the header menu configuration for adding the column header menu
404 // with hide/show actions
405 natTable
.addConfiguration(new AbstractHeaderMenuConfiguration(natTable
) {
408 protected PopupMenuBuilder
createColumnHeaderMenu(NatTable natTable
) {
409 return super.createColumnHeaderMenu(natTable
)
410 .withHideColumnMenuItem()
411 .withShowAllColumnsMenuItem();
415 protected PopupMenuBuilder
createRowHeaderMenu(NatTable natTable
) {
416 return super.createRowHeaderMenu(natTable
)
417 .withHideRowMenuItem()
418 .withShowAllRowsMenuItem();
422 protected PopupMenuBuilder
createCornerMenu(NatTable natTable
) {
423 return super.createCornerMenu(natTable
)
424 .withShowAllColumnsMenuItem()
425 .withShowAllRowsMenuItem();
429 // add custom configuration for data conversion and add column labels to viewport layer
430 viewportLayer
.addConfiguration(new AbstractRegistryConfiguration() {
432 public void configureRegistry(IConfigRegistry configRegistry
) {
433 //add display converter for string representation
434 configRegistry
.registerConfigAttribute(
435 CellConfigAttributes
.DISPLAY_CONVERTER
,
436 new SupplementalInfoDisplayConverter(),
439 configRegistry
.registerConfigAttribute(
440 CellConfigAttributes
.DISPLAY_CONVERTER
,
441 new SupplementalInfoDisplayConverter(),
444 configRegistry
.registerConfigAttribute(
445 CellConfigAttributes
.DISPLAY_CONVERTER
,
446 new SupplementalInfoDisplayConverter(),
449 configRegistry
.registerConfigAttribute(
450 CellConfigAttributes
.DISPLAY_CONVERTER
,
451 new SupplementalInfoDisplayConverter(),
454 features
.forEach(feature
->registerColumnConfiguration(feature
, configRegistry
));
459 //register aggregation configuration for each feature
460 features
.forEach(feature
->summaryRowLayer
.addConfiguration(new AggregationConfiguration(bodyDataProvider
, feature
)));
462 // add the ExportCommandHandler to the ViewportLayer in order to make
464 viewportLayer
.registerCommandHandler(new ExportCommandHandler(viewportLayer
));
466 //propagate single cell selection
467 natTable
.addLayerListener(new ILayerListener() {
469 public void handleLayerEvent(ILayerEvent event
) {
470 if(event
instanceof CellSelectionEvent
){
471 CellSelectionEvent cellSelectionEvent
= (CellSelectionEvent
)event
;
472 Collection
<ILayerCell
> selectedCells
= cellSelectionEvent
.getSelectionLayer().getSelectedCells();
473 StructuredSelection selection
= new StructuredSelection();
474 if(selectedCells
.size()==1){
475 ILayerCell cell
= selectedCells
.iterator().next();
476 Object dataValue
= cell
.getDataValue();
478 selection
= new StructuredSelection(dataValue
);
481 selService
.setSelection(selection
);
486 natTable
.configure();
488 GridDataFactory
.fillDefaults().grab(true, true).applyTo(natTable
);
493 Label wsLabel
= new Label(toolbarComposite
, SWT
.NONE
);
494 wsLabel
.setText(workingSet
.getLabel());
495 wsLabel
.setLayoutData(new GridData(SWT
.FILL
, SWT
.CENTER
, true, false));
499 * Expand/Collapse button
501 Button collapseAllButton
= new Button(toolbarComposite
, SWT
.PUSH
);
502 collapseAllButton
.setImage(ImageResources
.getImage(ImageResources
.COLLAPSE_ALL
));
503 collapseAllButton
.setToolTipText("Collapse all");
504 collapseAllButton
.addSelectionListener(new SelectionAdapter() {
506 public void widgetSelected(SelectionEvent e
) {
507 natTable
.doCommand(new TreeCollapseAllCommand());
510 Button expandAllButton
= new Button(toolbarComposite
, SWT
.PUSH
);
511 expandAllButton
.setImage(ImageResources
.getImage(ImageResources
.EXPAND_ALL
));
512 expandAllButton
.setToolTipText("Expand all");
513 expandAllButton
.addSelectionListener(new SelectionAdapter() {
515 public void widgetSelected(SelectionEvent e
) {
516 natTable
.doCommand(new TreeExpandAllCommand());
521 * Table state persistence
523 natTableState
= new Properties();
524 //load persisted state
525 File statePropertiesFile
= getStatePropertiesFile();
526 FileInputStream inputStream
;
528 inputStream
= new FileInputStream(statePropertiesFile
);
529 natTableState
.load(inputStream
);
530 } catch (IOException e
) {
531 MessagingUtils
.info("No initial state properties file found for character matrix");
534 DisplayPersistenceDialogCommandHandler handler
=
535 new DisplayPersistenceDialogCommandHandler(natTableState
, natTable
);
536 gridLayer
.registerCommandHandler(handler
);
537 // create a combobox for showing the available view states
538 ComboViewer comboStates
= new ComboViewer(toolbarComposite
, SWT
.DROP_DOWN
) ;
539 Collection
<String
> availableStates
= PersistenceHelper
.getAvailableStates(natTableState
);
540 comboStates
.setLabelProvider(new LabelProvider(){
542 public String
getText(Object element
) {
543 if(element
instanceof String
&& ((String
) element
).isEmpty()){
546 return super.getText(element
);
549 comboStates
.setContentProvider(new ArrayContentProvider());
550 comboStates
.addSelectionChangedListener(e
->
552 int index
= comboStates
.getCombo().getSelectionIndex();
554 String selected
= comboStates
.getCombo().getItem(index
);
556 natTable
.loadState(selected
, natTableState
);
557 natTableState
.setProperty(PersistenceDialog
.ACTIVE_VIEW_CONFIGURATION_KEY
, selected
);
560 comboStates
.setInput(availableStates
);
561 if(comboStates
.getCombo().getItemCount()>0){
562 comboStates
.getCombo().select(0);
565 // add listener to update the combo on view state management changes
566 handler
.addStateChangeListener(new IStateChangedListener() {
568 public void handleStateChange(StateChangeEvent event
) {
569 comboStates
.setInput(PersistenceHelper
.getAvailableStates(natTableState
));
570 selectStateItem(comboStates
, event
.getViewConfigName());
574 // add button to show dialog
575 Button btnManageState
= new Button(toolbarComposite
, SWT
.PUSH
);
576 btnManageState
.setImage(ImageResources
.getImage(ImageResources
.SETTINGS
));
577 btnManageState
.setToolTipText("View configuration");
578 btnManageState
.addSelectionListener(new SelectionAdapter() {
580 public void widgetSelected(SelectionEvent e
) {
581 natTable
.doCommand(new DisplayPersistenceDialogCommand(natTable
));
582 selectStateItem(comboStates
, natTableState
.get(PersistenceDialog
.ACTIVE_VIEW_CONFIGURATION_KEY
).toString());
589 Button btnExcelExport
= new Button(toolbarComposite
, SWT
.PUSH
);
590 btnExcelExport
.setToolTipText("Export to Excel");
591 btnExcelExport
.setImage(ImageResources
.getImage(ImageResources
.EXPORT
));
592 btnExcelExport
.addSelectionListener(new SelectionAdapter() {
594 public void widgetSelected(SelectionEvent e
) {
597 natTable
.getConfigRegistry(),
598 natTable
.getShell()));
603 * bottom button toolbar
605 Composite buttonPanel
= new Composite(parent
, SWT
.NONE
);
606 buttonPanel
.setLayout(new RowLayout());
607 GridDataFactory
.fillDefaults().grab(true, false).applyTo(buttonPanel
);
610 * Add description button
612 Button btnAddDescription
= new Button(buttonPanel
, SWT
.PUSH
);
613 btnAddDescription
.setImage(ImageResources
.getImage(ImageResources
.ADD_ICON
));
614 btnAddDescription
.addSelectionListener(new SelectionAdapter() {
616 public void widgetSelected(SelectionEvent e
) {
617 SpecimenSelectionDialog dialog
= new SpecimenSelectionDialog(natTable
.getShell(), CharacterMatrix
.this);
618 if(dialog
.open()==Window
.OK
){
619 Collection
<SpecimenOrObservationBase
> specimens
= dialog
.getSpecimen();
620 boolean hasAdded
= false;
621 for (SpecimenOrObservationBase specimen
: specimens
) {
622 SpecimenDescription description
= getDescriptionForWorkingSet(specimen
);
623 if(!workingSet
.getDescriptions().contains(description
)){
624 CharacterMatrix
.this.descriptions
.add(new RowWrapper(description
));
625 workingSet
.addDescription(description
);
639 private void selectStateItem(ComboViewer comboStates
, String stateName
){
640 String
[] items
= comboStates
.getCombo().getItems();
641 for(int i
=0;i
<items
.length
;i
++){
642 if(items
[i
].equals(stateName
)){
643 comboStates
.getCombo().select(i
);
649 private SpecimenDescription
getDescriptionForWorkingSet(SpecimenOrObservationBase specimen
){
650 Set
<Feature
> wsFeatures
= workingSet
.getDescriptiveSystem().getDistinctFeatures();
651 List
<DescriptionElementBase
> matchingDescriptionElements
= new ArrayList
<>();
653 for (SpecimenDescription specimenDescription
: (Set
<SpecimenDescription
>) specimen
.getDescriptions()) {
654 Set
<Feature
> specimenDescriptionFeatures
= new HashSet
<>();
655 //gather specimen description features and check for match with WS features
656 for (DescriptionElementBase specimenDescriptionElement
: specimenDescription
.getElements()) {
657 Feature feature
= specimenDescriptionElement
.getFeature();
658 specimenDescriptionFeatures
.add(feature
);
659 if(wsFeatures
.contains(feature
)){
660 matchingDescriptionElements
.add(specimenDescriptionElement
);
663 //if description with the exact same features is found return the description
664 if(specimenDescriptionFeatures
.equals(wsFeatures
)){
665 return specimenDescription
;
668 //Create new specimen description if no match was found
670 SpecimenDescription newDesription
= SpecimenDescription
.NewInstance(specimen
);
671 newDesription
.setTitleCache("WorkingSet "+workingSet
.getLabel()+": "+newDesription
.generateTitle(), true);
673 //check for equals description element (same feature and same values)
674 Map
<Feature
, List
<DescriptionElementBase
>> featureToElementMap
= new HashMap
<>();
675 for(DescriptionElementBase element
:matchingDescriptionElements
){
676 List
<DescriptionElementBase
> list
= featureToElementMap
.get(element
.getFeature());
678 list
= new ArrayList
<>();
681 featureToElementMap
.put(element
.getFeature(), list
);
683 Set
<DescriptionElementBase
> descriptionElementsToClone
= new HashSet
<>();
684 for(Feature feature
:featureToElementMap
.keySet()){
685 List
<DescriptionElementBase
> elements
= featureToElementMap
.get(feature
);
686 //no duplicate description elements found for this feature
687 if(elements
.size()==1){
688 descriptionElementsToClone
.add(elements
.get(0));
690 //duplicates found -> check if all are equal
692 DescriptionElementBase match
= null;
693 for (DescriptionElementBase descriptionElementBase
: elements
) {
695 match
= descriptionElementBase
;
697 else if(!new DescriptionElementCompareWrapper(match
).equals(new DescriptionElementCompareWrapper(descriptionElementBase
))){
699 MessagingUtils
.informationDialog("Multiple data found",
700 String
.format("Multiple description elements with different values "
701 + "found for feature '%s'.\nData will not be copied to new specimen description.", feature
.getLabel()));
706 descriptionElementsToClone
.add(match
);
710 //clone matching descriptionElements
711 for (DescriptionElementBase descriptionElementBase
: descriptionElementsToClone
) {
712 DescriptionElementBase clone
;
714 clone
= descriptionElementBase
.clone(newDesription
);
715 clone
.getSources().forEach(source
-> source
.setOriginalNameString(DescriptionHelper
.getLabel(descriptionElementBase
)));
716 } catch (CloneNotSupportedException e
) {
717 MessagingUtils
.error(CharacterMatrix
.class, e
);
720 return newDesription
;
724 private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator
,
725 int index
, Feature feature
) {
727 columnLabelAccumulator
.registerColumnOverrides(index
+LEADING_COLUMN_COUNT
, MatrixUtility
.getProperty(feature
));
728 indexToFeatureMap
.put(index
+LEADING_COLUMN_COUNT
, feature
);
730 String featureLabel
= feature
.getLabel();
731 String property
= featureLabel
;
732 //show unit for quantitative data
733 if(feature
.isSupportsQuantitativeData()){
734 Set
<MeasurementUnit
> recommendedMeasurementUnits
= feature
.getRecommendedMeasurementUnits();
735 if(recommendedMeasurementUnits
.size()!=1){
736 MessagingUtils
.warningDialog("Column initialization problem", CharacterMatrix
.class,
737 String
.format("Only one unit is allowed for quantitative data: %s", feature
.getLabel()));
740 MeasurementUnit unit
= recommendedMeasurementUnits
.iterator().next();
741 featureLabel
+= " ["+unit
.getLabel()+"]";
743 propertyToLabelMap
.put(property
, featureLabel
);
746 private void registerColumnConfiguration(Feature feature
, IConfigRegistry configRegistry
) {
748 configRegistry
.registerConfigAttribute(
749 EditConfigAttributes
.CELL_EDITABLE_RULE
,
750 IEditableRule
.ALWAYS_EDITABLE
,
752 MatrixUtility
.getProperty(feature
)
754 if(feature
.isSupportsQuantitativeData()){
755 //add display converter for string representation
756 configRegistry
.registerConfigAttribute(
757 CellConfigAttributes
.DISPLAY_CONVERTER
,
758 new QuantitativeDataDisplayConverter(),
760 MatrixUtility
.getProperty(feature
));
761 //register quantitative editor
762 configRegistry
.registerConfigAttribute(
763 EditConfigAttributes
.CELL_EDITOR
,
764 new QuantitativeDataCellEditor(feature
, this),
766 MatrixUtility
.getProperty(feature
));
768 else if(feature
.isSupportsCategoricalData()){
769 //add display converter for string representation
770 configRegistry
.registerConfigAttribute(
771 CellConfigAttributes
.DISPLAY_CONVERTER
,
772 new CategoricalDataDisplayConverter(),
774 MatrixUtility
.getProperty(feature
));
776 //add combo box cell editor
777 CategoricalDataCellEditor comboBoxCellEditor
= new CategoricalDataCellEditor(new IComboBoxDataProvider() {
780 public List
<?
> getValues(int columnIndex
, int rowIndex
) {
781 List
<State
> states
= new ArrayList
<>();
782 Feature feature
= indexToFeatureMap
.get(columnIndex
);
783 if(feature
.isSupportsCategoricalData()){
784 Set
<TermVocabulary
<State
>> stateVocs
= feature
.getSupportedCategoricalEnumerations();
785 for (TermVocabulary
<State
> voc
: stateVocs
) {
786 states
.addAll(voc
.getTerms());
791 }, 5, this, feature
);
793 configRegistry
.registerConfigAttribute(EditConfigAttributes
.CELL_EDITOR
,
796 MatrixUtility
.getProperty(feature
));
802 private List
<RowWrapper
> getDescriptions(WorkingSet workingSet
) {
803 List
<RowWrapper
> rowWrappers
= new ArrayList
<>();
804 Set
<DescriptionBase
> wsDescriptions
= workingSet
.getDescriptions();
805 for (DescriptionBase descriptionBase
: wsDescriptions
) {
806 if(descriptionBase
instanceof SpecimenDescription
){
807 rowWrappers
.add(new RowWrapper((SpecimenDescription
) descriptionBase
));
813 public Map
<Integer
, Feature
> getIndexToFeatureMap() {
814 return indexToFeatureMap
;
817 public LinkedMap
<String
, String
> getPropertyToLabelMap() {
818 return propertyToLabelMap
;
821 public void setDirty() {
822 this.dirty
.setDirty(true);
825 public NatTable
getNatTable() {
829 public WorkingSet
getWorkingSet() {
833 public Collection
<SpecimenOrObservationBase
> getSpecimenCache() {
834 return specimenCache
;
837 public void setSpecimenCache(Collection
<SpecimenOrObservationBase
> specimenCache
) {
838 this.specimenCache
= specimenCache
;
842 * @return the bodyDataProvider
844 public ListDataProvider
<Object
> getBodyDataProvider() {
845 return bodyDataProvider
;
850 public void save(IProgressMonitor monitor
) {
851 CdmStore
.getService(IWorkingSetService
.class).merge(workingSet
, true);
852 conversation
.commit();
853 dirty
.setDirty(false);
857 public void setFocus(){
858 if(conversation
!=null){
861 if(cdmEntitySession
!= null) {
862 cdmEntitySession
.bind();
867 public void dispose(){
868 if (conversation
!= null) {
869 conversation
.close();
872 if(cdmEntitySession
!= null) {
873 cdmEntitySession
.dispose();
874 cdmEntitySession
= null;
876 dirty
.setDirty(false);
877 if(natTableState
!=null){
878 try (FileOutputStream tableStateStream
=
879 new FileOutputStream(getStatePropertiesFile())) {
880 natTableState
.store(tableStateStream
, null);
881 } catch (IOException ioe
) {
882 ioe
.printStackTrace();
887 private File
getStatePropertiesFile() {
888 return new File(WorkbenchUtility
.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES
);
895 public void update(CdmDataChangeMap arg0
) {
902 public ConversationHolder
getConversationHolder() {
910 public void changed(Object element
) {
919 public void forceDirty() {
928 public ICdmEntitySession
getCdmEntitySession() {
929 return cdmEntitySession
;
937 public Collection
<WorkingSet
> getRootEntities() {
938 return Collections
.singleton(this.workingSet
);
946 public Map
<Object
, List
<String
>> getPropertyPathsMap() {
947 Map
<Object
, List
<String
>> propertyMap
= new HashMap
<>();
948 propertyMap
.put(SpecimenOrObservationBase
.class,WS_PROPERTY_PATH
);