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