commit e738c599583098a002f060f09566c70d298f7f32
Author: Patrick Plitzner <p.plitzner@bgbm.org>
Date:   Thu Jan 4 15:06:45 2018 +0100

    ref #7095 Add summary row to top of grid

diff --git a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/AggregationConfiguration.java b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/AggregationConfiguration.java
new file mode 100644
index 000000000..549bf7a00
--- /dev/null
+++ b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/AggregationConfiguration.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (C) 2018 EDIT
+ * European Distributed Institute of Taxonomy
+ * http://www.e-taxonomy.eu
+ *
+ * The contents of this file are subject to the Mozilla Public License Version 1.1
+ * See LICENSE.TXT at the top of this package for the full license terms.
+ */
+package eu.etaxonomy.taxeditor.editor.workingSet.matrix;
+
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
+import org.eclipse.nebula.widgets.nattable.summaryrow.DefaultSummaryRowConfiguration;
+import org.eclipse.nebula.widgets.nattable.summaryrow.ISummaryProvider;
+import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer;
+import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
+
+import eu.etaxonomy.cdm.model.description.Feature;
+import eu.etaxonomy.cdm.model.description.QuantitativeData;
+
+/**
+ * @author pplitzner
+ * @since Jan 4, 2018
+ *
+ */
+public class AggregationConfiguration extends DefaultSummaryRowConfiguration {
+
+    private final IDataProvider dataProvider;
+    private Feature feature;
+
+    public AggregationConfiguration(IDataProvider dataProvider, Feature feature) {
+        this.dataProvider = dataProvider;
+        this.feature = feature;
+        this.summaryRowBgColor = GUIHelper.COLOR_BLUE;
+        this.summaryRowFgColor = GUIHelper.COLOR_WHITE;
+    }
+
+    @Override
+    public void addSummaryProviderConfig(IConfigRegistry configRegistry) {
+        if(feature.isSupportsQuantitativeData()){
+            configRegistry.registerConfigAttribute(
+                    SummaryRowConfigAttributes.SUMMARY_PROVIDER,
+                    new QuantitativeSummaryProvider(),
+                    DisplayMode.NORMAL,
+                    SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+MatrixUtility.getProperty(feature));
+        }
+    }
+
+    class QuantitativeSummaryProvider implements ISummaryProvider {
+        @Override
+        public Object summarize(int columnIndex) {
+            Float minTotal = null;
+            Float maxTotal = null;
+            int rowCount = AggregationConfiguration.this.dataProvider.getRowCount();
+
+            for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
+                Object dataValue = AggregationConfiguration.this.dataProvider.getDataValue(columnIndex,
+                        rowIndex);
+                if(dataValue instanceof QuantitativeData){
+                    QuantitativeData quantitativeData = (QuantitativeData)dataValue;
+                    Float min = quantitativeData.getMin();
+                    Float max = quantitativeData.getMax();
+                    if(minTotal==null || min<minTotal){
+                        minTotal = min;
+                    }
+                    if(maxTotal==null || max>maxTotal){
+                        maxTotal = max;
+                    }
+                }
+            }
+            String summaryString = "";
+            if(minTotal!=null){
+                summaryString += "from "+minTotal;
+            }
+            if(maxTotal!=null){
+                summaryString += " up to "+maxTotal;
+            }
+            return summaryString;
+        }
+    }
+
+}
diff --git a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/CharacterMatrix.java b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/CharacterMatrix.java
index 55e3d77ad..ad6663509 100644
--- a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/CharacterMatrix.java
+++ b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/CharacterMatrix.java
@@ -51,15 +51,18 @@ import org.eclipse.nebula.widgets.nattable.export.command.ExportCommand;
 import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler;
 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
 import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.FixedSummaryRowHeaderLayer;
 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
-import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
 import org.eclipse.nebula.widgets.nattable.hideshow.RowHideShowLayer;
+import org.eclipse.nebula.widgets.nattable.layer.CompositeLayer;
 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
 import org.eclipse.nebula.widgets.nattable.layer.ILayer;
 import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;
@@ -78,7 +81,10 @@ import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;
 import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
+import org.eclipse.nebula.widgets.nattable.summaryrow.FixedSummaryRowLayer;
+import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer;
 import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;
+import org.eclipse.nebula.widgets.nattable.ui.menu.DebugMenuConfiguration;
 import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
 import org.eclipse.swt.SWT;
@@ -184,10 +190,11 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
         List<Feature> features = new ArrayList<>(tree.getDistinctFeatures());
         Collections.sort(features);
 
-
         descriptions = GlazedLists.eventList(getDescriptions(workingSet));
         SortedList<RowWrapper> sortedList = new SortedList<>(descriptions, null);
 
+        ConfigRegistry configRegistry = new ConfigRegistry();
+
         /**
          * data provider
          */
@@ -196,23 +203,53 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
 
         /**
          * BODY layer
+         *
+         *
+
+        CompositeLayer
+         - (top) SummaryRowLayer
+         - (bottom) ViewportLayer
+
+             ^
+        ViewportLayer
+
+             ^
+        SelectionLayer
+
+             ^
+        RowHideShowLayer
+
+             ^
+        ColumnHideShowLayer
+
+             ^
+        ColumnReorderLayer
+
+             ^
+        RowReorderLayer
+
+             ^
+        DataLayer
+
+             *
+
          */
         DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
 
         //register labels for columns
-        final ColumnOverrideLabelAccumulator columnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
-        bodyDataLayer.setConfigLabelAccumulator(columnLabelAccumulator);
+        ColumnOverrideLabelAccumulator bodyColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
+        bodyDataLayer.setConfigLabelAccumulator(bodyColumnLabelAccumulator);
         propertyToLabelMap.put(TAXON_COLUMN, "Taxon");
-        columnLabelAccumulator.registerColumnOverrides(0, TAXON_COLUMN);
+        bodyColumnLabelAccumulator.registerColumnOverrides(0, TAXON_COLUMN);
         propertyToLabelMap.put(COLLECTOR_COLUMN, "Collector + No");
-        columnLabelAccumulator.registerColumnOverrides(1, COLLECTOR_COLUMN);
+        bodyColumnLabelAccumulator.registerColumnOverrides(1, COLLECTOR_COLUMN);
         propertyToLabelMap.put(IDENTIFIER_COLUMN, "Identifier");
-        columnLabelAccumulator.registerColumnOverrides(2, IDENTIFIER_COLUMN);
+        bodyColumnLabelAccumulator.registerColumnOverrides(2, IDENTIFIER_COLUMN);
         propertyToLabelMap.put(COUNTRY_COLUMN, "Country");
-        columnLabelAccumulator.registerColumnOverrides(3, COUNTRY_COLUMN);
+        bodyColumnLabelAccumulator.registerColumnOverrides(3, COUNTRY_COLUMN);
         for(int i=0;i<features.size();i++){
             Feature feature = features.get(i);
-            initLabels(columnLabelAccumulator, i, feature);
+            initLabels(bodyColumnLabelAccumulator, i, feature);
         }
 
         GlazedListsEventLayer<RowWrapper> eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, sortedList);
@@ -224,6 +261,33 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
         SelectionLayer selectionLayer = new SelectionLayer(rowHideShowLayer);
         ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
 
+
+        // create a standalone FixedSummaryRowLayer
+        // since the summary row should be fixed at the top of the body
+        // region the horizontal dependency of the FixedSummaryRowLayer
+        // is the ViewportLayer
+        FixedSummaryRowLayer summaryRowLayer =
+                new FixedSummaryRowLayer(bodyDataLayer, viewportLayer, configRegistry, false);
+        //regoster labels with summary prefix for summary layer
+        ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer);
+        summaryRowLayer.setConfigLabelAccumulator(summaryColumnLabelAccumulator);
+        for(int i=0;i<features.size();i++){
+            Feature feature = features.get(i);
+            summaryColumnLabelAccumulator.registerColumnOverrides(
+                    i+LEADING_COLUMN_COUNT,
+                    SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX+MatrixUtility.getProperty(feature));
+        }
+        // because the horizontal dependency is the ViewportLayer
+        // we need to set the composite dependency to false
+        summaryRowLayer.setHorizontalCompositeDependency(false);
+
+        CompositeLayer composite = new CompositeLayer(1, 2);
+        composite.setChildLayer("SUMMARY", summaryRowLayer, 0, 0);
+        composite.setChildLayer(GridRegion.BODY, viewportLayer, 0, 1);
+
+
+
+
         /**
          * column header layer
          */
@@ -232,8 +296,6 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
         DataLayer columnHeaderDataLayer = new DataLayer(columnHeaderDataProvider);
         ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
 
-        ConfigRegistry configRegistry = new ConfigRegistry();
-
         // add the SortHeaderLayer to the column header layer stack
         // as we use GlazedLists, we use the GlazedListsSortModel which
         // delegates the sorting to the SortedList
@@ -250,8 +312,10 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
          * row header layer
          */
         IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider);
-        DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider, 40, 20);
-        ILayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
+        DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
+        FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer = new FixedSummaryRowHeaderLayer(rowHeaderDataLayer,
+                composite, selectionLayer);
+        fixedSummaryRowHeaderLayer.setSummaryRowLabel("\u2211");
 
 
         /**
@@ -259,13 +323,14 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
          */
         ILayer cornerLayer = new CornerLayer(
                 new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)),
-                rowHeaderLayer, sortHeaderLayer);
+                fixedSummaryRowHeaderLayer, sortHeaderLayer);
 
 
         /**
          * GRID layer (composition of all other layers)
          */
-        GridLayer gridLayer = new GridLayer(viewportLayer, sortHeaderLayer, rowHeaderLayer, cornerLayer);
+        GridLayer gridLayer = new GridLayer(composite, sortHeaderLayer, fixedSummaryRowHeaderLayer, cornerLayer);
+
 
 
         natTable = new NatTable(parent, gridLayer, false);
@@ -284,6 +349,9 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
         // to sort on a single click
         natTable.addConfiguration(new SingleClickSortConfiguration());
 
+        //FIXME: this is for DEBUG ONLY
+        natTable.addConfiguration(new DebugMenuConfiguration(natTable));
+
         // add the header menu configuration for adding the column header menu
         // with hide/show actions
         natTable.addConfiguration(new AbstractHeaderMenuConfiguration(natTable) {
@@ -310,7 +378,7 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
             }
         });
 
-        // add custom configuration for data conversion and add column labels
+        // add custom configuration for data conversion and add column labels to viewport layer
         viewportLayer.addConfiguration(new AbstractRegistryConfiguration() {
             @Override
             public void configureRegistry(IConfigRegistry configRegistry) {
@@ -340,6 +408,9 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
 
         });
 
+        //register aggregation configuration for each feature
+        features.forEach(feature->summaryRowLayer.addConfiguration(new AggregationConfiguration(bodyDataProvider, feature)));
+
         // add the ExportCommandHandler to the ViewportLayer in order to make
         // exporting work
         viewportLayer.registerCommandHandler(new ExportCommandHandler(viewportLayer));
@@ -354,7 +425,10 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
                     StructuredSelection selection = new StructuredSelection();
                     if(selectedCells.size()==1){
                         ILayerCell cell = selectedCells.iterator().next();
-                        selection = new StructuredSelection(cell.getDataValue());
+                        Object dataValue = cell.getDataValue();
+                        if(dataValue!=null){
+                            selection = new StructuredSelection(dataValue);
+                        }
                     }
                     selService.setSelection(selection);
                 }
@@ -468,7 +542,7 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
     private void initLabels(final ColumnOverrideLabelAccumulator columnLabelAccumulator,
             int index, Feature feature) {
 
-        columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, getProperty(feature));
+        columnLabelAccumulator.registerColumnOverrides(index+LEADING_COLUMN_COUNT, MatrixUtility.getProperty(feature));
         indexToFeatureMap.put(index+LEADING_COLUMN_COUNT, feature);
 
         String featureLabel = feature.getLabel();
@@ -493,7 +567,7 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
                 EditConfigAttributes.CELL_EDITABLE_RULE,
                 IEditableRule.ALWAYS_EDITABLE,
                 DisplayMode.EDIT,
-                getProperty(feature)
+                MatrixUtility.getProperty(feature)
                 );
         if(feature.isSupportsQuantitativeData()){
             //add display converter for string representation
@@ -501,13 +575,13 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
                     CellConfigAttributes.DISPLAY_CONVERTER,
                     new QuantitativeDataDisplayConverter(),
                     DisplayMode.NORMAL,
-                    getProperty(feature));
+                    MatrixUtility.getProperty(feature));
             //register quantitative editor
             configRegistry.registerConfigAttribute(
                     EditConfigAttributes.CELL_EDITOR,
                     new QuantitativeDataCellEditor(feature.getRecommendedStatisticalMeasures(), this),
                     DisplayMode.EDIT,
-                    getProperty(feature));
+                    MatrixUtility.getProperty(feature));
         }
         else if(feature.isSupportsCategoricalData()){
             //add display converter for string representation
@@ -515,7 +589,7 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
                     CellConfigAttributes.DISPLAY_CONVERTER,
                     new CategoricalDataDisplayConverter(),
                     DisplayMode.NORMAL,
-                    getProperty(feature));
+                    MatrixUtility.getProperty(feature));
 
             //add combo box cell editor
             CategoricalDataCellEditor comboBoxCellEditor = new CategoricalDataCellEditor(new IComboBoxDataProvider() {
@@ -537,7 +611,7 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
             configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
                     comboBoxCellEditor,
                     DisplayMode.EDIT,
-                    getProperty(feature));
+                    MatrixUtility.getProperty(feature));
 
         }
 
@@ -554,10 +628,6 @@ public class CharacterMatrix implements IE4SavablePart, IPartContentHasDetails,
         return rowWrappers;
     }
 
-    private String getProperty(Feature feature){
-        return feature.getLabel();
-    }
-
     public Map<Integer, Feature> getIndexToFeatureMap() {
         return indexToFeatureMap;
     }
diff --git a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/MatrixUtility.java b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/MatrixUtility.java
new file mode 100644
index 000000000..1b4b576fc
--- /dev/null
+++ b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/workingSet/matrix/MatrixUtility.java
@@ -0,0 +1,29 @@
+/**
+* Copyright (C) 2018 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.taxeditor.editor.workingSet.matrix;
+
+import eu.etaxonomy.cdm.model.description.Feature;
+
+/**
+ * Utility class for the character matrix editor
+ * @author pplitzner
+ * @since Jan 4, 2018
+ *
+ */
+public class MatrixUtility {
+
+    /**
+     * Returns the column property string for the given {@link Feature}
+     * @param feature
+     * @return
+     */
+    public static String getProperty(Feature feature){
+        return feature.getLabel();
+    }
+}
