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