From: Reino Ruusu Date: Mon, 5 Oct 2020 12:52:51 +0000 (+0300) Subject: Add enable/disable feature for tech type tables X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=157a2a58f52d8243c365b4701cbef64d7c36c6c9;p=simantics%2Fdistrict.git Add enable/disable feature for tech type tables gitlab #101 Also: - Some new SCL entry points for DB requests - Some refactoring of tech type table data provider - Some refactoring of TechTypeTableData request Change-Id: I1b335b49f387e0b9b2651879279f79f40ed293f8 (cherry picked from commit 63b3e619ad5b28b382f97d5b607060365b6e1caa) --- diff --git a/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph b/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph index d63271af..16f315c2 100644 --- a/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph +++ b/org.simantics.district.network.ontology/graph/DistrictNetworkTechType.pgraph @@ -10,10 +10,15 @@ table = TT.TechTypeTable -- table.HasData L0.String + >-- table.HasEnabledItems L0.IntegerArray // Link to corresponding component type >-- table.HasComponentType STR.ComponentType + + @L0.assert table.HasEnabledItems + [] : L0.IntegerArray TT.Functions : L0.Library TT.Functions.techTypeCodeValueAccessor : L0.ExternalValue diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java index 5ebe6ed4..6ab176ed 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/adapters/TechTypeEditorAdapter.java @@ -64,11 +64,9 @@ public class TechTypeEditorAdapter extends AbstractResourceEditorAdapter impleme public void run(ReadGraph graph) throws DatabaseException { DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); Resource componentType = graph.getPossibleObject(table, DN.TechType_TechTypeTable_HasComponentType); - String data = graph.getPossibleRelatedValue2(table, DN.TechType_TechTypeTable_HasData); TechTypeTableView.table.getDisplay().asyncExec(() -> { TechTypeTableView.table.setComponentType(componentType); - TechTypeTableView.table.setTechTypeData(data); }); } }); diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/EnableListener.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/EnableListener.java new file mode 100644 index 00000000..71fceacc --- /dev/null +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/EnableListener.java @@ -0,0 +1,5 @@ +package org.simantics.district.network.ui.techtype.table; + +public interface EnableListener { + void rowEnabled(int rowIndex, boolean enabled); +} diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java index 8ee627a5..fa0a1acb 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java @@ -4,7 +4,11 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.eclipse.jface.layout.GridDataFactory; @@ -14,8 +18,11 @@ import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration; import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.config.IEditableRule; import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataCommandHandler; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; +import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor; import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer; import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer; import org.eclipse.nebula.widgets.nattable.grid.GridRegion; @@ -29,16 +36,16 @@ import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer; import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel; import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; -import org.eclipse.nebula.widgets.nattable.hover.HoverLayer; -import org.eclipse.nebula.widgets.nattable.hover.config.BodyHoverStylingBindings; import org.eclipse.nebula.widgets.nattable.layer.DataLayer; import org.eclipse.nebula.widgets.nattable.layer.ILayer; import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; import org.eclipse.nebula.widgets.nattable.layer.LabelStack; import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter; import org.eclipse.nebula.widgets.nattable.reorder.RowReorderLayer; 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.CellStyleAttributes; import org.eclipse.nebula.widgets.nattable.style.DisplayMode; import org.eclipse.nebula.widgets.nattable.style.Style; @@ -51,23 +58,35 @@ import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Text; import org.simantics.Simantics; +import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.db.procedure.Listener; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.simantics.district.network.techtype.requests.EnableTechTypeItem; import org.simantics.district.network.techtype.requests.PossibleTechTypeKeyName; import org.simantics.district.network.techtype.requests.PossibleTechTypeTable; import org.simantics.district.network.techtype.requests.WriteTechTypeTable; +import org.simantics.utils.datastructures.Triple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TechTypeTable extends Composite { - private static final String INVALID_LABEL = "INVALID"; + private static final CheckBoxCellEditor CHECK_BOX_CELL_EDITOR = new CheckBoxCellEditor(); + + private static final CheckBoxPainter CHECK_BOX_PAINTER = new CheckBoxPainter(); + + private static final String INVALID_LABEL = "INVALID"; + protected static final String CHECK_BOX_LABEL = "CHECK_BOX"; private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTable.class); + NatTable table; private TechTypeTableDataProvider bodyDataProvider; DataLayer bodyDataLayer; @@ -86,18 +105,19 @@ public class TechTypeTable extends Composite { private Resource componentType; private Resource tableResource; + private String keyName; protected Set validationResult; - public TechTypeTable(Composite parent, int style, Resource componentType, Resource tableResource, String data) { + public TechTypeTable(Composite parent, int style, Resource componentType) { super(parent, style); + this.tableResource = null; this.componentType = componentType; - this.tableResource = tableResource; - defaultInitializeUI(data); + defaultInitializeUI(); } - private void defaultInitializeUI(String data) { + private void defaultInitializeUI() { GridDataFactory.fillDefaults().grab(true, true).applyTo(this); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this); @@ -111,7 +131,7 @@ public class TechTypeTable extends Composite { GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(tableComposite); - createTable(tableComposite, data); + createTable(tableComposite); } private void createFilterBar(Composite filterComposite) { @@ -130,26 +150,76 @@ public class TechTypeTable extends Composite { } - private void createTable(Composite parent, String data) { + private void createTable(Composite parent) { + Triple> tableData; + String data = null; + int[] enabled = null; + if (componentType != null) { + try { + tableData = Simantics.getSession().syncRequest(new TableDataRequest(componentType), new TableDataListener(componentType)); + if (tableData != null) { + this.tableResource = tableData.first; + data = tableData.second; + enabled = tableData.third.stream().mapToInt(Integer::intValue).toArray(); + } else { + this.tableResource = null; + } + } catch (DatabaseException e) { + LOGGER.error("Failed to read tech type table data for {}", componentType, e); + } + } + // build the body layer stack // Usually you would create a new layer stack by extending // AbstractIndexLayerTransform and // setting the ViewportLayer as underlying layer. But in this case using the // ViewportLayer // directly as body layer is also working. - bodyDataProvider = new TechTypeTableDataProvider(data); + bodyDataProvider = new TechTypeTableDataProvider(data, enabled); bodyDataLayer = new DataLayer(bodyDataProvider); + bodyDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() { + @Override + public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { + if (columnPosition == 0) { + configLabels.addLabel(CHECK_BOX_LABEL); + } else if (validationResult != null && keyName != null) { + int keyColumn = bodyDataProvider.getVariableIndex(keyName); + if (keyColumn >= 0) { + String key = (String) bodyDataProvider.getDataValue(keyColumn, rowPosition); + String columnName = bodyDataProvider.getVariableName(columnPosition); + + if (validationResult.contains(key + "/" + columnName)) { + configLabels.addLabel(INVALID_LABEL); + } + } + } + } + }); + + bodyDataProvider.addEnableListener((rowIndex, enable) -> { + if (this.tableResource != null) { + try { + Simantics.getSession().syncRequest( + new EnableTechTypeItem(this.tableResource, rowIndex, enable) + ); + } catch (DatabaseException e) { + LOGGER.error("Failed to update enable state for {}", tableResource, e); + } + } + }); + RowReorderLayer rowReorderLayer = new RowReorderLayer( columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer)); - HoverLayer hoverLayer = new HoverLayer(rowReorderLayer, false); - // we need to ensure that the hover styling is removed when the mouse - // cursor moves out of the cell area - hoverLayer.addConfiguration(new BodyHoverStylingBindings(hoverLayer)); - - selectionLayer = new SelectionLayer(hoverLayer); +// HoverLayer hoverLayer = new HoverLayer(rowReorderLayer, false); +// // we need to ensure that the hover styling is removed when the mouse +// // cursor moves out of the cell area +// hoverLayer.addConfiguration(new BodyHoverStylingBindings(hoverLayer)); +// +// selectionLayer = new SelectionLayer(hoverLayer); + selectionLayer = new SelectionLayer(rowReorderLayer); viewportLayer = new ViewportLayer(selectionLayer); viewportLayer.setRegionName(GridRegion.BODY); @@ -188,18 +258,38 @@ public class TechTypeTable extends Composite { table = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, gridLayer, false); GridDataFactory.fillDefaults().grab(true, true).applyTo(table); + table.addConfiguration(new SingleClickSortConfiguration()); + // Show entries labeled "INVALID" with red text table.addConfiguration(new AbstractRegistryConfiguration() { @Override public void configureRegistry(IConfigRegistry configRegistry) { Style cellStyle = new Style(); cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED); + + configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE, + IEditableRule.ALWAYS_EDITABLE); + configRegistry.registerConfigAttribute( CellConfigAttributes.CELL_STYLE, cellStyle, DisplayMode.NORMAL, INVALID_LABEL ); + + configRegistry.registerConfigAttribute( + EditConfigAttributes.CELL_EDITOR, + CHECK_BOX_CELL_EDITOR, + DisplayMode.EDIT, + CHECK_BOX_LABEL + ); + + configRegistry.registerConfigAttribute( + CellConfigAttributes.CELL_PAINTER, + CHECK_BOX_PAINTER, + DisplayMode.NORMAL, + CHECK_BOX_LABEL + ); } }); @@ -239,6 +329,68 @@ public class TechTypeTable extends Composite { cpb.dispose(); super.dispose(); } + + public void setComponentType(Resource componentType) { + if (Objects.equals(this.componentType, componentType)) + return; + + this.componentType = componentType; + this.keyName = getKeyColumnName(componentType); + + Simantics.getSession().asyncRequest(new TableDataRequest(componentType), new TableDataListener(componentType)); + } + + private final class TableDataListener implements Listener>> { + private final Resource componentType; + + private TableDataListener(Resource componentType) { + this.componentType = componentType; + } + + @Override + public void execute(Triple> result) { + TechTypeTable.this.getDisplay().asyncExec(() -> { + TechTypeTable.this.tableResource = result.first; + String data = result.second; + int[] enabled = result.third.stream().mapToInt(Integer::intValue).toArray(); + + setTechTypeData(data, enabled); + setValidationResult(null); + }); + } + + @Override + public void exception(Throwable t) { + LOGGER.error("Error updating tech type table data for {}", componentType, t); + } + + @Override + public boolean isDisposed() { + return TechTypeTable.this.table != null && TechTypeTable.this.table.isDisposed() || + !Objects.equals(TechTypeTable.this.componentType, componentType); + } + } + + private final class TableDataRequest extends ResourceRead>> { + private TableDataRequest(Resource componentType) { + super(componentType); + } + + @Override + public Triple> perform(ReadGraph graph) throws DatabaseException { + Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); + if (model == null) + return null; + Resource tableResource = graph.syncRequest(new PossibleTechTypeTable(model, this.resource)); + if (tableResource == null) + return null; + + String data = graph.getRelatedValue2(tableResource, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasData); + int[] enabled = graph.getRelatedValue2(tableResource, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasEnabledItems); + List list = Arrays.stream(enabled).mapToObj(Integer::valueOf).collect(Collectors.toList()); + return Triple.make(tableResource, data, list); + } + } public void setTechTypePath(String path) { String data; @@ -254,29 +406,21 @@ public class TechTypeTable extends Composite { @Override public void perform(WriteGraph graph) throws DatabaseException { graph.syncRequest(new WriteTechTypeTable(componentType, data)); - Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); - if (model != null) { - tableResource = graph.syncRequest(new PossibleTechTypeTable(model, componentType)); - } } }); } catch (DatabaseException e) { LOGGER.error("Failed to write tech type table data to model", e); } - setTechTypeData(data); - setValidationResult(null); + setComponentType(componentType); } - public void setTechTypeData(String data) { + public void setTechTypeData(String data, int[] enabled) { bodyDataProvider.setData(data); + bodyDataProvider.setEnabledFlags(enabled); table.refresh(true); } - public void setComponentType(Resource componentType) { - this.componentType = componentType; - } - /** * Set results of a validation operation * @@ -292,26 +436,7 @@ public class TechTypeTable extends Composite { this.validationResult = result; if (result != null) { - String keyName = getKeyColumnName(componentType); - - bodyDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() { - @Override - public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { - if (keyName != null) { - int keyColumn = bodyDataProvider.getVariableIndex(keyName); - if (keyColumn >= 0) { - String key = (String) bodyDataProvider.getDataValue(keyColumn, rowPosition); - String columnName = bodyDataProvider.getVariableName(columnPosition); - - if (validationResult.contains(key + "/" + columnName)) { - configLabels.addLabel(INVALID_LABEL); - } - } - } - } - }); - } else { - bodyDataLayer.setConfigLabelAccumulator(null); + keyName = getKeyColumnName(componentType); } table.refresh(); diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java index d6b0c7d2..c23a6079 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableDataProvider.java @@ -7,9 +7,10 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.commons.csv.CSVRecord; +import org.eclipse.core.runtime.ListenerList; import org.eclipse.nebula.widgets.nattable.data.IDataProvider; import org.simantics.district.imports.DistrictImportUtils; import org.slf4j.Logger; @@ -17,43 +18,86 @@ import org.slf4j.LoggerFactory; public class TechTypeTableDataProvider implements IDataProvider { - @SuppressWarnings("unused") private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableDataProvider.class); private List records = new ArrayList<>(); - private List filteredRecords = new ArrayList<>(); - private String filter = ""; + private boolean[] enabled; + private String filter = null; private List variables = null; private List headers = null; + private int[] filteredRows; + private ListenerList enableListeners = new ListenerList(); + + public TechTypeTableDataProvider(String data, int[] enabledList) { + setData(data); + setEnabledFlags(enabledList); + } + public TechTypeTableDataProvider(String data) { // load csv setData(data); } + + public void setEnabledFlags(int[] enabledList) { + this.enabled = new boolean[records.size()]; + if (enabledList != null) { + for (int i : enabledList) { + if (i >= 0 && i < enabled.length) + enabled[i] = true; + } + } + } public String getVariableName(int columnIndex) { - return variables != null && columnIndex < variables.size() ? variables.get(columnIndex) : null; + return variables != null && columnIndex > 0 && columnIndex <= variables.size() ? variables.get(columnIndex - 1) : null; } public int getVariableIndex(String variableName) { - return variables != null ? variables.indexOf(variableName) : -1; + return variables != null ? variables.indexOf(variableName) + 1 : -1; + } + + public CSVRecord getRecord(int rowIndex) { + return records.get(filteredRows[rowIndex]); + } + + public boolean isEnabled(int rowIndex) { + return enabled[filteredRows[rowIndex]]; } public String getHeaderValue(int columnIndex) { if (headers == null) { return ""; + } else if (columnIndex == 0) { + return "Enabled"; + } else { + return headers.get(columnIndex - 1); } - return headers.get(columnIndex); } @Override public Object getDataValue(int columnIndex, int rowIndex) { - return filteredRecords.get(rowIndex).get(columnIndex); + if (columnIndex == 0) { + return isEnabled(rowIndex); + } + return getRecord(rowIndex).get(columnIndex - 1); } @Override public void setDataValue(int columnIndex, int rowIndex, Object newValue) { + if (columnIndex == 0) { + boolean value = Boolean.parseBoolean((String) newValue); + enabled[filteredRows[rowIndex]] = value; + fireEnableEvent(rowIndex, value); + } + } + + public void addEnableListener(EnableListener listener) { + enableListeners.add(listener); + } + private void fireEnableEvent(int rowIndex, boolean newValue) { + enableListeners.forEach(l -> l.rowEnabled(rowIndex, newValue)); } @Override @@ -61,30 +105,38 @@ public class TechTypeTableDataProvider implements IDataProvider { if (records.isEmpty()) { return 0; } - return records.get(0).size(); + return records.get(0).size() + 1; } @Override public int getRowCount() { - return filteredRecords.size(); + return filteredRows.length; } public boolean isEditable(int columnIndex, int rowIndex) { - return false; + return columnIndex == 0; } public void setFilter(String text) { - this.filter = text.toLowerCase(); + this.filter = text != null ? text.toLowerCase() : null; - filteredRecords = records.stream().filter(record -> { - for (int i = 0; i < record.size(); i++) { - String columnContent = record.get(i); - if (columnContent.toLowerCase().contains(filter)) { - return true; - } + filteredRows = IntStream.range(0, records.size()) + .filter(k -> isMatch(records.get(k), filter)) + .toArray(); + } + + private static boolean isMatch(CSVRecord record, String filterString) { + if (filterString == null || filterString.isEmpty()) + return true; + + for (int i = 0; i < record.size(); i++) { + String columnContent = record.get(i); + if (columnContent.toLowerCase().contains(filterString)) { + return true; } - return false; - }).collect(Collectors.toList()); + } + + return false; } /** @@ -96,7 +148,7 @@ public class TechTypeTableDataProvider implements IDataProvider { */ public void setPath(String path) { records.clear(); - filteredRecords.clear(); + if (path != null) { Path techTypeCsv = Paths.get(path); try { @@ -109,7 +161,7 @@ public class TechTypeTableDataProvider implements IDataProvider { } } - setFilter(""); + setFilter(null); } /** @@ -121,7 +173,7 @@ public class TechTypeTableDataProvider implements IDataProvider { */ public void setData(String data) { records.clear(); - filteredRecords.clear(); + if (data != null) { long ncommas = data.chars().filter(c -> c == ',').count(); long nsemis = data.chars().filter(c -> c == ';').count(); @@ -154,8 +206,10 @@ public class TechTypeTableDataProvider implements IDataProvider { headers.add(variable + (unit != null && !unit.isEmpty() && !(unit.startsWith("(") && unit.endsWith(")")) ? " [" + unit + "]" : "")); } } + + enabled = new boolean[records.size()]; - setFilter(""); + setFilter(null); } } \ No newline at end of file diff --git a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java index 8f4b57c9..d66c9a8c 100644 --- a/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java +++ b/org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTableView.java @@ -16,15 +16,10 @@ import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; import org.eclipse.e4.ui.model.application.ui.menu.MToolBarElement; import org.eclipse.e4.ui.workbench.modeling.ESelectionService; import org.eclipse.swt.widgets.Composite; -import org.simantics.Simantics; import org.simantics.db.Resource; import org.simantics.db.common.NamedResource; -import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.exception.DatabaseException; -import org.simantics.db.layer0.request.PossibleActiveModel; import org.simantics.district.network.DistrictNetworkUtil; -import org.simantics.district.network.techtype.requests.PossibleTechTypeTable; -import org.simantics.district.network.techtype.requests.PossibleTechTypeTableData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,21 +89,7 @@ public class TechTypeTableView { LOGGER.debug("Pipe component type is {}", pipe); - String data = null; - Resource tableResource = null; - if (pipe != null) { - try { - Resource model = Simantics.getSession().syncRequest(new PossibleActiveModel(Simantics.getProjectResource())); - if (model != null) { - tableResource = Simantics.getSession().syncRequest(new PossibleTechTypeTable(model, pipe), TransientCacheListener.instance()); - data = Simantics.getSession().syncRequest(new PossibleTechTypeTableData(model, pipe), TransientCacheListener.instance()); - } - } catch (DatabaseException e) { - LOGGER.error("Failed to read tech type table data for {}", pipe, e); - } - } - - table = new TechTypeTable(parent, 0, pipe, tableResource, data); + table = new TechTypeTable(parent, 0, pipe); } @PreDestroy diff --git a/org.simantics.district.network/scl/Simantics/District/TechType.scl b/org.simantics.district.network/scl/Simantics/District/TechType.scl index e8ab4eec..33d40360 100644 --- a/org.simantics.district.network/scl/Simantics/District/TechType.scl +++ b/org.simantics.district.network/scl/Simantics/District/TechType.scl @@ -1,4 +1,5 @@ import "Simantics/DB" +import "Map" as Map importJava "org.simantics.district.network.techtype.TechTypeUtils" where "Update component properties from tech type table: `updateComponent component`" @@ -9,3 +10,14 @@ importJava "org.simantics.district.network.techtype.TechTypeUtils" where resetMapElements :: Resource -> () "Load a tech type table for a given component type from a CSV file: `loadTechTypeTable componentType filePath`" loadTechTypeTable :: Resource -> String -> () + + "Get a possible tech type key property name for a component type" + getPossibleTechTypeKeyName :: Resource -> Maybe String + "Get a possible tech type table in a model for a component type: `getPossibleTechTypeTable model componentType`" + getPossibleTechTypeTable :: Resource -> Resource -> Maybe Resource + "Get data from a tech type table" + getTechTypeData :: Resource -> Map.T String (Map.T String String) + "Get data for enabled items in a tech type table" + getEnabledTechTypeData :: Resource -> Map.T String (Map.T String String) + "Get a possible row from a tech type table for the given key value" + getPossibleTechTypeItem :: Resource -> String -> Maybe (Map.T String String) diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java index 377253dd..930435ba 100644 --- a/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/TechTypeUtils.java @@ -32,6 +32,7 @@ import org.simantics.db.layer0.request.PropertyInfoRequest; import org.simantics.db.layer0.variable.Variable; import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.district.network.techtype.requests.PossibleTechTypeItem; +import org.simantics.district.network.techtype.requests.PossibleTechTypeKeyName; import org.simantics.district.network.techtype.requests.PossibleTechTypeTable; import org.simantics.district.network.techtype.requests.TechTypeTableData; import org.simantics.district.network.techtype.requests.TechTypeTableKeyName; @@ -341,11 +342,86 @@ public class TechTypeUtils { } } + /** + * Find the tech type table key property name for a table. + * + * @param graph A read interface to the graph db + * @param table A tech type table resource + * @return Name of the key property + * @throws DatabaseException + */ public static String getKeyPropertyName(ReadGraph graph, Resource table) throws DatabaseException { String name = graph.syncRequest(new TechTypeTableKeyName(table), TransientCacheListener.instance()); if (name == null) name = "_" + DEFAULT_KEY_NAME; return name; } + + /** + * Get a possible tech type table key property for a component type + * + * @param graph A read interface to the graph db + * @param componentType An STR.ComponentType instance + * @return The name of a property that uses DN.TechType.Functions.techTypeCodeValueAccessor or null if not found + * @throws DatabaseException + */ + public static String getPossibleTechTypeKeyName(ReadGraph graph, Resource componentType) throws DatabaseException { + return graph.syncRequest(new PossibleTechTypeKeyName(componentType), TransientCacheListener.instance()); + } + + /** + * Get a possible tech type table from a given model, for a given component type. + * + * @param graph A read interface to the graph db + * @param model A model resource (or other index root) + * @param componentType An STR.ComponentType instance + * @return A tech type table resource or null if not found + * @throws DatabaseException + */ + public static Resource getPossibleTechTypeTable(ReadGraph graph, Resource model, Resource componentType) throws DatabaseException { + return graph.syncRequest(new PossibleTechTypeTable(model, componentType), TransientCacheListener.instance()); + } + + /** + * Get the contents of a tech type table, indexed by item code and property name. + * + * @param graph A read interface to the graph db + * @param table A tech type table resource + * @return A map from item code to a map of property values + * @throws DatabaseException + */ + public static Map> getTechTypeData(ReadGraph graph, Resource table) throws DatabaseException { + return graph.syncRequest(new TechTypeTableData(table), TransientCacheListener.instance()); + } + + /** + * Get the enabled contents of a tech type table, indexed by item code and property name. + * + * The returned map is a subset of the one returned by {@link #getTechTypeData(ReadGraph, Resource)} + * with only the items whose index is listed in the HasEnabledItems property. + * + * @param graph A read interface to the graph db + * @param table A tech type table resource + * @return A map from item code to a map of property values + * @throws DatabaseException + */ + public static Map> getEnabledTechTypeData(ReadGraph graph, Resource table) throws DatabaseException { + return graph.syncRequest(new TechTypeTableData(table, true), TransientCacheListener.instance()); + } + + /** + * Get a single tech type table item that has the given item code, or null, if not found. + * + * The returned map is a subset of the one returned by {@link #getTechTypeData(ReadGraph, Resource)} + * with only the items whose index is listed in the HasEnabledItems property. + * + * @param graph A read interface to the graph db + * @param table A tech type table resource + * @return A map of property values or null if not found + * @throws DatabaseException + */ + public static Map getPossibleTechTypeItem(ReadGraph graph, Resource table, String itemCode) throws DatabaseException { + return graph.syncRequest(new PossibleTechTypeItem(table, itemCode), TransientCacheListener.instance()); + } /** * Compare strings so that contained numbers are sorted in numerical order instead of lexicographic. diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/EnableTechTypeItem.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/EnableTechTypeItem.java new file mode 100644 index 00000000..c202325e --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/EnableTechTypeItem.java @@ -0,0 +1,64 @@ +package org.simantics.district.network.techtype.requests; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.Datatypes; +import org.simantics.databoard.binding.Binding; +import org.simantics.databoard.binding.impl.ArrayListBinding; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.CommonDBUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.util.Layer0Utils; +import org.simantics.db.service.UndoRedoSupport; +import org.simantics.district.network.ontology.DistrictNetworkResource; + +/** + * Change the enabled/disabled state of a single tech type table record + */ +public class EnableTechTypeItem extends WriteRequest { + + Resource table; + int itemIndex; + boolean enable; + + public EnableTechTypeItem(Resource table, int itemIndex, boolean enable) { + super(); + this.table = table; + this.itemIndex = itemIndex; + this.enable = enable; + } + + @Override + public void perform(WriteGraph graph) throws DatabaseException { + DistrictNetworkResource DN = DistrictNetworkResource.getInstance(graph); + + Binding binding = new ArrayListBinding(Datatypes.INTEGER_ARRAY, Bindings.INTEGER); + List enabled = graph.getPossibleRelatedValue2(table, DN.TechType_TechTypeTable_HasEnabledItems, binding); + if (enabled == null) + enabled = Collections.emptyList(); + + if (enable) { + if (!enabled.contains(itemIndex)) { + enabled = new ArrayList<>(enabled); + enabled.add((Integer) itemIndex); + graph.getSession().markUndoPoint(); + graph.claimLiteral(table, DN.TechType_TechTypeTable_HasEnabledItems, enabled, binding); + Layer0Utils.addCommentMetadata(graph, "Enable tech type table item " + (itemIndex + 1)); + } + } else { + if (enabled.contains(itemIndex)) { + enabled = new ArrayList<>(enabled); + enabled.remove((Integer) itemIndex); // note - without the cast, itemIndex is list index, not element value + graph.getSession().markUndoPoint(); + graph.claimLiteral(table, DN.TechType_TechTypeTable_HasEnabledItems, enabled, binding); + Layer0Utils.addCommentMetadata(graph, "Disable tech type table item " + (itemIndex + 1)); + } + } + } + +} diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java index cbedd1ac..60929edd 100644 --- a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableData.java @@ -1,97 +1,75 @@ package org.simantics.district.network.techtype.requests; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVParser; -import org.apache.commons.csv.CSVRecord; +import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; -import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.common.procedure.adapter.TransientCacheListener; +import org.simantics.db.common.request.BinaryRead; import org.simantics.db.exception.DatabaseException; import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.district.network.techtype.TechTypeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class TechTypeTableData extends ResourceRead>> { +/** + * Get a map of tech type table records, indexed by the key column. + * + * The query result value is list of records in the form of a map from property name + * to string value. + * + * The second constructor argument allows selection of enabled records only. + * + * @author Reino Ruusu + */ +public class TechTypeTableData extends BinaryRead>> { - private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableData.class); + final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableData.class); public TechTypeTableData(Resource table) { - super(table); + super(table, Boolean.FALSE); + } + + public TechTypeTableData(Resource table, boolean enabledOnly) { + super(table, enabledOnly); } @Override public Map> perform(ReadGraph graph) throws DatabaseException { - Resource table = this.resource; + Resource table = this.parameter; + boolean enabledOnly = this.parameter2; - String data = graph.getRelatedValue2(table, - DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasData); - if (data == null) - return Collections.emptyMap(); + String keyName = getKeyName(graph, table); - String keyName = TechTypeUtils.getKeyPropertyName(graph, table); - if (keyName.startsWith("_")) - keyName = keyName.substring(1); - - Map> map = new HashMap<>(); - - long ncommas = data.chars().filter(c -> c == ',').count(); - long nsemis = data.chars().filter(c -> c == ';').count(); - char delim = nsemis > ncommas ? ';' : ','; - StringReader reader = new StringReader(data); - - List records = new ArrayList<>(); - try { - CSVFormat format = CSVFormat.newFormat(delim).withQuote('"'); - try (CSVParser parser = format.parse(reader)) { - Iterator it = parser.iterator(); - while (it.hasNext()) { - records.add(it.next()); - } - } - } catch (IOException e) { - LOGGER.error("Error reading CSV data", e); + List> records = graph.syncRequest(new TechTypeTableRecords(table), TransientCacheListener.instance()); + if (records == null || records.size() < 2) return Collections.emptyMap(); - } - - CSVRecord header = records.remove(0); - records.remove(0); // Eliminate units row - int keyIndex = 0; - for (int i = 0; i < header.size(); i++) { - if (header.get(i).equals(keyName)) { - keyIndex = i; - break; - } - } - - for (CSVRecord r : records) { - String key = r.get(keyIndex).trim().intern(); - if (key == null) - continue; - - Map valueMap = new HashMap<>(); - map.put(key, valueMap); - - Iterator h = header.iterator(); - Iterator v = r.iterator(); - - while (h.hasNext() && v.hasNext()) { - String name = h.next().trim().intern(); - String value = v.next().trim(); - valueMap.put(name, value); - } + Stream> enabled; + if (enabledOnly) { + int[] enabledKeys = graph.getRelatedValue2(table, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasEnabledItems, Bindings.INT_ARRAY); + enabled = Arrays.stream(enabledKeys) + .filter(i -> i >= 0 && i < records.size()) + .mapToObj(i -> records.get(i)); + } else { + enabled = records.stream(); } - return map; + return enabled + .filter(r -> r.containsKey(keyName)) + .collect(Collectors.toMap(r -> r.get(keyName), r -> r)); + } + + private String getKeyName(ReadGraph graph, Resource table) throws DatabaseException { + String keyName = TechTypeUtils.getKeyPropertyName(graph, table); + if (keyName.startsWith("_")) + keyName = keyName.substring(1); + return keyName; } -} \ No newline at end of file +} diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableRecords.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableRecords.java new file mode 100644 index 00000000..81775b72 --- /dev/null +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/TechTypeTableRecords.java @@ -0,0 +1,86 @@ +package org.simantics.district.network.techtype.requests; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.district.network.ontology.DistrictNetworkResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Get a list of the tech type table records in the order of appearance. + * + * The query result value is list of records in the form of a map from property name + * to string value. + * + * @author Reino Ruusu + */ +public class TechTypeTableRecords extends ResourceRead>> { + + final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTableRecords.class); + + public TechTypeTableRecords(Resource resource) { + super(resource); + } + + @Override + public List> perform(ReadGraph graph) throws DatabaseException { + Resource table = this.resource; + + String data = graph.getRelatedValue2(table, + DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasData); + if (data == null) + return null; + + long ncommas = data.chars().filter(c -> c == ',').count(); + long nsemis = data.chars().filter(c -> c == ';').count(); + char delim = nsemis > ncommas ? ';' : ','; + StringReader reader = new StringReader(data); + + List records = new ArrayList<>(); + try { + CSVFormat format = CSVFormat.newFormat(delim).withQuote('"'); + try (CSVParser parser = format.parse(reader)) { + Iterator it = parser.iterator(); + while (it.hasNext()) { + records.add(it.next()); + } + } + } catch (IOException e) { + LOGGER.error("Error reading CSV data", e); + throw new DatabaseException("Invalid CSV data in table " + table, e); + } + + CSVRecord header = records.remove(0); + records.remove(0); // Eliminate units row + + List> result = new ArrayList<>(); + for (CSVRecord r : records) { + Map valueMap = new HashMap<>(); + result.add(valueMap); + + Iterator h = header.iterator(); + Iterator v = r.iterator(); + + while (h.hasNext() && v.hasNext()) { + String name = h.next().trim().intern(); + String value = v.next().trim(); + valueMap.put(name, value); + } + } + + return result; + } +} diff --git a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java index 1f85c5a4..500c83e3 100644 --- a/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java +++ b/org.simantics.district.network/src/org/simantics/district/network/techtype/requests/WriteTechTypeTable.java @@ -9,6 +9,7 @@ import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.request.PossibleActiveModel; +import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.district.network.ontology.DistrictNetworkResource; import org.simantics.layer0.Layer0; @@ -32,12 +33,18 @@ public final class WriteTechTypeTable extends WriteRequest { Resource ttt = graph.syncRequest(new PossibleTechTypeTable(model, componentType), TransientCacheListener.instance()); + graph.getSession().markUndoPoint(); + if (ttt == null) { ttt = graph.newResource(); graph.claim(ttt, L0.InstanceOf, DN.TechType_TechTypeTable); graph.claimLiteral(ttt, L0.HasName, UUID.randomUUID().toString()); graph.claim(model, L0.ConsistsOf, ttt); graph.claim(ttt, DN.TechType_TechTypeTable_HasComponentType, componentType); + Layer0Utils.addCommentMetadata(graph, "Create tech type table"); + } else { + graph.deny(ttt, DN.TechType_TechTypeTable_HasEnabledItems); + Layer0Utils.addCommentMetadata(graph, "Update tech type table"); } graph.claimLiteral(ttt, DN.TechType_TechTypeTable_HasData, data);