Revision aba1b9d9
Added by Patrick Plitzner over 5 years ago
eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CellEditorDataConversionConfiguration.java | ||
---|---|---|
24 | 24 |
import eu.etaxonomy.cdm.model.description.Feature; |
25 | 25 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataCellEditor; |
26 | 26 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.categorical.CategoricalDataDisplayConverter; |
27 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataCellEditor; |
|
28 | 27 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataDialogEditor; |
29 | 28 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataEditModeDisplayConverter; |
30 | 29 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative.QuantitativeDataNormalModeDisplayConverter; |
... | ... | |
112 | 111 |
DisplayMode.NORMAL, |
113 | 112 |
CharacterMatrixConfigLabelAccumulator.QUANTITATIVE); |
114 | 113 |
|
115 |
/** |
|
116 |
* exact value editor |
|
117 |
*/ |
|
118 | 114 |
//Open cell editor in dialog |
119 | 115 |
configRegistry.registerConfigAttribute( |
120 | 116 |
EditConfigAttributes.OPEN_IN_DIALOG, |
121 | 117 |
Boolean.TRUE, |
122 | 118 |
DisplayMode.EDIT, |
123 |
CharacterMatrixConfigLabelAccumulator.QUANT_EXACT);
|
|
119 |
CharacterMatrixConfigLabelAccumulator.QUANTITATIVE);
|
|
124 | 120 |
//register quantitative editor |
125 | 121 |
configRegistry.registerConfigAttribute( |
126 | 122 |
EditConfigAttributes.CELL_EDITOR, |
127 | 123 |
new QuantitativeDataDialogEditor(matrix), |
128 | 124 |
DisplayMode.EDIT, |
129 |
CharacterMatrixConfigLabelAccumulator.QUANT_EXACT);
|
|
125 |
CharacterMatrixConfigLabelAccumulator.QUANTITATIVE);
|
|
130 | 126 |
// configure custom dialog settings |
131 | 127 |
Map<String, Object> editDialogSettings = new HashMap<>(); |
132 | 128 |
editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_TITLE, "Quantitative Data"); |
... | ... | |
135 | 131 |
Point size = new Point(250, 300); |
136 | 132 |
editDialogSettings.put(ICellEditDialog.DIALOG_SHELL_SIZE, size); |
137 | 133 |
|
138 |
// add custum message |
|
139 | 134 |
//FIXME: this is not displayed because the cell editor |
140 | 135 |
//is already a dialog. Maybe the cell editor and the |
141 | 136 |
//dialog can be merged |
... | ... | |
145 | 140 |
EditConfigAttributes.EDIT_DIALOG_SETTINGS, |
146 | 141 |
editDialogSettings, |
147 | 142 |
DisplayMode.EDIT, |
148 |
CharacterMatrixConfigLabelAccumulator.QUANT_EXACT);
|
|
143 |
CharacterMatrixConfigLabelAccumulator.QUANTITATIVE);
|
|
149 | 144 |
|
150 | 145 |
|
151 | 146 |
//TODO: this for loop can maybe be avoided |
... | ... | |
153 | 148 |
} |
154 | 149 |
|
155 | 150 |
private void registerColumnConfiguration(Feature feature, IConfigRegistry configRegistry) { |
156 |
if(feature.isSupportsQuantitativeData()){ |
|
157 |
/** |
|
158 |
* aggregated value editor |
|
159 |
*/ |
|
160 |
configRegistry.registerConfigAttribute( |
|
161 |
EditConfigAttributes.CELL_EDITOR, |
|
162 |
new QuantitativeDataCellEditor(feature, matrix), |
|
163 |
DisplayMode.EDIT, |
|
164 |
CharacterMatrixConfigLabelAccumulator.QUANT_AGGREGATED); |
|
165 |
} |
|
166 |
else if(feature.isSupportsCategoricalData()){ |
|
151 |
if(feature.isSupportsCategoricalData()){ |
|
167 | 152 |
//add combo box cell editor |
168 | 153 |
//register editor |
169 | 154 |
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, |
... | ... | |
172 | 157 |
MatrixUtility.getProperty(feature)); |
173 | 158 |
|
174 | 159 |
} |
175 |
|
|
176 | 160 |
} |
177 | 161 |
} |
eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrixConfigLabelAccumulator.java | ||
---|---|---|
22 | 22 |
public class CharacterMatrixConfigLabelAccumulator implements IConfigLabelAccumulator { |
23 | 23 |
|
24 | 24 |
public static final String QUANTITATIVE = "QUANTITATIVE"; |
25 |
public static final String QUANT_AGGREGATED = QUANTITATIVE+"_AGGREGATED"; |
|
26 |
public static final String QUANT_EXACT = QUANTITATIVE+"_EXACT"; |
|
27 | 25 |
public static final String CATEGORICAL = "CATEGORICAL"; |
28 | 26 |
|
29 | 27 |
private CharacterMatrix matrix; |
... | ... | |
33 | 31 |
this.matrix = matrix; |
34 | 32 |
} |
35 | 33 |
|
36 |
|
|
37 | 34 |
@Override |
38 | 35 |
public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { |
39 | 36 |
Object rowObject = matrix.getBodyDataProvider().getRowObject(rowPosition); |
... | ... | |
60 | 57 |
} |
61 | 58 |
else if(feature.isSupportsQuantitativeData()){ |
62 | 59 |
configLabels.addLabel(QUANTITATIVE); |
63 |
configLabels.addLabel(matrix.isExactQuantitativeValue()?QUANT_EXACT:QUANT_AGGREGATED); |
|
64 | 60 |
} |
65 | 61 |
} |
66 | 62 |
} |
eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/quantitative/QuantitativeDataCellEditor.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.descriptiveDataSet.matrix.quantitative; |
|
10 |
|
|
11 |
import java.util.HashMap; |
|
12 |
import java.util.Map; |
|
13 |
import java.util.Set; |
|
14 |
|
|
15 |
import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor; |
|
16 |
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; |
|
17 |
import org.eclipse.swt.SWT; |
|
18 |
import org.eclipse.swt.layout.FillLayout; |
|
19 |
import org.eclipse.swt.layout.GridData; |
|
20 |
import org.eclipse.swt.layout.GridLayout; |
|
21 |
import org.eclipse.swt.widgets.Composite; |
|
22 |
import org.eclipse.swt.widgets.Control; |
|
23 |
import org.eclipse.swt.widgets.Label; |
|
24 |
import org.eclipse.swt.widgets.Listener; |
|
25 |
import org.eclipse.swt.widgets.Text; |
|
26 |
|
|
27 |
import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO; |
|
28 |
import eu.etaxonomy.cdm.model.description.Feature; |
|
29 |
import eu.etaxonomy.cdm.model.description.QuantitativeData; |
|
30 |
import eu.etaxonomy.cdm.model.description.StatisticalMeasure; |
|
31 |
import eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.CharacterMatrix; |
|
32 |
|
|
33 |
/** |
|
34 |
* Cell editor for QuantitativeData |
|
35 |
* @author pplitzner |
|
36 |
* @since Dec 7, 2017 |
|
37 |
* |
|
38 |
*/ |
|
39 |
public class QuantitativeDataCellEditor extends AbstractCellEditor{ |
|
40 |
|
|
41 |
private Control editorControl; |
|
42 |
|
|
43 |
private Feature feature; |
|
44 |
|
|
45 |
private Map<StatisticalMeasure, Text> measureToTextMap = new HashMap<>(); |
|
46 |
|
|
47 |
private Object editorValue; |
|
48 |
|
|
49 |
private CharacterMatrix matrix; |
|
50 |
|
|
51 |
private boolean dirty = false; |
|
52 |
|
|
53 |
public QuantitativeDataCellEditor(Feature feature, CharacterMatrix matrix) { |
|
54 |
super(); |
|
55 |
this.matrix = matrix; |
|
56 |
this.feature = feature; |
|
57 |
} |
|
58 |
|
|
59 |
|
|
60 |
/** |
|
61 |
* {@inheritDoc} |
|
62 |
*/ |
|
63 |
@Override |
|
64 |
public Object getEditorValue() { |
|
65 |
return this.editorValue; |
|
66 |
} |
|
67 |
|
|
68 |
/** |
|
69 |
* {@inheritDoc} |
|
70 |
*/ |
|
71 |
@Override |
|
72 |
public void setEditorValue(Object value) { |
|
73 |
this.editorValue = value; |
|
74 |
} |
|
75 |
|
|
76 |
/** |
|
77 |
* {@inheritDoc} |
|
78 |
*/ |
|
79 |
@Override |
|
80 |
public Control getEditorControl() { |
|
81 |
return editorControl; |
|
82 |
} |
|
83 |
|
|
84 |
/** |
|
85 |
* {@inheritDoc} |
|
86 |
*/ |
|
87 |
@Override |
|
88 |
public Control createEditorControl(Composite parent) { |
|
89 |
Composite composite = new Composite(parent, SWT.NONE); |
|
90 |
composite.setLayout(new FillLayout(SWT.HORIZONTAL)); |
|
91 |
|
|
92 |
for (StatisticalMeasure measure: feature.getRecommendedStatisticalMeasures()) { |
|
93 |
Composite measureComposite = new Composite(composite, SWT.NONE); |
|
94 |
GridLayout layout = new GridLayout(2, false); |
|
95 |
layout.horizontalSpacing = 0; |
|
96 |
layout.marginBottom = 0; |
|
97 |
layout.marginHeight = 0; |
|
98 |
layout.marginLeft = 0; |
|
99 |
layout.marginRight = 0; |
|
100 |
layout.marginTop = 0; |
|
101 |
layout.marginWidth = 0; |
|
102 |
layout.verticalSpacing = 0; |
|
103 |
measureComposite.setLayout(layout); |
|
104 |
Label label = new Label(measureComposite, SWT.NONE); |
|
105 |
GridData layoutData = new GridData(SWT.CENTER, SWT.TOP, false, false); |
|
106 |
label.setLayoutData(layoutData); |
|
107 |
label.setText(measure.getLabel().substring(0, 3)); |
|
108 |
|
|
109 |
Text text = new Text(measureComposite, SWT.NONE); |
|
110 |
label.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, false)); |
|
111 |
|
|
112 |
measureToTextMap.put(measure, text); |
|
113 |
} |
|
114 |
return composite; |
|
115 |
} |
|
116 |
|
|
117 |
/** |
|
118 |
* {@inheritDoc} |
|
119 |
*/ |
|
120 |
@Override |
|
121 |
public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) { |
|
122 |
if(dirty){ |
|
123 |
matrix.setDirty(); |
|
124 |
dirty = false; |
|
125 |
} |
|
126 |
return super.commit(direction, closeAfterCommit, skipValidation); |
|
127 |
} |
|
128 |
|
|
129 |
/** |
|
130 |
* {@inheritDoc} |
|
131 |
*/ |
|
132 |
@Override |
|
133 |
protected Control activateCell(Composite parent, Object originalCanonicalValue) { |
|
134 |
Object rowObject = matrix.getBodyDataProvider().getRowObject(this.getRowIndex()); |
|
135 |
if(rowObject instanceof RowWrapperDTO){ |
|
136 |
this.editorControl = createEditorControl(parent); |
|
137 |
if(originalCanonicalValue==null){ |
|
138 |
originalCanonicalValue = ((RowWrapperDTO) rowObject).addQuantitativeData(feature); |
|
139 |
} |
|
140 |
this.setEditorValue(originalCanonicalValue); |
|
141 |
|
|
142 |
if(originalCanonicalValue instanceof QuantitativeData){ |
|
143 |
QuantitativeData quantitativeData = (QuantitativeData)originalCanonicalValue; |
|
144 |
Set<StatisticalMeasure> statisticalValues = quantitativeData.getFeature().getRecommendedStatisticalMeasures(); |
|
145 |
//Fill text controls with actual values and add modify listeners for editing |
|
146 |
for (StatisticalMeasure value : statisticalValues) { |
|
147 |
Text text = measureToTextMap.get(value); |
|
148 |
text.addModifyListener(e->{ |
|
149 |
try { |
|
150 |
float parseFloat = Float.parseFloat(text.getText()); |
|
151 |
quantitativeData.setSpecificStatisticalValue(parseFloat, null, value); |
|
152 |
dirty = true; |
|
153 |
} catch (NumberFormatException|NullPointerException ex) { |
|
154 |
// ignore exception for wrong number format |
|
155 |
} |
|
156 |
}); |
|
157 |
|
|
158 |
Float specificStatisticalValue = quantitativeData.getSpecificStatisticalValue(value); |
|
159 |
if(specificStatisticalValue!=null){ |
|
160 |
Listener[] listeners = text.getListeners(SWT.Modify); |
|
161 |
for (Listener listener : listeners) { |
|
162 |
text.removeListener(SWT.Modify, listener); |
|
163 |
} |
|
164 |
text.setText(Float.toString(specificStatisticalValue)); |
|
165 |
for (Listener listener : listeners) { |
|
166 |
text.addListener(SWT.Modify, listener); |
|
167 |
} |
|
168 |
} |
|
169 |
} |
|
170 |
} |
|
171 |
} |
|
172 |
return this.editorControl; |
|
173 |
} |
|
174 |
|
|
175 |
} |
eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/quantitative/QuantitativeDataDialog.java | ||
---|---|---|
9 | 9 |
package eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative; |
10 | 10 |
|
11 | 11 |
import java.util.List; |
12 |
import java.util.stream.Collectors;
|
|
12 |
import java.util.Map;
|
|
13 | 13 |
|
14 | 14 |
import org.eclipse.jface.window.Window; |
15 | 15 |
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; |
... | ... | |
68 | 68 |
return super.close(); |
69 | 69 |
} |
70 | 70 |
|
71 |
void updateQuantitativeData(QuantitativeData object){ |
|
72 |
QuantitativeData quantitativeData = object; |
|
73 |
//get all non-exact values |
|
74 |
List<StatisticalMeasurementValue> values = quantitativeData.getStatisticalValues().stream() |
|
75 |
.filter(value->!value.getType().equals(StatisticalMeasure.EXACT_VALUE())) |
|
76 |
.collect(Collectors.toList()); |
|
71 |
void updateQuantitativeData(QuantitativeData quantitativeData){ |
|
77 | 72 |
//clear values |
78 | 73 |
quantitativeData.getStatisticalValues().clear(); |
79 |
//add back all non-exact values |
|
80 |
values.forEach(value->quantitativeData.addStatisticalValue(value)); |
|
81 |
//get exact values from text fields |
|
82 |
List<Text> textFields = composite.getTextFields(); |
|
83 |
for (Text text : textFields) { |
|
74 |
//add back all values from text fields |
|
75 |
Map<StatisticalMeasure, List<Text>> textFields = composite.getTextFields(); |
|
76 |
textFields.forEach((measure, texts)->{ |
|
77 |
texts.forEach(text->{ |
|
84 | 78 |
String string = text.getText(); |
85 | 79 |
try { |
86 | 80 |
float exactValue = Float.parseFloat(string); |
87 |
quantitativeData.addStatisticalValue(StatisticalMeasurementValue.NewInstance(StatisticalMeasure.EXACT_VALUE(), exactValue));
|
|
81 |
quantitativeData.addStatisticalValue(StatisticalMeasurementValue.NewInstance(measure, exactValue));
|
|
88 | 82 |
} catch (NumberFormatException e) { |
89 | 83 |
} |
90 |
} |
|
84 |
}); |
|
85 |
}); |
|
91 | 86 |
} |
92 | 87 |
|
93 | 88 |
} |
eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/quantitative/QuantitativeDataDialogComposite.java | ||
---|---|---|
10 | 10 |
package eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix.quantitative; |
11 | 11 |
|
12 | 12 |
import java.util.ArrayList; |
13 |
import java.util.HashMap; |
|
13 | 14 |
import java.util.List; |
15 |
import java.util.Map; |
|
14 | 16 |
|
15 | 17 |
import org.eclipse.swt.SWT; |
16 | 18 |
import org.eclipse.swt.custom.ScrolledComposite; |
... | ... | |
20 | 22 |
import org.eclipse.swt.layout.GridLayout; |
21 | 23 |
import org.eclipse.swt.widgets.Button; |
22 | 24 |
import org.eclipse.swt.widgets.Composite; |
25 |
import org.eclipse.swt.widgets.Label; |
|
23 | 26 |
import org.eclipse.swt.widgets.Text; |
24 | 27 |
|
25 | 28 |
import eu.etaxonomy.cdm.model.description.QuantitativeData; |
... | ... | |
33 | 36 |
*/ |
34 | 37 |
public class QuantitativeDataDialogComposite extends Composite { |
35 | 38 |
|
36 |
private List<Text> textFields = new ArrayList<>();
|
|
39 |
private Map<StatisticalMeasure, List<Text>> textFieldMap = new HashMap<>();
|
|
37 | 40 |
|
38 | 41 |
public QuantitativeDataDialogComposite(Character initialInput, QuantitativeData editorValue, Composite parent, int style) { |
39 | 42 |
super(parent, style); |
... | ... | |
41 | 44 |
|
42 | 45 |
setLayout(new GridLayout(1, false)); |
43 | 46 |
|
44 |
|
|
45 | 47 |
ScrolledComposite scrolledComposite_1 = new ScrolledComposite(this, SWT.BORDER | SWT.V_SCROLL); |
46 | 48 |
scrolledComposite_1.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); |
47 | 49 |
scrolledComposite_1.setExpandHorizontal(true); |
... | ... | |
53 | 55 |
gl_valuesComposite.marginHeight = 0; |
54 | 56 |
valuesComposite.setLayout(gl_valuesComposite); |
55 | 57 |
|
56 |
//add empty text field |
|
57 |
Text emptyTextField = addValue(initialInput==null?null:initialInput.toString(), valuesComposite);
|
|
58 |
//add empty text field for exact value
|
|
59 |
Text emptyTextField = addText(valuesComposite, StatisticalMeasure.EXACT_VALUE(), initialInput==null?null:initialInput.toString());
|
|
58 | 60 |
emptyTextField.setFocus(); |
59 | 61 |
emptyTextField.setSelection(emptyTextField.getText().length()); |
60 | 62 |
|
61 |
//add exisiting values
|
|
63 |
//add existing exact values
|
|
62 | 64 |
editorValue.getStatisticalValues().stream() |
63 |
.filter(value->value.getType().equals(StatisticalMeasure.EXACT_VALUE())) |
|
64 |
.forEach(exact->addValue(Float.toString(exact.getValue()), valuesComposite)); |
|
65 |
|
|
65 |
.filter(measure->measure.getType().equals(StatisticalMeasure.EXACT_VALUE())) |
|
66 |
.forEach(exact->addText(valuesComposite, exact.getType(), Float.toString(exact.getValue()))); |
|
67 |
|
|
68 |
//add aggregation values |
|
69 |
editorValue.getFeature().getRecommendedStatisticalMeasures() |
|
70 |
.stream() |
|
71 |
.filter(sm->!sm.equals(StatisticalMeasure.EXACT_VALUE())) |
|
72 |
.forEach(measure->{ |
|
73 |
Float specificStatisticalValue = editorValue.getSpecificStatisticalValue(measure); |
|
74 |
addText(valuesComposite, measure, specificStatisticalValue!=null?Float.toString(specificStatisticalValue):null); |
|
75 |
}); |
|
66 | 76 |
|
67 | 77 |
scrolledComposite_1.setContent(valuesComposite); |
68 | 78 |
scrolledComposite_1.setMinSize(valuesComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
69 | 79 |
} |
70 | 80 |
|
71 |
private Text addValue(String value, Composite valuesComposite) {
|
|
81 |
private Text addText(Composite valuesComposite, StatisticalMeasure type, String value){
|
|
72 | 82 |
Composite composite_2 = new Composite(valuesComposite, SWT.NONE); |
73 | 83 |
composite_2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1)); |
74 |
GridLayout gl_composite_2 = new GridLayout(2, false);
|
|
84 |
GridLayout gl_composite_2 = new GridLayout(4, false);
|
|
75 | 85 |
gl_composite_2.marginHeight = 0; |
76 | 86 |
gl_composite_2.marginWidth = 0; |
77 | 87 |
gl_composite_2.verticalSpacing = 0; |
78 | 88 |
gl_composite_2.horizontalSpacing = 2; |
79 | 89 |
composite_2.setLayout(gl_composite_2); |
80 | 90 |
|
91 |
Label lblNewLabel = new Label(composite_2, SWT.NONE); |
|
92 |
lblNewLabel.setText(type.getLabel().substring(0, 3)); |
|
93 |
|
|
81 | 94 |
Text text = new Text(composite_2, SWT.BORDER); |
82 | 95 |
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); |
83 | 96 |
if(value!=null){ |
84 | 97 |
text.setText(value); |
85 | 98 |
} |
86 |
textFields.add(text); |
|
99 |
|
|
100 |
List<Text> list = textFieldMap.get(type); |
|
101 |
if(list==null){ |
|
102 |
list = new ArrayList<>(); |
|
103 |
} |
|
104 |
textFieldMap.put(type, list); |
|
105 |
list.add(text); |
|
87 | 106 |
|
88 | 107 |
Button btnNewButton_2 = new Button(composite_2, SWT.NONE); |
89 | 108 |
btnNewButton_2.setImage(ImageResources.getImage(ImageResources.TRASH_ICON)); |
109 |
new Label(composite_2, SWT.NONE); |
|
90 | 110 |
btnNewButton_2.addSelectionListener(new SelectionAdapter() { |
91 | 111 |
@Override |
92 | 112 |
public void widgetSelected(SelectionEvent e) { |
... | ... | |
96 | 116 |
return text; |
97 | 117 |
} |
98 | 118 |
|
99 |
public List<Text> getTextFields() {
|
|
100 |
return textFields;
|
|
119 |
public Map<StatisticalMeasure, List<Text>> getTextFields() {
|
|
120 |
return textFieldMap;
|
|
101 | 121 |
} |
102 | 122 |
|
103 | 123 |
} |
Also available in: Unified diff
ref #7575 Merge aggregated and exact value cell editor