]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/componentTypeEditor/ComponentTypeViewerData.java
Allow client-logic in component type interface property name editing
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / componentTypeEditor / ComponentTypeViewerData.java
index 6a3e7f1482e81a54166eda050ea3f9438bfa2bae..e1f17996505f6d34ec2131f489f97552f0743327 100644 (file)
-package org.simantics.modeling.ui.componentTypeEditor;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.regex.Matcher;\r
-import java.util.regex.Pattern;\r
-\r
-import org.eclipse.jface.dialogs.IMessageProvider;\r
-import org.eclipse.jface.layout.GridDataFactory;\r
-import org.eclipse.jface.layout.GridLayoutFactory;\r
-import org.eclipse.swt.SWT;\r
-import org.eclipse.swt.custom.StyledText;\r
-import org.eclipse.swt.custom.TableEditor;\r
-import org.eclipse.swt.events.SelectionAdapter;\r
-import org.eclipse.swt.events.SelectionEvent;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.Rectangle;\r
-import org.eclipse.swt.widgets.Button;\r
-import org.eclipse.swt.widgets.Combo;\r
-import org.eclipse.swt.widgets.Composite;\r
-import org.eclipse.swt.widgets.Display;\r
-import org.eclipse.swt.widgets.Event;\r
-import org.eclipse.swt.widgets.Label;\r
-import org.eclipse.swt.widgets.Shell;\r
-import org.eclipse.swt.widgets.Table;\r
-import org.eclipse.swt.widgets.TableItem;\r
-import org.eclipse.swt.widgets.Text;\r
-import org.eclipse.ui.forms.widgets.Form;\r
-import org.eclipse.ui.forms.widgets.FormToolkit;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.type.NumberType;\r
-import org.simantics.databoard.units.internal.library.UnitLibrary;\r
-import org.simantics.databoard.util.Limit;\r
-import org.simantics.databoard.util.Range;\r
-import org.simantics.databoard.util.RangeException;\r
-import org.simantics.db.RequestProcessor;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.NamedResource;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.userComponent.ComponentTypeCommands;\r
-import org.simantics.scl.runtime.function.Function4;\r
-import org.simantics.utils.ui.ErrorLogger;\r
-\r
-public class ComponentTypeViewerData {\r
-    /**\r
-     * Used to validate property names.\r
-     */\r
-    public static final Pattern PROPERTY_NAME_PATTERN =\r
-            Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");\r
-\r
-    public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {\r
-        "Double",\r
-        "Integer",\r
-        "Float",\r
-        "String",\r
-        "Boolean",\r
-        "Long",\r
-        "[Double]",\r
-        "[Integer]",\r
-        "[Float]",\r
-        "[String]",\r
-        "[Boolean]",\r
-        "[Long]",\r
-        "Vector Double",\r
-        "Vector Integer",\r
-        "Vector Float",\r
-        "Vector String",\r
-        "Vector Boolean",\r
-        "Vector Long"        \r
-    };\r
-    \r
-    public Resource componentType;\r
-    public FormToolkit tk;\r
-    public Form form;\r
-    public UnitLibrary unitLibrary = UnitLibrary.createDefault();\r
-    public boolean readOnly;\r
-    public NamedResource[] connectionPoints;\r
-    public ComponentTypeViewerPropertyInfo[] properties;\r
-\r
-    public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {\r
-        this.tk = tk;\r
-        this.componentType = componentType;\r
-        this.form = form;\r
-    }\r
-    \r
-    public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,\r
-            Pattern namePattern) {\r
-        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
-        final Text text = new Text(table, SWT.NONE | extraStyle);\r
-        org.eclipse.swt.widgets.Listener listener = \r
-                new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                if (e.type == SWT.Dispose) {\r
-                    form.setMessage(null);\r
-                    return;\r
-                }\r
-\r
-                if (e.type == SWT.Modify) {\r
-                    // validate current name\r
-                    String error = validatePropertyName(propertyInfo, text.getText(), namePattern);\r
-                    if (error != null) {\r
-                        text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));\r
-                        form.setMessage(error, IMessageProvider.ERROR);\r
-                    } else {\r
-                        text.setBackground(null);\r
-                        form.setMessage(null);\r
-                    }\r
-                    return;\r
-                }\r
-\r
-                if (e.type == SWT.Traverse) {\r
-                    if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
-                        text.dispose();\r
-                        e.doit = false;\r
-                        return;\r
-                    }\r
-                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)\r
-                        return;\r
-                    e.doit = false;\r
-                }\r
-                final String newValue = text.getText();\r
-                text.dispose();\r
-\r
-                String error = validatePropertyName(propertyInfo, newValue, namePattern);\r
-                if (error != null)\r
-                    return;\r
-\r
-                if (propertyInfo.immutable)\r
-                    return;\r
-\r
-                Simantics.getSession().async(new WriteRequest() {\r
-                    @Override\r
-                    public void perform(WriteGraph graph)\r
-                            throws DatabaseException {\r
-                        graph.markUndoPoint();\r
-                        Layer0 L0 = Layer0.getInstance(graph);\r
-                        String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);\r
-                        String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";\r
-                        String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);\r
-                        boolean setLabel = oldLabel == null\r
-                                || oldLabel.isEmpty()\r
-                                || oldCamelCasedLabel.isEmpty()\r
-                                || oldCamelCasedLabel.equals(oldLabel);\r
-\r
-                        ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);\r
-                        if (setLabel)\r
-                            ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));\r
-                    }\r
-                });\r
-            }\r
-        };\r
-        text.addListener(SWT.Modify, listener);\r
-        text.addListener(SWT.Deactivate, listener);\r
-        text.addListener(SWT.Traverse, listener);\r
-        text.addListener(SWT.Dispose, listener);\r
-\r
-        text.setText(selectedItem.getText(column));\r
-        text.selectAll();\r
-        text.setFocus();\r
-\r
-        editor.setEditor(text, selectedItem, column);\r
-    }\r
-\r
-    public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {\r
-        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
-        final Combo combo = new Combo(table, SWT.NONE | extraStyle);\r
-        combo.setText(selectedItem.getText(column));\r
-        for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)\r
-            combo.add(suggestion);\r
-        org.eclipse.swt.widgets.Listener listener = \r
-                new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                if(e.type == SWT.Traverse) {\r
-                    if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
-                        combo.dispose();\r
-                        e.doit = false;\r
-                        return;\r
-                    }\r
-                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
-                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
-                            || e.detail == SWT.TRAVERSE_MNEMONIC)\r
-                        return;\r
-                }\r
-                final String newValue = combo.getText();\r
-                if (e.type == SWT.Traverse) {\r
-                    e.doit = false;\r
-                }\r
-                combo.dispose();\r
-\r
-                if (propertyInfo.immutable)\r
-                    return;\r
-\r
-                Simantics.getSession().async(new WriteRequest() {\r
-                    @Override\r
-                    public void perform(WriteGraph graph)\r
-                            throws DatabaseException {\r
-                        graph.markUndoPoint();\r
-                        ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);\r
-                    }\r
-                });\r
-            }\r
-        };\r
-        combo.setFocus();\r
-        editor.setEditor(combo, selectedItem, column);\r
-        combo.addListener(SWT.FocusOut, listener);\r
-        combo.addListener(SWT.Traverse, listener);\r
-    }\r
-\r
-    protected void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {\r
-        // Disallow unit editing for non-numeric configuration properties\r
-        if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)\r
-            return;\r
-\r
-        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
-        final Combo combo = new Combo(table, SWT.NONE | extraStyle);\r
-        String initialValue = selectedItem.getText(column);\r
-        List<String> units = new ArrayList<>( unitLibrary.getUnits() );\r
-        Collections.sort(units, String.CASE_INSENSITIVE_ORDER);\r
-        int i = -1;\r
-        int selected = -1;\r
-        for (String unit : units) {\r
-            combo.add(unit);\r
-            if (unit.equals(initialValue))\r
-                combo.select(i);\r
-        }\r
-        if (selected == -1)\r
-            combo.setText(initialValue);\r
-\r
-        org.eclipse.swt.widgets.Listener listener = \r
-                new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                if(e.type == SWT.Traverse) {\r
-                    if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
-                        combo.dispose();\r
-                        e.doit = false;\r
-                        return;\r
-                    }\r
-                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
-                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
-                            || e.detail == SWT.TRAVERSE_MNEMONIC)\r
-                        return;\r
-                }\r
-                final String newValue = combo.getText();\r
-                if(e.type == SWT.Traverse) {\r
-                    e.doit = false;\r
-                }\r
-                combo.dispose();\r
-\r
-                if (propertyInfo.immutable)\r
-                    return;\r
-\r
-                Simantics.getSession().async(new WriteRequest() {\r
-                    @Override\r
-                    public void perform(WriteGraph graph)\r
-                            throws DatabaseException {\r
-                        graph.markUndoPoint();\r
-                        ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);\r
-                    }\r
-                });\r
-            }\r
-        };\r
-        combo.setFocus();\r
-        editor.setEditor(combo, selectedItem, column);\r
-        combo.addListener(SWT.Deactivate, listener);\r
-        combo.addListener(SWT.Traverse, listener);\r
-    }\r
-\r
-    public void editValue(Table table, TableEditor editor,\r
-            final ComponentTypeViewerPropertyInfo propertyInfo,\r
-            TableItem selectedItem, int column,\r
-            final StringWriter writer,\r
-            final Function4<RequestProcessor, Resource, Resource, String, String> validator)\r
-    {\r
-        int extraStyle = writer == null ? SWT.READ_ONLY : 0;\r
-        final Text text = new Text(table, SWT.NONE | extraStyle);\r
-        text.setText(selectedItem.getText(column));\r
-        org.eclipse.swt.widgets.Listener listener = \r
-                new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                if(e.type == SWT.Traverse) {\r
-                    if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
-                        text.dispose();\r
-                        e.doit = false;\r
-                        return;\r
-                    }\r
-                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
-                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
-                            || e.detail == SWT.TRAVERSE_MNEMONIC)\r
-                        return;\r
-                }\r
-                final String newValue = text.getText();\r
-                if(e.type == SWT.Traverse) {\r
-                    e.doit = false;\r
-                }\r
-                text.dispose();\r
-\r
-                if (writer != null) {\r
-                    Simantics.getSession().async(new WriteRequest() {\r
-                        @Override\r
-                        public void perform(WriteGraph graph) throws DatabaseException {\r
-                            writer.perform(graph, newValue);\r
-                        }\r
-                    });\r
-                }\r
-            }\r
-        };\r
-        text.selectAll();\r
-        text.setFocus();\r
-        editor.setEditor(text, selectedItem, column);\r
-        text.addListener(SWT.FocusOut, listener);\r
-        text.addListener(SWT.Traverse, listener);\r
-\r
-        if (validator != null) {\r
-            org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {\r
-                @Override\r
-                public void handleEvent(Event e) {\r
-                    final String newValue = text.getText();\r
-                    String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);\r
-                    if (error != null) {\r
-                        text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));\r
-                        text.setToolTipText(error);\r
-                        return;\r
-                    } else {\r
-                        text.setBackground(null);\r
-                        text.setToolTipText(null);\r
-                    }\r
-                }\r
-            };\r
-            text.addListener(SWT.Modify, validationListener);\r
-        }\r
-    }\r
-\r
-    private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {\r
-        try {\r
-            String rangeStr = (lowInclusive ? "[" : "(")  + minStr + ".." + maxStr + (highInclusive ? "]" : ")");\r
-            return Range.valueOf(rangeStr);\r
-        } catch (IllegalArgumentException e) {\r
-            // Limits are invalid\r
-            throw new RangeException(e.getMessage(), e);\r
-        }\r
-    }\r
-    \r
-    private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {\r
-        Combo rng = new Combo(parent, SWT.READ_ONLY);\r
-        rng.add("Inclusive");\r
-        rng.add("Exclusive");\r
-        rng.select(inclusive ? 0 : 1);\r
-        return rng;\r
-    }\r
-    \r
-    protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {\r
-        // Disallow range editing when the property is not numeric\r
-        if (propertyInfo.numberType == null)\r
-            return;\r
-\r
-        int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;\r
-\r
-        // Parse initial range value\r
-        Range range = null;\r
-        String rangeStr = selectedItem.getText(column);\r
-        try {\r
-            range = Range.valueOf(rangeStr);\r
-        } catch (RangeException ex) {\r
-            range = new Range(Limit.nolimit(), Limit.nolimit());\r
-        }\r
-\r
-        final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);\r
-        GridLayoutFactory.fillDefaults().applyTo(shell);\r
-\r
-        Composite composite = new Composite(shell, SWT.NONE);\r
-        GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);\r
-        GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);\r
-\r
-        Label low = new Label(composite, SWT.NONE);\r
-        low.setText("Minimum Value:");\r
-        final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);\r
-        GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);\r
-        final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());\r
-        Label high = new Label(composite, SWT.NONE);\r
-        high.setText("Maximum Value:");\r
-        final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);\r
-        GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);\r
-        final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());\r
-\r
-        Composite buttonComposite = new Composite(shell, SWT.NONE);\r
-        GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);\r
-        GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);\r
-\r
-        Button ok = new Button(buttonComposite, SWT.NONE);\r
-        ok.setText("&OK");\r
-        GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);\r
-        Button cancel = new Button(buttonComposite, SWT.NONE);\r
-        cancel.setText("&Cancel");\r
-        GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);\r
-\r
-        if (range.getLower().getValue() != null)\r
-            lowText.setText(range.getLower().getValue().toString());\r
-        if (range.getUpper().getValue() != null)\r
-            highText.setText(range.getUpper().getValue().toString());\r
-\r
-        shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event event) {\r
-                shell.dispose();\r
-            }\r
-        });\r
-\r
-        ok.addSelectionListener(new SelectionAdapter() {\r
-            public void widgetSelected(SelectionEvent e) {\r
-                try {\r
-                    final Range newRange = parseRange(propertyInfo.numberType,\r
-                            lowText.getText().trim(),\r
-                            highText.getText().trim(),\r
-                            lowSelector.getSelectionIndex() == 0 ? true : false,\r
-                            highSelector.getSelectionIndex() == 0 ? true : false);\r
-\r
-                    shell.dispose();\r
-\r
-                    if (propertyInfo.immutable)\r
-                        return;\r
-\r
-                    Simantics.getSession().async(new WriteRequest() {\r
-                        @Override\r
-                        public void perform(WriteGraph graph)\r
-                                throws DatabaseException {\r
-                            graph.markUndoPoint();\r
-                            ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());\r
-                        }\r
-                    });\r
-                } catch (RangeException ex) {\r
-                    ErrorLogger.defaultLogError(ex);\r
-                }\r
-            }\r
-        });\r
-        cancel.addSelectionListener(new SelectionAdapter() {\r
-            public void widgetSelected(SelectionEvent e) {\r
-                shell.dispose();\r
-            }\r
-        });\r
-\r
-        shell.pack();\r
-        Point size = shell.getSize();\r
-\r
-        Display display = table.getDisplay();\r
-        Rectangle clientArea = display.getClientArea();\r
-        Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);\r
-        Rectangle b = selectedItemBounds;\r
-        b.x = bt.x;\r
-        b.y = bt.y;\r
-        b.width = size.x;\r
-        b.height = size.y;\r
-        if ((b.x + b.width) > clientArea.width)\r
-            b.x -= b.x + b.width - clientArea.width;\r
-        if (b.height > clientArea.height)\r
-            b.height = clientArea.height;\r
-        if ((b.y + b.height) > clientArea.height)\r
-            b.y -= b.y + b.height - clientArea.height;\r
-\r
-        shell.setBounds(selectedItemBounds);\r
-        shell.open();\r
-    }\r
-\r
-    protected void editMultilineText(Table table, TableEditor editor,\r
-            final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,\r
-            Rectangle selectedItemBounds, int column, final StringWriter writer)\r
-    {\r
-        final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);\r
-        GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);\r
-        final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));\r
-        GridDataFactory.fillDefaults().grab(true, true).applyTo(text);\r
-        text.setText(selectedItem.getText(column));\r
-        org.eclipse.swt.widgets.Listener listener = \r
-                new org.eclipse.swt.widgets.Listener() {\r
-            @Override\r
-            public void handleEvent(Event e) {\r
-                final String newValue = text.getText();\r
-\r
-                if (e.type == SWT.Traverse) {\r
-                    if (e.detail == SWT.TRAVERSE_ESCAPE) {\r
-                        shell.dispose();\r
-                        e.doit = false;\r
-                        return;\r
-                    }\r
-                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT\r
-                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS\r
-                            || e.detail == SWT.TRAVERSE_MNEMONIC)\r
-                        return;\r
-                    if ((e.stateMask & SWT.CTRL) == 0)\r
-                        return;\r
-                    e.doit = false;\r
-                }\r
-\r
-                shell.dispose();\r
-\r
-                if (propertyInfo.immutable)\r
-                    return;\r
-\r
-                if (writer != null) {\r
-                    Simantics.getSession().async(new WriteRequest() {\r
-                        @Override\r
-                        public void perform(WriteGraph graph) throws DatabaseException {\r
-                            writer.perform(graph, newValue);\r
-                        }\r
-                    });\r
-                }\r
-            }\r
-        };\r
-\r
-        String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";\r
-        Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);\r
-        GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);\r
-        help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );\r
-\r
-        Display display = table.getDisplay();\r
-        Rectangle clientArea = display.getClientArea();\r
-        Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);\r
-        Rectangle b = selectedItemBounds;\r
-        b.x = bt.x;\r
-        b.y = bt.y;\r
-        b.height = 200;\r
-        if ((b.x + b.width) > clientArea.width)\r
-            b.x -= b.x + b.width - clientArea.width;\r
-        if (b.height > clientArea.height)\r
-            b.height = clientArea.height;\r
-        if ((b.y + b.height) > clientArea.height)\r
-            b.y -= b.y + b.height - clientArea.height;\r
-\r
-        shell.setBounds(selectedItemBounds);\r
-        shell.open();\r
-\r
-        text.selectAll();\r
-        text.setFocus();\r
-\r
-        text.addListener(SWT.Traverse, listener);\r
-        shell.addListener(SWT.Deactivate, listener);\r
-    }\r
-\r
-    private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {\r
-        if (propertyName.equals(propertyInfo.name))\r
-            return null;\r
-        for (ComponentTypeViewerPropertyInfo info : properties) {\r
-            if (propertyName.equals(info.name))\r
-                return "Property name '" + propertyName + "' is already in use.";\r
-        }\r
-        for (NamedResource cp : connectionPoints) {\r
-            if (propertyName.equals(cp.getName()))\r
-                return "Name '" + propertyName + "' is already used for a terminal.";\r
-        }\r
-        Matcher m = namePattern.matcher(propertyName);\r
-        if (!m.matches())\r
-            return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "\r
-                    + namePattern.pattern() + ".";\r
-        return null;\r
-    }\r
-\r
-}\r
+package org.simantics.modeling.ui.componentTypeEditor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.TableEditor;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.forms.widgets.Form;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.simantics.Simantics;
+import org.simantics.databoard.type.NumberType;
+import org.simantics.databoard.units.internal.library.UnitLibrary;
+import org.simantics.databoard.util.Limit;
+import org.simantics.databoard.util.Range;
+import org.simantics.databoard.util.RangeException;
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.NamedResource;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.function.DbConsumer;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.userComponent.ComponentTypeCommands;
+import org.simantics.scl.runtime.function.Function2;
+import org.simantics.scl.runtime.function.Function4;
+import org.simantics.utils.ui.ErrorLogger;
+
+public class ComponentTypeViewerData {
+    /**
+     * Used to validate property names.
+     */
+    public static final Pattern PROPERTY_NAME_PATTERN =
+            Pattern.compile("([a-z]|_[0-9a-zA-Z_])[0-9a-zA-Z_]*");
+
+    public static final String[] PROPERTY_TYPE_SUGGESTIONS = new String[] {
+        "Double",
+        "Integer",
+        "Float",
+        "String",
+        "Boolean",
+        "Long",
+        "[Double]",
+        "[Integer]",
+        "[Float]",
+        "[String]",
+        "[Boolean]",
+        "[Long]",
+        "Vector Double",
+        "Vector Integer",
+        "Vector Float",
+        "Vector String",
+        "Vector Boolean",
+        "Vector Long"        
+    };
+    
+    public Resource componentType;
+    public FormToolkit tk;
+    public Form form;
+    public UnitLibrary unitLibrary = UnitLibrary.createDefault();
+    public boolean readOnly;
+    public NamedResource[] connectionPoints;
+    public ComponentTypeViewerPropertyInfo[] properties;
+
+    public ComponentTypeViewerData(FormToolkit tk, Resource componentType, Form form) {
+        this.tk = tk;
+        this.componentType = componentType;
+        this.form = form;
+    }
+
+    public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+            Pattern namePattern) {
+        editName(table, editor, propertyInfo, selectedItem, column, namePattern, null);
+    }
+
+    public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+            Pattern namePattern, DbConsumer<WriteGraph> extraWriter) {
+        editName(table, editor, propertyInfo, selectedItem, column,
+                (pInfo, name) -> validatePropertyName(pInfo, name, namePattern),
+                extraWriter);
+    }
+
+    public void editName(Table table, TableEditor editor, ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+            Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator)
+    {
+        editName(table, editor, propertyInfo, selectedItem, column, nameValidator, null);
+    }
+
+    public void editName(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column,
+            Function2<ComponentTypeViewerPropertyInfo, String, String> nameValidator, DbConsumer<WriteGraph> extraWriter) {
+        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+        final Text text = new Text(table, SWT.NONE | extraStyle);
+        org.eclipse.swt.widgets.Listener listener = 
+                new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if (e.type == SWT.Dispose) {
+                    form.setMessage(null);
+                    return;
+                }
+
+                if (e.type == SWT.Modify) {
+                    // validate current name
+                    String error = nameValidator.apply(propertyInfo, text.getText());
+                    if (error != null) {
+                        text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
+                        form.setMessage(error, IMessageProvider.ERROR);
+                    } else {
+                        text.setBackground(null);
+                        form.setMessage(null);
+                    }
+                    return;
+                }
+
+                if (e.type == SWT.Traverse) {
+                    if (e.detail == SWT.TRAVERSE_ESCAPE) {
+                        text.dispose();
+                        e.doit = false;
+                        return;
+                    }
+                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_MNEMONIC)
+                        return;
+                    e.doit = false;
+                }
+                final String newValue = text.getText();
+                text.dispose();
+
+                String error = nameValidator.apply(propertyInfo, newValue);
+                if (error != null)
+                    return;
+
+                if (propertyInfo.immutable)
+                    return;
+
+                Simantics.getSession().async(new WriteRequest() {
+                    @Override
+                    public void perform(WriteGraph graph)
+                            throws DatabaseException {
+                        graph.markUndoPoint();
+                        Layer0 L0 = Layer0.getInstance(graph);
+                        String prevName = graph.getPossibleRelatedValue2(propertyInfo.resource, L0.HasName);
+                        String oldCamelCasedLabel = prevName != null ? ComponentTypeCommands.camelCaseNameToLabel(prevName) : "";
+                        String oldLabel = graph.getPossibleRelatedValue(propertyInfo.resource, L0.HasLabel);
+                        boolean setLabel = oldLabel == null
+                                || oldLabel.isEmpty()
+                                || oldCamelCasedLabel.isEmpty()
+                                || oldCamelCasedLabel.equals(oldLabel);
+
+                        ComponentTypeCommands.rename(graph, propertyInfo.resource, newValue);
+                        if (setLabel)
+                            ComponentTypeCommands.setLabel(graph, propertyInfo.resource, ComponentTypeCommands.camelCaseNameToLabel(newValue));
+
+                        if (extraWriter != null)
+                            extraWriter.accept(graph);
+                    }
+                });
+            }
+        };
+        text.addListener(SWT.Modify, listener);
+        text.addListener(SWT.Deactivate, listener);
+        text.addListener(SWT.Traverse, listener);
+        text.addListener(SWT.Dispose, listener);
+
+        text.setText(selectedItem.getText(column));
+        text.selectAll();
+        text.setFocus();
+
+        editor.setEditor(text, selectedItem, column);
+    }
+
+    public void editType(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column, final boolean convertDefaultValue) {
+        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+        final Combo combo = new Combo(table, SWT.NONE | extraStyle);
+        combo.setText(selectedItem.getText(column));
+        for(String suggestion : PROPERTY_TYPE_SUGGESTIONS)
+            combo.add(suggestion);
+        org.eclipse.swt.widgets.Listener listener = 
+                new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if(e.type == SWT.Traverse) {
+                    if (e.detail == SWT.TRAVERSE_ESCAPE) {
+                        combo.dispose();
+                        e.doit = false;
+                        return;
+                    }
+                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+                            || e.detail == SWT.TRAVERSE_MNEMONIC)
+                        return;
+                }
+                final String newValue = combo.getText();
+                if (e.type == SWT.Traverse) {
+                    e.doit = false;
+                }
+                combo.dispose();
+
+                if (propertyInfo.immutable)
+                    return;
+
+                Simantics.getSession().async(new WriteRequest() {
+                    @Override
+                    public void perform(WriteGraph graph)
+                            throws DatabaseException {
+                        graph.markUndoPoint();
+                        ComponentTypeCommands.editType(graph, componentType, propertyInfo.resource, convertDefaultValue, newValue);
+                    }
+                });
+            }
+        };
+        combo.setFocus();
+        editor.setEditor(combo, selectedItem, column);
+        combo.addListener(SWT.FocusOut, listener);
+        combo.addListener(SWT.Traverse, listener);
+    }
+
+    protected void editUnit(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, int column) {
+        // Disallow unit editing for non-numeric configuration properties
+        if (propertyInfo.numberType == null && propertyInfo.sectionSpecificData == null)
+            return;
+
+        int extraStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+        final Combo combo = new Combo(table, SWT.NONE | extraStyle);
+        String initialValue = selectedItem.getText(column);
+        List<String> units = new ArrayList<>( unitLibrary.getUnits() );
+        Collections.sort(units, String.CASE_INSENSITIVE_ORDER);
+        int i = -1;
+        int selected = -1;
+        for (String unit : units) {
+            combo.add(unit);
+            if (unit.equals(initialValue))
+                combo.select(i);
+        }
+        if (selected == -1)
+            combo.setText(initialValue);
+
+        org.eclipse.swt.widgets.Listener listener = 
+                new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if(e.type == SWT.Traverse) {
+                    if (e.detail == SWT.TRAVERSE_ESCAPE) {
+                        combo.dispose();
+                        e.doit = false;
+                        return;
+                    }
+                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+                            || e.detail == SWT.TRAVERSE_MNEMONIC)
+                        return;
+                }
+                final String newValue = combo.getText();
+                if(e.type == SWT.Traverse) {
+                    e.doit = false;
+                }
+                combo.dispose();
+
+                if (propertyInfo.immutable)
+                    return;
+
+                Simantics.getSession().async(new WriteRequest() {
+                    @Override
+                    public void perform(WriteGraph graph)
+                            throws DatabaseException {
+                        graph.markUndoPoint();
+                        ComponentTypeCommands.setUnit(graph, componentType, propertyInfo.resource, newValue);
+                    }
+                });
+            }
+        };
+        combo.setFocus();
+        editor.setEditor(combo, selectedItem, column);
+        combo.addListener(SWT.Deactivate, listener);
+        combo.addListener(SWT.Traverse, listener);
+    }
+
+    public void editValue(Table table, TableEditor editor,
+            final ComponentTypeViewerPropertyInfo propertyInfo,
+            TableItem selectedItem, int column,
+            final StringWriter writer,
+            final Function4<RequestProcessor, Resource, Resource, String, String> validator)
+    {
+        int extraStyle = writer == null ? SWT.READ_ONLY : 0;
+        final Text text = new Text(table, SWT.NONE | extraStyle);
+        text.setText(selectedItem.getText(column));
+        org.eclipse.swt.widgets.Listener listener = 
+                new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                if(e.type == SWT.Traverse) {
+                    if (e.detail == SWT.TRAVERSE_ESCAPE) {
+                        text.dispose();
+                        e.doit = false;
+                        return;
+                    }
+                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+                            || e.detail == SWT.TRAVERSE_MNEMONIC)
+                        return;
+                }
+                final String newValue = text.getText();
+                if(e.type == SWT.Traverse) {
+                    e.doit = false;
+                }
+                text.dispose();
+
+                if (validator != null) {
+                    String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
+                    if (error != null)
+                        return;
+                }
+
+                if (writer != null) {
+                    Simantics.getSession().async(new WriteRequest() {
+                        @Override
+                        public void perform(WriteGraph graph) throws DatabaseException {
+                            writer.perform(graph, newValue);
+                        }
+                    });
+                }
+            }
+        };
+        text.selectAll();
+        text.setFocus();
+        editor.setEditor(text, selectedItem, column);
+        text.addListener(SWT.FocusOut, listener);
+        text.addListener(SWT.Traverse, listener);
+
+        if (validator != null) {
+            org.eclipse.swt.widgets.Listener validationListener = new org.eclipse.swt.widgets.Listener() {
+                @Override
+                public void handleEvent(Event e) {
+                    final String newValue = text.getText();
+                    String error = validator.apply(Simantics.getSession(), componentType, propertyInfo.resource, newValue);
+                    if (error != null) {
+                        text.setBackground(text.getDisplay().getSystemColor(SWT.COLOR_RED));
+                        text.setToolTipText(error);
+                        return;
+                    } else {
+                        text.setBackground(null);
+                        text.setToolTipText(null);
+                    }
+                }
+            };
+            text.addListener(SWT.Modify, validationListener);
+        }
+    }
+
+    private Range parseRange(NumberType numberType, String minStr, String maxStr, boolean lowInclusive, boolean highInclusive) throws RangeException {
+        try {
+            String rangeStr = (lowInclusive ? "[" : "(")  + minStr + ".." + maxStr + (highInclusive ? "]" : ")");
+            return Range.valueOf(rangeStr);
+        } catch (IllegalArgumentException e) {
+            // Limits are invalid
+            throw new RangeException(e.getMessage(), e);
+        }
+    }
+    
+    private static Combo createRangeInclusionCombo(Composite parent, boolean inclusive) {
+        Combo rng = new Combo(parent, SWT.READ_ONLY);
+        rng.add("Inclusive");
+        rng.add("Exclusive");
+        rng.select(inclusive ? 0 : 1);
+        return rng;
+    }
+    
+    protected void editRange(Table table, TableEditor editor, final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem, Rectangle selectedItemBounds, int column) {
+        // Disallow range editing when the property is not numeric
+        if (propertyInfo.numberType == null)
+            return;
+
+        int extraTextStyle = propertyInfo.immutable ? SWT.READ_ONLY : 0;
+
+        // Parse initial range value
+        Range range = null;
+        String rangeStr = selectedItem.getText(column);
+        try {
+            range = Range.valueOf(rangeStr);
+        } catch (RangeException ex) {
+            range = new Range(Limit.nolimit(), Limit.nolimit());
+        }
+
+        final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
+        GridLayoutFactory.fillDefaults().applyTo(shell);
+
+        Composite composite = new Composite(shell, SWT.NONE);
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);
+        GridLayoutFactory.swtDefaults().numColumns(3).applyTo(composite);
+
+        Label low = new Label(composite, SWT.NONE);
+        low.setText("Minimum Value:");
+        final Text lowText = new Text(composite, SWT.BORDER | extraTextStyle);
+        GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(lowText);
+        final Combo lowSelector = createRangeInclusionCombo(composite, !range.getLower().isExclusive());
+        Label high = new Label(composite, SWT.NONE);
+        high.setText("Maximum Value:");
+        final Text highText = new Text(composite, SWT.BORDER | extraTextStyle);
+        GridDataFactory.fillDefaults().grab(true, false).hint(100, SWT.DEFAULT).applyTo(highText);
+        final Combo highSelector = createRangeInclusionCombo(composite, !range.getUpper().isExclusive());
+
+        Composite buttonComposite = new Composite(shell, SWT.NONE);
+        GridDataFactory.fillDefaults().grab(true, false).align(SWT.TRAIL, SWT.FILL).applyTo(buttonComposite);
+        GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(true).applyTo(buttonComposite);
+
+        Button ok = new Button(buttonComposite, SWT.NONE);
+        ok.setText("&OK");
+        GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
+        Button cancel = new Button(buttonComposite, SWT.NONE);
+        cancel.setText("&Cancel");
+        GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).applyTo(ok);
+
+        if (range.getLower().getValue() != null)
+            lowText.setText(range.getLower().getValue().toString());
+        if (range.getUpper().getValue() != null)
+            highText.setText(range.getUpper().getValue().toString());
+
+        shell.addListener(SWT.Deactivate, new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event event) {
+                shell.dispose();
+            }
+        });
+
+        ok.addSelectionListener(new SelectionAdapter() {
+            public void widgetSelected(SelectionEvent e) {
+                try {
+                    final Range newRange = parseRange(propertyInfo.numberType,
+                            lowText.getText().trim(),
+                            highText.getText().trim(),
+                            lowSelector.getSelectionIndex() == 0 ? true : false,
+                            highSelector.getSelectionIndex() == 0 ? true : false);
+
+                    shell.dispose();
+
+                    if (propertyInfo.immutable)
+                        return;
+
+                    Simantics.getSession().async(new WriteRequest() {
+                        @Override
+                        public void perform(WriteGraph graph)
+                                throws DatabaseException {
+                            graph.markUndoPoint();
+                            ComponentTypeCommands.setRange(graph, componentType, propertyInfo.resource, newRange == null ? null : newRange.toString());
+                        }
+                    });
+                } catch (RangeException ex) {
+                    ErrorLogger.defaultLogError(ex);
+                }
+            }
+        });
+        cancel.addSelectionListener(new SelectionAdapter() {
+            public void widgetSelected(SelectionEvent e) {
+                shell.dispose();
+            }
+        });
+
+        shell.pack();
+        Point size = shell.getSize();
+
+        Display display = table.getDisplay();
+        Rectangle clientArea = display.getClientArea();
+        Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
+        Rectangle b = selectedItemBounds;
+        b.x = bt.x;
+        b.y = bt.y;
+        b.width = size.x;
+        b.height = size.y;
+        if ((b.x + b.width) > clientArea.width)
+            b.x -= b.x + b.width - clientArea.width;
+        if (b.height > clientArea.height)
+            b.height = clientArea.height;
+        if ((b.y + b.height) > clientArea.height)
+            b.y -= b.y + b.height - clientArea.height;
+
+        shell.setBounds(selectedItemBounds);
+        shell.open();
+    }
+
+    protected void editMultilineText(Table table, TableEditor editor,
+            final ComponentTypeViewerPropertyInfo propertyInfo, TableItem selectedItem,
+            Rectangle selectedItemBounds, int column, final StringWriter writer)
+    {
+        final Shell shell = new Shell(table.getShell(), SWT.ON_TOP);
+        GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(shell);
+        final StyledText text = new StyledText(shell, SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | (propertyInfo.immutable ? SWT.READ_ONLY : 0));
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(text);
+        text.setText(selectedItem.getText(column));
+        org.eclipse.swt.widgets.Listener listener = 
+                new org.eclipse.swt.widgets.Listener() {
+            @Override
+            public void handleEvent(Event e) {
+                final String newValue = text.getText();
+
+                if (e.type == SWT.Traverse) {
+                    if (e.detail == SWT.TRAVERSE_ESCAPE) {
+                        shell.dispose();
+                        e.doit = false;
+                        return;
+                    }
+                    if (e.detail == SWT.TRAVERSE_ARROW_NEXT
+                            || e.detail == SWT.TRAVERSE_ARROW_PREVIOUS
+                            || e.detail == SWT.TRAVERSE_MNEMONIC)
+                        return;
+                    if ((e.stateMask & SWT.CTRL) == 0)
+                        return;
+                    e.doit = false;
+                }
+
+                shell.dispose();
+
+                if (propertyInfo.immutable)
+                    return;
+
+                if (writer != null) {
+                    Simantics.getSession().async(new WriteRequest() {
+                        @Override
+                        public void perform(WriteGraph graph) throws DatabaseException {
+                            writer.perform(graph, newValue);
+                        }
+                    });
+                }
+            }
+        };
+
+        String helpText = propertyInfo.immutable ? "ESC to close." : "Ctrl+Enter to apply changes, ESC to cancel.";
+        Label help = tk.createLabel(shell, helpText, SWT.BORDER | SWT.FLAT);
+        GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(help);
+        help.setForeground( tk.getColors().createColor( "fg", tk.getColors().getSystemColor(SWT.COLOR_LIST_SELECTION) ) );
+
+        Display display = table.getDisplay();
+        Rectangle clientArea = display.getClientArea();
+        Point bt = table.toDisplay(selectedItemBounds.x, selectedItemBounds.y);
+        Rectangle b = selectedItemBounds;
+        b.x = bt.x;
+        b.y = bt.y;
+        b.height = 200;
+        if ((b.x + b.width) > clientArea.width)
+            b.x -= b.x + b.width - clientArea.width;
+        if (b.height > clientArea.height)
+            b.height = clientArea.height;
+        if ((b.y + b.height) > clientArea.height)
+            b.y -= b.y + b.height - clientArea.height;
+
+        shell.setBounds(selectedItemBounds);
+        shell.open();
+
+        text.selectAll();
+        text.setFocus();
+
+        text.addListener(SWT.Traverse, listener);
+        shell.addListener(SWT.Deactivate, listener);
+    }
+
+    private String validatePropertyName(ComponentTypeViewerPropertyInfo propertyInfo, String propertyName, Pattern namePattern) {
+        if (propertyName.equals(propertyInfo.name))
+            return null;
+        for (ComponentTypeViewerPropertyInfo info : properties) {
+            if (propertyName.equals(info.name))
+                return "Property name '" + propertyName + "' is already in use.";
+        }
+        for (NamedResource cp : connectionPoints) {
+            if (propertyName.equals(cp.getName()))
+                return "Name '" + propertyName + "' is already used for a terminal.";
+        }
+        Matcher m = namePattern.matcher(propertyName);
+        if (!m.matches())
+            return "Property name '" + propertyName + "' contains invalid characters, does not match pattern "
+                    + namePattern.pattern() + ".";
+        return null;
+    }
+
+}