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.layer0.Layer0; import org.simantics.modeling.userComponent.ComponentTypeCommands; 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) { 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 = validatePropertyName(propertyInfo, text.getText(), namePattern); 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 = validatePropertyName(propertyInfo, newValue, namePattern); 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)); } }); } }; 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 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 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 (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; } }