]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network.ui/src/org/simantics/district/network/ui/techtype/table/TechTypeTable.java
a8db44c789200fb8015596916ade28ed8283e9fa
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / techtype / table / TechTypeTable.java
1 package org.simantics.district.network.ui.techtype.table;
2
3 import java.io.IOException;
4 import java.nio.charset.Charset;
5 import java.nio.file.Files;
6 import java.nio.file.Paths;
7 import java.util.Arrays;
8 import java.util.List;
9 import java.util.Objects;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12
13 import org.eclipse.jface.layout.GridDataFactory;
14 import org.eclipse.jface.layout.GridLayoutFactory;
15 import org.eclipse.nebula.widgets.nattable.NatTable;
16 import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
17 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
18 import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
19 import org.eclipse.nebula.widgets.nattable.config.EditableRule;
20 import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
21 import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataCommandHandler;
22 import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
23 import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
24 import org.eclipse.nebula.widgets.nattable.edit.editor.CheckBoxCellEditor;
25 import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;
26 import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;
27 import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
28 import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
29 import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
30 import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
31 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
32 import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
33 import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
34 import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
35 import org.eclipse.nebula.widgets.nattable.group.ColumnGroupHeaderLayer;
36 import org.eclipse.nebula.widgets.nattable.group.ColumnGroupModel;
37 import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
38 import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
39 import org.eclipse.nebula.widgets.nattable.layer.ILayer;
40 import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
41 import org.eclipse.nebula.widgets.nattable.layer.LabelStack;
42 import org.eclipse.nebula.widgets.nattable.layer.cell.IConfigLabelAccumulator;
43 import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;
44 import org.eclipse.nebula.widgets.nattable.reorder.RowReorderLayer;
45 import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
46 import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
47 import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
48 import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
49 import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
50 import org.eclipse.nebula.widgets.nattable.style.Style;
51 import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
52 import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
53 import org.eclipse.swt.SWT;
54 import org.eclipse.swt.dnd.Clipboard;
55 import org.eclipse.swt.events.ModifyEvent;
56 import org.eclipse.swt.events.ModifyListener;
57 import org.eclipse.swt.widgets.Composite;
58 import org.eclipse.swt.widgets.Text;
59 import org.simantics.Simantics;
60 import org.simantics.db.ReadGraph;
61 import org.simantics.db.Resource;
62 import org.simantics.db.WriteGraph;
63 import org.simantics.db.common.request.ResourceRead;
64 import org.simantics.db.common.request.WriteRequest;
65 import org.simantics.db.exception.DatabaseException;
66 import org.simantics.db.layer0.request.PossibleActiveModel;
67 import org.simantics.db.procedure.Listener;
68 import org.simantics.district.network.ontology.DistrictNetworkResource;
69 import org.simantics.district.network.techtype.requests.EnableTechTypeItem;
70 import org.simantics.district.network.techtype.requests.PossibleTechTypeKeyName;
71 import org.simantics.district.network.techtype.requests.PossibleTechTypeTable;
72 import org.simantics.district.network.techtype.requests.WriteTechTypeTable;
73 import org.simantics.utils.datastructures.Triple;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 public class TechTypeTable extends Composite {
78
79         private static final CheckBoxCellEditor CHECK_BOX_CELL_EDITOR = new CheckBoxCellEditor();
80
81         private static final CheckBoxPainter CHECK_BOX_PAINTER = new CheckBoxPainter();
82
83     private static final String INVALID_LABEL = "INVALID";
84     protected static final String CHECK_BOX_LABEL = "CHECK_BOX";
85
86         private final static Logger LOGGER = LoggerFactory.getLogger(TechTypeTable.class);
87
88
89         NatTable table;
90         private TechTypeTableDataProvider bodyDataProvider;
91         DataLayer bodyDataLayer;
92         private IConfigRegistry summaryConfigRegistry;
93         private IUniqueIndexLayer summaryRowLayer;
94         private ViewportLayer viewportLayer;
95         private CompositeFreezeLayer compositeFreezeLayer;
96         private FreezeLayer freezeLayer;
97         // private TableDataSortModel sortModel;
98         private ColumnHideShowLayer columnHideShowLayer;
99         private ColumnGroupModel columnGroupModel = new ColumnGroupModel();
100         private TechTypeColumnHeaderTableDataProvider columnHeaderDataProvider;
101         Clipboard cpb;
102         public SelectionLayer selectionLayer;
103         private TechTypeTableSortModel sortModel;
104
105         private Resource componentType;
106         private Resource tableResource;
107         private String keyName;
108
109         protected Set<String> validationResult;
110
111         public TechTypeTable(Composite parent, int style, Resource componentType) {
112                 super(parent, style);
113                 this.tableResource = null;
114                 this.componentType = componentType;
115
116                 defaultInitializeUI();
117         }
118
119         private void defaultInitializeUI() {
120                 GridDataFactory.fillDefaults().grab(true, true).applyTo(this);
121                 GridLayoutFactory.fillDefaults().numColumns(1).applyTo(this);
122
123                 Composite filterComposite = new Composite(this, SWT.NONE);
124                 GridDataFactory.fillDefaults().grab(true, false).applyTo(filterComposite);
125                 GridLayoutFactory.fillDefaults().numColumns(1).applyTo(filterComposite);
126
127                 createFilterBar(filterComposite);
128
129                 Composite tableComposite = new Composite(this, SWT.NONE);
130                 GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
131                 GridLayoutFactory.fillDefaults().numColumns(1).applyTo(tableComposite);
132
133                 createTable(tableComposite);
134         }
135
136         private void createFilterBar(Composite filterComposite) {
137
138                 Text filterText = new Text(filterComposite, SWT.BORDER);
139                 GridDataFactory.fillDefaults().grab(true, true).applyTo(filterText);
140                 filterText.addModifyListener(new ModifyListener() {
141
142                         @Override
143                         public void modifyText(ModifyEvent e) {
144                                 System.out.println("text modified");
145                                 bodyDataProvider.setFilter(filterText.getText());
146                                 table.refresh(true);
147                         }
148                 });
149
150         }
151
152         private void createTable(Composite parent) {
153
154                 Triple<Resource, String, List<Integer>> tableData;
155                 String data = null;
156                 int[] enabled = null;
157                 if (componentType != null) {
158                         try {
159                                 tableData = Simantics.getSession().syncRequest(new TableDataRequest(componentType), new TableDataListener(componentType));
160                                 if (tableData != null) {
161                                         this.tableResource = tableData.first;
162                                         data = tableData.second;
163                                         enabled = tableData.third.stream().mapToInt(Integer::intValue).toArray();
164                                 } else {
165                                         this.tableResource = null;
166                                 }
167                         } catch (DatabaseException e) {
168                                 LOGGER.error("Failed to read tech type table data for {}", componentType, e);
169                         }
170                 }
171                 
172                 // build the body layer stack
173                 // Usually you would create a new layer stack by extending
174                 // AbstractIndexLayerTransform and
175                 // setting the ViewportLayer as underlying layer. But in this case using the
176                 // ViewportLayer
177                 // directly as body layer is also working.
178                 bodyDataProvider = new TechTypeTableDataProvider(data, enabled);
179                 bodyDataLayer = new DataLayer(bodyDataProvider);
180                 
181                 bodyDataLayer.setConfigLabelAccumulator(new IConfigLabelAccumulator() {
182                         @Override
183                         public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {
184                                 if (columnPosition == 0) {
185                                         configLabels.addLabel(CHECK_BOX_LABEL);
186                                 } else if (validationResult != null && keyName != null) {
187                                         int keyColumn = bodyDataProvider.getVariableIndex(keyName);
188                                         if (keyColumn >= 0) {
189                                                 String key = (String) bodyDataProvider.getDataValue(keyColumn, rowPosition);
190                                                 String columnName = bodyDataProvider.getVariableName(columnPosition);
191                                                 
192                                                 if (validationResult.contains(key + "/" + columnName)) {
193                                                         configLabels.addLabel(INVALID_LABEL);
194                                                 }
195                                         }
196                                 }
197                         }
198                 });
199                 
200                 bodyDataProvider.addEnableListener((rowIndex, enable) -> {
201                         if (this.tableResource != null) {
202                                 try {
203                                         Simantics.getSession().syncRequest(
204                                                         new EnableTechTypeItem(this.tableResource, rowIndex, enable)
205                                                 );
206                                 } catch (DatabaseException e) {
207                                         LOGGER.error("Failed to update enable state for {}", tableResource, e);
208                                 }
209                         }
210                 });
211                 
212                 RowReorderLayer rowReorderLayer = new RowReorderLayer(
213                                 columnHideShowLayer = new ColumnHideShowLayer(bodyDataLayer));
214
215 //              HoverLayer hoverLayer = new HoverLayer(rowReorderLayer, false);
216 //              // we need to ensure that the hover styling is removed when the mouse
217 //              // cursor moves out of the cell area
218 //              hoverLayer.addConfiguration(new BodyHoverStylingBindings(hoverLayer));
219 //
220 //              selectionLayer = new SelectionLayer(hoverLayer);
221                 selectionLayer = new SelectionLayer(rowReorderLayer);
222
223                 viewportLayer = new ViewportLayer(selectionLayer);
224                 viewportLayer.setRegionName(GridRegion.BODY);
225                 freezeLayer = new FreezeLayer(selectionLayer);
226                 compositeFreezeLayer = new CompositeFreezeLayer(freezeLayer, viewportLayer, selectionLayer);
227
228                 // build the column header layer
229                 columnHeaderDataProvider = new TechTypeColumnHeaderTableDataProvider(bodyDataProvider);
230                 DataLayer columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
231                 columnHeaderDataLayer.setRowsResizableByDefault(false);
232                 columnHeaderDataLayer.setColumnsResizableByDefault(true);
233                 ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, compositeFreezeLayer,
234                                 selectionLayer);
235                 ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer, selectionLayer,
236                                 columnGroupModel);
237                 columnGroupHeaderLayer.setCalculateHeight(true);
238                 SortHeaderLayer<String> columnSortHeaderLayer = new SortHeaderLayer<>(columnGroupHeaderLayer,
239                                 sortModel = new TechTypeTableSortModel(bodyDataProvider));
240
241                 // build the row header layer
242                 IDataProvider rowHeaderDataProvider = new TechTypeRowHeaderTableDataProvider(bodyDataProvider);
243                 DataLayer rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
244                 rowHeaderDataLayer.setRowsResizableByDefault(false);
245                 rowHeaderDataLayer.setColumnsResizableByDefault(false);
246                 RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, compositeFreezeLayer, selectionLayer);
247
248                 // build the corner layer
249                 IDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider,
250                                 rowHeaderDataProvider);
251                 DataLayer cornerDataLayer = new DataLayer(cornerDataProvider);
252                 ILayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnSortHeaderLayer);
253
254                 // build the grid layer
255                 GridLayer gridLayer = new GridLayer(compositeFreezeLayer, columnSortHeaderLayer, rowHeaderLayer, cornerLayer);
256
257                 table = new NatTable(parent, NatTable.DEFAULT_STYLE_OPTIONS | SWT.BORDER, gridLayer, false);
258                 GridDataFactory.fillDefaults().grab(true, true).applyTo(table);
259                 
260                 table.addConfiguration(new SingleClickSortConfiguration());
261                 
262                 // Show entries labeled "INVALID" with red text
263                 table.addConfiguration(new AbstractRegistryConfiguration() {
264                         @Override
265                         public void configureRegistry(IConfigRegistry configRegistry) {
266                                 Style cellStyle = new Style();
267                                 cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper.COLOR_RED);
268                                 
269                                 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE,
270                                                 new EditableRule() {
271                                                         @Override
272                                                         public boolean isEditable(int columnIndex, int rowIndex) {
273                                                                 return columnIndex == 0;
274                                                         }
275                                                 });
276                                 
277                                 configRegistry.registerConfigAttribute(
278                                                 CellConfigAttributes.CELL_STYLE,
279                                                 cellStyle,
280                                                 DisplayMode.NORMAL,
281                                                 INVALID_LABEL
282                                         );
283                                 
284                                 configRegistry.registerConfigAttribute(
285                                                 EditConfigAttributes.CELL_EDITOR,
286                                                 CHECK_BOX_CELL_EDITOR,
287                                                 DisplayMode.EDIT,
288                                                 CHECK_BOX_LABEL
289                                         );
290                                 
291                                 configRegistry.registerConfigAttribute(
292                                                 CellConfigAttributes.CELL_PAINTER,
293                                                 CHECK_BOX_PAINTER,
294                                                 DisplayMode.NORMAL,
295                                                 CHECK_BOX_LABEL
296                                         );
297                         }
298                 });
299
300                 // Register a CopyDataCommandHandler that also copies the headers and
301                 // uses the configured IDisplayConverters
302                 CopyDataCommandHandler copyHandler = new CopyDataCommandHandler(selectionLayer, columnHeaderDataLayer,
303                                 rowHeaderDataLayer);
304                 copyHandler.setCopyFormattedText(true);
305                 gridLayer.registerCommandHandler(copyHandler);
306
307                 // initialize paste handler with SWT clipboard
308                 cpb = new Clipboard(getDisplay());
309                 // PasteDataCommandHandler pasteHandler = new
310                 // PasteDataCommandHandler(bodyDataProvider, bodyDataLayer, selectionLayer,
311                 // cpb);
312                 // bodyDataLayer.registerCommandHandler(pasteHandler);
313
314                 table.addConfiguration(new DefaultNatTableStyleConfiguration());
315                 // table.addConfiguration(new EditingSupportConfiguration(bodyDataProvider));
316                 table.configure();
317         }
318
319         private static String getKeyColumnName(Resource componentType) {
320                 String keyName = null;
321                 if (componentType != null) {
322                         try {
323                                 keyName = Simantics.getSession().syncRequest(new PossibleTechTypeKeyName(componentType));
324                         } catch (DatabaseException e) {
325                                 LOGGER.error("Failed to read possible tech type key name for {}", componentType, e);
326                         }
327                 }
328                 return keyName.startsWith("_") ? keyName.substring(1) : keyName;
329         }
330
331         @Override
332         public void dispose() {
333                 cpb.dispose();
334                 super.dispose();
335         }
336         
337         public void setComponentType(Resource componentType) {
338                 if (Objects.equals(this.componentType, componentType))
339                         return;
340                 
341                 this.componentType = componentType;
342                 this.keyName = getKeyColumnName(componentType);
343                 
344                 Simantics.getSession().asyncRequest(new TableDataRequest(componentType), new TableDataListener(componentType));
345         }
346
347         private final class TableDataListener implements Listener<Triple<Resource, String, List<Integer>>> {
348                 private final Resource componentType;
349
350                 private TableDataListener(Resource componentType) {
351                         this.componentType = componentType;
352                 }
353
354                 @Override
355                 public void execute(Triple<Resource, String, List<Integer>> result) {
356                         TechTypeTable.this.getDisplay().asyncExec(() -> {
357                                 TechTypeTable.this.tableResource = result.first;
358                                 String data = result.second;
359                                 int[] enabled = result.third.stream().mapToInt(Integer::intValue).toArray();
360                                 
361                                 setTechTypeData(data, enabled);
362                                 setValidationResult(null);
363                         });
364                 }
365
366                 @Override
367                 public void exception(Throwable t) {
368                         LOGGER.error("Error updating tech type table data for {}", componentType, t);
369                 }
370
371                 @Override
372                 public boolean isDisposed() {
373                         return TechTypeTable.this.table != null && TechTypeTable.this.table.isDisposed() ||
374                                         !Objects.equals(TechTypeTable.this.componentType, componentType);
375                 }
376         }
377
378         private final class TableDataRequest extends ResourceRead<Triple<Resource, String, List<Integer>>> {
379                 private TableDataRequest(Resource componentType) {
380                         super(componentType);
381                 }
382         
383                 @Override
384                 public Triple<Resource, String, List<Integer>> perform(ReadGraph graph) throws DatabaseException {
385                         Resource model = graph.syncRequest(new PossibleActiveModel(Simantics.getProjectResource()));
386                         if (model == null)
387                                 return null;
388                         Resource tableResource = graph.syncRequest(new PossibleTechTypeTable(model, this.resource));
389                         if (tableResource == null)
390                                 return null;
391                         
392                         String data = graph.getRelatedValue2(tableResource, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasData);
393                         int[] enabled = graph.getRelatedValue2(tableResource, DistrictNetworkResource.getInstance(graph).TechType_TechTypeTable_HasEnabledItems);
394                         List<Integer> list = Arrays.stream(enabled).mapToObj(Integer::valueOf).collect(Collectors.toList());
395                         return Triple.make(tableResource, data, list);
396                 }
397         }
398
399         public void setTechTypePath(String path) {
400                 String data;
401                 try {
402                         data = Files.lines(Paths.get(path), Charset.defaultCharset()).collect(Collectors.joining("\n"));
403                 } catch (IOException e) {
404                         LOGGER.error("Failed to read contents of file '{}' as {}", path, Charset.defaultCharset(), e);
405                         return;
406                 }
407
408                 try {
409                         Simantics.getSession().syncRequest(new WriteRequest() {
410                                 @Override
411                                 public void perform(WriteGraph graph) throws DatabaseException {
412                                         graph.syncRequest(new WriteTechTypeTable(componentType, data));
413                                 }
414                         });
415                 } catch (DatabaseException e) {
416                         LOGGER.error("Failed to write tech type table data to model", e);
417                 }
418
419                 setComponentType(componentType);
420         }
421
422         public void setTechTypeData(String data, int[] enabled) {
423                 bodyDataProvider.setData(data);
424                 bodyDataProvider.setEnabledFlags(enabled);
425                 table.refresh(true);
426         }
427
428         /**
429          * Set results of a validation operation
430          * 
431          * Invalid entries are designated by a string of the form "<type_code>/<property_name>".
432          * 
433          * This method must be called in the SWT thread.
434          * 
435          * @param result  A set of strings representing invalid entries
436          */
437         public void setValidationResult(Set<String> result) {
438                 if (result != null && result.isEmpty())
439                         result = null;
440                 
441                 this.validationResult = result;
442                 if (result != null) {
443                         keyName = getKeyColumnName(componentType);
444                 }
445                 
446                 table.refresh();
447         }
448
449         /**
450          * Get a resource representation of the currently open table, or null if
451          * table is not stored in the model.
452          */
453         public Resource getCurrentTable() {
454                 return tableResource;
455         }
456 }