X-Git-Url: https://dev.e-taxonomy.eu/gitweb/taxeditor.git/blobdiff_plain/d6eb611592f9cbf2160b408c7f08523854fba3aa..66889a4215d74759957fbf7a08733210a5ebe7e1:/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrix.java diff --git a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrix.java b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrix.java index 29009798a..17bd0adf7 100644 --- a/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrix.java +++ b/eu.etaxonomy.taxeditor.editor/src/main/java/eu/etaxonomy/taxeditor/editor/descriptiveDataSet/matrix/CharacterMatrix.java @@ -11,11 +11,16 @@ package eu.etaxonomy.taxeditor.editor.descriptiveDataSet.matrix; import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.TreeSet; import java.util.UUID; import java.util.stream.Collectors; @@ -28,16 +33,25 @@ import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.ui.di.UIEventTopic; import org.eclipse.e4.ui.di.UISynchronize; +import org.eclipse.e4.ui.services.EMenuService; import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.viewers.ComboViewer; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.copy.command.InternalCopyDataCommandHandler; +import org.eclipse.nebula.widgets.nattable.copy.command.InternalPasteDataCommandHandler; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; import org.eclipse.nebula.widgets.nattable.export.command.ExportCommandHandler; +import org.eclipse.nebula.widgets.nattable.extension.e4.selection.E4SelectionListener; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel; import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData; @@ -45,56 +59,71 @@ import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedList import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer; import org.eclipse.nebula.widgets.nattable.freeze.FreezeHelper; import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer; -import org.eclipse.nebula.widgets.nattable.grid.GridRegion; import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand; 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.group.ColumnGroupGroupHeaderLayer; +import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer; +import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel; import org.eclipse.nebula.widgets.nattable.layer.AbstractLayer; -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.cell.ColumnOverrideLabelAccumulator; -import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack; +import org.eclipse.nebula.widgets.nattable.persistence.gui.PersistenceDialog; import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer; import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; -import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum; import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration; -import org.eclipse.nebula.widgets.nattable.summaryrow.FixedSummaryRowLayer; -import org.eclipse.nebula.widgets.nattable.summaryrow.SummaryRowLayer; import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel; import org.eclipse.nebula.widgets.nattable.tree.TreeLayer; +import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandToLevelCommand; +import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; +import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction; +import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; import ca.odell.glazedlists.BasicEventList; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.SortedList; import ca.odell.glazedlists.TreeList; +import eu.etaxonomy.cdm.api.application.CdmApplicationState; import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService; +import eu.etaxonomy.cdm.api.service.IOccurrenceService; import eu.etaxonomy.cdm.api.service.UpdateResult; +import eu.etaxonomy.cdm.api.service.config.RemoveDescriptionsFromDescriptiveDataSetConfigurator; +import eu.etaxonomy.cdm.api.service.dto.FieldUnitDTO; import eu.etaxonomy.cdm.api.service.dto.RowWrapperDTO; +import eu.etaxonomy.cdm.api.service.dto.SpecimenRowWrapperDTO; import eu.etaxonomy.cdm.common.monitor.IRemotingProgressMonitor; +import eu.etaxonomy.cdm.model.common.CdmBase; +import eu.etaxonomy.cdm.model.description.DescriptionBase; import eu.etaxonomy.cdm.model.description.DescriptiveDataSet; -import eu.etaxonomy.cdm.model.description.Feature; -import eu.etaxonomy.cdm.model.description.FeatureNode; -import eu.etaxonomy.cdm.model.description.FeatureTree; -import eu.etaxonomy.cdm.model.description.MeasurementUnit; import eu.etaxonomy.cdm.model.description.SpecimenDescription; -import eu.etaxonomy.cdm.model.description.State; +import eu.etaxonomy.cdm.model.occurrence.FieldUnit; +import eu.etaxonomy.cdm.persistence.dto.DescriptiveDataSetBaseDto; +import eu.etaxonomy.cdm.persistence.dto.FeatureDto; import eu.etaxonomy.cdm.persistence.dto.SpecimenNodeWrapper; +import eu.etaxonomy.cdm.persistence.dto.TermDto; +import eu.etaxonomy.cdm.persistence.dto.TermNodeDto; +import eu.etaxonomy.cdm.persistence.dto.TermTreeDto; +import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache; +import eu.etaxonomy.taxeditor.editor.internal.TaxeditorEditorPlugin; import eu.etaxonomy.taxeditor.editor.l10n.Messages; +import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants; import eu.etaxonomy.taxeditor.model.MessagingUtils; +import eu.etaxonomy.taxeditor.session.ICdmEntitySession; import eu.etaxonomy.taxeditor.store.CdmStore; import eu.etaxonomy.taxeditor.workbench.WorkbenchUtility; @@ -115,50 +144,65 @@ public class CharacterMatrix extends Composite { static final String COUNTRY_COLUMN = "country_column"; //$NON-NLS-1$ static final String LABEL_TAXON_ROW = "TAXON_ROW"; //$NON-NLS-1$ + static final String LABEL_TAXON_AGGREGATED_DESCRIPTION = "TAXON_AGGREGATED_DESCRIPTION"; //$NON-NLS-1$ + static final String LABEL_TAXON_AGGREGATED_DESCRIPTION_ICON = "TAXON_AGGREGATED_DESCRIPTION_ICON"; //$NON-NLS-1$ + static final String LABEL_TAXON_DEFAULT_DESCRIPTION = "TAXON_DEFAULT_DESCRIPTION"; //$NON-NLS-1$ + static final String LABEL_TAXON_DEFAULT_DESCRIPTION_ICON = "TAXON_DEFAULT_DESCRIPTION_ICON"; //$NON-NLS-1$ + static final String LABEL_TAXON_LITERATURE_DESCRIPTION = "TAXON_LITERATURE_DESCRIPTION"; //$NON-NLS-1$ + static final String LABEL_TAXON_LITERATURE_DESCRIPTION_ICON = "TAXON_LITERATURE_DESCRIPTION_ICON"; //$NON-NLS-1$ + static final String LABEL_TAXON_DESCRIPTION = "LABEL_TAXON_DESCRIPTION"; //$NON-NLS-1$ + static final String LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA = "LABEL_DESCRIPTION_HAS_SUPPLEMENTAL_DATA"; //$NON-NLS-1$ @Inject private UISynchronize sync; - private DescriptiveDataSet descriptiveDataSet; + @Inject + private EMenuService menuService; private NatTable natTable; - private Map indexToFeatureMap = new HashMap<>(); +// private Collection> rowsToSave = new HashSet<>(); + private HashMap> rowsToMerge = new HashMap<>(); + + private Map indexToFeatureMap = new HashMap<>(); - private Map> categoricalFeatureToStateMap = new HashMap<>(); + private Map> categoricalFeatureToStateMap = new HashMap<>(); private LinkedMap propertyToLabelMap = new LinkedMap<>(); - //FIXME use more concrete generic private EventList descriptions; private Collection specimenCache = null; - private Map featureToHistogramMap = new HashMap<>(); - - private Map featureToQuantDataStatisticsMap = new HashMap<>(); + private Map featureToHistogramMap = new HashMap<>(); private ListDataProvider bodyDataProvider; private FreezeLayer freezeLayer; - private List features; + private List features; private CharacterMatrixPart part; private AbstractLayer topMostLayer; - private FixedSummaryRowLayer summaryRowLayer; - private ConfigRegistry configRegistry; - private DefaultBodyLayerStack bodyLayer; + private MatrixBodyLayerStack bodyLayer; private boolean isTreeView = true; private CharacterMatrixToolbar toolbar; - private boolean isShowTooltips = true; + private DescriptionTreeFormat treeFormat; +// private List descriptionUuidsToDelete; + Map> descriptionUuidsToDelete; + + private List specimenToAdd; + + private MouseEventMatcher mouseEventMatcher = null; + + private Set descriptionsToSave = new HashSet<>(); public CharacterMatrix(Composite parent, CharacterMatrixPart part) { super(parent, SWT.NONE); @@ -169,7 +213,7 @@ public class CharacterMatrix extends Composite { natTable = new NatTable(this, false); - createBottomToolbar(); +// createBottomToolbar(); } @@ -185,10 +229,8 @@ public class CharacterMatrix extends Composite { private void applyStyles(){ ModernNatTableThemeConfiguration configuration = new ModernNatTableThemeConfiguration(); - configuration.summaryRowHAlign = HorizontalAlignmentEnum.CENTER; // NOTE: Getting the colors and fonts from the GUIHelper ensures that // they are disposed properly (required by SWT) - configuration.summaryRowBgColor = GUIHelper.getColor(255, 255, 153); configuration.cHeaderBgColor = GUIHelper.getColor(211, 211, 211); configuration.rHeaderBgColor = GUIHelper.getColor(211, 211, 211); natTable.addConfiguration(configuration); @@ -197,18 +239,19 @@ public class CharacterMatrix extends Composite { void toggleTreeFlat(boolean isTree, Button btnToggleFlat, Button btnToggleTree, Button btnCollapseAll, Button btnExpandAll, Button btnFreezeSuppInfo) { isTreeView = isTree; - createTable(isTree, freezeLayer.isFrozen()); + createTable(isTree, freezeLayer.isFrozen(), true); btnToggleFlat.setEnabled(isTree); btnToggleTree.setEnabled(!isTree); btnCollapseAll.setEnabled(isTree); btnExpandAll.setEnabled(isTree); + } public boolean isTreeView() { return isTreeView; } - public void createTable(boolean treeView, boolean freezeSupplementalColumns){ + public void createTable(boolean treeView, boolean freezeSupplementalColumns, boolean isInitialExpandToDeepestTaxonLevel){ /** * layers */ @@ -217,18 +260,19 @@ public class CharacterMatrix extends Composite { /** * configuration */ - configureNatTable(treeView, configRegistry, topMostLayer, summaryRowLayer); + + configureNatTable(treeView, configRegistry, topMostLayer); /** * handlers and listeners */ - registerHandlersAndListeners(topMostLayer); + registerHandlersAndListeners(); //grab all space GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable); - //update label to current data set - toolbar.getWsLabel().setText(descriptiveDataSet.getLabel()); + //update label to current dataset + toolbar.getWsLabel().setText(getDescriptiveDataSet().getTitleCache()); toolbar.getWsLabel().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); toolbar.getWsLabel().getParent().layout(); @@ -237,55 +281,85 @@ public class CharacterMatrix extends Composite { //add tooltip to table - new CategoricalChartTooltip(this); - new QuantitativeChartTooltip(this); +// new CategoricalChartTooltip(this); +// new QuantitativeChartTooltip(this); + this.layout(); natTable.doCommand(new ClientAreaResizeCommand(natTable)); + + // expand all taxa + if(isInitialExpandToDeepestTaxonLevel){ + Integer deepestTaxonLevel = treeFormat.getDeepestTaxonLevel(); + if(deepestTaxonLevel!=null){ + natTable.doCommand(new TreeExpandToLevelCommand(deepestTaxonLevel-2)); + } + } + + // clean up table state + getNatTableState().remove(NatTable.INITIAL_PAINT_COMPLETE_FLAG); + getNatTableState().remove(PersistenceDialog.ACTIVE_VIEW_CONFIGURATION_KEY); } - private List initFeatureList(FeatureNode node){ - List features = new ArrayList<>(); - node.getChildNodes().forEach(childNode-> - { - features.add(childNode.getFeature()); - features.addAll(initFeatureList(childNode)); - }); + private List initFeatureList(TermNodeDto node){ + List features = new ArrayList<>(); + List childNodes = node.getChildren(); + for (TermNodeDto childNode : childNodes) { + if (childNode != null){ + features.add((FeatureDto) childNode.getTerm()); + features.addAll(initFeatureList(childNode)); + } + } return features; } - public void initDescriptiveDataSet(DescriptiveDataSet descriptiveDataSet){ - this.descriptiveDataSet = descriptiveDataSet; - //get features/columns stored in descriptive data set - FeatureTree tree = descriptiveDataSet.getDescriptiveSystem(); + public void initDescriptiveDataSet(){ + //get features/columns stored in descriptive dataset + TermTreeDto tree = getDescriptiveDataSet().getDescriptiveSystem(); features = initFeatureList(tree.getRoot()); + Set duplicateFeatures = features.stream().filter(i -> Collections.frequency(features, i) >1) + .collect(Collectors.toSet()); + + if (!duplicateFeatures.isEmpty()) { + throw new IllegalArgumentException("Duplicate features found: " + + duplicateFeatures.stream().map(feature -> feature.getRepresentation_L10n()).collect(Collectors.joining(","))); + } + //init state data for categorical features - features.forEach(feature-> - { - if(feature.isSupportsCategoricalData()){ - List supportedStates = new ArrayList<>(); - feature.getSupportedCategoricalEnumerations().forEach(voc->supportedStates.addAll(voc.getTerms())); - categoricalFeatureToStateMap.put(feature, supportedStates); - } - }); +// features.forEach(feature->fetchSupportedStates(feature)); + fetchSupportedStates(features); + descriptions = new BasicEventList<>(); } + private void fetchSupportedStates(List features) { + Set featureUuids = new HashSet<>(); + features.forEach(i->featureUuids.add(i.getUuid())); + categoricalFeatureToStateMap = CdmStore.getService(IDescriptiveDataSetService.class).getSupportedStatesForFeature(featureUuids); + + } + + + private void createLayers(boolean treeView) { - // use the SortedList constructor with 'null' for the Comparator - // because the Comparator will be set by configuration + SortedList sortedList = new SortedList<>(descriptions, new MatrixRowComparator()); // wrap the SortedList with the TreeList - TreeList treeList = new TreeList(sortedList, new DescriptionTreeFormat(descriptiveDataSet), TreeList.NODES_START_EXPANDED); + treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet()); + TreeList treeList = new TreeList(sortedList, treeFormat, TreeList.NODES_START_COLLAPSED); + // wrap the SortedList with the TreeList +// treeFormat = new DescriptionTreeFormat(getDescriptiveDataSet()); + /** * data provider */ SpecimenColumnPropertyAccessor columnPropertyAccessor = new SpecimenColumnPropertyAccessor(this); bodyDataProvider = treeView?new ListDataProvider<>(treeList, columnPropertyAccessor):new ListDataProvider<>(sortedList, columnPropertyAccessor); - configRegistry = new ConfigRegistry(); + DataLayer bodyDataLayer = new DataLayer(bodyDataProvider); + bodyDataLayer.registerCommandHandler(new CopyPasteUpdateDataCommandHandler(bodyDataLayer)); /** @@ -293,11 +367,6 @@ public class CharacterMatrix extends Composite { * * - CompositeLayer - - (top) SummaryRowLayer - - (bottom) ViewportLayer - - ^ ViewportLayer ^ @@ -318,17 +387,14 @@ public class CharacterMatrix extends Composite { ^ ColumnHideShowLayer - ^ - ColumnReorderLayer - ^ DataLayer * */ - DataLayer bodyDataLayer = new DataLayer(bodyDataProvider); + configRegistry = new ConfigRegistry(); //register labels CharacterMatrixConfigLabelAccumulator labelAccumulator = new CharacterMatrixConfigLabelAccumulator(this); bodyDataLayer.setConfigLabelAccumulator(labelAccumulator); @@ -339,41 +405,32 @@ public class CharacterMatrix extends Composite { propertyToLabelMap.put(IDENTIFIER_COLUMN, Messages.CharacterMatrix_IDENTIFIER); propertyToLabelMap.put(COUNTRY_COLUMN, Messages.CharacterMatrix_COUNTRY); for(int i=0;i(bodyDataLayer, treeList); + GlazedListsEventLayer eventLayer = new GlazedListsEventLayer<>(bodyDataLayer, (EventList)bodyDataProvider.getList()); + GlazedListTreeData treeData = new GlazedListTreeData<>(treeList); ITreeRowModel treeRowModel = new GlazedListTreeRowModel<>(treeData); - bodyLayer = new DefaultBodyLayerStack(eventLayer); + // assemble the column groups + LinkedList columnGroups = new LinkedList<>(); + List rootChildren = getDescriptiveDataSet().getDescriptiveSystem().getRoot().getChildren(); + buildHeader(rootChildren, columnGroups); + + bodyLayer = new MatrixBodyLayerStack(eventLayer, columnGroups); final SelectionLayer selectionLayer = bodyLayer.getSelectionLayer(); freezeLayer = new FreezeLayer(selectionLayer); final CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer( freezeLayer, bodyLayer.getViewportLayer(), selectionLayer); - TreeLayer treeLayer = new TreeLayer(compositeFreezeLayer, treeRowModel); - - topMostLayer = treeView?treeLayer:compositeFreezeLayer; - - summaryRowLayer = new FixedSummaryRowLayer(bodyDataLayer, topMostLayer, configRegistry, false); - //regoster labels with summary prefix for summary layer - ColumnOverrideLabelAccumulator summaryColumnLabelAccumulator =new ColumnOverrideLabelAccumulator(bodyDataLayer); - summaryRowLayer.setConfigLabelAccumulator(summaryColumnLabelAccumulator); - for(int i=0;i> entry: groupLevel1.getColumnGroupToIndexMap().entrySet()) { + TermNodeDto group = entry.getKey(); + TreeSet indexList = entry.getValue(); + int[] intArray = indexList.stream().mapToInt(Integer::intValue).toArray(); + groupLayerLevel1.addColumnsIndexesToGroup(group.getTerm().getTitleCache(), intArray); + groupLayerLevel1.setGroupUnbreakable(indexList.iterator().next()); + } + topHeaderLayer = groupLayerLevel1; + + // second group level + if(columnGroups.size()>1){ + ColumnGroupWrapper groupLevel2 = columnGroups.get(1); + groupLayerLevel2 = new ColumnGroupGroupHeaderLayer(groupLayerLevel1, selectionLayer, groupLevel2.getModel()); + for (Entry> entry: groupLevel2.getColumnGroupToIndexMap().entrySet()) { + TermNodeDto group = entry.getKey(); + TreeSet indexList = entry.getValue(); + int[] intArray = indexList.stream().mapToInt(Integer::intValue).toArray(); + groupLayerLevel2.addColumnsIndexesToGroup(group.getTerm().getTitleCache(), intArray); + groupLayerLevel2.setGroupUnbreakable(indexList.iterator().next()); + } + topHeaderLayer = groupLayerLevel2; + } + } + // add the SortHeaderLayer to the column header layer stack // as we use GlazedLists, we use the GlazedListsSortModel which // delegates the sorting to the SortedList - final SortHeaderLayer sortHeaderLayer = new SortHeaderLayer<>( - columnHeaderLayer, + final SortHeaderLayer sortHeaderLayer = new SortHeaderLayer<>( + topHeaderLayer, new GlazedListsSortModel<>( sortedList, columnPropertyAccessor, @@ -396,14 +485,14 @@ public class CharacterMatrix extends Composite { columnHeaderDataLayer)); + /** * row header layer */ IDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(bodyDataProvider); DefaultRowHeaderDataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider); - FixedSummaryRowHeaderLayer fixedSummaryRowHeaderLayer = new FixedSummaryRowHeaderLayer(rowHeaderDataLayer, - composite, selectionLayer); - fixedSummaryRowHeaderLayer.setSummaryRowLabel("\u2211"); //$NON-NLS-1$ + RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, + topMostLayer, selectionLayer); /** @@ -411,37 +500,83 @@ public class CharacterMatrix extends Composite { */ ILayer cornerLayer = new CornerLayer( new DataLayer(new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider)), - fixedSummaryRowHeaderLayer, sortHeaderLayer); + rowHeaderLayer, sortHeaderLayer); /** * GRID layer (composition of all other layers) */ - GridLayer gridLayer = new GridLayer(composite, sortHeaderLayer, fixedSummaryRowHeaderLayer, cornerLayer); + GridLayer gridLayer = new GridLayer(topMostLayer, sortHeaderLayer, rowHeaderLayer, cornerLayer); natTable.setLayer(gridLayer); } - private void registerHandlersAndListeners(AbstractLayer topMostLayer) { - // add the ExportCommandHandler to the ViewportLayer in order to make - // exporting work - topMostLayer.registerCommandHandler(new ExportCommandHandler(topMostLayer)); + private TreeSet recurseChildIndexes(TermNodeDto node){ + TreeSet childIndexes = new TreeSet<>(); + if(node.getChildren().size() >0){ + List childNodes = node.getChildren(); + for (TermNodeDto childNode: childNodes) { + if (childNode != null){ + childIndexes.addAll(recurseChildIndexes(childNode)); + } + } + } + childIndexes.add(features.indexOf(node.getTerm())+LEADING_COLUMN_COUNT); + return childIndexes; + } - //propagate single cell selection - natTable.addLayerListener(new CellSelectionListener(part)); + private void buildHeader(List nodes, LinkedList columnGroups){ + Map> columnGroupToIndexMap = new HashMap<>(); + List childNodes = new ArrayList<>(); + for (TermNodeDto node : nodes) { + if (node != null){ + TreeSet childIndexes = recurseChildIndexes(node); + if(childIndexes.size()>1){ + // filter out groups that only have one member + columnGroupToIndexMap.put(node, childIndexes); + } + childNodes.addAll(node.getChildren()); + } + } + if(!columnGroupToIndexMap.isEmpty()){ + columnGroups.addFirst(new ColumnGroupWrapper(new ColumnGroupModel(), columnGroupToIndexMap)); + } + if(!childNodes.isEmpty()){ + buildHeader(childNodes, columnGroups); + } + } + + private void registerHandlersAndListeners() { + natTable.registerCommandHandler(new ExportCommandHandler(natTable)); + + //selection listener + E4SelectionListener selectionListener = new CellSelectionListener(part.getSelectionService(), + bodyLayer.getSelectionLayer(), bodyDataProvider, part); + bodyLayer.getSelectionLayer().addLayerListener(selectionListener); + selectionListener.setFullySelectedRowsOnly(false); //register handler for view configuration menu natTable.registerCommandHandler(toolbar.getDisplayPersistenceDialogCommandHandler()); + + //register handlers for copy&paste + natTable.registerCommandHandler( + new InternalPasteDataCommandHandler(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard())); + natTable.registerCommandHandler( + new InternalCopyDataCommandHandler(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard())); } private void configureNatTable(boolean treeView, ConfigRegistry configRegistry, - AbstractLayer topMostLayer, - FixedSummaryRowLayer summaryRowLayer) { + AbstractLayer topMostLayer) { /** * CONFIGURATION */ + //+++CONTEXT MENU+++ + menuService.registerContextMenu(natTable, "eu.etaxonomy.taxeditor.editor.popupmenu.charactermatrix"); //$NON-NLS-1$ + // get the menu registered by EMenuService + final Menu e4Menu = natTable.getMenu(); + natTable.setConfigRegistry(configRegistry); applyStyles(); @@ -449,8 +584,8 @@ public class CharacterMatrix extends Composite { //add default configuration because autoconfigure is set to false in constructor natTable.addConfiguration(new DefaultNatTableStyleConfiguration()); - //FIXME: this is for DEBUG ONLY - // natTable.addConfiguration(new DebugMenuConfiguration(natTable)); + // this is for DEBUG ONLY +// natTable.addConfiguration(new DebugMenuConfiguration(natTable)); // override the default sort configuration and change the mouse bindings // to sort on a single click @@ -462,60 +597,91 @@ public class CharacterMatrix extends Composite { // add the header menu configuration for adding the column header menu // with hide/show actions - natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable)); +// natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable)); // add custom configuration for data conversion and add column labels to viewport layer topMostLayer.addConfiguration(new CellEditorDataConversionConfiguration(this)); - //register aggregation configuration - summaryRowLayer.addConfiguration(new AggregationConfiguration(this)); + //copy&paste configuration + natTable.addConfiguration(new CopyPasteEditBindings(bodyLayer.getSelectionLayer(), natTable.getInternalCellClipboard())); + + + // remove the menu reference from NatTable instance + natTable.setMenu(null); + + +// natTable.addConfiguration(new CharacterMatrixHeaderMenuConfiguration(natTable)); + natTable.configure(); + + + natTable.addConfiguration( + new AbstractUiBindingConfiguration() { + @Override + public void configureUiBindings( + UiBindingRegistry uiBindingRegistry) { + // add e4 menu to NatTable + + + uiBindingRegistry.unregisterMouseDownBinding(mouseEventMatcher); + new PopupMenuBuilder(natTable, e4Menu) + .withHideColumnMenuItem() + .withShowAllColumnsMenuItem() + .build(); + + // register the UI binding for header, corner and body region + mouseEventMatcher = new MouseEventMatcher( + SWT.NONE, + null, + MouseEventMatcher.RIGHT_BUTTON); + uiBindingRegistry.registerMouseDownBinding(mouseEventMatcher, + new PopupMenuAction(e4Menu)); + } + }); + } void freezeSupplementalColumns(boolean freeze){ + int rightMostFreezeColumIndex = LEADING_COLUMN_COUNT-1; + Collection hiddenColumnIndexes = bodyLayer.getColumnHideShowLayer().getHiddenColumnIndexes(); + for (Integer integer : hiddenColumnIndexes) { + if(integer recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits(); - if(recommendedMeasurementUnits.size()>1){ - MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class, - String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel())); - } + Set recommendedMeasurementUnits = feature.getRecommendedMeasurementUnits(); +// if(recommendedMeasurementUnits.size()>1){ +// MessagingUtils.warningDialog(Messages.CharacterMatrix_INIT_PROBLEM, CharacterMatrix.class, +// String.format(Messages.CharacterMatrix_INIT_PROBLEM_MESSAGE, feature.getLabel())); +// } if(recommendedMeasurementUnits.size()==1){ - MeasurementUnit unit = recommendedMeasurementUnits.iterator().next(); - featureLabel += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$ + TermDto unit = recommendedMeasurementUnits.iterator().next(); + label += " ["+unit.getIdInVocabulary()+"]"; //$NON-NLS-1$ //$NON-NLS-2$ } } - propertyToLabelMap.put(property, featureLabel); + propertyToLabelMap.put(property, label); } - public void loadDescriptions(DescriptiveDataSet descriptiveDataSet) { - UUID monitorUuid = CdmStore.getService(IDescriptiveDataSetService.class).monitGetRowWrapper(descriptiveDataSet); + public void loadDescriptions(boolean isInitialExpandToDeepestTaxonLevel, boolean initialLoading) { + UUID monitorUuid = CdmApplicationState.getLongRunningTasksService().monitGetRowWrapper(this.getDescriptiveDataSet().getUuid()); final Collection wrappers = new ArrayList<>(); String jobLabel = Messages.CharacterMatrix_LOAD_CHARACTER_DATA; @@ -540,7 +706,10 @@ public class CharacterMatrix extends Composite { if(result instanceof Collection){ wrappers.addAll((Collection) result); } - if(wrappers.isEmpty()){ + if(result instanceof Exception){ + MessagingUtils.errorDialog("Exception during description loading", this.getClass(), "An exception occured during loading", TaxeditorEditorPlugin.PLUGIN_ID, (Throwable) result, true); + } + else if(wrappers.isEmpty()){ MessagingUtils.informationDialog(Messages.CharacterMatrix_NO_DESCRIPTION_TITLE, Messages.CharacterMatrix_NO_DESCRIPTION_MESSAGE); } @@ -553,7 +722,7 @@ public class CharacterMatrix extends Composite { List rowsWithoutTaxonNode = wrappers.stream().filter(row->row.getTaxonNode()==null).collect(Collectors.toList()); if(!rowsWithoutTaxonNode.isEmpty()){ String collect = rowsWithoutTaxonNode.stream(). - map(row->row.getSpecimenDescription().toString()) + map(row->row.getDescription().toString()) .collect(Collectors.joining("\n\n - ")); //$NON-NLS-1$ MessagingUtils.warningDialog( Messages.CharacterMatrix_NO_NODE_FOUND_TITLE, @@ -561,23 +730,48 @@ public class CharacterMatrix extends Composite { String.format(Messages.CharacterMatrix_NO_NODE_FOUND_MESSAGE, collect) ); } + descriptions.clear(); wrappers.stream().filter(row->row.getTaxonNode()!=null).forEach(wrapper->CharacterMatrix.this.descriptions.add(wrapper)); - loadingDone(); + if(initialLoading){ + loadingDone(isInitialExpandToDeepestTaxonLevel); + } + }); } }); job.schedule(); } - private void loadingDone() { + public IStructuredSelection getSelection(){ + Set selectedRowPositions = bodyLayer.getSelectionLayer().getSelectedRowPositions(); + List selectedObjects = new ArrayList<>(); + for (Range range : selectedRowPositions) { + for(int i=range.start;i getSupportedStatesForCategoricalFeature(UUID featureUuid){ + return categoricalFeatureToStateMap.get(featureUuid); + } + + public Set getDescriptionsToSave() { + return descriptionsToSave; } - public List getSupportedStatesForCategoricalFeature(Feature feature){ - return categoricalFeatureToStateMap.get(feature); + public void addDescriptionToSave(DescriptionBase descriptionToSave) { + this.descriptionsToSave.add(descriptionToSave); } - public Map getIndexToFeatureMap() { + public Map getIndexToFeatureMap() { return indexToFeatureMap; } @@ -589,6 +783,50 @@ public class CharacterMatrix extends Composite { part.setDirty(); } + public Map> getDescriptionsToDelete() { + return descriptionUuidsToDelete; + } + + public void addDescriptionToDelete(UUID descriptionToDelete, RemoveDescriptionsFromDescriptiveDataSetConfigurator config) { + if (descriptionUuidsToDelete == null){ + descriptionUuidsToDelete = new HashMap<>(); + } + if (descriptionUuidsToDelete.get(config) != null){ + descriptionUuidsToDelete.get(config).add(descriptionToDelete); + }else{ + List uuidList = new ArrayList<>(); + uuidList.add(descriptionToDelete); + this.descriptionUuidsToDelete.put(config, uuidList); + } + + + } + + public List getSpecimenToAdd() { + return specimenToAdd; + } + + public void addSpecimenToAdd(Collection specimenToAdd) { + if (this.specimenToAdd == null){ + this.specimenToAdd = new ArrayList(); + } + + this.specimenToAdd.addAll(specimenToAdd); + this.specimenCache.removeAll(specimenToAdd); + } + public void addSpecimenToAdd(SpecimenRowWrapperDTO specimenToAdd) { + if (this.specimenToAdd == null){ + this.specimenToAdd = new ArrayList(); + } + + this.specimenToAdd.add(specimenToAdd); + this.specimenCache.remove(specimenToAdd); + } + + public CharacterMatrixPart getPart() { + return part; + } + public NatTable getNatTable() { return natTable; } @@ -597,12 +835,16 @@ public class CharacterMatrix extends Composite { return descriptions; } - public DescriptiveDataSet getDescriptiveDataSet() { - return descriptiveDataSet; + public DescriptiveDataSetBaseDto getDescriptiveDataSet() { + return part.getDescriptiveDataSet(); + } + + public void setDescriptiveDataSet(DescriptiveDataSetBaseDto dataSet) { + part.setDescriptiveDataSet(dataSet); } public Collection getSpecimenCache() { - return specimenCache; + return specimenCache; } public void setSpecimenCache(Collection specimenCache) { @@ -610,14 +852,34 @@ public class CharacterMatrix extends Composite { .filter(wrapper -> //map descriptions on a list of uuids of the described specimen !this.descriptions.stream() - .map(o->((RowWrapperDTO)o).getSpecimen().getUuid()) + .filter(rowWrapper->rowWrapper instanceof SpecimenRowWrapperDTO) + .map(specimenRowWrapper->((SpecimenRowWrapperDTO) specimenRowWrapper).getSpecimenDto().getUuid()) .collect(Collectors.toList()) - //an check if the specimen to add is already contained + //and check if the specimen to add is already contained .contains(wrapper.getUuidAndTitleCache().getUuid()) ) .collect(Collectors.toList()); } +// public void addRowToSave(RowWrapperDTO row){ +// rowsToMerge.put(row.getDescription().getDescription().getUuid(), row); +// } +// +// public HashMap> getRowsToSave() { +// return rowsToMerge; +// } + + public HashMap> getRowsToMerge() { + return rowsToMerge; + } + + public void putRowToMerge(RowWrapperDTO rowToMerge) { + if (this.rowsToMerge == null){ + this.rowsToMerge = new HashMap<>(); + } + this.rowsToMerge.put(rowToMerge.getDescription().getDescriptionUuid(), rowToMerge); + } + public Properties getNatTableState() { return toolbar.getNatTableState(); } @@ -626,7 +888,7 @@ public class CharacterMatrix extends Composite { return bodyDataProvider; } - DefaultBodyLayerStack getBodyLayer() { + MatrixBodyLayerStack getBodyLayer() { return bodyLayer; } @@ -634,28 +896,132 @@ public class CharacterMatrix extends Composite { return new File(WorkbenchUtility.getBaseLocation(), CHARACTER_MATRIX_STATE_PROPERTIES); } - public List getFeatures() { + public List getFeatures() { return features; } - public void addUpdateResult(UpdateResult result){ - part.addUpdateResult(result); + public Map getFeatureToHistogramMap() { + return featureToHistogramMap; } - public Map getFeatureToHistogramMap() { - return featureToHistogramMap; + + + public ICdmEntitySession getCdmEntitiySession(){ + return part.getCdmEntitySession(); } - public Map getFeatureToQuantDataStatisticsMap() { - return featureToQuantDataStatisticsMap; + @Inject + @Optional + private void updateSpecimenList(@UIEventTopic(WorkbenchEventConstants.REFRESH_DESCRIPTIVE_DATASET)UUID uuid){ + if(uuid!= null && uuid.equals(part.getDescriptiveDataSet().getUuid())){ + this.specimenCache = null; + + } } - public void toogleIsShowTooltips() { - this.isShowTooltips = !this.isShowTooltips; + public void addRowsToMatrix(Collection wrappers){ + + List specimenUuids = new ArrayList(); + wrappers.forEach(wrapper -> specimenUuids.add(wrapper.getUuidAndTitleCache().getUuid())); +// List specimens = CdmStore.getService(IOccurrenceService.class).load(specimenUuids, null); + Map> specimenMap = new HashMap(); + + for (UUID specimenUuid: specimenUuids ){ + try{ + FieldUnitDTO fieldUnitDto = CdmStore.getService(IOccurrenceService.class).loadFieldUnitDTO(specimenUuid); + if (fieldUnitDto != null){ + UuidAndTitleCache fieldUnit = new UuidAndTitleCache(FieldUnit.class, fieldUnitDto.getUuid(), null, fieldUnitDto.getLabel()); + specimenMap.put(specimenUuid, fieldUnit); + } + }catch(Exception e){ + e.printStackTrace(); + } + + } + for (SpecimenNodeWrapper wrapper: wrappers){ + SpecimenRowWrapperDTO rowWrapper = CdmStore.getService(IDescriptiveDataSetService.class).createSpecimenRowWrapper(wrapper.getUuidAndTitleCache().getUuid(), wrapper.getTaxonNode().getUuid(), getDescriptiveDataSet().getUuid()); + addSpecimenToAdd(rowWrapper); +// SpecimenRowWrapperDTO rowWrapper = new SpecimenRowWrapperDTO(wrapper.getUuidAndTitleCache(), wrapper.getType(), wrapper.getTaxonNode(), specimenMap.get(wrapper.getUuidAndTitleCache().getUuid()), null, null); + descriptions.add(rowWrapper); + } + + + setDirty(); + } + + public HashMap addSpecimensToDescriptiveDataSet(){ + if (specimenToAdd == null || specimenToAdd.isEmpty()){ + return new HashMap<>(); + } + UpdateResult result = CdmStore.getService(IDescriptiveDataSetService.class).addRowWrapperToDataset(specimenToAdd, getDescriptiveDataSet().getUuid()); + if(!result.getExceptions().isEmpty()){ + MessagingUtils.warningDialog(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_TITLE, this, + String.format(Messages.CharacterMatrixBottomToolbar_ERROR_ROW_CREATION_MESSAGE, result.getExceptions() + .stream().map(ex->ex.toString()) + .collect(Collectors.joining("\n")))); + } + DescriptiveDataSet dataSet = (DescriptiveDataSet) result.getCdmEntity(); + HashMap resultMap = new HashMap<>(); + for (CdmBase updated: result.getUpdatedObjects()){ + if (updated instanceof SpecimenDescription){ + resultMap.put(updated.getUuid(), (DescriptionBase)updated); + } + + + } + dataSet = this.getCdmEntitiySession().load(dataSet, true); + // update local dataset + DescriptiveDataSetBaseDto dto = DescriptiveDataSetBaseDto.fromDescriptiveDataSet(dataSet); + this.setDescriptiveDataSet(dto); + + //these descriptions are already updated + for (SpecimenRowWrapperDTO row: specimenToAdd){ + this.rowsToMerge.remove(row.getDescription().getDescriptionUuid()); + } + specimenToAdd.clear(); + return resultMap; + } - public boolean isShowTooltips() { - return isShowTooltips; + @Inject + @Optional + private void updateView(@UIEventTopic(WorkbenchEventConstants.ADD_TO_MERGE_ROWS) UUID uuid) { + + } + + @Override + public void dispose () { + super.dispose(); + descriptions.dispose(); + descriptions = null; + this.natTable.dispose(); + this.natTable = null; + categoricalFeatureToStateMap = null; + rowsToMerge.clear(); + rowsToMerge = null; + indexToFeatureMap = null; + propertyToLabelMap = null; + specimenCache = null; + featureToHistogramMap = null; + bodyDataProvider = null; + freezeLayer = null; + features = null; + topMostLayer.dispose(); + topMostLayer = null; + configRegistry = null; + bodyLayer.dispose(); + bodyLayer = null; + toolbar.dispose(); + toolbar = null; + treeFormat = null; + descriptionUuidsToDelete = null; + specimenToAdd = null; + mouseEventMatcher = null; + descriptionsToSave = null; + menuService = null; + sync = null; + part = null; + } }