ref #7095 Fill matrix asynchronously
[taxeditor.git] / eu.etaxonomy.taxeditor.editor / src / main / java / eu / etaxonomy / taxeditor / editor / workingSet / matrix / CharacterMatrix.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.editor.workingSet.matrix;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Properties;
22 import java.util.Set;
23
24 import org.apache.commons.collections4.map.LinkedMap;
25 import org.eclipse.core.runtime.ICoreRunnable;
26 import org.eclipse.core.runtime.jobs.Job;
27 import org.eclipse.jface.layout.GridDataFactory;
28 import org.eclipse.jface.viewers.ArrayContentProvider;
29 import org.eclipse.jface.viewers.ComboViewer;
30 import org.eclipse.jface.viewers.LabelProvider;
31 import org.eclipse.jface.viewers.StructuredSelection;
32 import org.eclipse.jface.window.Window;
33 import org.eclipse.nebula.widgets.nattable.NatTable;
34 import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommand;
35 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
36 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
37 import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
38 import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
39 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
40 import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
41 import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;
42 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
43 import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
44 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
45 import org.eclipse.nebula.widgets.nattable.export.command.ExportCommand;
46 import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
47 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
48 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
49 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
50 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
51 import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
52 import org.eclipse.nebula.widgets.nattable.freeze.FreezeHelper;
53 import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
54 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
55 import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand;
56 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
57 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
58 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
59 import org.eclipse.nebula.widgets.nattable.grid.data.FixedSummaryRowHeaderLayer;
60 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
61 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
62 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
63 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
64 import org.eclipse.nebula.widgets.nattable.grid.layer.config.DefaultRowStyleConfiguration;
65 import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer;
66 import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
67 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
68 import org.eclipse.nebula.widgets.nattable.layer.ILayer;
69 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
70 import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;
71 import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
72 import org.eclipse.nebula.widgets.nattable.layer.config.DefaultColumnHeaderStyleConfiguration;
73 import org.eclipse.nebula.widgets.nattable.layer.config.DefaultRowHeaderStyleConfiguration;
74 import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;
75 import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
76 import org.eclipse.nebula.widgets.nattable.persistence.PersistenceHelper;
77 import org.eclipse.nebula.widgets.nattable.persistence.command.DisplayPersistenceDialogCommand;
78 import org.eclipse.nebula.widgets.nattable.persistence.command.DisplayPersistenceDialogCommandHandler;
79 import org.eclipse.nebula.widgets.nattable.persistence.command.IStateChangedListener;
80 import org.eclipse.nebula.widgets.nattable.persistence.command.StateChangeEvent;
81 import org.eclipse.nebula.widgets.nattable.persistence.gui.PersistenceDialog;
82 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
83 import org.eclipse.nebula.widgets.nattable.selection.config.DefaultSelectionStyleConfiguration;
84 import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
85 import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
86 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
87 import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
88 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
89 import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
90 import org.eclipse.nebula.widgets.nattable.style.Style;
91 import org.eclipse.nebula.widgets.nattable.style.VerticalAlignmentEnum;
92 import org.eclipse.nebula.widgets.nattable.summaryrow.DefaultSummaryRowConfiguration;
93 import org.eclipse.nebula.widgets.nattable.summaryrow.FixedSummaryRowLayer;
94 import org.eclipse.nebula.widgets.nattable.summaryrow.ISummaryProvider;
95 import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowConfigAttributes;
96 import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer;
97 import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;
98 import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
99 import org.eclipse.nebula.widgets.nattable.tree.command.TreeCollapseAllCommand;
100 import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandAllCommand;
101 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
102 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
103 import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
104 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
105 import org.eclipse.swt.SWT;
106 import org.eclipse.swt.events.SelectionAdapter;
107 import org.eclipse.swt.events.SelectionEvent;
108 import org.eclipse.swt.graphics.Color;
109 import org.eclipse.swt.graphics.FontData;
110 import org.eclipse.swt.layout.GridData;
111 import org.eclipse.swt.layout.GridLayout;
112 import org.eclipse.swt.layout.RowLayout;
113 import org.eclipse.swt.widgets.Button;
114 import org.eclipse.swt.widgets.Composite;
115 import org.eclipse.swt.widgets.Label;
116
117 import ca.odell.glazedlists.BasicEventList;
118 import ca.odell.glazedlists.EventList;
119 import ca.odell.glazedlists.SortedList;
120 import ca.odell.glazedlists.TreeList;
121 import eu.etaxonomy.cdm.model.description.DescriptionBase;
122 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
123 import eu.etaxonomy.cdm.model.description.Feature;
124 import eu.etaxonomy.cdm.model.description.FeatureNode;
125 import eu.etaxonomy.cdm.model.description.FeatureTree;
126 import eu.etaxonomy.cdm.model.description.MeasurementUnit;
127 import eu.etaxonomy.cdm.model.description.SpecimenDescription;
128 import eu.etaxonomy.cdm.model.description.State;
129 import eu.etaxonomy.cdm.model.description.WorkingSet;
130 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
131 import eu.etaxonomy.taxeditor.editor.l10n.Messages;
132 import eu.etaxonomy.taxeditor.editor.workingSet.matrix.categorical.CategoricalDataCellEditor;
133 import eu.etaxonomy.taxeditor.editor.workingSet.matrix.categorical.CategoricalDataDisplayConverter;
134 import eu.etaxonomy.taxeditor.editor.workingSet.matrix.quantitative.QuantitativeDataCellEditor;
135 import eu.etaxonomy.taxeditor.editor.workingSet.matrix.quantitative.QuantitativeDataDisplayConverter;
136 import eu.etaxonomy.taxeditor.editor.workingSet.matrix.supplementalInfo.SupplementalInfoDisplayConverter;
137 import eu.etaxonomy.taxeditor.model.ColorResources;
138 import eu.etaxonomy.taxeditor.model.DescriptionHelper;
139 import eu.etaxonomy.taxeditor.model.ImageResources;
140 import eu.etaxonomy.taxeditor.model.MessagingUtils;
141 import eu.etaxonomy.taxeditor.preference.Resources;
142 import eu.etaxonomy.taxeditor.workbench.WorkbenchUtility;
143
144 /**
145 * Character matrix editor for editing specimen/taxon descriptions in a table
146 * @author pplitzner
147 * @since Nov 26, 2017
148 *
149 */
150 public class CharacterMatrix extends Composite {
151
152 private static final List<String> WS_PROPERTY_PATH = Arrays.asList(new String[] {
153 "descriptions", //$NON-NLS-1$
154 "descriptions.descriptionElements", //$NON-NLS-1$
155 "descriptions.descriptionElements.inDescription", //$NON-NLS-1$
156 "descriptions.descriptionElements.inDescription.descriptionElements", //$NON-NLS-1$
157 "descriptions.descriptionElements.feature", //$NON-NLS-1$
158 });
159
160 private static final String CHARACTER_MATRIX_STATE_PROPERTIES = "characterMatrixState.properties"; //$NON-NLS-1$
161
162 private static final int LEADING_COLUMN_COUNT = 4;
163 private static final String TAXON_COLUMN = "taxon_column"; //$NON-NLS-1$
164 private static final String COLLECTOR_COLUMN = "collector_column"; //$NON-NLS-1$
165 private static final String IDENTIFIER_COLUMN = "identifier_column"; //$NON-NLS-1$
166 private static final String COUNTRY_COLUMN = "country_column"; //$NON-NLS-1$
167
168 private WorkingSet workingSet;
169
170 private NatTable natTable;
171
172 private Map<Integer, Feature> indexToFeatureMap = new HashMap<>();
173
174 private Map<Feature, List<State>> categoricalFeatureToStateMap = new HashMap<>();
175
176 private LinkedMap<String, String> propertyToLabelMap = new LinkedMap<>();
177
178 private Properties natTableState;
179
180 private EventList<Object> descriptions;
181
182 private List<SpecimenWrapper> specimenCache = null;
183
184 private ListDataProvider<Object> bodyDataProvider;
185
186 private FreezeLayer freezeLayer;
187
188 private ViewportLayer viewportLayer;
189
190 private Label wsLabel;
191
192 private List<Feature> features;
193
194 private DisplayPersistenceDialogCommandHandler displayPersistenceDialogCommandHandler;
195
196 private CharacterMatrixPart part;
197
198 private AbstractLayer topMostLayer;
199
200 private FixedSummaryRowLayer summaryRowLayer;
201
202 private ConfigRegistry configRegistry;
203
204 private DefaultBodyLayerStack bodyLayer;
205
206 private boolean isTreeView = true;
207
208
209 public CharacterMatrix(Composite parent, CharacterMatrixPart part) {
210 super(parent, SWT.NONE);
211 this.part = part;
212 this.setLayout(new GridLayout());
213
214 createToolBar();
215
216 natTable = new NatTable(this, false);
217
218 applyStyles();
219
220 createBottomToolbar();
221
222 }
223
224 private void createToolBar(){
225 Composite toolbarComposite = new Composite(this, SWT.NONE);
226 toolbarComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
227 toolbarComposite.setLayout(new GridLayout(9, false));
228
229 wsLabel = new Label(toolbarComposite, SWT.NONE);
230
231 Button btnToggleTree = new Button(toolbarComposite, SWT.PUSH);
232 Button btnToggleFlat = new Button(toolbarComposite, SWT.PUSH);
233 Button btnCollapseAll = new Button(toolbarComposite, SWT.PUSH);
234 Button btnExpandAll = new Button(toolbarComposite, SWT.PUSH);
235 Button btnFreezeSuppInfo = new Button(toolbarComposite, SWT.TOGGLE);
236 ComboViewer comboStates = new ComboViewer(toolbarComposite, SWT.DROP_DOWN);
237 Button btnManageState = new Button(toolbarComposite, SWT.PUSH);
238 Button btnExcelExport = new Button(toolbarComposite, SWT.PUSH);
239
240 /**
241 * Toogle tree button
242 */
243 btnToggleTree.setImage(ImageResources.getImage(ImageResources.HIERARCHICAL));
244 btnToggleTree.setToolTipText(Messages.CharacterMatrix_SHOW_HIERARCHY);
245 btnToggleTree.setSelection(true);
246 btnToggleTree.setEnabled(false);
247 btnToggleTree.addSelectionListener(new SelectionAdapter() {
248 @Override
249 public void widgetSelected(SelectionEvent e) {
250 toggleTreeFlat(true, btnToggleFlat, btnToggleTree, btnCollapseAll, btnExpandAll, btnFreezeSuppInfo);
251 }
252 });
253
254 /**
255 * Toogle flat button
256 */
257 btnToggleFlat.setImage(ImageResources.getImage(ImageResources.FLAT));
258 btnToggleFlat.setToolTipText(Messages.CharacterMatrix_SHOW_FLAT_LIST);
259 btnToggleFlat.addSelectionListener(new SelectionAdapter() {
260 @Override
261 public void widgetSelected(SelectionEvent e) {
262 toggleTreeFlat(false, btnToggleFlat, btnToggleTree, btnCollapseAll, btnExpandAll, btnFreezeSuppInfo);
263 }
264 });
265
266 /**
267 *
268 * Collapse button
269 */
270 btnCollapseAll.setImage(ImageResources.getImage(ImageResources.COLLAPSE_ALL));
271 btnCollapseAll.setToolTipText(Messages.CharacterMatrix_COLLAPSE);
272 btnCollapseAll.addSelectionListener(new SelectionAdapter() {
273 @Override
274 public void widgetSelected(SelectionEvent e) {
275 natTable.doCommand(new TreeCollapseAllCommand());
276 }
277 });
278
279 /**
280 * Expand button
281 */
282 btnExpandAll.setImage(ImageResources.getImage(ImageResources.EXPAND_ALL));
283 btnExpandAll.setToolTipText(Messages.CharacterMatrix_EXPAND);
284 btnExpandAll.addSelectionListener(new SelectionAdapter() {
285 @Override
286 public void widgetSelected(SelectionEvent e) {
287 natTable.doCommand(new TreeExpandAllCommand());
288 }
289 });
290
291 /**
292 * Freeze supplemental info button
293 */
294 btnFreezeSuppInfo.setImage(ImageResources.getImage(ImageResources.LOCK_ICON));
295 btnFreezeSuppInfo.setToolTipText(Messages.CharacterMatrix_LOCK_COLUMNS);
296 btnFreezeSuppInfo.setSelection(true);
297 btnFreezeSuppInfo.addSelectionListener(new SelectionAdapter() {
298 @Override
299 public void widgetSelected(SelectionEvent e) {
300 boolean isSelected = btnFreezeSuppInfo.getSelection();
301 freezeSupplementalColumns(isSelected);
302 btnFreezeSuppInfo.setImage(isSelected?
303 ImageResources.getImage(ImageResources.LOCK_ICON):
304 ImageResources.getImage(ImageResources.LOCK_OPEN_ICON));
305 }
306 });
307
308 /**
309 * Table state persistence
310 */
311 natTableState = new Properties();
312 //load persisted state
313 File statePropertiesFile = getStatePropertiesFile();
314 FileInputStream inputStream;
315 try {
316 inputStream = new FileInputStream(statePropertiesFile);
317 natTableState.load(inputStream);
318 } catch (IOException e) {
319 MessagingUtils.info("No initial state properties file found for character matrix"); //$NON-NLS-1$
320 }
321
322 // create a combobox for showing the available view states
323 Collection<String> availableStates = PersistenceHelper.getAvailableStates(natTableState);
324 comboStates.setLabelProvider(new LabelProvider(){
325 @Override
326 public String getText(Object element) {
327 if(element instanceof String && ((String) element).isEmpty()){
328 return Messages.CharacterMatrix_DEFAULT;
329 }
330 return super.getText(element);
331 }
332 });
333 comboStates.setContentProvider(new ArrayContentProvider());
334 comboStates.addSelectionChangedListener(e->
335 {
336 int index = comboStates.getCombo().getSelectionIndex();
337 if (index >= 0) {
338 String selected = comboStates.getCombo().getItem(index);
339 // load the state
340 natTable.loadState(selected, natTableState);
341 natTableState.setProperty(PersistenceDialog.ACTIVE_VIEW_CONFIGURATION_KEY, selected);
342 }
343 });
344 comboStates.setInput(availableStates);
345 if(comboStates.getCombo().getItemCount()>0){
346 comboStates.getCombo().select(0);
347 }
348
349 displayPersistenceDialogCommandHandler = new DisplayPersistenceDialogCommandHandler(natTableState, natTable);
350 // add listener to update the combo on view state management changes
351 displayPersistenceDialogCommandHandler.addStateChangeListener(new IStateChangedListener() {
352 @Override
353 public void handleStateChange(StateChangeEvent event) {
354 comboStates.setInput(PersistenceHelper.getAvailableStates(natTableState));
355 selectStateItem(comboStates, event.getViewConfigName());
356 }
357 });
358
359 // add button to show dialog
360 btnManageState.setImage(ImageResources.getImage(ImageResources.SETTINGS));
361 btnManageState.setToolTipText(Messages.CharacterMatrix_VIEW_CONFIG);
362 btnManageState.addSelectionListener(new SelectionAdapter() {
363 @Override
364 public void widgetSelected(SelectionEvent e) {
365 natTable.doCommand(new DisplayPersistenceDialogCommand(natTable));
366 selectStateItem(comboStates, natTableState.get(PersistenceDialog.ACTIVE_VIEW_CONFIGURATION_KEY).toString());
367 }
368 });
369
370 /**
371 * excel export
372 */
373 btnExcelExport.setToolTipText(Messages.CharacterMatrix_EXPORT);
374 btnExcelExport.setImage(ImageResources.getImage(ImageResources.EXPORT));
375 btnExcelExport.addSelectionListener(new SelectionAdapter() {
376 @Override
377 public void widgetSelected(SelectionEvent e) {
378 natTable.doCommand(
379 new ExportCommand(
380 natTable.getConfigRegistry(),
381 natTable.getShell()));
382 }
383 });
384 }
385
386 private void createBottomToolbar() {
387 Composite buttonPanel = new Composite(this, SWT.NONE);
388
389 buttonPanel.setLayout(new RowLayout());
390 GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonPanel);
391
392 /**
393 * Add description button
394 */
395 Button btnAddDescription = new Button(buttonPanel, SWT.PUSH);
396 btnAddDescription.setImage(ImageResources.getImage(ImageResources.ADD_ICON_GREEN));
397 btnAddDescription.addSelectionListener(new SelectionAdapter() {
398 @Override
399 public void widgetSelected(SelectionEvent e) {
400 SpecimenSelectionDialog dialog = new SpecimenSelectionDialog(natTable.getShell(), CharacterMatrix.this);
401 if(dialog.open()==Window.OK){
402 Collection<SpecimenOrObservationBase> specimens = dialog.getSpecimen();
403 boolean hasAdded = false;
404 for (SpecimenOrObservationBase specimen : specimens) {
405 SpecimenDescription description = getDescriptionForWorkingSet(specimen);
406 if(!workingSet.getDescriptions().contains(description)){
407 CharacterMatrix.this.descriptions.add(new RowWrapper(description, workingSet));
408 workingSet.addDescription(description);
409 hasAdded = true;
410 }
411 }
412 if(hasAdded){
413 setDirty();
414 }
415 }
416 }
417 });
418 /**
419 * Remove description button
420 */
421 Button btnRemoveDescription = new Button(buttonPanel, SWT.PUSH);
422 btnRemoveDescription.setImage(ImageResources.getImage(ImageResources.ACTIVE_DELETE_ICON));
423 btnRemoveDescription.addSelectionListener(new SelectionAdapter() {
424 @Override
425 public void widgetSelected(SelectionEvent e) {
426 int[] fullySelectedRowPositions = bodyLayer.getSelectionLayer().getFullySelectedRowPositions();
427 for (int i : fullySelectedRowPositions) {
428 Object rowObject = bodyDataProvider.getRowObject(i);
429 if(rowObject instanceof RowWrapper){
430 CharacterMatrix.this.descriptions.remove(rowObject);
431 workingSet.removeDescription(((RowWrapper) rowObject).getSpecimenDescription());
432 setDirty();
433 }
434 }
435 }
436 });
437 }
438
439 private void applyStyles(){
440 // NOTE: Getting the colors and fonts from the GUIHelper ensures that
441 // they are disposed properly (required by SWT)
442 DefaultNatTableStyleConfiguration natTableConfiguration = new DefaultNatTableStyleConfiguration();
443 natTableConfiguration.bgColor = GUIHelper.getColor(249, 172, 7);
444 natTableConfiguration.fgColor = GUIHelper.getColor(30, 76, 19);
445 natTableConfiguration.hAlign = HorizontalAlignmentEnum.LEFT;
446 natTableConfiguration.vAlign = VerticalAlignmentEnum.TOP;
447 // natTableConfiguration.borderStyle = new BorderStyle(1, GUIHelper.getColor(249, 172, 7), LineStyleEnum.SOLID);
448
449 // Setup even odd row colors - row colors override the NatTable default
450 // colors
451 DefaultRowStyleConfiguration rowStyleConfiguration = new DefaultRowStyleConfiguration();
452 rowStyleConfiguration.oddRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_ODD);
453 rowStyleConfiguration.evenRowBgColor = ColorResources.getColor(Resources.COLOR_LIST_EVEN);
454
455 // Setup selection styling
456 DefaultSelectionStyleConfiguration selectionStyle = new DefaultSelectionStyleConfiguration();
457 // selectionStyle.selectionFont = GUIHelper.getFont(new FontData("Verdana", 8, SWT.NORMAL));
458 // selectionStyle.selectionBgColor = GUIHelper.getColor(217, 232, 251);
459 // selectionStyle.selectionFgColor = GUIHelper.COLOR_BLACK;
460 // selectionStyle.anchorBorderStyle = new BorderStyle(1, GUIHelper.COLOR_DARK_GRAY, LineStyleEnum.SOLID);
461 // selectionStyle.anchorBgColor = GUIHelper.getColor(65, 113, 43);
462 selectionStyle.selectedHeaderBgColor = GUIHelper.getColor(156, 209, 103);
463
464 // Add all style configurations to NatTable
465 natTable.addConfiguration(natTableConfiguration);
466 natTable.addConfiguration(rowStyleConfiguration);
467 natTable.addConfiguration(selectionStyle);
468
469 // Column/Row header style and custom painters
470 DefaultRowHeaderStyleConfiguration rowHeaderConfig = new DefaultRowHeaderStyleConfiguration();
471 Color rowColumnColor = GUIHelper.getColor(230, 255, 255);
472 rowHeaderConfig.bgColor = rowColumnColor;
473 natTable.addConfiguration(rowHeaderConfig);
474 DefaultColumnHeaderStyleConfiguration columnHeaderStyle = new DefaultColumnHeaderStyleConfiguration();
475 columnHeaderStyle.bgColor = rowColumnColor;
476 columnHeaderStyle.font = GUIHelper.getFont(new FontData("Verdana", 9, SWT.BOLD)); //$NON-NLS-1$
477 natTable.addConfiguration(columnHeaderStyle);
478
479 }
480
481 private void toggleTreeFlat(boolean isTree, Button btnToggleFlat, Button btnToggleTree, Button btnCollapseAll, Button btnExpandAll, Button btnFreezeSuppInfo) {
482 isTreeView = isTree;
483 createLayers(isTree);
484 btnToggleFlat.setEnabled(isTree);
485 btnToggleTree.setEnabled(!isTree);
486 btnCollapseAll.setEnabled(isTree);
487 btnExpandAll.setEnabled(isTree);
488 natTable.doCommand(new ClientAreaResizeCommand(natTable));
489 natTable.doCommand(new VisualRefreshCommand());
490 freezeSupplementalColumns(btnFreezeSuppInfo.getSelection());
491 }
492
493 public boolean isTreeView() {
494 return isTreeView;
495 }
496
497 public void createTable(boolean treeView){
498 /**
499 * layers
500 */
501 createLayers(treeView);
502
503 /**
504 * configuration
505 */
506 configureNatTable(treeView, configRegistry, topMostLayer, summaryRowLayer);
507
508 /**
509 * handlers and listeners
510 */
511 registerHandlersAndListeners(topMostLayer);
512
513 GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
514
515 wsLabel.setText(workingSet.getLabel());
516 wsLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
517 wsLabel.getParent().layout();
518
519 freezeSupplementalColumns(true);
520
521 this.layout();
522 }
523
524 private List<Feature> initFeatureList(FeatureNode node){
525 List<Feature> features = new ArrayList<>();
526 node.getChildNodes().forEach(childNode->
527 {
528 features.add(childNode.getFeature());
529 features.addAll(initFeatureList(childNode));
530 });
531 return features;
532 }
533
534 public void initWorkingSet(WorkingSet workingSet){
535 this.workingSet = workingSet;
536 //get features/columns stored in working set
537 FeatureTree tree = workingSet.getDescriptiveSystem();
538 features = initFeatureList(tree.getRoot());
539
540 //init state data for categorical features
541 features.forEach(feature->
542 {
543 if(feature.isSupportsCategoricalData()){
544 List<State> supportedStates = new ArrayList<>();
545 feature.getSupportedCategoricalEnumerations().forEach(voc->supportedStates.addAll(voc.getTerms()));
546 categoricalFeatureToStateMap.put(feature, supportedStates);
547 }
548 });
549 descriptions = new BasicEventList<>();
550
551 }
552
553 private void createLayers(boolean treeView) {
554 // use the SortedList constructor with 'null' for the Comparator
555 // because the Comparator will be set by configuration
556 SortedList<Object> sortedList = new SortedList<>(descriptions, new MatrixRowComparator());
557 // wrap the SortedList with the TreeList
558 TreeList<Object> treeList = new TreeList(sortedList, new DescriptionTreeFormat(workingSet), TreeList.NODES_START_EXPANDED);
559 /**
560 * data provider
561 */
562 SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this);
563 bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor);
564
565 configRegistry = new ConfigRegistry();
566
567
568 /**
569 * BODY layer
570 *
571 *
572
573 CompositeLayer
574 - (top) SummaryRowLayer
575 - (bottom) ViewportLayer
576
577 ^
578 ViewportLayer
579
580 ^
581 TreeLayer (default visible)
582
583 ^
584 CompositeFreezeLayer
585 - viewportLayer
586 - selectionLayer
587 - freezeLayer
588
589 ^
590 FreezeLayer
591
592 ^
593 SelectionLayer
594
595 ^
596 ColumnHideShowLayer
597
598 ^
599 ColumnReorderLayer
600
601 ^
602 DataLayer
603
604 *
605
606 */
607 DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
608
609 //register labels for columns
610 ColumnOverrideLabelAccumulator bodyColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
611 bodyDataLayer.setConfigLabelAccumulator(bodyColumnLabelAccumulator);
612 propertyToLabelMap.put(TAXON_COLUMN, Messages.CharacterMatrix_TAXON);
613 bodyColumnLabelAccumulator.registerColumnOverrides(0, TAXON_COLUMN);
614 propertyToLabelMap.put(COLLECTOR_COLUMN, Messages.CharacterMatrix_COLLECTOR_NO);
615 bodyColumnLabelAccumulator.registerColumnOverrides(1, COLLECTOR_COLUMN);
616 propertyToLabelMap.put(IDENTIFIER_COLUMN, Messages.CharacterMatrix_IDENTIFIER);
617 bodyColumnLabelAccumulator.registerColumnOverrides(2, IDENTIFIER_COLUMN);
618 propertyToLabelMap.put(COUNTRY_COLUMN, Messages.CharacterMatrix_COUNTRY);
619 bodyColumnLabelAccumulator.registerColumnOverrides(3, COUNTRY_COLUMN);
620 for(int i=0;i<features.size();i++){
621 Feature feature = features.get(i);
622 initLabels(bodyColumnLabelAccumulator, i, feature);
623 }
624
625 // layer for event handling of GlazedLists and PropertyChanges
626 GlazedListsEventLayer eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, treeList);
627 GlazedListTreeData treeData = new GlazedListTreeData<>(treeList);
628 ITreeRowModel treeRowModel = new GlazedListTreeRowModel<>(treeData);
629
630 bodyLayer = new DefaultBodyLayerStack(
631 eventLayer);
632 viewportLayer = bodyLayer.getViewportLayer();
633 final SelectionLayer selectionLayer = bodyLayer.getSelectionLayer();
634 freezeLayer = new FreezeLayer(selectionLayer);
635 final CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(
636 freezeLayer, bodyLayer.getViewportLayer(), selectionLayer);
637 TreeLayer treeLayer = new TreeLayer(compositeFreezeLayer, treeRowModel);
638
639 topMostLayer = treeView?treeLayer:compositeFreezeLayer;
640
641 summaryRowLayer = new FixedSummaryRowLayer(bodyDataLayer, topMostLayer, configRegistry, false);
642 //regoster labels with summary prefix for summary layer
643 ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
644 summaryRowLayer.setConfigLabelAccumulator(summaryColumnLabelAccumulator);
645 for(int i=0;i<features.size();i++){
646 Feature feature = features.get(i);
647 summaryColumnLabelAccumulator.registerColumnOverrides(
648 i+LEADING_COLUMN_COUNT,
649 SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+MatrixUtility.getProperty(feature));
650 }
651 // because the horizontal dependency is the ViewportLayer
652 // we need to set the composite dependency to false
653 summaryRowLayer.setHorizontalCompositeDependency(false);
654
655 CompositeLayer composite = new CompositeLayer(1, 2);
656 composite.setChildLayer("SUMMARY", summaryRowLayer, 0, 0); //$NON-NLS-1$
657 composite.setChildLayer(GridRegion.BODY, topMostLayer, 0, 1);
658
659
660 /**
661 * column header layer
662 */
663 IDataProvider columnHeaderDataProvider = new DefaultColumnHeaderDataProvider(
664 propertyToLabelMap.values().toArray(new String[] {}), propertyToLabelMap);
665 DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
666 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, topMostLayer, selectionLayer);
667
668 // add the SortHeaderLayer to the column header layer stack
669 // as we use GlazedLists, we use the GlazedListsSortModel which
670 // delegates the sorting to the SortedList
671 final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
672 columnHeaderLayer,
673 new GlazedListsSortModel<>(
674 sortedList,
675 columnPropertyAccessor,
676 configRegistry,
677 columnHeaderDataLayer));
678
679
680 /**
681 * row header layer
682 */
683 IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
684 DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
685 FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer = new FixedSummaryRowHeaderLayer(rowHeaderDataLayer,
686 composite, selectionLayer);
687 fixedSummaryRowHeaderLayer.setSummaryRowLabel("\u2211"); //$NON-NLS-1$
688
689
690 /**
691 * corner layer
692 */
693 ILayer cornerLayer = new CornerLayer(
694 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
695 fixedSummaryRowHeaderLayer, sortHeaderLayer);
696
697
698 /**
699 * GRID layer (composition of all other layers)
700 */
701 GridLayer gridLayer = new GridLayer(composite, sortHeaderLayer, fixedSummaryRowHeaderLayer, cornerLayer);
702
703 natTable.setLayer(gridLayer);
704
705 }
706
707 private void registerHandlersAndListeners(AbstractLayer topMostLayer) {
708 // add the ExportCommandHandler to the ViewportLayer in order to make
709 // exporting work
710 topMostLayer.registerCommandHandler(new ExportCommandHandler(topMostLayer));
711
712 //propagate single cell selection
713 natTable.addLayerListener(new ILayerListener() {
714 @Override
715 public void handleLayerEvent(ILayerEvent event) {
716 if(event instanceof CellSelectionEvent){
717 CellSelectionEvent cellSelectionEvent = (CellSelectionEvent)event;
718 int columnPosition = cellSelectionEvent.getColumnPosition();
719 if(columnPosition>LEADING_COLUMN_COUNT){
720 Collection<ILayerCell> selectedCells = cellSelectionEvent.getSelectionLayer().getSelectedCells();
721 StructuredSelection selection = new StructuredSelection();
722 if(selectedCells.size()==1){
723 ILayerCell cell = selectedCells.iterator().next();
724 Object dataValue = cell.getDataValue();
725 if(dataValue!=null){
726 selection = new StructuredSelection(dataValue);
727 }
728 }
729 part.getSelectionService().setSelection(selection);
730 }
731 }
732 }
733 });
734
735 //register handler for view configuration menu
736 natTable.registerCommandHandler(displayPersistenceDialogCommandHandler);
737 }
738
739 private void configureNatTable(boolean treeView, ConfigRegistry configRegistry, AbstractLayer topMostLayer,
740 FixedSummaryRowLayer summaryRowLayer) {
741 /**
742 * CONFIGURATION
743 */
744 natTable.setConfigRegistry(configRegistry);
745
746 //add default configuration because autoconfigure is set to false in constructor
747 natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
748
749 //FIXME: this is for DEBUG ONLY
750 // natTable.addConfiguration(new DebugMenuConfiguration(natTable));
751
752 // override the default sort configuration and change the mouse bindings
753 // to sort on a single click
754 if(!treeView){
755 natTable.addConfiguration(new SingleClickSortConfiguration());
756 }
757
758 // add the header menu configuration for adding the column header menu
759 // with hide/show actions
760 natTable.addConfiguration(new AbstractHeaderMenuConfiguration(natTable) {
761
762 @Override
763 protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
764 return super.createColumnHeaderMenu(natTable)
765 .withHideColumnMenuItem()
766 .withShowAllColumnsMenuItem();
767 }
768
769 });
770
771 Style cellStyle = new Style();
772 cellStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT);
773
774 // add custom configuration for data conversion and add column labels to viewport layer
775 topMostLayer.addConfiguration(new AbstractRegistryConfiguration() {
776 @Override
777 public void configureRegistry(IConfigRegistry configRegistry) {
778 //add display converter for string representation
779 configRegistry.registerConfigAttribute(
780 CellConfigAttributes.DISPLAY_CONVERTER,
781 new SupplementalInfoDisplayConverter(CharacterMatrix.this),
782 DisplayMode.NORMAL,
783 TAXON_COLUMN);
784 configRegistry.registerConfigAttribute(
785 CellConfigAttributes.CELL_STYLE,
786 cellStyle,
787 DisplayMode.NORMAL,
788 TAXON_COLUMN);
789 configRegistry.registerConfigAttribute(
790 CellConfigAttributes.DISPLAY_CONVERTER,
791 new SupplementalInfoDisplayConverter(CharacterMatrix.this),
792 DisplayMode.NORMAL,
793 COLLECTOR_COLUMN);
794 configRegistry.registerConfigAttribute(
795 CellConfigAttributes.CELL_STYLE,
796 cellStyle,
797 DisplayMode.NORMAL,
798 COLLECTOR_COLUMN);
799 configRegistry.registerConfigAttribute(
800 CellConfigAttributes.DISPLAY_CONVERTER,
801 new SupplementalInfoDisplayConverter(CharacterMatrix.this),
802 DisplayMode.NORMAL,
803 IDENTIFIER_COLUMN);
804 configRegistry.registerConfigAttribute(
805 CellConfigAttributes.CELL_STYLE,
806 cellStyle,
807 DisplayMode.NORMAL,
808 IDENTIFIER_COLUMN);
809 configRegistry.registerConfigAttribute(
810 CellConfigAttributes.DISPLAY_CONVERTER,
811 new SupplementalInfoDisplayConverter(CharacterMatrix.this),
812 DisplayMode.NORMAL,
813 COUNTRY_COLUMN);
814 configRegistry.registerConfigAttribute(
815 CellConfigAttributes.CELL_STYLE,
816 cellStyle,
817 DisplayMode.NORMAL,
818 COUNTRY_COLUMN);
819 features.forEach(feature->registerColumnConfiguration(feature, configRegistry));
820 }
821
822 });
823
824 //no summary for the supplemental columns
825 for(int i=0;i<LEADING_COLUMN_COUNT;i++){
826 int index = i;
827 summaryRowLayer.addConfiguration(new DefaultSummaryRowConfiguration() {
828 @Override
829 public void addSummaryProviderConfig(IConfigRegistry configRegistry) {
830 configRegistry.registerConfigAttribute(
831 SummaryRowConfigAttributes.SUMMARY_PROVIDER,
832 new ISummaryProvider() {
833
834 @Override
835 public Object summarize(int columnIndex) {
836 return "";
837 }
838 },
839 DisplayMode.NORMAL,
840 SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+index);
841 }
842 });
843 }
844 //register aggregation configuration for each feature
845 features.forEach(feature->summaryRowLayer.addConfiguration(new AggregationConfiguration(bodyDataProvider, feature)));
846
847 natTable.configure();
848 }
849
850 private void freezeSupplementalColumns(boolean freeze){
851 if(freeze){
852 FreezeHelper.freeze(freezeLayer, viewportLayer,
853 new PositionCoordinate(viewportLayer, 0, 0),
854 new PositionCoordinate(viewportLayer, LEADING_COLUMN_COUNT-1, -1));
855 }
856 else{
857 FreezeHelper.unfreeze(freezeLayer, viewportLayer);
858 }
859 }
860
861 private void selectStateItem(ComboViewer comboStates, String stateName){
862 String[] items = comboStates.getCombo().getItems();
863 for(int i=0;i<items.length;i++){
864 if(items[i].equals(stateName)){
865 comboStates.getCombo().select(i);
866 break;
867 }
868 }
869 }
870
871 private SpecimenDescription getDescriptionForWorkingSet(SpecimenOrObservationBase specimen){
872 Set<Feature> wsFeatures = workingSet.getDescriptiveSystem().getDistinctFeatures();
873 List<DescriptionElementBase> matchingDescriptionElements = new ArrayList<>();
874
875 for (SpecimenDescription specimenDescription : (Set<SpecimenDescription>) specimen.getDescriptions()) {
876 Set<Feature> specimenDescriptionFeatures = new HashSet<>();
877 //gather specimen description features and check for match with WS features
878 for (DescriptionElementBase specimenDescriptionElement : specimenDescription.getElements()) {
879 Feature feature = specimenDescriptionElement.getFeature();
880 specimenDescriptionFeatures.add(feature);
881 if(wsFeatures.contains(feature)){
882 matchingDescriptionElements.add(specimenDescriptionElement);
883 }
884 }
885 //if description with the exact same features is found return the description
886 if(specimenDescriptionFeatures.equals(wsFeatures)){
887 return specimenDescription;
888 }
889 }
890 //Create new specimen description if no match was found
891 setDirty();
892 SpecimenDescription newDesription = SpecimenDescription.NewInstance(specimen);
893 newDesription.setTitleCache(Messages.CharacterMatrix_WORKING_SET+workingSet.getLabel()+": "+newDesription.generateTitle(), true); //$NON-NLS-2$
894
895 //check for equals description element (same feature and same values)
896 Map<Feature, List<DescriptionElementBase>> featureToElementMap = new HashMap<>();
897 for(DescriptionElementBase element:matchingDescriptionElements){
898 List<DescriptionElementBase> list = featureToElementMap.get(element.getFeature());
899 if(list==null){
900 list = new ArrayList<>();
901 }
902 list.add(element);
903 featureToElementMap.put(element.getFeature(), list);
904 }
905 Set<DescriptionElementBase> descriptionElementsToClone = new HashSet<>();
906 for(Feature feature:featureToElementMap.keySet()){
907 List<DescriptionElementBase> elements = featureToElementMap.get(feature);
908 //no duplicate description elements found for this feature
909 if(elements.size()==1){
910 descriptionElementsToClone.add(elements.get(0));
911 }
912 //duplicates found -> check if all are equal
913 else{
914 DescriptionElementBase match = null;
915 for (DescriptionElementBase descriptionElementBase : elements) {
916 if(match==null){
917 match = descriptionElementBase;
918 }
919 else if(!new DescriptionElementCompareWrapper(match).equals(new DescriptionElementCompareWrapper(descriptionElementBase))){
920 match = null;
921 MessagingUtils.informationDialog(Messages.CharacterMatrix_MULTIPLE_DATA,
922 String.format(Messages.CharacterMatrix_MULTIPLE_DATA_MESSAGE, feature.getLabel()));
923 break;
924 }
925 }
926 if(match!=null){
927 descriptionElementsToClone.add(match);
928 }
929 }
930 }
931 //clone matching descriptionElements
932 for (DescriptionElementBase descriptionElementBase : descriptionElementsToClone) {
933 DescriptionElementBase clone;
934 try {
935 clone = descriptionElementBase.clone(newDesription);
936 clone.getSources().forEach(source -> source.setOriginalNameString(DescriptionHelper.getLabel(descriptionElementBase)));
937 } catch (CloneNotSupportedException e) {
938 MessagingUtils.error(CharacterMatrix.class, e);
939 }
940 }
941 return newDesription;
942
943 }
944
945 private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator,
946 int index, Feature feature) {
947
948 columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, MatrixUtility.getProperty(feature));
949 indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
950
951 String featureLabel = feature.getLabel();
952 String property = featureLabel;
953 //show unit for quantitative data
954 if(feature.isSupportsQuantitativeData()){
955 Set<MeasurementUnit> recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits();
956 if(recommendedMeasurementUnits.size()>1){
957 MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class,
958 String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel()));
959 }
960 if(recommendedMeasurementUnits.size()==1){
961 MeasurementUnit unit = recommendedMeasurementUnits.iterator().next();
962 featureLabel += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$
963 }
964 }
965 propertyToLabelMap.put(property, featureLabel);
966 }
967
968 private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) {
969 //make cell editable
970 configRegistry.registerConfigAttribute(
971 EditConfigAttributes.CELL_EDITABLE_RULE,
972 IEditableRule.ALWAYS_EDITABLE,
973 DisplayMode.EDIT,
974 MatrixUtility.getProperty(feature)
975 );
976 if(feature.isSupportsQuantitativeData()){
977 //add display converter for string representation
978 configRegistry.registerConfigAttribute(
979 CellConfigAttributes.DISPLAY_CONVERTER,
980 new QuantitativeDataDisplayConverter(),
981 DisplayMode.NORMAL,
982 MatrixUtility.getProperty(feature));
983 //register quantitative editor
984 configRegistry.registerConfigAttribute(
985 EditConfigAttributes.CELL_EDITOR,
986 new QuantitativeDataCellEditor(feature, this),
987 DisplayMode.EDIT,
988 MatrixUtility.getProperty(feature));
989 }
990 else if(feature.isSupportsCategoricalData()){
991 //add display converter for string representation
992 configRegistry.registerConfigAttribute(
993 CellConfigAttributes.DISPLAY_CONVERTER,
994 new CategoricalDataDisplayConverter(),
995 DisplayMode.NORMAL,
996 MatrixUtility.getProperty(feature));
997
998 //add combo box cell editor
999 //register editor
1000 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
1001 new CategoricalDataCellEditor(getSupportedStatesForCategoricalFeature(feature), this, feature),
1002 DisplayMode.EDIT,
1003 MatrixUtility.getProperty(feature));
1004
1005 }
1006
1007 }
1008
1009
1010 public void loadDescriptions(WorkingSet workingSet) {
1011 Job job = Job.create("Load character data", (ICoreRunnable) monitor -> {
1012 List<RowWrapper> rowWrappers = new ArrayList<>();
1013 Set<DescriptionBase> wsDescriptions = workingSet.getDescriptions();
1014 for (DescriptionBase descriptionBase : wsDescriptions) {
1015 if(descriptionBase instanceof SpecimenDescription){
1016 CharacterMatrix.this.descriptions.add(new RowWrapper((SpecimenDescription) descriptionBase, workingSet));
1017 }
1018 }
1019 });
1020 job.schedule();
1021 }
1022
1023 public List<State> getSupportedStatesForCategoricalFeature(Feature feature){
1024 return categoricalFeatureToStateMap.get(feature);
1025 }
1026
1027 public Map<Integer, Feature> getIndexToFeatureMap() {
1028 return indexToFeatureMap;
1029 }
1030
1031 public LinkedMap<String, String> getPropertyToLabelMap() {
1032 return propertyToLabelMap;
1033 }
1034
1035 public void setDirty() {
1036 part.setDirty();
1037 }
1038
1039 public NatTable getNatTable() {
1040 return natTable;
1041 }
1042
1043 public WorkingSet getWorkingSet() {
1044 return workingSet;
1045 }
1046
1047 public List<SpecimenWrapper> getSpecimenCache() {
1048 return specimenCache;
1049 }
1050
1051 public void setSpecimenCache(List<SpecimenWrapper> specimenCache) {
1052 this.specimenCache = specimenCache;
1053 }
1054
1055 public Properties getNatTableState() {
1056 return natTableState;
1057 }
1058
1059 public ListDataProvider<Object> getBodyDataProvider() {
1060 return bodyDataProvider;
1061 }
1062
1063 private File getStatePropertiesFile() {
1064 return new File(WorkbenchUtility.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES);
1065 }
1066
1067 }