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