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