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