d6b312426253fa33226aeb13f36505455ddea238
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / componentTypeEditor / ComponentTypeViewerData.java
1 package org.simantics.modeling.ui.componentTypeEditor;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.List;
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
8
9 import org.eclipse.jface.dialogs.IMessageProvider;
10 import org.eclipse.jface.layout.GridDataFactory;
11 import org.eclipse.jface.layout.GridLayoutFactory;
12 import org.eclipse.swt.SWT;
13 import org.eclipse.swt.custom.StyledText;
14 import org.eclipse.swt.custom.TableEditor;
15 import org.eclipse.swt.events.SelectionAdapter;
16 import org.eclipse.swt.events.SelectionEvent;
17 import org.eclipse.swt.graphics.Point;
18 import org.eclipse.swt.graphics.Rectangle;
19 import org.eclipse.swt.widgets.Button;
20 import org.eclipse.swt.widgets.Combo;
21 import org.eclipse.swt.widgets.Composite;
22 import org.eclipse.swt.widgets.Display;
23 import org.eclipse.swt.widgets.Event;
24 import org.eclipse.swt.widgets.Label;
25 import org.eclipse.swt.widgets.Shell;
26 import org.eclipse.swt.widgets.Table;
27 import org.eclipse.swt.widgets.TableItem;
28 import org.eclipse.swt.widgets.Text;
29 import org.eclipse.ui.forms.widgets.Form;
30 import org.eclipse.ui.forms.widgets.FormToolkit;
31 import org.simantics.Simantics;
32 import org.simantics.databoard.type.NumberType;
33 import org.simantics.databoard.units.internal.library.UnitLibrary;
34 import org.simantics.databoard.util.Limit;
35 import org.simantics.databoard.util.Range;
36 import org.simantics.databoard.util.RangeException;
37 import org.simantics.db.RequestProcessor;
38 import org.simantics.db.Resource;
39 import org.simantics.db.WriteGraph;
40 import org.simantics.db.common.NamedResource;
41 import org.simantics.db.common.request.WriteRequest;
42 import org.simantics.db.exception.DatabaseException;
43 import org.simantics.layer0.Layer0;
44 import org.simantics.modeling.userComponent.ComponentTypeCommands;
45 import org.simantics.scl.runtime.function.Function2;
46 import org.simantics.scl.runtime.function.Function4;
47 import org.simantics.utils.ui.ErrorLogger;
48
49 public class ComponentTypeViewerData {
50     /**
51      * Used to validate property names.
52      */
53     public static final Pattern PROPERTY_NAME_PATTERN =
54             Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
55
56     public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
57         "Double",
58         "Integer",
59         "Float",
60         "String",
61         "Boolean",
62         "Long",
63         "[Double]",
64         "[Integer]",
65         "[Float]",
66         "[String]",
67         "[Boolean]",
68         "[Long]",
69         "Vector Double",
70         "Vector Integer",
71         "Vector Float",
72         "Vector String",
73         "Vector Boolean",
74         "Vector Long"        
75     };
76     
77     public Resource componentType;
78     public FormToolkit tk;
79     public Form form;
80     public UnitLibrary unitLibrary = UnitLibrary.createDefault();
81     public boolean readOnly;
82     public NamedResource[] connectionPoints;
83     public ComponentTypeViewerPropertyInfo[] properties;
84
85     public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
86         this.tk = tk;
87         this.componentType = componentType;
88         this.form = form;
89     }
90
91     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
92             Pattern namePattern) {
93         editName(table, editor, propertyInfo, selectedItem, column,
94                 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern));
95     }
96
97     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
98             Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator) {
99         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
100         final Text text = new Text(table, SWT.NONE | extraStyle);
101         org.eclipse.swt.widgets.Listener listener = 
102                 new org.eclipse.swt.widgets.Listener() {
103             @Override
104             public void handleEvent(Event e) {
105                 if (e.type == SWT.Dispose) {
106                     form.setMessage(null);
107                     return;
108                 }
109
110                 if (e.type == SWT.Modify) {
111                     // validate current name
112                     String error = nameValidator.apply(propertyInfo, text.getText());
113                     if (error != null) {
114                         text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
115                         form.setMessage(error, IMessageProvider.ERROR);
116                     } else {
117                         text.setBackground(null);
118                         form.setMessage(null);
119                     }
120                     return;
121                 }
122
123                 if (e.type == SWT.Traverse) {
124                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
125                         text.dispose();
126                         e.doit = false;
127                         return;
128                     }
129                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
130                         return;
131                     e.doit = false;
132                 }
133                 final String newValue = text.getText();
134                 text.dispose();
135
136                 String error = nameValidator.apply(propertyInfo, newValue);
137                 if (error != null)
138                     return;
139
140                 if (propertyInfo.immutable)
141                     return;
142
143                 Simantics.getSession().async(new WriteRequest() {
144                     @Override
145                     public void perform(WriteGraph graph)
146                             throws DatabaseException {
147                         graph.markUndoPoint();
148                         Layer0 L0 = Layer0.getInstance(graph);
149                         String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
150                         String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
151                         String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
152                         boolean setLabel = oldLabel == null
153                                 || oldLabel.isEmpty()
154                                 || oldCamelCasedLabel.isEmpty()
155                                 || oldCamelCasedLabel.equals(oldLabel);
156
157                         ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
158                         if (setLabel)
159                             ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
160                     }
161                 });
162             }
163         };
164         text.addListener(SWT.Modify, listener);
165         text.addListener(SWT.Deactivate, listener);
166         text.addListener(SWT.Traverse, listener);
167         text.addListener(SWT.Dispose, listener);
168
169         text.setText(selectedItem.getText(column));
170         text.selectAll();
171         text.setFocus();
172
173         editor.setEditor(text, selectedItem, column);
174     }
175
176     public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
177         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
178         final Combo combo = new Combo(table, SWT.NONE | extraStyle);
179         combo.setText(selectedItem.getText(column));
180         for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
181             combo.add(suggestion);
182         org.eclipse.swt.widgets.Listener listener = 
183                 new org.eclipse.swt.widgets.Listener() {
184             @Override
185             public void handleEvent(Event e) {
186                 if(e.type == SWT.Traverse) {
187                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
188                         combo.dispose();
189                         e.doit = false;
190                         return;
191                     }
192                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
193                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
194                             || e.detail == SWT.TRAVERSE_MNEMONIC)
195                         return;
196                 }
197                 final String newValue = combo.getText();
198                 if (e.type == SWT.Traverse) {
199                     e.doit = false;
200                 }
201                 combo.dispose();
202
203                 if (propertyInfo.immutable)
204                     return;
205
206                 Simantics.getSession().async(new WriteRequest() {
207                     @Override
208                     public void perform(WriteGraph graph)
209                             throws DatabaseException {
210                         graph.markUndoPoint();
211                         ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
212                     }
213                 });
214             }
215         };
216         combo.setFocus();
217         editor.setEditor(combo, selectedItem, column);
218         combo.addListener(SWT.FocusOut, listener);
219         combo.addListener(SWT.Traverse, listener);
220     }
221
222     protected void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
223         // Disallow unit editing for non-numeric configuration properties
224         if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
225             return;
226
227         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
228         final Combo combo = new Combo(table, SWT.NONE | extraStyle);
229         String initialValue = selectedItem.getText(column);
230         List<String> units = new ArrayList<>( unitLibrary.getUnits() );
231         Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
232         int i = -1;
233         int selected = -1;
234         for (String unit : units) {
235             combo.add(unit);
236             if (unit.equals(initialValue))
237                 combo.select(i);
238         }
239         if (selected == -1)
240             combo.setText(initialValue);
241
242         org.eclipse.swt.widgets.Listener listener = 
243                 new org.eclipse.swt.widgets.Listener() {
244             @Override
245             public void handleEvent(Event e) {
246                 if(e.type == SWT.Traverse) {
247                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
248                         combo.dispose();
249                         e.doit = false;
250                         return;
251                     }
252                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
253                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
254                             || e.detail == SWT.TRAVERSE_MNEMONIC)
255                         return;
256                 }
257                 final String newValue = combo.getText();
258                 if(e.type == SWT.Traverse) {
259                     e.doit = false;
260                 }
261                 combo.dispose();
262
263                 if (propertyInfo.immutable)
264                     return;
265
266                 Simantics.getSession().async(new WriteRequest() {
267                     @Override
268                     public void perform(WriteGraph graph)
269                             throws DatabaseException {
270                         graph.markUndoPoint();
271                         ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
272                     }
273                 });
274             }
275         };
276         combo.setFocus();
277         editor.setEditor(combo, selectedItem, column);
278         combo.addListener(SWT.Deactivate, listener);
279         combo.addListener(SWT.Traverse, listener);
280     }
281
282     public void editValue(Table table, TableEditor editor,
283             final ComponentTypeViewerPropertyInfo propertyInfo,
284             TableItem selectedItem, int column,
285             final StringWriter writer,
286             final Function4<RequestProcessor, Resource, Resource, String, String> validator)
287     {
288         int extraStyle = writer == null ? SWT.READ_ONLY : 0;
289         final Text text = new Text(table, SWT.NONE | extraStyle);
290         text.setText(selectedItem.getText(column));
291         org.eclipse.swt.widgets.Listener listener = 
292                 new org.eclipse.swt.widgets.Listener() {
293             @Override
294             public void handleEvent(Event e) {
295                 if(e.type == SWT.Traverse) {
296                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
297                         text.dispose();
298                         e.doit = false;
299                         return;
300                     }
301                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
302                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
303                             || e.detail == SWT.TRAVERSE_MNEMONIC)
304                         return;
305                 }
306                 final String newValue = text.getText();
307                 if(e.type == SWT.Traverse) {
308                     e.doit = false;
309                 }
310                 text.dispose();
311
312                 if (validator != null) {
313                     String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
314                     if (error != null)
315                         return;
316                 }
317
318                 if (writer != null) {
319                     Simantics.getSession().async(new WriteRequest() {
320                         @Override
321                         public void perform(WriteGraph graph) throws DatabaseException {
322                             writer.perform(graph, newValue);
323                         }
324                     });
325                 }
326             }
327         };
328         text.selectAll();
329         text.setFocus();
330         editor.setEditor(text, selectedItem, column);
331         text.addListener(SWT.FocusOut, listener);
332         text.addListener(SWT.Traverse, listener);
333
334         if (validator != null) {
335             org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
336                 @Override
337                 public void handleEvent(Event e) {
338                     final String newValue = text.getText();
339                     String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
340                     if (error != null) {
341                         text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
342                         text.setToolTipText(error);
343                         return;
344                     } else {
345                         text.setBackground(null);
346                         text.setToolTipText(null);
347                     }
348                 }
349             };
350             text.addListener(SWT.Modify, validationListener);
351         }
352     }
353
354     private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
355         try {
356             String rangeStr = (lowInclusive ? "[" : "(")  + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
357             return Range.valueOf(rangeStr);
358         } catch (IllegalArgumentException e) {
359             // Limits are invalid
360             throw new RangeException(e.getMessage(), e);
361         }
362     }
363     
364     private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
365         Combo rng = new Combo(parent, SWT.READ_ONLY);
366         rng.add("Inclusive");
367         rng.add("Exclusive");
368         rng.select(inclusive ? 0 : 1);
369         return rng;
370     }
371     
372     protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
373         // Disallow range editing when the property is not numeric
374         if (propertyInfo.numberType == null)
375             return;
376
377         int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
378
379         // Parse initial range value
380         Range range = null;
381         String rangeStr = selectedItem.getText(column);
382         try {
383             range = Range.valueOf(rangeStr);
384         } catch (RangeException ex) {
385             range = new Range(Limit.nolimit(), Limit.nolimit());
386         }
387
388         final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
389         GridLayoutFactory.fillDefaults().applyTo(shell);
390
391         Composite composite = new Composite(shell, SWT.NONE);
392         GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
393         GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
394
395         Label low = new Label(composite, SWT.NONE);
396         low.setText("Minimum Value:");
397         final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
398         GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
399         final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
400         Label high = new Label(composite, SWT.NONE);
401         high.setText("Maximum Value:");
402         final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
403         GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
404         final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
405
406         Composite buttonComposite = new Composite(shell, SWT.NONE);
407         GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
408         GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
409
410         Button ok = new Button(buttonComposite, SWT.NONE);
411         ok.setText("&OK");
412         GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
413         Button cancel = new Button(buttonComposite, SWT.NONE);
414         cancel.setText("&Cancel");
415         GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
416
417         if (range.getLower().getValue() != null)
418             lowText.setText(range.getLower().getValue().toString());
419         if (range.getUpper().getValue() != null)
420             highText.setText(range.getUpper().getValue().toString());
421
422         shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
423             @Override
424             public void handleEvent(Event event) {
425                 shell.dispose();
426             }
427         });
428
429         ok.addSelectionListener(new SelectionAdapter() {
430             public void widgetSelected(SelectionEvent e) {
431                 try {
432                     final Range newRange = parseRange(propertyInfo.numberType,
433                             lowText.getText().trim(),
434                             highText.getText().trim(),
435                             lowSelector.getSelectionIndex() == 0 ? true : false,
436                             highSelector.getSelectionIndex() == 0 ? true : false);
437
438                     shell.dispose();
439
440                     if (propertyInfo.immutable)
441                         return;
442
443                     Simantics.getSession().async(new WriteRequest() {
444                         @Override
445                         public void perform(WriteGraph graph)
446                                 throws DatabaseException {
447                             graph.markUndoPoint();
448                             ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
449                         }
450                     });
451                 } catch (RangeException ex) {
452                     ErrorLogger.defaultLogError(ex);
453                 }
454             }
455         });
456         cancel.addSelectionListener(new SelectionAdapter() {
457             public void widgetSelected(SelectionEvent e) {
458                 shell.dispose();
459             }
460         });
461
462         shell.pack();
463         Point size = shell.getSize();
464
465         Display display = table.getDisplay();
466         Rectangle clientArea = display.getClientArea();
467         Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
468         Rectangle b = selectedItemBounds;
469         b.x = bt.x;
470         b.y = bt.y;
471         b.width = size.x;
472         b.height = size.y;
473         if ((b.x + b.width) > clientArea.width)
474             b.x -= b.x + b.width - clientArea.width;
475         if (b.height > clientArea.height)
476             b.height = clientArea.height;
477         if ((b.y + b.height) > clientArea.height)
478             b.y -= b.y + b.height - clientArea.height;
479
480         shell.setBounds(selectedItemBounds);
481         shell.open();
482     }
483
484     protected void editMultilineText(Table table, TableEditor editor,
485             final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
486             Rectangle selectedItemBounds, int column, final StringWriter writer)
487     {
488         final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
489         GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
490         final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
491         GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
492         text.setText(selectedItem.getText(column));
493         org.eclipse.swt.widgets.Listener listener = 
494                 new org.eclipse.swt.widgets.Listener() {
495             @Override
496             public void handleEvent(Event e) {
497                 final String newValue = text.getText();
498
499                 if (e.type == SWT.Traverse) {
500                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
501                         shell.dispose();
502                         e.doit = false;
503                         return;
504                     }
505                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
506                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
507                             || e.detail == SWT.TRAVERSE_MNEMONIC)
508                         return;
509                     if ((e.stateMask & SWT.CTRL) == 0)
510                         return;
511                     e.doit = false;
512                 }
513
514                 shell.dispose();
515
516                 if (propertyInfo.immutable)
517                     return;
518
519                 if (writer != null) {
520                     Simantics.getSession().async(new WriteRequest() {
521                         @Override
522                         public void perform(WriteGraph graph) throws DatabaseException {
523                             writer.perform(graph, newValue);
524                         }
525                     });
526                 }
527             }
528         };
529
530         String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
531         Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
532         GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
533         help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
534
535         Display display = table.getDisplay();
536         Rectangle clientArea = display.getClientArea();
537         Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
538         Rectangle b = selectedItemBounds;
539         b.x = bt.x;
540         b.y = bt.y;
541         b.height = 200;
542         if ((b.x + b.width) > clientArea.width)
543             b.x -= b.x + b.width - clientArea.width;
544         if (b.height > clientArea.height)
545             b.height = clientArea.height;
546         if ((b.y + b.height) > clientArea.height)
547             b.y -= b.y + b.height - clientArea.height;
548
549         shell.setBounds(selectedItemBounds);
550         shell.open();
551
552         text.selectAll();
553         text.setFocus();
554
555         text.addListener(SWT.Traverse, listener);
556         shell.addListener(SWT.Deactivate, listener);
557     }
558
559     private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
560         if (propertyName.equals(propertyInfo.name))
561             return null;
562         for (ComponentTypeViewerPropertyInfo info : properties) {
563             if (propertyName.equals(info.name))
564                 return "Property name '" + propertyName + "' is already in use.";
565         }
566         for (NamedResource cp : connectionPoints) {
567             if (propertyName.equals(cp.getName()))
568                 return "Name '" + propertyName + "' is already used for a terminal.";
569         }
570         Matcher m = namePattern.matcher(propertyName);
571         if (!m.matches())
572             return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
573                     + namePattern.pattern() + ".";
574         return null;
575     }
576
577 }