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