package org.simantics.databoard.forms; import java.util.ArrayList; import java.util.List; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.simantics.databoard.Accessors; import org.simantics.databoard.Bindings; import org.simantics.databoard.accessor.Accessor; import org.simantics.databoard.accessor.BooleanAccessor; import org.simantics.databoard.accessor.RecordAccessor; import org.simantics.databoard.accessor.StringAccessor; import org.simantics.databoard.accessor.UnionAccessor; import org.simantics.databoard.accessor.error.AccessorConstructionException; import org.simantics.databoard.accessor.error.AccessorException; import org.simantics.databoard.accessor.error.ReferenceException; import org.simantics.databoard.accessor.reference.ChildReference; import org.simantics.databoard.accessor.reference.LabelReference; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.NumberBinding; import org.simantics.databoard.binding.RecordBinding; import org.simantics.databoard.binding.StringBinding; import org.simantics.databoard.binding.UnionBinding; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.mutable.TaggedObject; import org.simantics.databoard.parser.repository.DataTypeSyntaxError; import org.simantics.databoard.type.BooleanType; import org.simantics.databoard.type.Component; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.type.NumberType; import org.simantics.databoard.type.RecordType; import org.simantics.databoard.type.StringType; import org.simantics.databoard.type.UnionType; import org.simantics.databoard.util.Bean; import org.simantics.databoard.util.StringUtil; import org.simantics.databoard.util.URIUtil; /** * Databoard form creates SWT user interface from databoard record type. * * See DataboardFormExample for example usage. * * @author toni.kalajainen@semantum.fi */ public class DataboardForm { public static final Datatype TEXTBOX; public static final Datatype PASSWORD; public int column1Width = -1; private RecordType allfields = new RecordType(); /** * Make file type * * @param namesAndExts array of names and extensions * @param exts * @return */ public static StringType fileSaveDialog(String...namesAndExts) { StringType result = new StringType(); result.metadata.put("style", "filesave"); StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); for (int i=0; i0) sb.append(','); sb.append(namesAndExts[i]); } result.metadata.put("filetypes", sb1.toString()); result.metadata.put("fileexts", sb2.toString()); return result; } /** * Make file type * * @param namesAndExts array of names and extensions * @param exts * @return */ public static StringType fileOpenDialog(String...namesAndExts) { StringType result = new StringType(); result.metadata.put("style", "fileopen"); StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); for (int i=0; i0) sb.append(','); sb.append(namesAndExts[i]); } result.metadata.put("filetypes", sb1.toString()); result.metadata.put("fileexts", sb2.toString()); return result; } public static StringType directoryDialog() { StringType result = new StringType(); result.metadata.put("style", "directory"); return result; } public DataboardForm() { } public void setFirstColumnWidth(int width) { column1Width = width; } /** * Validate the fields for valid values. * StringTypes can be restricted with regular expressions. * NumberTypes with value ranges. * * @param composite * @return list of problems */ public List validate( Composite composite ) { List result = new ArrayList(); _validateFields(composite, result); return result; } public static List toStrings(List problems) { List result = new ArrayList( problems.size() ); for (Problem p : problems) result.add(p.error); return result; } public static class Problem extends Bean { /** Reference to the field with problem */ public String fieldReference; /** The field with problem */ public transient Control control; /** The validation error */ public String error; } protected void _validateFields( Control control, List result ) { // Read this control Object data = control.getData(); if ( data != null && data instanceof String ) { String refStr = (String) data; ChildReference ref = ChildReference.parsePath( refStr); if ( ref != null ) { try { Datatype fieldType = allfields.getChildType( ref ); // Read Combo Union if ( fieldType instanceof UnionType && control instanceof Combo ) { // Nothing to validate } else // Read Radio Union if ( fieldType instanceof UnionType && control instanceof Composite ) { // Nothing to validate } else // Read Boolean if ( fieldType instanceof BooleanType && control instanceof Button ) { // Nothing to validate } else // Text + Dialog button if ( fieldType instanceof RecordType && control instanceof Text ) { try { Text text = (Text) control; RecordBinding fieldBinding = Bindings.getMutableBinding(fieldType); Object value = parse(fieldBinding, text.getText()); fieldBinding.assertInstaceIsValid( value ); } catch (BindingException er) { Problem problem = new Problem(); if ( er.getCause()!=null ) { problem.error = er.getCause().getMessage(); } else { problem.error = er.getMessage(); } problem.fieldReference = refStr; problem.control = control; result.add( problem ); } } else // Read Text if ( fieldType instanceof StringAccessor && control instanceof Text ) { try { Text text = (Text) control; StringBinding binding = Bindings.getBinding(fieldType); Object value = binding.create(text.getText()); binding.assertInstaceIsValid( value ); } catch (BindingException er) { Problem problem = new Problem(); if ( er.getCause()!=null ) { problem.error = er.getCause().getMessage(); } else { problem.error = er.getMessage(); } problem.fieldReference = refStr; problem.control = control; result.add( problem ); } } else // Read Numbers if ( fieldType instanceof NumberType && control instanceof Text ) { try { Text text = (Text) control; NumberBinding binding = Bindings.getBinding(fieldType); Object value = binding.create(text.getText()); binding.assertInstaceIsValid( value ); } catch (BindingException er) { Problem problem = new Problem(); if ( er.getCause()!=null && er.getCause() instanceof NumberFormatException) { NumberFormatException nfe = (NumberFormatException) er.getCause(); problem.error = nfe.getMessage(); } else { problem.error = er.getMessage(); } problem.fieldReference = refStr; problem.control = control; result.add( problem ); } } } catch (AccessorConstructionException e) { Problem problem = new Problem(); problem.error = e.getMessage(); problem.fieldReference = refStr; problem.control = control; result.add( problem ); } } } // Recursion if ( control instanceof Composite ) { Composite composite = (Composite) control; for (Control child : composite.getChildren()) { _validateFields(child, result); } } } public RecordType type() { return allfields; } /** * Find control by reference * @param control * @param ref * @return control or null */ public Control getControl( Control control, ChildReference ref ) { return getControl( control, ref.toPath() ); } /** * Find control by reference. * * @param composite * @param ref * @return control or null */ public Control getControl( Control control, String ref ) { Object data = control.getData(); if ( data != null && data instanceof String && data.equals(ref)) return control; // Recursion if ( control instanceof Composite ) { Composite composite = (Composite) control; for (Control child : composite.getChildren()) { Control result = getControl(child, ref); if ( result != null ) return result; } } return null; } /** * Reads values of fields into a record * * @param composite the composite that holds the widgets * @param binding record binding * @param dst the record where values are read to * @throws BindingException * @throws AccessorConstructionException * @throws AccessorException */ public void readFields( Composite composite, RecordBinding binding, Object dst) throws BindingException, AccessorConstructionException, AccessorException { RecordAccessor ra = Accessors.getAccessor(binding, dst); _readFields(composite, ra); } protected void _readFields( Control control, RecordAccessor ra ) throws AccessorException, BindingException { // Read this control Object data = control.getData(); if ( data != null && data instanceof String ) { ChildReference ref = ChildReference.parsePath( (String) data); if ( ref != null ) { try { Accessor fieldAccessor = ra.getComponent( ref ); // Read Combo Union if ( fieldAccessor instanceof UnionAccessor && control instanceof Combo ) { UnionAccessor sa = (UnionAccessor) fieldAccessor; Combo combo = (Combo) control; String text = combo.getText(); int tag = sa.type().getComponentIndex2(text); Datatype tagType = sa.type().components[tag].type; Binding tagBinding = Bindings.getBinding(tagType); Object defaultValue = tagBinding.createDefault(); sa.setComponentValue(tag, tagBinding, defaultValue); } else // Read Radio Union if ( fieldAccessor instanceof UnionAccessor && control instanceof Composite ) { Composite radioComposite = (Composite) control; for (Control c : radioComposite.getChildren()) { if ( c instanceof Button && ((Button)c).getSelection() ) { Object data2 = c.getData(); if (data2==null) continue; String name = getName( data2.toString() ); if ( name==null ) continue; UnionAccessor sa = (UnionAccessor) fieldAccessor; int tag = sa.type().getComponentIndex2( name ); if ( tag>=0 ) { Datatype tagType = sa.type().components[tag].type; Binding tagBinding = Bindings.getBinding(tagType); Object defaultValue = tagBinding.createDefault(); sa.setComponentValue(tag, tagBinding, defaultValue); break; } } } } else // Read Boolean if ( fieldAccessor instanceof BooleanAccessor && control instanceof Button ) { BooleanAccessor sa = (BooleanAccessor) fieldAccessor; sa.setValue(((Button)control).getSelection()); } else // Read Text + Dialog Button if ( fieldAccessor instanceof RecordAccessor && control instanceof Text ) { RecordAccessor raa = (RecordAccessor) fieldAccessor; Text text = (Text) control; RecordBinding fieldBinding = Bindings.getMutableBinding( raa.type() ); Object value = parse( fieldBinding, text.getText() ); raa.setValue(fieldBinding, value); } else // Read Text if ( fieldAccessor instanceof StringAccessor && control instanceof Text ) { StringAccessor sa = (StringAccessor) fieldAccessor; sa.setValue(((Text)control).getText()); } else // Read Numbers if ( fieldAccessor.type() instanceof NumberType && control instanceof Text ) { NumberBinding binding = Bindings.getBinding( fieldAccessor.type() ); Object value = binding.create( ((Text)control).getText() ); fieldAccessor.setValue(binding, value); } } catch (AccessorConstructionException e) { //e.printStackTrace(); } } } // Recursion if ( control instanceof Composite ) { Composite composite = (Composite) control; for (Control child : composite.getChildren()) { _readFields(child, ra); } } } public void writeFields( Composite composite, RecordBinding binding, Object src) throws AccessorException, BindingException, AccessorConstructionException { RecordAccessor ra = Accessors.getAccessor(binding, src); _writeFields(composite, ra); } void _writeFields( Control control, RecordAccessor ra ) throws AccessorException, BindingException, AccessorConstructionException { // Read this control Object data = control.getData(); if ( data != null && data instanceof String ) { ChildReference ref = ChildReference.parsePath( (String) data); if ( ref != null ) { try { Accessor fieldAccessor = ra.getComponent( ref ); // Read Combo Union if ( fieldAccessor instanceof UnionAccessor && control instanceof Combo ) { UnionAccessor sa = (UnionAccessor) fieldAccessor; Combo combo = (Combo) control; int tag = sa.getTag(); combo.setText( sa.type().getComponent(tag).name ); } else // Read Radio Union if ( fieldAccessor instanceof UnionAccessor && control instanceof Composite) { Composite radioComposite = (Composite) control; UnionAccessor sa = (UnionAccessor) fieldAccessor; int tag = sa.getTag(); for (int i=0; i0) new Label(parent, 0); CTabItem item = new CTabItem(folder, SWT.NONE); item.setText(childName); Composite composite = new Composite(folder, 0); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); composite.setLayout( new GridLayout(3, false) ); composite.setData(ref); if ( childType instanceof RecordType ) { addRecord( composite, (RecordType)childType, childRef); } else { addWidget( composite, childType, childRef ); } item.setControl( composite ); } folder.setSelection(0); } else // Method 1: Dialog = Label + Text + [Button] if ( dialog ) { Label label = new Label(parent, 0); label.setText(fieldName + ":"); label.setLayoutData( lblLayout ); final String title = fieldName; final Shell shell = parent.getShell(); final RecordBinding fieldBinding = Bindings.getMutableBinding( fieldType ); final Object fieldValue = fieldBinding.createDefaultUnchecked(); final Text text = new Text(parent, SWT.BORDER); final Button select = new Button(parent, SWT.PUSH); text.setLayoutData( new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1 ) ); text.setText( print(fieldBinding, fieldValue) ); text.setData(ref); text.addListener(SWT.Verify, new Listener() { public void handleEvent(Event e) { try { String newText = applyEventToString( text.getText(), e ); Object value = parse(fieldBinding, newText); fieldBinding.assertInstaceIsValid( value ); text.setBackground(null); } catch (BindingException er) { Color error = new Color(text.getDisplay(), 255, 222, 222); text.setBackground(error); error.dispose(); } } }); //text.setEditable( false ); select.setText("Select"); select.setLayoutData(new GridData(SWT.RIGHT, SWT.BEGINNING, false, false)); select.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { Object initialValue; try { initialValue = parse(fieldBinding, text.getText()); } catch (BindingException e1) { initialValue = fieldBinding.createDefaultUnchecked(); } DataboardDialog dialog = new DataboardDialog( shell, title, fieldBinding, initialValue); int code = dialog.open(); if ( code == Window.OK ) { Object result = dialog.getResult(); String str = print(fieldBinding, result); text.setText( str ); } } }); } else // Method 2: Label + composite if ( allBooleans(record) ) { Label label = new Label(parent, 0); label.setText(fieldName + ":"); label.setLayoutData( lblLayout ); for (int i=0; i0) new Label(parent, 0); addWidget( parent, childType, childRef ); } } else // Method 3: Groups { Group group = new Group(parent, 0); group.setText(fieldName); group.setLayout( new GridLayout(3, false) ); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); group.setData(ref); addRecord(group, record, ref); } } else if (fieldType instanceof UnionType) { Label label = new Label(parent, 0); label.setText(fieldName + ":"); label.setLayoutData( lblLayout ); addUnion( parent, (UnionType) fieldType, ref ); } } protected void addWidget(Composite parent, Datatype fieldType, String ref) { if (fieldType instanceof StringType) { addString(parent, (StringType) fieldType, ref); } else if (fieldType instanceof BooleanType) { addBoolean(parent, (BooleanType) fieldType, ref); } else if (fieldType instanceof NumberType) { addNumber(parent, (NumberType) fieldType, ref); } else if (fieldType instanceof RecordType) { RecordType rt = (RecordType) fieldType; Group group = new Group(parent, 0); String name = getName(ref); group.setText(name); group.setLayout( new GridLayout(3, false) ); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); group.setData(ref); addRecord(group, rt, ref); } else if (fieldType instanceof UnionType) { addUnion( parent, (UnionType) fieldType, ref ); } } public CTabFolder addTabFolder( Composite parent, RecordType record, String ref ) { CTabFolder folder = new CTabFolder(parent, SWT.TOP | SWT.BORDER); folder.setSimple(false); folder.setLayout( new GridLayout(3, false) ); folder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); for (int i=0; i0) new Label(parent, 0); CTabItem item = new CTabItem(folder, SWT.NONE); item.setText(childName); Composite composite = new Composite(folder, 0); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); composite.setLayout( new GridLayout(3, false) ); composite.setData(ref); if ( childType instanceof RecordType ) { addRecord( composite, (RecordType)childType, childRef); } else { addWidget( composite, childType, childRef ); } item.setControl( composite ); allfields.addComponent(childName, childType); } folder.setSelection(0); return folder; } protected void addRecord( Composite parent, RecordType record, String ref ) { String options = record.metadata.get("style"); boolean dialog = options==null?false:options.contains("dialog"); boolean tabbed = options==null?false:options.contains("tabbed"); // Method 0: Tabbed, each field is a tab page if ( tabbed ) { addTabFolder( parent, record, ref ); return; } // Normal Record GridData lblLayout = new GridData(SWT.LEFT, SWT.BEGINNING, false, false, 1, 1); if ( getDepth(ref) < 3 ) lblLayout.widthHint = column1Width; for (int i=0; i0 ) sb.append(", "); sb.append(recordBinding.type().getComponent(i).name); j++; } catch (BindingException e) { continue; } } return sb.toString(); } else try { return recordBinding.toString(value, true); } catch (BindingException e) { return e.toString(); } } static Object parse(RecordBinding recordBinding, String txt) throws BindingException { if ( allBooleans(recordBinding.type() ) ) { Object result = recordBinding.createDefaultUnchecked(); String[] tokens = txt.split(", "); for ( String token : tokens ) { if ( token.isEmpty() ) continue; int i = recordBinding.type().getComponentIndex2(token); if ( i>=0 ) { try { recordBinding.setBoolean(result, i, true); } catch (BindingException e) { throw e; } } else { throw new BindingException("There is no field \""+token+"\""); } } return result; } else { try { return recordBinding.parseValueDefinition(txt); } catch (DataTypeSyntaxError e) { throw new BindingException(e); } } } static { PASSWORD = new StringType(); PASSWORD.metadata.put("style", "password"); TEXTBOX = new StringType(); TEXTBOX.metadata.put("style", "multi"); } }