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;
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.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;
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;
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.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;
private TechTypeTableSortModel sortModel;
private Resource componentType;
+ private Resource tableResource;
+ private String keyName;
+
+ protected Set<String> 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);
GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(tableComposite);
- createTable(tableComposite, data);
+ createTable(tableComposite);
}
private void createFilterBar(Composite filterComposite) {
}
- private void createTable(Composite parent, String data) {
-
+ private void createTable(Composite parent) {
+
+ Triple<Resource, String, List<Integer>> 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);
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
+ );
+ }
+ });
// Register a CopyDataCommandHandler that also copies the headers and
// uses the configured IDisplayConverters
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.startsWith("_") ? keyName.substring(1) : keyName;
+ }
+
@Override
public void dispose() {
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<Triple<Resource, String, List<Integer>>> {
+ private final Resource componentType;
+
+ private TableDataListener(Resource componentType) {
+ this.componentType = componentType;
+ }
+
+ @Override
+ public void execute(Triple<Resource, String, List<Integer>> 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<Triple<Resource, String, List<Integer>>> {
+ private TableDataRequest(Resource componentType) {
+ super(componentType);
+ }
+
+ @Override
+ public Triple<Resource, String, List<Integer>> 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<Integer> list = Arrays.stream(enabled).mapToObj(Integer::valueOf).collect(Collectors.toList());
+ return Triple.make(tableResource, data, list);
+ }
+ }
public void setTechTypePath(String path) {
String data;
}
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);
}
- setTechTypeData(data);
+ 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
+ *
+ * Invalid entries are designated by a string of the form "<type_code>/<property_name>".
+ *
+ * This method must be called in the SWT thread.
+ *
+ * @param result A set of strings representing invalid entries
+ */
+ public void setValidationResult(Set<String> 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;
+ }
}
-