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