1abe8434897ee8bc3071ff528baf45b41e76bbee
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / componentTypeEditor / DerivedPropertiesSection.java
1 package org.simantics.modeling.ui.componentTypeEditor;
2
3 import java.util.ArrayList;
4 import java.util.HashSet;
5 import java.util.List;
6 import java.util.Set;
7
8 import org.eclipse.jface.layout.GridDataFactory;
9 import org.eclipse.jface.layout.GridLayoutFactory;
10 import org.eclipse.jface.layout.TableColumnLayout;
11 import org.eclipse.jface.viewers.ColumnWeightData;
12 import org.eclipse.swt.SWT;
13 import org.eclipse.swt.custom.TableEditor;
14 import org.eclipse.swt.events.MouseAdapter;
15 import org.eclipse.swt.events.MouseEvent;
16 import org.eclipse.swt.events.SelectionAdapter;
17 import org.eclipse.swt.events.SelectionEvent;
18 import org.eclipse.swt.graphics.Color;
19 import org.eclipse.swt.graphics.Rectangle;
20 import org.eclipse.swt.layout.FillLayout;
21 import org.eclipse.swt.widgets.Button;
22 import org.eclipse.swt.widgets.Composite;
23 import org.eclipse.swt.widgets.Control;
24 import org.eclipse.swt.widgets.Table;
25 import org.eclipse.swt.widgets.TableColumn;
26 import org.eclipse.swt.widgets.TableItem;
27 import org.eclipse.ui.forms.widgets.Form;
28 import org.eclipse.ui.forms.widgets.FormToolkit;
29 import org.eclipse.ui.forms.widgets.Section;
30 import org.simantics.Simantics;
31 import org.simantics.db.ReadGraph;
32 import org.simantics.db.RequestProcessor;
33 import org.simantics.db.Resource;
34 import org.simantics.db.WriteGraph;
35 import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
36 import org.simantics.db.common.request.UniqueRead;
37 import org.simantics.db.common.request.WriteRequest;
38 import org.simantics.db.exception.DatabaseException;
39 import org.simantics.db.layer0.request.PropertyInfo;
40 import org.simantics.db.layer0.request.PropertyInfoRequest;
41 import org.simantics.db.layer0.variable.StandardGraphChildVariable;
42 import org.simantics.db.layer0.variable.StandardGraphPropertyVariable;
43 import org.simantics.db.layer0.variable.Variable;
44 import org.simantics.layer0.Layer0;
45 import org.simantics.modeling.scl.CompileProceduralSCLMonitorRequest;
46 import org.simantics.modeling.scl.CompileSCLMonitorRequest;
47 import org.simantics.modeling.userComponent.ComponentTypeCommands;
48 import org.simantics.scl.runtime.SCLContext;
49 import org.simantics.scl.runtime.function.Function1;
50 import org.simantics.scl.runtime.function.Function4;
51 import org.simantics.structural.stubs.StructuralResource2;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 public class DerivedPropertiesSection implements ComponentTypeViewerSection {
56     private static final String[] COLUMN_NAMES = {
57             Messages.DerivedPropertiesSection_Name,
58             Messages.DerivedPropertiesSection_Type,
59             Messages.DerivedPropertiesSection_Expression,
60             Messages.DerivedPropertiesSection_Unit,
61             Messages.DerivedPropertiesSection_Label,
62             Messages.DerivedPropertiesSection_Description
63     };
64     private static final int[] COLUMN_LENGTHS =
65             new int[] { 120, 100, 100, 70, 100, 100 };
66     private static final int[] COLUMN_WEIGHTS =
67             new int[] { 0, 0, 100, 0, 0, 0 };
68     private static Function4<RequestProcessor, Resource, Resource, String, String> VALIDATE_MONITOR_EXPRESSION =
69             new Function4<RequestProcessor, Resource, Resource, String, String>() {
70         @Override
71         public String apply(RequestProcessor p0, Resource p1, Resource p2, String p3) {
72             return validateMonitorExpression(p0, p1, p2, p3);
73         }
74     };
75     
76     private static final Logger LOGGER = LoggerFactory.getLogger(DerivedPropertiesSection.class);
77     
78     ComponentTypeViewerData data;
79     Table table;
80     TableColumn[] columns;
81     TableEditor editor;
82     Button newProperty;
83     Button removeProperty;
84     
85     Section section;
86     
87     public DerivedPropertiesSection(ComponentTypeViewerData data) {
88         this.data = data;
89         FormToolkit tk = data.tk;
90         Form form = data.form;
91         section = tk.createSection(form.getBody(), Section.TITLE_BAR | Section.EXPANDED);
92         section.setLayout(new FillLayout());
93         section.setText(Messages.DerivedPropertiesSection_DerivedProperties);
94
95         Composite sectionBody = tk.createComposite(section);
96         GridLayoutFactory.fillDefaults().numColumns(2).applyTo(sectionBody);
97         section.setClient(sectionBody);
98
99         Composite tableComposite = tk.createComposite(sectionBody);
100         GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(tableComposite);
101         TableColumnLayout tcl = new TableColumnLayout();
102         tableComposite.setLayout(tcl);
103
104         table = tk.createTable(tableComposite, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER);
105         table.setLinesVisible(true);
106         table.setHeaderVisible(true);
107
108         columns = new TableColumn[COLUMN_NAMES.length];
109         for(int i=0;i<COLUMN_NAMES.length;++i) {
110             TableColumn column = new TableColumn(table, SWT.NONE);
111             columns[i] = column;
112             tcl.setColumnData(column, new ColumnWeightData(COLUMN_WEIGHTS[i], COLUMN_LENGTHS[i], true));
113             column.setText(COLUMN_NAMES[i]);
114         }
115
116         // Table editor
117         editor = new TableEditor(table);
118         editor.grabHorizontal = true;
119         editor.grabVertical = true;
120         editor.horizontalAlignment = SWT.LEFT;
121         table.addMouseListener(new MouseAdapter() {
122             @Override
123             public void mouseDown(MouseEvent e) {
124                 // Clean up any previous editor control
125                 Control oldEditor = editor.getEditor();
126                 if (oldEditor != null) oldEditor.dispose();
127
128                 if (data.readOnly)
129                     return;
130
131                 // Relative position
132                 Rectangle tableBounds = table.getClientArea();
133                 int rx = e.x - tableBounds.x;
134                 int ry = e.y - tableBounds.y;
135
136                 // Find cell
137                 TableItem selectedItem = null;
138                 int selectedColumn = -1;
139                 Rectangle selectedItemBounds = null;
140                 for(TableItem item : table.getItems()) {
141                     for(int column = 0;column < COLUMN_NAMES.length;++column) {
142                         Rectangle bounds = item.getBounds(column);
143                         if(bounds.contains(rx, ry)) {
144                             selectedItemBounds = bounds;
145                             selectedItem = item;
146                             selectedColumn = column;
147                             break;
148                         }
149                     }
150                 }
151                 if(selectedItem == null) {
152                     return;
153                 }
154
155                 // Table editor
156                 final int column = selectedColumn; 
157                 final ComponentTypeViewerPropertyInfo propertyInfo = (ComponentTypeViewerPropertyInfo)selectedItem.getData();
158                 final Resource resource = propertyInfo.resource;
159                 switch (column) {
160                 case 0:
161                     data.editName(table, editor, propertyInfo, selectedItem, column, ComponentTypeViewerData.PROPERTY_NAME_PATTERN);
162                     break;
163
164                 case 1:
165                     data.editType(table, editor, propertyInfo, selectedItem, column, null, false);
166                     break;
167
168                 case 2:
169                     data.editValue(table, editor, propertyInfo, selectedItem, column, propertyInfo.immutable ? null : new StringWriter() {
170                         @Override
171                         public void perform(WriteGraph graph, String newValue) throws DatabaseException {
172                             ComponentTypeCommands.setMonitorExpression(graph, data.componentType, resource, newValue);
173                         }
174                     }, VALIDATE_MONITOR_EXPRESSION);
175                     break;
176
177                 case 3:
178                     data.editUnit(table, editor, propertyInfo, selectedItem, column);
179                     break;
180
181                 case 4:
182                     data.editValue(table, editor, propertyInfo, selectedItem, column, propertyInfo.immutable ? null : new StringWriter() {
183                         @Override
184                         public void perform(WriteGraph graph, String newValue) throws DatabaseException {
185                             graph.markUndoPoint();
186                             String value = newValue.isEmpty() ? null : newValue;
187                             ComponentTypeCommands.setLabel(graph, resource, value);
188                         }
189                     }, null);
190                     break;
191
192                 case 5:
193                     data.editMultilineText(table, editor, propertyInfo, selectedItem, selectedItemBounds, column, new StringWriter() {
194                         @Override
195                         public void perform(WriteGraph graph, String newValue) throws DatabaseException {
196                             graph.markUndoPoint();
197                             String value = newValue.isEmpty() ? null : newValue;
198                             ComponentTypeCommands.setDescription(graph, resource, value);
199                         }
200                     });
201                     break;
202                 }
203             }
204
205         });
206
207         // Buttons
208
209         Composite buttons = tk.createComposite(sectionBody);
210         GridDataFactory.fillDefaults().applyTo(buttons);
211         GridLayoutFactory.fillDefaults().applyTo(buttons);
212
213         newProperty = tk.createButton(buttons, Messages.DerivedPropertiesSection_NewProperty, SWT.PUSH);
214         GridDataFactory.fillDefaults().applyTo(newProperty);
215         removeProperty = tk.createButton(buttons, Messages.DerivedPropertiesSection_RemoveProperty, SWT.PUSH);
216         GridDataFactory.fillDefaults().applyTo(removeProperty);
217
218         // Actions
219
220         newProperty.addSelectionListener(new SelectionAdapter() {
221             @Override
222             public void widgetSelected(SelectionEvent e) {
223                 if(editor.getEditor() != null)
224                     editor.getEditor().dispose();
225                 Simantics.getSession().async(new WriteRequest() {
226                     @Override
227                     public void perform(WriteGraph graph)
228                             throws DatabaseException {
229                         ComponentTypeCommands.createMonitorPropertyWithDefaults(graph, data.componentType);
230                     }
231                 });
232             }
233         });
234
235         removeProperty.addSelectionListener(new SelectionAdapter() {
236             @Override
237             public void widgetSelected(SelectionEvent e) {
238                 if(editor.getEditor() != null)
239                     editor.getEditor().dispose();
240                 final List<Resource> propertiesToBeRemoved = 
241                         new ArrayList<>();
242                 for(TableItem item : table.getSelection())
243                     propertiesToBeRemoved.add(((ComponentTypeViewerPropertyInfo)item.getData()).resource);
244                 //System.out.println("remove " + propertiesToBeRemoved.size() + " resources");
245                 if(!propertiesToBeRemoved.isEmpty())
246                     Simantics.getSession().async(new WriteRequest() {
247                         @Override
248                         public void perform(WriteGraph graph)
249                                 throws DatabaseException {
250                             graph.markUndoPoint();
251                             for(Resource property : propertiesToBeRemoved)
252                                 ComponentTypeCommands.removeProperty(graph, data.componentType, property);
253                         }
254                     });
255             }
256         });
257     }
258
259     @Override
260     public void setReadOnly(boolean readOnly) {
261         boolean e = !readOnly;
262         newProperty.setEnabled(e);
263         removeProperty.setEnabled(e);
264     }
265
266     public static String validateMonitorExpression(final RequestProcessor processor, final Resource componentType, final Resource relation, final String expression) {
267
268         if (expression.trim().isEmpty()) {
269             return Messages.DerivedPropertiesSection_ExpressionIsEmpty;
270         }
271
272         if (expression.trim().isEmpty()) {
273             return Messages.DerivedPropertiesSection_ExpressionIsEmpty;
274         }
275         try {
276             return processor.sync(new UniqueRead<String>() {
277
278                 @Override
279                 public String perform(ReadGraph graph) throws DatabaseException {
280
281                     StructuralResource2 STR = StructuralResource2.getInstance(graph);
282
283                     // TODO: this is a bit hackish but should get the job done in most parts and
284                     // importantly indicates something for the user
285                     PropertyInfo info = graph.syncRequest(new PropertyInfoRequest(relation), TransientCacheAsyncListener.<PropertyInfo>instance());
286                     Variable parent = new StandardGraphChildVariable(null, null, componentType) {
287                         
288                         public Resource getType(ReadGraph graph) throws DatabaseException {
289                             return componentType;
290                         };
291                         
292                         public Variable getPossibleProperty(ReadGraph graph, String name) throws DatabaseException {
293                             Variable prop = super.getPossibleProperty(graph, name);
294                             if (prop != null) {
295                                 return prop;
296                             } else {
297                                 return getChild(graph, name);
298                             }
299                         };
300                     };
301                     
302                     for(Resource literal : graph.getAssertedObjects(componentType, relation)) {
303
304                         try {
305                             Variable context = new StandardGraphPropertyVariable(parent, null, null, info, literal);
306                             if(graph.isInstanceOf(componentType, STR.ProceduralComponentType)) {
307                                 CompileProceduralSCLMonitorRequest.compileAndEvaluate(graph, context);
308                             } else {
309                                 compileAndEvaluate(graph, context, expression);
310                             }
311                             
312                         } catch (Exception e) {
313                             String msg = e.getMessage();
314                             int index = msg.indexOf(":"); //$NON-NLS-1$
315                             if(index > 0) msg = msg.substring(index);
316                             return msg;
317                         }
318                     }
319                     return null;
320                 }
321                 
322             });
323         } catch (DatabaseException e) {
324             LOGGER.error("Could not validate ", e);
325         }
326         return null;
327     }
328
329     public static void compileAndEvaluate(ReadGraph graph, Variable context, String expression) throws DatabaseException {
330         SCLContext sclContext = SCLContext.getCurrent();
331         Object oldGraph = sclContext.get("graph");
332         try {
333             CompileSCLMonitorRequest compileSCLMonitorRequest = new ValidationCompilationRequest(graph, context, expression);
334             Function1<Variable,Object> exp = graph.syncRequest(compileSCLMonitorRequest);
335             sclContext.put("graph", graph);
336             //return exp.apply(context.getParent(graph));
337         } catch (DatabaseException e) {
338             throw (DatabaseException)e;
339         } catch (Throwable t) {
340             throw new DatabaseException(t);
341         } finally {
342             sclContext.put("graph", oldGraph);
343         }
344     }
345     
346     @Override
347     public void update(ComponentTypePropertiesResult result) {
348         if (table.isDisposed())
349             return;
350
351         Set<ComponentTypeViewerPropertyInfo> selected = new HashSet<>();
352         List<TableItem> selectedItems = new ArrayList<TableItem>(selected.size());
353         for (int i : table.getSelectionIndices()) {
354             TableItem item = table.getItem(i);
355             selected.add((ComponentTypeViewerPropertyInfo) item.getData());
356         }
357         
358         int topIndex = table.getTopIndex();
359         table.removeAll();
360         if(editor.getEditor() != null)
361             editor.getEditor().dispose();
362         
363         for(ComponentTypeViewerPropertyInfo info : result.getProperties()) {
364             boolean immutable = result.isImmutable() || info.immutable;
365             Color fg = immutable ? table.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY) : null;
366             if(info.sectionSpecificData != MONITOR)
367                 continue;
368
369             TableItem item = new TableItem(table, SWT.NONE);
370
371             item.setText(0, info.valid != null ? info.name + " (!)" : info.name); //$NON-NLS-1$
372             item.setText(1, info.type);
373             item.setText(2, info.expression);
374             item.setText(3, info.unitString());
375             item.setText(4, info.label);
376             item.setText(5, info.description);
377
378             item.setForeground(fg);
379
380             item.setData(info);
381
382             if (selected.contains(info))
383                 selectedItems.add(item);
384         }
385         
386         table.setTopIndex(topIndex);
387         table.setSelection(selectedItems.toArray(new TableItem[selectedItems.size()]));
388         table.redraw();
389     }
390
391     @Override
392     public Section getSection() {
393         return section;
394     }
395
396     @Override
397     public double getPriority() {
398         return 100.0;
399     }
400
401     private static final Object MONITOR = new Object();
402     
403     @Override
404     public Object getSectionSpecificData(ReadGraph graph,
405             ComponentTypeViewerPropertyInfo info) throws DatabaseException {
406         Layer0 L0 = Layer0.getInstance(graph);
407         StructuralResource2 STR = StructuralResource2.getInstance(graph);
408         Resource range = graph.getPossibleObject(info.resource, L0.HasRange);
409         if(range != null && graph.isInstanceOf(range, STR.MonitorValueType))
410             return MONITOR;
411         else
412             return null;
413     }
414     
415     @Override
416     public double getDataPriority() {
417         return 100.0;
418     }
419
420     private static final class ValidationCompilationRequest extends CompileSCLMonitorRequest {
421         private final String expression;
422     
423         private ValidationCompilationRequest(ReadGraph graph, Variable context, String expression)
424                 throws DatabaseException {
425             super(graph, context);
426             this.expression = expression;
427         }
428     
429         @Override
430         protected String getExpressionText(ReadGraph graph) throws DatabaseException {
431             return expression;
432         }
433     
434         @Override
435         public int hashCode() {
436             return super.hashCode() + 37 * expression.hashCode();
437         }
438     
439         @Override
440         public boolean equals(Object obj) {
441             return super.equals(obj) && ((ValidationCompilationRequest)obj).expression.equals(expression);
442         }
443     }
444
445 }