X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=org.simantics.district.network.ui%2Fsrc%2Forg%2Fsimantics%2Fdistrict%2Fnetwork%2Fui%2Ftechtype%2Ftable%2FTechTypeTable.java;h=7438269aba7071990b2b052cc21c539725d48d3d;hb=669fec715190918badd9d4f90575a6b703aa6b9f;hp=eb83c902ddf535c9bcd8ae236de40913d33bf4a6;hpb=ae7fa2cc4cfe235b2a392d6fb7e3038042ac4221;p=simantics%2Fdistrict.git 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 eb83c902..7438269a 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,15 +4,24 @@ 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.stream.Collectors; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.nebula.widgets.nattable.NatTable; +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.EditableRule; import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; 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; @@ -26,14 +35,20 @@ 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; +import org.eclipse.nebula.widgets.nattable.util.GUIHelper; import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; @@ -42,16 +57,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.district.network.ui.techtype.requests.WriteTechTypeTableRequest; +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 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; @@ -69,15 +103,20 @@ public class TechTypeTable extends Composite { private TechTypeTableSortModel sortModel; private Resource componentType; + private Resource tableResource; + private String keyName; + + protected Set validationResult; - public TechTypeTable(Composite parent, int style, Resource componentType, String data) { + public TechTypeTable(Composite parent, int style, Resource componentType) { super(parent, style); + this.tableResource = null; this.componentType = componentType; - defaultInitializeUI(data); + defaultInitializeUI(); } - private void defaultInitializeUI(String data) { + private void defaultInitializeUI() { GridDataFactory.fillDefaults().grab(true, true).applyTo(this); GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this); @@ -91,7 +130,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) { @@ -110,26 +149,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); @@ -167,6 +256,46 @@ 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, + new EditableRule() { + @Override + public boolean isEditable(int columnIndex, int rowIndex) { + return columnIndex == 0; + } + }); + + 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 + ); + } + }); // Register a CopyDataCommandHandler that also copies the headers and // uses the configured IDisplayConverters @@ -187,29 +316,150 @@ public class TechTypeTable extends Composite { table.configure(); } + private static String getKeyColumnName(Resource componentType) { + String keyName = null; + if (componentType != null) { + try { + keyName = Simantics.getSession().syncRequest(new PossibleTechTypeKeyName(componentType)); + } catch (DatabaseException e) { + LOGGER.error("Failed to read possible tech type key name for {}", componentType, e); + } + } + return keyName != null && keyName.startsWith("_") ? keyName.substring(1) : keyName; + } + @Override public void dispose() { cpb.dispose(); super.dispose(); } + + public Resource getComponentType() { + return this.componentType; + } + + public void setComponentType(Resource componentType) { + if (Objects.equals(this.componentType, componentType)) + return; + + this.componentType = componentType; + this.keyName = getKeyColumnName(componentType); + + if (componentType != null) { + 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(() -> { + if (isDisposed() || result == null) + return; + + 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; try { - data = Files.lines(Paths.get(path)).collect(Collectors.joining("\n")); + data = Files.lines(Paths.get(path), Charset.defaultCharset()).collect(Collectors.joining("\n")); } catch (IOException e) { LOGGER.error("Failed to read contents of file '{}' as {}", path, Charset.defaultCharset(), e); return; } try { - Simantics.getSession().syncRequest(new WriteTechTypeTableRequest(componentType, data)); + Simantics.getSession().syncRequest(new WriteRequest() { + @Override + public void perform(WriteGraph graph) throws DatabaseException { + graph.syncRequest(new WriteTechTypeTable(componentType, data)); + } + }); } catch (DatabaseException e) { LOGGER.error("Failed to write tech type table data to model", e); } + setComponentType(componentType); + } + + public void setTechTypeData(String data, int[] enabled) { bodyDataProvider.setData(data); + bodyDataProvider.setEnabledFlags(enabled); table.refresh(true); } + /** + * Set results of a validation operation + * + * Invalid entries are designated by a string of the form "/". + * + * This method must be called in the SWT thread. + * + * @param result A set of strings representing invalid entries + */ + public void setValidationResult(Set result) { + if (result != null && result.isEmpty()) + result = null; + + this.validationResult = result; + if (result != null) { + keyName = getKeyColumnName(componentType); + } + + table.refresh(); + } + + /** + * Get a resource representation of the currently open table, or null if + * table is not stored in the model. + */ + public Resource getCurrentTable() { + return tableResource; + } }