]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/componentTypeEditor/ComponentTypeViewerData.java
4b9ffbe801f6785e8782a006684b15f191db8b27
[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.Collection;
5 import java.util.Collections;
6 import java.util.HashSet;
7 import java.util.List;
8 import java.util.Set;
9 import java.util.concurrent.ScheduledFuture;
10 import java.util.concurrent.TimeUnit;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 import org.eclipse.jface.dialogs.IDialogConstants;
15 import org.eclipse.jface.dialogs.IMessageProvider;
16 import org.eclipse.jface.layout.GridDataFactory;
17 import org.eclipse.jface.layout.GridLayoutFactory;
18 import org.eclipse.osgi.util.NLS;
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.custom.StyledText;
21 import org.eclipse.swt.custom.TableEditor;
22 import org.eclipse.swt.events.SelectionAdapter;
23 import org.eclipse.swt.events.SelectionEvent;
24 import org.eclipse.swt.graphics.Point;
25 import org.eclipse.swt.graphics.Rectangle;
26 import org.eclipse.swt.widgets.Button;
27 import org.eclipse.swt.widgets.Combo;
28 import org.eclipse.swt.widgets.Composite;
29 import org.eclipse.swt.widgets.Display;
30 import org.eclipse.swt.widgets.Event;
31 import org.eclipse.swt.widgets.Label;
32 import org.eclipse.swt.widgets.Shell;
33 import org.eclipse.swt.widgets.Table;
34 import org.eclipse.swt.widgets.TableItem;
35 import org.eclipse.swt.widgets.Text;
36 import org.eclipse.ui.forms.widgets.Form;
37 import org.eclipse.ui.forms.widgets.FormToolkit;
38 import org.simantics.Simantics;
39 import org.simantics.databoard.Bindings;
40 import org.simantics.databoard.type.NumberType;
41 import org.simantics.databoard.units.internal.library.UnitLibrary;
42 import org.simantics.databoard.util.Limit;
43 import org.simantics.databoard.util.Range;
44 import org.simantics.databoard.util.RangeException;
45 import org.simantics.db.RequestProcessor;
46 import org.simantics.db.Resource;
47 import org.simantics.db.Statement;
48 import org.simantics.db.WriteGraph;
49 import org.simantics.db.common.NamedResource;
50 import org.simantics.db.common.request.IndexRoot;
51 import org.simantics.db.common.request.WriteRequest;
52 import org.simantics.db.exception.DatabaseException;
53 import org.simantics.db.function.DbConsumer;
54 import org.simantics.db.layer0.QueryIndexUtils;
55 import org.simantics.layer0.Layer0;
56 import org.simantics.modeling.ModelingResources;
57 import org.simantics.modeling.userComponent.ComponentTypeCommands;
58 import org.simantics.modeling.utils.ComponentTypeViewerPropertyInfo;
59 import org.simantics.scl.runtime.function.Function2;
60 import org.simantics.scl.runtime.function.Function4;
61 import org.simantics.utils.datastructures.Pair;
62 import org.simantics.utils.threads.ThreadUtils;
63 import org.simantics.utils.ui.ErrorLogger;
64
65 public class ComponentTypeViewerData {
66     /**
67      * Used to validate property names.
68      */
69     public static final Pattern PROPERTY_NAME_PATTERN =
70             Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*"); //$NON-NLS-1$
71
72     public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
73         "Double", //$NON-NLS-1$
74         "Integer", //$NON-NLS-1$
75         "Float", //$NON-NLS-1$
76         "String", //$NON-NLS-1$
77         "Boolean", //$NON-NLS-1$
78         "Long", //$NON-NLS-1$
79         "[Double]", //$NON-NLS-1$
80         "[Integer]", //$NON-NLS-1$
81         "[Float]", //$NON-NLS-1$
82         "[String]", //$NON-NLS-1$
83         "[Boolean]", //$NON-NLS-1$
84         "[Long]", //$NON-NLS-1$
85         "Vector Double", //$NON-NLS-1$
86         "Vector Integer", //$NON-NLS-1$
87         "Vector Float", //$NON-NLS-1$
88         "Vector String", //$NON-NLS-1$
89         "Vector Boolean", //$NON-NLS-1$
90         "Vector Long" //$NON-NLS-1$
91     };
92
93     public Resource componentType;
94     public FormToolkit tk;
95     public Form form;
96     public UnitLibrary unitLibrary = UnitLibrary.createDefault();
97     public boolean readOnly;
98     public NamedResource[] connectionPoints;
99     public ComponentTypeViewerPropertyInfo[] properties;
100
101     public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
102         this.tk = tk;
103         this.componentType = componentType;
104         this.form = form;
105     }
106
107     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
108             Pattern namePattern) {
109         editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
110     }
111
112     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
113             Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
114         editName(table, editor, propertyInfo, selectedItem, column,
115                 null,
116                 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
117                 extraWriter);
118     }
119
120     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
121             Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter, Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
122         editName(table, editor, propertyInfo, selectedItem, column, nameFilter,
123                 (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
124                 extraWriter);
125     }
126
127     public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
128             Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
129     {
130         editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
131     }
132
133     public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
134             Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
135         editName(table, editor, propertyInfo, selectedItem, column, null, nameValidator, extraWriter);
136     }
137
138     public void editName(
139             Table table,
140             TableEditor editor,
141             final ComponentTypeViewerPropertyInfo propertyInfo,
142             TableItem selectedItem,
143             int column,
144             Function2<ComponentTypeViewerPropertyInfo, String, String> nameFilter,
145             Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator,
146             DbConsumer<WriteGraph> extraWriter) {
147         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
148         final Text text = new Text(table, SWT.NONE | extraStyle);
149         org.eclipse.swt.widgets.Listener listener = 
150                 new org.eclipse.swt.widgets.Listener() {
151             @Override
152             public void handleEvent(Event e) {
153                 if (e.type == SWT.Dispose) {
154                     form.setMessage(null);
155                     return;
156                 } else if (e.type == SWT.Verify) {
157                     // Filter input if necessary
158                     e.text = nameFilter != null ? nameFilter.apply(propertyInfo, e.text) : e.text;
159                     return;
160                 } else if (e.type == SWT.Modify) {
161                     // validate current name
162                     String error = nameValidator.apply(propertyInfo, text.getText());
163                     if (error != null) {
164                         text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
165                         form.setMessage(error, IMessageProvider.ERROR);
166                     } else {
167                         text.setBackground(null);
168                         form.setMessage(null);
169                     }
170                     return;
171                 } else if (e.type == SWT.Traverse) {
172                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
173                         text.dispose();
174                         e.doit = false;
175                         return;
176                     }
177                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
178                         return;
179                     e.doit = false;
180                 }
181                 final String newValue = text.getText();
182                 text.dispose();
183
184                 String error = nameValidator.apply(propertyInfo, newValue);
185                 if (error != null)
186                     return;
187
188                 if (propertyInfo.immutable)
189                     return;
190
191                 Simantics.getSession().async(new WriteRequest() {
192                     @Override
193                     public void perform(WriteGraph graph)
194                             throws DatabaseException {
195                         graph.markUndoPoint();
196                         Layer0 L0 = Layer0.getInstance(graph);
197                         String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
198                         String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : ""; //$NON-NLS-1$
199                         String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
200                         boolean setLabel = oldLabel == null
201                                 || oldLabel.isEmpty()
202                                 || oldCamelCasedLabel.isEmpty()
203                                 || oldCamelCasedLabel.equals(oldLabel);
204
205                         ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
206                         if (setLabel)
207                             ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
208
209                         if (extraWriter != null)
210                             extraWriter.accept(graph);
211                     }
212                 });
213             }
214         };
215         if (nameFilter != null)
216             text.addListener(SWT.Verify, listener);
217         text.addListener(SWT.Modify, listener);
218         text.addListener(SWT.Deactivate, listener);
219         text.addListener(SWT.Traverse, listener);
220         text.addListener(SWT.Dispose, listener);
221
222         text.setText(selectedItem.getText(column));
223         text.selectAll();
224         text.setFocus();
225
226         editor.setEditor(text, selectedItem, column);
227     }
228
229     public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, String range, final boolean convertDefaultValue) {
230         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
231         final Combo combo = new Combo(table, SWT.NONE | extraStyle);
232         combo.setText(selectedItem.getText(column));
233         for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
234             combo.add(suggestion);
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                 
260                 Simantics.getSession().async(new WriteRequest() {
261                     @Override
262                     public void perform(WriteGraph graph)
263                             throws DatabaseException {
264                         graph.markUndoPoint();
265
266                         String newValue2 = newValue;
267
268                         Resource possibleGraphType = null;
269
270                         Layer0 L0 = Layer0.getInstance(graph);
271                         ModelingResources MOD = ModelingResources.getInstance(graph);
272
273                         Resource previousRange = graph.getPossibleObject(propertyInfo.resource, L0.HasRange);
274                         if (previousRange != null && graph.isInheritedFrom(previousRange, MOD.MonitorValue)) {
275                             // Do not override range of derived properties
276                             possibleGraphType = previousRange;
277                         } else {
278                             Resource root = graph.syncRequest(new IndexRoot(componentType));
279
280                             Resource L0Res = graph.getResource("http://www.simantics.org/Layer0-1.1");
281
282                             Collection<Resource> graphTypes1 = QueryIndexUtils.searchByTypeAndName(graph, L0Res, L0.ValueType, newValue);
283                             Collection<Resource> graphTypes2 = QueryIndexUtils.searchByTypeAndName(graph, root, L0.ValueType, newValue);
284
285                             Collection<Resource> graphTypes = new HashSet<>(graphTypes1);
286                             graphTypes.addAll(graphTypes2);
287
288                             Set<Pair<Resource, String>> candidates = new HashSet<>();
289                             for (Resource graphType : graphTypes) {
290                                 Collection<Statement> stms = graph.getAssertedStatements(graphType, L0.HasValueType);
291                                 if(stms.size() == 1) {
292                                     // Only accept valueType if it asserts HasValueType with the same name
293                                     String hasValueType = graph.getValue(stms.iterator().next().getObject(), Bindings.STRING);
294                                     if (hasValueType.equals(newValue)) {
295                                         candidates.add(new Pair<>(graphType, hasValueType));
296                                     }
297                                 }
298                             }
299
300                             // We support only graph types with unique name at this point. Later we could implement UI to let the user to select from multiple graph types.
301                             if (candidates.size() == 1) {
302                                 Pair<Resource, String> result = candidates.iterator().next();
303                                 possibleGraphType = result.first;
304                                 newValue2 = result.second;
305                             }
306                         }
307
308                         ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue2, possibleGraphType);
309                         if (range != null) ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, range);
310                     }
311                 });
312             }
313         };
314         combo.setFocus();
315         editor.setEditor(combo, selectedItem, column);
316         combo.addListener(SWT.FocusOut, listener);
317         combo.addListener(SWT.Traverse, listener);
318     }
319
320     public void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
321         // Disallow unit editing for non-numeric configuration properties
322         if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
323             return;
324
325         int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
326         final Combo combo = new Combo(table, SWT.NONE | extraStyle);
327         String initialValue = selectedItem.getText(column);
328         List<String> units = new ArrayList<>( unitLibrary.getUnits() );
329         Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
330         int i = -1;
331         int selected = -1;
332         for (String unit : units) {
333             combo.add(unit);
334             if (unit.equals(initialValue))
335                 combo.select(i);
336         }
337         if (selected == -1)
338             combo.setText(initialValue);
339
340         org.eclipse.swt.widgets.Listener listener = 
341                 new org.eclipse.swt.widgets.Listener() {
342             @Override
343             public void handleEvent(Event e) {
344                 if(e.type == SWT.Traverse) {
345                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
346                         combo.dispose();
347                         e.doit = false;
348                         return;
349                     }
350                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
351                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
352                             || e.detail == SWT.TRAVERSE_MNEMONIC)
353                         return;
354                 }
355                 final String newValue = combo.getText();
356                 if(e.type == SWT.Traverse) {
357                     e.doit = false;
358                 }
359                 combo.dispose();
360
361                 if (propertyInfo.immutable)
362                     return;
363
364                 Simantics.getSession().async(new WriteRequest() {
365                     @Override
366                     public void perform(WriteGraph graph)
367                             throws DatabaseException {
368                         graph.markUndoPoint();
369                         ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
370                     }
371                 });
372             }
373         };
374         combo.setFocus();
375         editor.setEditor(combo, selectedItem, column);
376         combo.addListener(SWT.Deactivate, listener);
377         combo.addListener(SWT.Traverse, listener);
378     }
379
380     public void editValue(Table table, TableEditor editor,
381             final ComponentTypeViewerPropertyInfo propertyInfo,
382             TableItem selectedItem, int column,
383             final StringWriter writer,
384             final Function4<RequestProcessor, Resource, Resource, String, String> validator)
385     {
386         int extraStyle = writer == null ? SWT.READ_ONLY : 0;
387         final Text text = new Text(table, SWT.NONE | extraStyle);
388         text.setText(selectedItem.getText(column));
389         org.eclipse.swt.widgets.Listener listener = 
390                 new org.eclipse.swt.widgets.Listener() {
391             @Override
392             public void handleEvent(Event e) {
393                 if(e.type == SWT.Traverse) {
394                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
395                         text.dispose();
396                         e.doit = false;
397                         return;
398                     }
399                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
400                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
401                             || e.detail == SWT.TRAVERSE_MNEMONIC)
402                         return;
403                 }
404                 final String newValue = text.getText();
405                 if(e.type == SWT.Traverse) {
406                     e.doit = false;
407                 }
408                 text.dispose();
409
410                 if (validator != null) {
411                     String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
412                     if (error != null)
413                         return;
414                 }
415
416                 if (writer != null) {
417                     Simantics.getSession().async(new WriteRequest() {
418                         @Override
419                         public void perform(WriteGraph graph) throws DatabaseException {
420                             writer.perform(graph, newValue);
421                         }
422                     });
423                 }
424             }
425         };
426         text.selectAll();
427         text.setFocus();
428         editor.setEditor(text, selectedItem, column);
429         text.addListener(SWT.FocusOut, listener);
430         text.addListener(SWT.Traverse, listener);
431
432         if (validator != null) {
433             org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
434                 
435                 private ScheduledFuture<?> future;
436                 
437                 @Override
438                 public void handleEvent(Event e) {
439                     final String newValue = text.getText();
440                     if (future != null && !future.isCancelled())
441                         future.cancel(true);
442                     future = ThreadUtils.getNonBlockingWorkExecutor().schedule(() -> {
443                         String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
444                         if (!text.isDisposed()) {
445                             text.getDisplay().asyncExec(() -> {
446                                 if (!text.isDisposed()) {
447                                     if (error != null) {
448                                         text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
449                                         text.setToolTipText(error);
450                                         return;
451                                     } else {
452                                         text.setBackground(null);
453                                         text.setToolTipText(null);
454                                     }
455                                 }
456                                 
457                             });
458                         }
459                     }, 500, TimeUnit.MILLISECONDS);
460                 }
461             };
462             text.addListener(SWT.Modify, validationListener);
463         }
464     }
465
466     private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
467         try {
468             String rangeStr = (lowInclusive ? "[" : "(")  + minStr + ".." + maxStr + (highInclusive ? "]" : ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
469             return Range.valueOf(rangeStr);
470         } catch (IllegalArgumentException e) {
471             // Limits are invalid
472             throw new RangeException(e.getMessage(), e);
473         }
474     }
475     
476     private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
477         Combo rng = new Combo(parent, SWT.READ_ONLY);
478         rng.add(Messages.ComponentTypeViewerData_Inclusive);
479         rng.add(Messages.ComponentTypeViewerData_Exclusive);
480         rng.select(inclusive ? 0 : 1);
481         return rng;
482     }
483     
484     protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
485         // Disallow range editing when the property is not numeric
486         if (propertyInfo.numberType == null)
487             return;
488
489         int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
490
491         // Parse initial range value
492         Range range = null;
493         String rangeStr = selectedItem.getText(column);
494         try {
495             range = Range.valueOf(rangeStr);
496         } catch (RangeException ex) {
497             range = new Range(Limit.nolimit(), Limit.nolimit());
498         }
499
500         final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
501         GridLayoutFactory.fillDefaults().applyTo(shell);
502
503         Composite composite = new Composite(shell, SWT.NONE);
504         GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
505         GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
506
507         Label low = new Label(composite, SWT.NONE);
508         low.setText(Messages.ComponentTypeViewerData_MinimumValue);
509         final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
510         GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
511         final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
512         Label high = new Label(composite, SWT.NONE);
513         high.setText(Messages.ComponentTypeViewerData_MaximumValue);
514         final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
515         GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
516         final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
517
518         Composite buttonComposite = new Composite(shell, SWT.NONE);
519         GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
520         GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
521
522         Button ok = new Button(buttonComposite, SWT.NONE);
523         ok.setText(IDialogConstants.OK_LABEL);
524         GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
525         Button cancel = new Button(buttonComposite, SWT.NONE);
526         cancel.setText(IDialogConstants.CANCEL_LABEL);
527         GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
528
529         if (range.getLower().getValue() != null)
530             lowText.setText(range.getLower().getValue().toString());
531         if (range.getUpper().getValue() != null)
532             highText.setText(range.getUpper().getValue().toString());
533
534         shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
535             @Override
536             public void handleEvent(Event event) {
537                 shell.dispose();
538             }
539         });
540
541         ok.addSelectionListener(new SelectionAdapter() {
542             public void widgetSelected(SelectionEvent e) {
543                 try {
544                     final Range newRange = parseRange(propertyInfo.numberType,
545                             lowText.getText().trim(),
546                             highText.getText().trim(),
547                             lowSelector.getSelectionIndex() == 0 ? true : false,
548                             highSelector.getSelectionIndex() == 0 ? true : false);
549
550                     shell.dispose();
551
552                     if (propertyInfo.immutable)
553                         return;
554
555                     Simantics.getSession().async(new WriteRequest() {
556                         @Override
557                         public void perform(WriteGraph graph)
558                                 throws DatabaseException {
559                             graph.markUndoPoint();
560                             ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
561                         }
562                     });
563                 } catch (RangeException ex) {
564                     ErrorLogger.defaultLogError(ex);
565                 }
566             }
567         });
568         cancel.addSelectionListener(new SelectionAdapter() {
569             public void widgetSelected(SelectionEvent e) {
570                 shell.dispose();
571             }
572         });
573
574         shell.pack();
575         Point size = shell.getSize();
576
577         Display display = table.getDisplay();
578         Rectangle clientArea = display.getClientArea();
579         Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
580         Rectangle b = selectedItemBounds;
581         b.x = bt.x;
582         b.y = bt.y;
583         b.width = size.x;
584         b.height = size.y;
585         if ((b.x + b.width) > clientArea.width)
586             b.x -= b.x + b.width - clientArea.width;
587         if (b.height > clientArea.height)
588             b.height = clientArea.height;
589         if ((b.y + b.height) > clientArea.height)
590             b.y -= b.y + b.height - clientArea.height;
591
592         shell.setBounds(selectedItemBounds);
593         shell.open();
594     }
595
596     public void editMultilineText(Table table, TableEditor editor,
597             final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
598             Rectangle selectedItemBounds, int column, final StringWriter writer)
599     {
600         final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
601         GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
602         final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
603         GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
604         text.setText(selectedItem.getText(column));
605         org.eclipse.swt.widgets.Listener listener = 
606                 new org.eclipse.swt.widgets.Listener() {
607             @Override
608             public void handleEvent(Event e) {
609                 final String newValue = text.getText();
610
611                 if (e.type == SWT.Traverse) {
612                     if (e.detail == SWT.TRAVERSE_ESCAPE) {
613                         shell.dispose();
614                         e.doit = false;
615                         return;
616                     }
617                     if (e.detail == SWT.TRAVERSE_ARROW_NEXT
618                             || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
619                             || e.detail == SWT.TRAVERSE_MNEMONIC)
620                         return;
621                     if ((e.stateMask & SWT.CTRL) == 0)
622                         return;
623                     e.doit = false;
624                 }
625
626                 shell.dispose();
627
628                 if (propertyInfo.immutable)
629                     return;
630
631                 if (writer != null) {
632                     Simantics.getSession().async(new WriteRequest() {
633                         @Override
634                         public void perform(WriteGraph graph) throws DatabaseException {
635                             writer.perform(graph, newValue);
636                         }
637                     });
638                 }
639             }
640         };
641
642         String helpText = propertyInfo.immutable ? Messages.ComponentTypeViewerData_ESCToClose : Messages.ComponentTypeViewerData_CtrlEnterApplyChanges;
643         Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
644         GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
645         help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) ); //$NON-NLS-1$
646
647         Display display = table.getDisplay();
648         Rectangle clientArea = display.getClientArea();
649         Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
650         Rectangle b = selectedItemBounds;
651         b.x = bt.x;
652         b.y = bt.y;
653         b.height = 200;
654         if ((b.x + b.width) > clientArea.width)
655             b.x -= b.x + b.width - clientArea.width;
656         if (b.height > clientArea.height)
657             b.height = clientArea.height;
658         if ((b.y + b.height) > clientArea.height)
659             b.y -= b.y + b.height - clientArea.height;
660
661         shell.setBounds(selectedItemBounds);
662         shell.open();
663
664         text.selectAll();
665         text.setFocus();
666
667         text.addListener(SWT.Traverse, listener);
668         shell.addListener(SWT.Deactivate, listener);
669     }
670
671     private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
672         if (propertyName.equals(propertyInfo.name))
673             return null;
674         for (ComponentTypeViewerPropertyInfo info : properties) {
675             if (propertyName.equals(info.name))
676                 return NLS.bind(Messages.ComponentTypeViewerData_PropertyNameInUse, propertyName); 
677         }
678         for (NamedResource cp : connectionPoints) {
679             if (propertyName.equals(cp.getName()))
680                 return NLS.bind(Messages.ComponentTypeViewerData_NameInUse, propertyName ); 
681         }
682         Matcher m = namePattern.matcher(propertyName);
683         if (!m.matches())
684             return NLS.bind(Messages.ComponentTypeViewerData_ContainsInvalidCharacters, propertyName, namePattern.pattern());
685         return null;
686     }
687
688 }