]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/forms/DataboardForm.java
Improved Bindings.getBinding(Class) caching for Datatype.class
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / forms / DataboardForm.java
1 package org.simantics.databoard.forms;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import org.eclipse.jface.window.Window;
7 import org.eclipse.swt.SWT;
8 import org.eclipse.swt.custom.CTabFolder;
9 import org.eclipse.swt.custom.CTabItem;
10 import org.eclipse.swt.events.SelectionAdapter;
11 import org.eclipse.swt.events.SelectionEvent;
12 import org.eclipse.swt.graphics.Color;
13 import org.eclipse.swt.layout.GridData;
14 import org.eclipse.swt.layout.GridLayout;
15 import org.eclipse.swt.widgets.Button;
16 import org.eclipse.swt.widgets.Combo;
17 import org.eclipse.swt.widgets.Composite;
18 import org.eclipse.swt.widgets.Control;
19 import org.eclipse.swt.widgets.DirectoryDialog;
20 import org.eclipse.swt.widgets.Event;
21 import org.eclipse.swt.widgets.FileDialog;
22 import org.eclipse.swt.widgets.Group;
23 import org.eclipse.swt.widgets.Label;
24 import org.eclipse.swt.widgets.Listener;
25 import org.eclipse.swt.widgets.Shell;
26 import org.eclipse.swt.widgets.Text;
27 import org.simantics.databoard.Accessors;
28 import org.simantics.databoard.Bindings;
29 import org.simantics.databoard.accessor.Accessor;
30 import org.simantics.databoard.accessor.BooleanAccessor;
31 import org.simantics.databoard.accessor.RecordAccessor;
32 import org.simantics.databoard.accessor.StringAccessor;
33 import org.simantics.databoard.accessor.UnionAccessor;
34 import org.simantics.databoard.accessor.error.AccessorConstructionException;
35 import org.simantics.databoard.accessor.error.AccessorException;
36 import org.simantics.databoard.accessor.error.ReferenceException;
37 import org.simantics.databoard.accessor.reference.ChildReference;
38 import org.simantics.databoard.accessor.reference.LabelReference;
39 import org.simantics.databoard.binding.Binding;
40 import org.simantics.databoard.binding.NumberBinding;
41 import org.simantics.databoard.binding.RecordBinding;
42 import org.simantics.databoard.binding.StringBinding;
43 import org.simantics.databoard.binding.UnionBinding;
44 import org.simantics.databoard.binding.error.BindingException;
45 import org.simantics.databoard.binding.mutable.TaggedObject;
46 import org.simantics.databoard.parser.repository.DataTypeSyntaxError;
47 import org.simantics.databoard.type.BooleanType;
48 import org.simantics.databoard.type.Component;
49 import org.simantics.databoard.type.Datatype;
50 import org.simantics.databoard.type.NumberType;
51 import org.simantics.databoard.type.RecordType;
52 import org.simantics.databoard.type.StringType;
53 import org.simantics.databoard.type.UnionType;
54 import org.simantics.databoard.util.Bean;
55 import org.simantics.databoard.util.StringUtil;
56 import org.simantics.databoard.util.URIUtil;
57
58 /**
59  * Databoard form creates SWT user interface from databoard record type.
60  * 
61  * See DataboardFormExample for example usage.
62  *
63  * @author toni.kalajainen@semantum.fi
64  */
65 public class DataboardForm {    
66
67         public static final Datatype TEXTBOX;
68         public static final Datatype PASSWORD;
69         
70         public int column1Width = -1;
71         
72         private RecordType allfields = new RecordType();
73         
74         /**
75          * Make file type
76          * 
77          * @param namesAndExts array of names and extensions
78          * @param exts
79          * @return
80          */
81         public static StringType fileSaveDialog(String...namesAndExts)
82         {               
83             StringType result = new StringType();
84             result.metadata.put("style", "filesave");
85                     
86             StringBuilder sb1 = new StringBuilder();
87             StringBuilder sb2 = new StringBuilder();
88             
89             for (int i=0; i<namesAndExts.length; i++) {
90                 StringBuilder sb = (i%2==0?sb1:sb2);
91                 int j = i/2;
92                 if (j>0) sb.append(','); 
93                 sb.append(namesAndExts[i]);
94             }
95             
96                 result.metadata.put("filetypes", sb1.toString());
97             result.metadata.put("fileexts", sb2.toString());
98             return result;
99         }
100
101         /**
102          * Make file type
103          * 
104          * @param namesAndExts array of names and extensions
105          * @param exts
106          * @return
107          */
108         public static StringType fileOpenDialog(String...namesAndExts)
109         {               
110             StringType result = new StringType();
111             result.metadata.put("style", "fileopen");
112                     
113             StringBuilder sb1 = new StringBuilder();
114             StringBuilder sb2 = new StringBuilder();
115             
116             for (int i=0; i<namesAndExts.length; i++) {
117                 StringBuilder sb = (i%2==0?sb1:sb2);
118                 int j = i/2;
119                 if (j>0) sb.append(','); 
120                 sb.append(namesAndExts[i]);
121             }
122             
123                 result.metadata.put("filetypes", sb1.toString());
124             result.metadata.put("fileexts", sb2.toString());
125             return result;
126         }
127         
128         public static StringType directoryDialog()
129         {               
130             StringType result = new StringType();
131             result.metadata.put("style", "directory");
132             return result;
133         }
134         
135         public DataboardForm() {
136         }
137         
138         public void setFirstColumnWidth(int width) {
139                 column1Width = width;
140         }
141         
142         
143         /**
144          * Validate the fields for valid values.
145          * StringTypes can be restricted with regular expressions.
146          * NumberTypes with value ranges.
147          * 
148          * @param composite
149          * @return list of problems
150          */
151         public List<Problem> validate( Composite composite ) {
152                 List<Problem> result = new ArrayList<Problem>();
153                 _validateFields(composite, result);
154                 return result;
155         }
156         
157         public static List<String> toStrings(List<Problem> problems) {
158                 List<String> result = new ArrayList<String>( problems.size() );
159                 for (Problem p : problems) result.add(p.error);
160                 return result;
161         }
162         
163         public static class Problem extends Bean {
164                 /** Reference to the field with problem */
165                 public String fieldReference;
166                 /** The field with problem */
167                 public transient Control control;
168                 /** The validation error */
169                 public String error;
170         }
171
172         protected void _validateFields( Control control, List<Problem> result )
173         {
174                 // Read this control
175                 Object data = control.getData();
176                 if ( data != null && data instanceof String ) {
177                         String refStr = (String) data;
178                         ChildReference ref = ChildReference.parsePath( refStr);
179                         if ( ref != null ) {
180                                 try {
181                                         Datatype fieldType = allfields.getChildType( ref );
182                                         
183                                         // Read Combo Union
184                                         if ( fieldType instanceof UnionType && control instanceof Combo ) {
185                                                 // Nothing to validate
186                                         } else
187
188                                         // Read Radio Union
189                                         if ( fieldType instanceof UnionType && control instanceof Composite ) {
190                                                 // Nothing to validate
191                                         } else
192                                         
193                                         // Read Boolean
194                                         if ( fieldType instanceof BooleanType && control instanceof Button ) {
195                                                 // Nothing to validate
196                                         } else
197
198                                         // Text + Dialog button
199                                         if ( fieldType instanceof RecordType && control instanceof Text ) {
200                                                 try {
201                                                         Text text = (Text) control;
202                                                         RecordBinding fieldBinding = Bindings.getMutableBinding(fieldType);
203                                                         Object value = parse(fieldBinding, text.getText());
204                                                         fieldBinding.assertInstaceIsValid( value );
205                                                 } catch (BindingException er) {
206                                                         Problem problem = new Problem();
207                                                         if ( er.getCause()!=null ) {
208                                                                 problem.error = er.getCause().getMessage();
209                                                         } else {
210                                                                 problem.error = er.getMessage();
211                                                         }
212                                                         problem.fieldReference = refStr;
213                                                         problem.control = control;
214                                                         result.add( problem );
215                                                 }                                               
216                                                 
217                                         } else
218                                                 
219                                         // Read Text
220                                         if ( fieldType instanceof StringAccessor && control instanceof Text ) {
221                                                 try {
222                                                         Text text = (Text) control;
223                                                         StringBinding binding = Bindings.getBinding(fieldType);
224                                                         Object value = binding.create(text.getText());
225                                                         binding.assertInstaceIsValid( value );
226                                                 } catch (BindingException er) {
227                                                         Problem problem = new Problem();
228                                                         if ( er.getCause()!=null ) {
229                                                                 problem.error = er.getCause().getMessage();
230                                                         } else {
231                                                                 problem.error = er.getMessage();
232                                                         }
233                                                         problem.fieldReference = refStr;
234                                                         problem.control = control;
235                                                         result.add( problem );
236                                                 }                                               
237                                         } else
238                                         
239                                         // Read Numbers
240                                         if ( fieldType instanceof NumberType && control instanceof Text ) {
241                                                 try {
242                                                         Text text = (Text) control;
243                                                         NumberBinding binding = Bindings.getBinding(fieldType);
244                                                         Object value = binding.create(text.getText());
245                                                         binding.assertInstaceIsValid( value );
246                                                 } catch (BindingException er) {
247                                                         Problem problem = new Problem();
248                                                         if ( er.getCause()!=null && er.getCause() instanceof NumberFormatException) {
249                                                                 NumberFormatException nfe = (NumberFormatException) er.getCause();
250                                                                 problem.error = nfe.getMessage();
251                                                         } else {
252                                                                 problem.error = er.getMessage();
253                                                         }
254                                                         problem.fieldReference = refStr;
255                                                         problem.control = control;
256                                                         result.add( problem );
257                                                 }                                               
258                                         }
259                                         
260                                 } catch (AccessorConstructionException e) {
261                                         Problem problem = new Problem();
262                                         problem.error = e.getMessage();
263                                         problem.fieldReference = refStr;
264                                         problem.control = control;
265                                         result.add( problem );
266                                 }
267                         }
268                 }
269                 
270                 // Recursion
271                 if ( control instanceof Composite ) {
272                         Composite composite = (Composite) control;
273                         for (Control child : composite.getChildren()) 
274                         {
275                                 _validateFields(child, result);
276                         }
277                 }
278                 
279         }
280         
281         public RecordType type() {
282                 return allfields;
283         }
284
285         /**
286          * Find control by reference 
287          * @param control
288          * @param ref
289          * @return control or null
290          */
291         public Control getControl( Control control, ChildReference ref )
292         {
293                 return getControl( control, ref.toPath() );
294         }
295         
296         /**
297          * Find control by reference. 
298          * 
299          * @param composite
300          * @param ref
301          * @return control or null
302          */
303         public Control getControl( Control control, String ref )
304         {
305                 Object data = control.getData();
306                 if ( data != null && data instanceof String && data.equals(ref)) return control;
307                         
308                 // Recursion
309                 if ( control instanceof Composite ) {
310                         Composite composite = (Composite) control;
311                         for (Control child : composite.getChildren()) 
312                         {
313                                 Control result = getControl(child, ref);
314                                 if ( result != null ) return result;
315                         }
316                 }
317                 return null;
318         }
319         
320         /**
321          * Reads values of fields into a record
322          * 
323          * @param composite the composite that holds the widgets
324          * @param binding record binding
325          * @param dst the record where values are read to
326          * @throws BindingException
327          * @throws AccessorConstructionException 
328          * @throws AccessorException 
329          */
330         public void readFields( Composite composite, RecordBinding binding, Object dst)
331         throws BindingException, AccessorConstructionException, AccessorException
332         {
333                 RecordAccessor ra = Accessors.getAccessor(binding, dst);
334                 _readFields(composite, ra);
335         }
336         
337         protected void _readFields( Control control, RecordAccessor ra ) 
338         throws AccessorException, BindingException
339         {
340                 // Read this control
341                 Object data = control.getData();
342                 if ( data != null && data instanceof String ) {
343                         ChildReference ref = ChildReference.parsePath( (String) data);
344                         if ( ref != null ) {
345                                 try {
346                                         Accessor fieldAccessor = ra.getComponent( ref );
347                                         
348                                         // Read Combo Union
349                                         if ( fieldAccessor instanceof UnionAccessor && control instanceof Combo ) {
350                                                 UnionAccessor sa = (UnionAccessor) fieldAccessor;
351                                                 Combo combo = (Combo) control;
352                                                 String text = combo.getText();
353                                                 int tag = sa.type().getComponentIndex2(text);
354                                                 Datatype tagType = sa.type().components[tag].type;
355                                                 Binding tagBinding = Bindings.getBinding(tagType);
356                                                 Object defaultValue = tagBinding.createDefault();                                                                       
357                                                 sa.setComponentValue(tag, tagBinding, defaultValue);
358                                         } else
359
360                                         // Read Radio Union
361                                         if ( fieldAccessor instanceof UnionAccessor && control instanceof Composite ) {
362                                                 Composite radioComposite = (Composite) control;
363                                                 for (Control c : radioComposite.getChildren()) {
364                                                         if ( c instanceof Button && ((Button)c).getSelection() ) {
365                                                                 Object data2 = c.getData();
366                                                                 if (data2==null) continue;
367                                                                 String name = getName( data2.toString() );
368                                                                 if ( name==null ) continue;
369                                                                 
370                                                                 UnionAccessor sa = (UnionAccessor) fieldAccessor;
371                                                                 int tag = sa.type().getComponentIndex2( name );
372                                                                 if ( tag>=0 ) {
373                                                                         Datatype tagType = sa.type().components[tag].type;
374                                                                         Binding tagBinding = Bindings.getBinding(tagType);
375                                                                         Object defaultValue = tagBinding.createDefault();                                                                       
376                                                                         sa.setComponentValue(tag, tagBinding, defaultValue);
377                                                                         break;
378                                                                 }
379                                                         }
380                                                 }
381                                         } else
382                                         
383                                         // Read Boolean
384                                         if ( fieldAccessor instanceof BooleanAccessor && control instanceof Button ) {
385                                                 BooleanAccessor sa = (BooleanAccessor) fieldAccessor;
386                                                 sa.setValue(((Button)control).getSelection());
387                                         } else
388
389                                         // Read Text + Dialog Button
390                                         if ( fieldAccessor instanceof RecordAccessor && control instanceof Text ) {
391                                                 RecordAccessor raa = (RecordAccessor) fieldAccessor;
392                                                 Text text = (Text) control;
393                                                 RecordBinding fieldBinding = Bindings.getMutableBinding( raa.type() );
394                                                 Object value = parse( fieldBinding, text.getText() );
395                                                 raa.setValue(fieldBinding, value);
396                                         } else
397                                                 
398                                         // Read Text
399                                         if ( fieldAccessor instanceof StringAccessor && control instanceof Text ) {
400                                                 StringAccessor sa = (StringAccessor) fieldAccessor;
401                                                 sa.setValue(((Text)control).getText());
402                                         } else
403                                         
404                                         // Read Numbers
405                                         if ( fieldAccessor.type() instanceof NumberType && control instanceof Text ) {
406                                                 NumberBinding binding = Bindings.getBinding( fieldAccessor.type() );
407                                                 Object value = binding.create( ((Text)control).getText() );
408                                                 fieldAccessor.setValue(binding, value);
409                                         }
410                                         
411                                 } catch (AccessorConstructionException e) {
412                                         //e.printStackTrace();
413                                 }
414                         }
415                 }
416                 
417                 // Recursion
418                 if ( control instanceof Composite ) {
419                         Composite composite = (Composite) control;
420                         for (Control child : composite.getChildren()) 
421                         {
422                                 _readFields(child, ra);
423                         }
424                 }
425         }
426
427         public void writeFields( Composite composite, RecordBinding binding, Object src)
428                         throws AccessorException, BindingException, AccessorConstructionException
429         {
430                 RecordAccessor ra = Accessors.getAccessor(binding, src);
431                 _writeFields(composite, ra);
432         }
433         
434         void _writeFields( Control control, RecordAccessor ra )
435                         throws AccessorException, BindingException, AccessorConstructionException
436         {
437                 // Read this control
438                 Object data = control.getData();
439                 if ( data != null && data instanceof String ) {
440                         ChildReference ref = ChildReference.parsePath( (String) data);
441                         if ( ref != null ) {
442                                 try {
443                                         Accessor fieldAccessor = ra.getComponent( ref );
444                                         
445                                         // Read Combo Union
446                                         if ( fieldAccessor instanceof UnionAccessor && control instanceof Combo ) {
447                                                 UnionAccessor sa = (UnionAccessor) fieldAccessor;
448                                                 Combo combo = (Combo) control;
449                                                 int tag = sa.getTag();                                          
450                                                 combo.setText( sa.type().getComponent(tag).name );
451                                         } else
452
453                                         // Read Radio Union
454                                         if ( fieldAccessor instanceof UnionAccessor && control instanceof Composite) {
455                                                 Composite radioComposite = (Composite) control;
456                                                 UnionAccessor sa = (UnionAccessor) fieldAccessor;
457                                                 int tag = sa.getTag();
458                                                 for (int i=0; i<sa.count(); i++) {
459                                                         Button button = (Button) radioComposite.getChildren()[i*2];
460                                                         button.setSelection(i==tag);
461                                                 }
462                                         } else
463                                         
464                                         // Read Boolean
465                                         if ( fieldAccessor instanceof BooleanAccessor && control instanceof Button ) {
466                                                 BooleanAccessor sa = (BooleanAccessor) fieldAccessor;
467                                                 ((Button)control).setSelection(sa.getValue());
468                                         } else
469
470                                         // Write Text + Dialog Selection
471                                         if ( fieldAccessor instanceof RecordAccessor && control instanceof Text ) {
472                                                 RecordAccessor raa = (RecordAccessor) fieldAccessor;
473                                                 Text text = (Text) control;
474                                                 RecordBinding fieldBinding = Bindings.getMutableBinding( raa.type() );
475                                                 String str = print( fieldBinding, raa.getValue(fieldBinding) );                                 
476                                                 text.setText( str );
477                                         } else
478                                                 
479                                         // Read Text
480                                         if ( fieldAccessor instanceof StringAccessor && control instanceof Text ) {
481                                                 StringAccessor sa = (StringAccessor) fieldAccessor;
482                                                 ((Text)control).setText( sa.getValue() );
483                                         } else
484                                         
485                                         // Read Numbers
486                                         if ( fieldAccessor.type() instanceof NumberType && control instanceof Text ) {
487                                                 NumberBinding binding = Bindings.getBinding( fieldAccessor.type() );
488                                                 Object value = fieldAccessor.getValue(binding);
489                                                 ((Text)control).setText( binding.toString(value, true) );
490                                         }
491                                         
492                                 } catch (AccessorConstructionException e) {
493                                         //e.printStackTrace();
494                                 }
495                         }
496                 }
497                 
498                 // Recursion
499                 if ( control instanceof Composite ) {
500                         Composite composite = (Composite) control;
501                         for (Control child : composite.getChildren()) 
502                         {
503                                 _writeFields(child, ra);
504                         }
505                 }
506                 
507         }
508         
509         public void clear( Control control ) 
510         {               
511                 _clearFields( control );
512                 allfields.clear();
513         }
514
515         protected void _clearFields( Control control ) 
516         {               
517                 // Recursion
518                 if ( control instanceof Composite ) {
519                         Composite composite = (Composite) control;
520                         for (Control child : composite.getChildren()) 
521                         {
522                                 _clearFields(child);
523                                 child.dispose();
524                         }
525                 }
526
527         }
528         
529         /**
530          * Add a listener to all the controls that represent the data type.
531          * 
532          * @param composite the composite that holds the widgets
533          * @param binding record binding
534          * @param dst the record where values are read to
535          * @throws BindingException
536          * @throws AccessorConstructionException 
537          * @throws AccessorException 
538          */
539         public void addListener( Composite composite, RecordType type, Listener listener )
540         throws BindingException, AccessorConstructionException, AccessorException
541         {
542                 _addListener(composite, type, listener );
543         }
544         
545         protected void _addListener( Control control, Datatype type, Listener listener ) 
546         throws AccessorException, BindingException
547         {
548                 // Read this control
549                 Object data = control.getData();
550                 ChildReference ref = null;
551                 if ( data != null && data instanceof String ) {
552                         ref = ChildReference.parsePath( (String) data);
553                 }
554                         
555                                 try {                                   
556                                         Datatype fieldType = ref==null ? type : type.getChildType( ref );
557
558                                         // Read Combo Union
559                                         if ( fieldType instanceof UnionType && control instanceof Combo ) {
560                                                 control.addListener(SWT.Selection, listener);
561                                         } else
562                                         // Read Radio Union
563                                         if ( fieldType instanceof UnionType && control instanceof Composite ) {
564                                                 Composite radioComposite = (Composite) control;
565                                                 for (Control c : radioComposite.getChildren()) {
566                                                         if ( c instanceof Button ) {
567                                                                 c.addListener(SWT.Selection, listener);
568                                                         }
569                                                 }
570                                         } else
571                                         
572                                         // Read Boolean
573                                         if ( fieldType instanceof BooleanType && control instanceof Button ) {
574                                                 control.addListener(SWT.Selection, listener);
575                                         } else
576                                         
577                                         // Read Text
578                                         if ( fieldType instanceof StringType && control instanceof Text ) {
579                                                 control.addListener(SWT.Modify, listener);
580                                         } else
581                                         
582                                         // Read Numbers
583                                         if ( fieldType instanceof NumberType && control instanceof Text ) {
584                                                 control.addListener(SWT.Modify, listener);
585                                         }
586                                 
587                                 } catch (ReferenceException re) {
588                                 }
589                 
590                 // Recursion
591                 if ( control instanceof Composite ) {
592                         Composite composite = (Composite) control;
593                         for (Control child : composite.getChildren()) 
594                         {
595                                 _addListener(child, type, listener);
596                         }
597                 }
598         }
599         
600         public void addField( Composite parent, String name, Datatype fieldType )
601         {
602                 addField(parent, fieldType, URIUtil.encodeURI(name), null);
603                 allfields.addComponent(name, fieldType);
604         }
605         
606         public void addField( Composite parent, String name, Datatype fieldType, String ref )
607         {
608                 addField(parent, fieldType, appendName(ref, name), null);
609                 
610                 RecordType root = allfields;
611                 if ( ref != null && !ref.isEmpty() ) {
612                         // find deeper root
613                         ChildReference path = ChildReference.parsePath(ref);
614                         while ( path!=null ) {
615                                 if ( path instanceof LabelReference == false ) {
616                                         throw new RuntimeException( "blaah" );                                          
617                                 }
618                                 LabelReference lr = (LabelReference) path;
619                                 String label = lr.label;
620                                 
621                                 if ( root.hasComponent(label) ) {
622                                         root = (RecordType) root.getComponent(label).type;
623                                 } else {
624                                         RecordType rt = new RecordType();
625                                         root.addComponent(label, rt);
626                                         root = rt;
627                                 }
628                                 path = path.childReference;
629                         }
630                 }
631                 if ( !root.hasComponent(name) ) root.addComponent(name, fieldType);             
632         }
633         
634         
635         public void addFields( Composite parent, RecordType source ) 
636         {
637                 for (Component component : source.getComponents()) 
638                         addField( parent, component.name, component.type );
639         }
640         
641         public void addFields( Composite parent, RecordType source, String ref ) 
642         {
643                 for (Component component : source.getComponents()) 
644                         addField( parent, component.name, component.type, ref );
645         }
646         
647         
648         public void addField( Composite parent, Datatype fieldType, String ref, GridData lblLayout )
649         {
650                 if (lblLayout==null) {
651                         lblLayout = new GridData(SWT.LEFT, SWT.BEGINNING, false, false, 1, 1);
652                         if ( getDepth(ref) < 3 ) lblLayout.widthHint = column1Width;
653                 }               
654                 String fieldName = getName(ref);
655                 
656                 if (fieldType instanceof StringType) {
657                         Label label = new Label(parent, 0);
658                         label.setText(fieldName + ":");
659                         label.setToolTipText(fieldName);
660                         label.setLayoutData( lblLayout );
661
662                         addString(parent, (StringType) fieldType, ref);
663                         
664                 } else if (fieldType instanceof BooleanType) {
665
666                         Label label = new Label(parent, 0);
667                         label.setLayoutData( lblLayout );
668                         
669                         addBoolean(parent, (BooleanType) fieldType, ref);
670
671                 } else if (fieldType instanceof NumberType) {
672
673                         Label label = new Label(parent, 0);
674                         label.setText(fieldName + ":");
675                         label.setLayoutData( lblLayout );
676
677                         addNumber(parent, (NumberType) fieldType, ref);
678
679                 } else if (fieldType instanceof RecordType) {
680                         RecordType record = (RecordType) fieldType;
681                         String options = record.metadata.get("style");
682                         boolean dialog = options==null?false:options.contains("dialog");
683                         boolean tabbed = options==null?false:options.contains("tabbed");
684                         
685                         // Method 0: Tabbed, each field is a tab page
686                         if ( tabbed ) {
687                                 Label label = new Label(parent, 0);
688                                 label.setText(fieldName + ":");
689                                 label.setLayoutData( lblLayout );
690                                 
691                                 CTabFolder folder = new CTabFolder(parent, SWT.TOP | SWT.BORDER);
692                                 //folder.setUnselectedCloseVisible(false);
693                                 folder.setSimple(false);
694                                 folder.setLayout( new GridLayout(3, false) );                   
695                                 folder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
696                                 
697                                 for (int i=0; i<record.getComponentCount(); i++) {
698                                         String childName = record.getComponent(i).name;
699                                         Datatype childType = record.getComponentType(i);
700                                         String childRef = appendName(ref, childName);
701                                         if (i>0) new Label(parent, 0);
702                                         
703                                         CTabItem item = new CTabItem(folder, SWT.NONE);
704                                         item.setText(childName);
705                                         Composite composite = new Composite(folder, 0);
706                                         composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
707                                         composite.setLayout( new GridLayout(3, false) );
708                                         composite.setData(ref);
709                                         
710                                         if ( childType instanceof RecordType ) {
711                                                 addRecord( composite, (RecordType)childType, childRef);                                         
712                                         } else {
713                                                 addWidget( composite, childType, childRef );
714                                         }
715                                         
716                                         item.setControl( composite );
717                                 }       
718                                 folder.setSelection(0);
719
720                         } else
721                                 
722                         // Method 1: Dialog = Label + Text + [Button]
723                         if ( dialog ) {
724                                 Label label = new Label(parent, 0);
725                                 label.setText(fieldName + ":");
726                                 label.setLayoutData( lblLayout );
727                                 
728                                 final String title = fieldName;
729                                 final Shell shell = parent.getShell();
730                                 final RecordBinding fieldBinding = Bindings.getMutableBinding( fieldType );
731                                 final Object fieldValue = fieldBinding.createDefaultUnchecked();
732                                 final Text text = new Text(parent, SWT.BORDER);
733                                 final Button select = new Button(parent, SWT.PUSH);
734                                 text.setLayoutData( new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1 ) );
735                                 text.setText( print(fieldBinding, fieldValue) );
736                                 text.setData(ref);                      
737                                 text.addListener(SWT.Verify, new Listener() {
738                                         public void handleEvent(Event e) {
739                                                 try {
740                                                         String newText = applyEventToString( text.getText(), e );
741                                                         Object value = parse(fieldBinding, newText);
742                                                         fieldBinding.assertInstaceIsValid( value );
743                                                         text.setBackground(null);
744                                                 } catch (BindingException er) {
745                                                         Color error = new Color(text.getDisplay(), 255, 222, 222); 
746                                                         text.setBackground(error);                                      
747                                                         error.dispose();
748                                                 }
749                                         }
750                                 });             
751                                 
752                                 //text.setEditable( false );
753                                 select.setText("Select");
754                                 select.setLayoutData(new GridData(SWT.RIGHT, SWT.BEGINNING, false, false));
755                                 select.addSelectionListener(new SelectionAdapter() {
756                                         public void widgetSelected(SelectionEvent e) {
757                                                 Object initialValue;
758                                                 try {
759                                                         initialValue = parse(fieldBinding, text.getText());
760                                                 } catch (BindingException e1) {
761                                                         initialValue = fieldBinding.createDefaultUnchecked();
762                                                 }
763                                                 DataboardDialog dialog = new DataboardDialog(
764                                                                 shell,
765                                                                 title, 
766                                                                 fieldBinding, 
767                                                                 initialValue);
768                                                                                                         
769                                                 int code = dialog.open();
770                                                 if ( code == Window.OK ) {
771                                                         Object result = dialog.getResult();
772                                                         String str = print(fieldBinding, result);
773                                                         text.setText( str );
774                                                 }
775                                         }
776                                 });
777                                 
778                         } else 
779
780                         // Method 2: Label + composite
781                         if ( allBooleans(record) ) {                    
782                                 Label label = new Label(parent, 0);
783                                 label.setText(fieldName + ":");
784                                 label.setLayoutData( lblLayout );
785                                 
786                                 for (int i=0; i<record.getComponentCount(); i++) {
787                                         String childName = record.getComponent(i).name;
788                                         Datatype childType = record.getComponentType(i);
789                                         String childRef = appendName(ref, childName);
790                                         if (i>0) new Label(parent, 0);
791                                         addWidget( parent, childType, childRef );
792                                 }               
793                                 
794                         } 
795                         else
796                         
797                         // Method 3: Groups
798                         {
799                                 Group group = new Group(parent, 0);
800                                 group.setText(fieldName);
801                                 group.setLayout( new GridLayout(3, false) );                    
802                                 group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
803                                 group.setData(ref);
804                                 addRecord(group, record, ref);
805                         }
806                         
807                 } else if (fieldType instanceof UnionType) {
808                         Label label = new Label(parent, 0);
809                         label.setText(fieldName + ":");
810                         label.setLayoutData( lblLayout );
811                         addUnion( parent, (UnionType) fieldType, ref );
812                 }
813                 
814         }
815
816         protected void addWidget(Composite parent, Datatype fieldType, String ref) {
817                 if (fieldType instanceof StringType) {
818                         addString(parent, (StringType) fieldType, ref);
819                 } else if (fieldType instanceof BooleanType) {
820                         addBoolean(parent, (BooleanType) fieldType, ref);
821                 } else if (fieldType instanceof NumberType) {
822                         addNumber(parent, (NumberType) fieldType, ref);
823                 } else if (fieldType instanceof RecordType) {
824                         RecordType rt = (RecordType) fieldType;
825                         Group group = new Group(parent, 0);
826                         String name = getName(ref);
827                         group.setText(name);
828                         group.setLayout( new GridLayout(3, false) );                    
829                         group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
830                         group.setData(ref);
831                         addRecord(group, rt, ref);
832                 } else if (fieldType instanceof UnionType) {
833                         addUnion( parent, (UnionType) fieldType, ref );
834                 }
835         }
836         
837         public CTabFolder addTabFolder( Composite parent, RecordType record, String ref )
838         {
839                 CTabFolder folder = new CTabFolder(parent, SWT.TOP | SWT.BORDER);
840                 folder.setSimple(false);
841                 folder.setLayout( new GridLayout(3, false) );                   
842                 folder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
843                 
844                 for (int i=0; i<record.getComponentCount(); i++) {
845                         String childName = record.getComponent(i).name;
846                         Datatype childType = record.getComponentType(i);
847                         String childRef = appendName(ref, childName);
848                         if (i>0) new Label(parent, 0);
849                         
850                         CTabItem item = new CTabItem(folder, SWT.NONE);
851                         item.setText(childName);
852                         Composite composite = new Composite(folder, 0);
853                         composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
854                         composite.setLayout( new GridLayout(3, false) );
855                         composite.setData(ref);
856                         
857                         if ( childType instanceof RecordType ) {
858                                 addRecord( composite, (RecordType)childType, childRef);                                         
859                         } else {
860                                 addWidget( composite, childType, childRef );
861                         }
862                         
863                         item.setControl( composite );
864                         allfields.addComponent(childName, childType);
865                 }       
866                 folder.setSelection(0);
867                 return folder;
868                 
869         }
870         
871         protected void addRecord( Composite parent, RecordType record, String ref )
872         {
873                 String options = record.metadata.get("style");
874                 boolean dialog = options==null?false:options.contains("dialog");
875                 boolean tabbed = options==null?false:options.contains("tabbed");
876                 
877                 // Method 0: Tabbed, each field is a tab page
878                 if ( tabbed ) {
879                         addTabFolder( parent, record, ref );
880                         return;
881                 }
882                 
883                 // Normal Record
884                 GridData lblLayout = new GridData(SWT.LEFT, SWT.BEGINNING, false, false, 1, 1);
885                 if ( getDepth(ref) < 3 ) lblLayout.widthHint = column1Width;
886                         
887                 for (int i=0; i<record.getComponentCount(); i++) {
888                         String fieldName = record.getComponent(i).name;
889                         Datatype fieldType = record.getComponentType(i);
890                         String fieldRef = appendName(ref, fieldName);
891                         addField( parent, fieldType, fieldRef, lblLayout);
892                 }               
893                 return;
894         }
895                 
896         protected void addUnion( Composite parent, UnionType union, String ref )
897         {
898                 if ( union.isEnumeration() ) {
899                         addEnum( parent, union, ref );
900                 } else {
901                         addRadio( parent, union, ref );
902                 }               
903         }
904         
905         protected void addEnum( Composite parent, UnionType union, String ref )
906         {
907                 Combo combo = new Combo(parent, SWT.READ_ONLY);
908                 String[] items = new String[union.getComponentCount()];
909                 for (int i = 0; i < items.length; i++) {
910                         items[i] = union.getComponent(i).name;
911                 }
912                 combo.setItems(items);
913                 combo.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
914                 combo.setData(ref);
915                 
916                 // Set default value
917                 UnionBinding binding = Bindings.getBinding(union);
918                 try {
919                         TaggedObject defaultOption = (TaggedObject) binding.createDefault();
920                         combo.setText( items[ defaultOption.tag ] );
921                 } catch (BindingException e) {
922                 }                               
923         }
924
925         protected void addRadio( Composite parent, UnionType union, String ref )
926         {
927                 Composite composite = new Composite(parent, 0);
928                 composite.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
929                 composite.setLayout( new GridLayout(3, false) );
930                 composite.setData(ref);
931                 final int count = union.getComponentCount();
932                 final Composite panels[] = new Composite[count];
933                 final Button buttons[] = new Button[count];
934                 for ( int i=0; i<count; i++ ) {
935                         Component component = union.getComponent(i);
936                         String childRef = ref+"/"+URIUtil.encodeURI(component.name);
937                         Button b = buttons[i] = new Button(composite, SWT.RADIO);
938                         b.setLayoutData(new GridData(SWT.LEFT, SWT.BEGINNING, false, false, 1, 1));
939                         b.setText(component.name);
940                         b.setData(childRef);
941                         
942                         String options = union.metadata.get("style");
943                         boolean border = options==null?true:!options.contains("no-border");
944                         int style = 0;                  
945                         if (border) style |= SWT.BORDER;
946                         
947                         Composite panel = panels[i] = new Composite(composite, style);
948                         panel.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
949                         panel.setLayout( new GridLayout(3, false) );
950                         panel.setData(childRef);
951                         
952                         Datatype componentType = union.getComponentType(i);
953                         if ( componentType instanceof RecordType ) {
954                                 addRecord(panel, (RecordType) componentType, childRef);
955                         } else {
956                                 addWidget(panel, componentType, childRef);
957                         }                       
958                 }
959                 
960                 // Set default value
961                 UnionBinding binding = Bindings.getBinding(union);
962                 try {
963                         TaggedObject defaultOption = (TaggedObject) binding.createDefault();
964                         buttons[defaultOption.tag].setSelection(true);
965                 } catch (BindingException e) {
966                 }                               
967                 
968         }
969         
970         protected void addNumber( Composite parent, NumberType number, String ref )
971         {
972                 String unit = number.getUnit();
973                 String options = number.metadata.get("style");
974                 boolean border = options==null?true:!options.contains("no-border");
975                 int style = 0;                  
976                 if (border) style |= SWT.BORDER;
977                 final Text text = new Text(parent, style);
978                 text.setData(ref);
979                 text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, unit==null?2:1, 1));
980                 
981                 // Set default value
982                 final NumberBinding binding = Bindings.getBinding( number );
983                 try {
984                         text.setText( binding.createDefault().toString() );
985                 } catch (BindingException e) {
986                 }
987                 
988                 // Add validator
989                 text.addListener(SWT.Verify, new Listener() {
990                         public void handleEvent(Event e) {
991                                 try {
992                                         String newText = applyEventToString( text.getText(), e );
993                                         Object value = binding.create( newText );
994                                         binding.assertInstaceIsValid( value );
995                                         text.setBackground(null);                                       
996                                 } catch (BindingException er) {
997                                         Color error = new Color(text.getDisplay(), 255, 222, 222); 
998                                         text.setBackground(error);
999                                         error.dispose();                                        
1000                                 }
1001                         }});
1002                 
1003                 if ( unit!=null ) {
1004                         Label unitLabel = new Label(parent, 0);
1005                         unitLabel.setText(unit);
1006                 }
1007         }
1008         
1009         static String applyEventToString(String orig, Event e) {
1010                 if (e.character==8) {
1011                         return orig.substring(0, e.start) + orig.substring(e.end, orig.length());
1012                 }
1013                 return orig.substring(0, e.start) + e.text + orig.substring(e.end, orig.length());              
1014         }
1015         
1016         protected void addBoolean( Composite parent, BooleanType booleanType, String ref )
1017         {
1018                 Button button = new Button(parent, SWT.CHECK);
1019                 button.setText( getName(ref) );
1020                 button.setData(ref);
1021                 button.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
1022         }
1023         
1024         protected void addString( Composite parent, StringType stringType, String ref )
1025         {               
1026                 String options = stringType.metadata.get("style");
1027                 boolean password = options==null?false:options.contains("password");
1028                 boolean filesave = options==null?false:options.contains("filesave");
1029                 boolean fileopen = options==null?false:options.contains("fileopen");
1030                 boolean directory = options==null?false:options.contains("directory");
1031                 boolean multi = options==null?false:options.contains("multi");
1032                 boolean border = options==null?true:!options.contains("no-border");
1033
1034                 int style = 0;
1035                 if (password) style |= SWT.PASSWORD;
1036                 if (multi) style |= SWT.MULTI;
1037                 if (border) style |= SWT.BORDER;
1038                 final Text text = new Text(parent, style);
1039                 text.setLayoutData( new GridData(SWT.FILL, multi?SWT.FILL:SWT.BEGINNING, true, false, filesave|fileopen|directory?1:2, 1 ) );
1040                 text.setData(ref);
1041
1042                 // Set default value
1043                 final StringBinding binding = Bindings.getBinding( stringType );
1044                 try {
1045                         text.setText( binding.createDefault().toString() );
1046                 } catch (BindingException e) {
1047                 }
1048                 
1049                 text.addListener(SWT.Verify, new Listener() {
1050                         public void handleEvent(Event e) {
1051                                 try {
1052                                         String newText = applyEventToString( text.getText(), e );
1053                                         Object value = binding.create( newText );
1054                                         binding.assertInstaceIsValid( value );
1055                                         text.setBackground(null);
1056                                 } catch (BindingException er) {
1057                                         Color error = new Color(text.getDisplay(), 255, 222, 222); 
1058                                         text.setBackground(error);                                      
1059                                         error.dispose();
1060                                 }
1061                         }
1062                 });             
1063                 if (filesave|fileopen) {
1064                         final FileDialog fd = new FileDialog(parent.getShell(), filesave?SWT.SAVE:SWT.OPEN);
1065                                         
1066                         String filetypesStr = stringType.metadata.get("filetypes");
1067                         String fileextsStr = stringType.metadata.get("fileexts");
1068                         String name = getName(ref);
1069                         String[] filetypes = filetypesStr==null?null:(String[]) filetypesStr.split(","); 
1070                         String[] fileexts = fileextsStr==null?null:(String[]) fileextsStr.split(",");                                   
1071                         fd.setFilterNames(filetypes);
1072                         fd.setFilterExtensions(fileexts);
1073                         
1074                         boolean nameMatchesExt = false;
1075                         if ( name!=null && !name.isEmpty() ) {
1076                                 for (String fileext : fileexts) {
1077                                         nameMatchesExt |= StringUtil.simplePatternMatch(name, fileext) && !name.contains(" ");
1078                                 }
1079                         }
1080                         final String initialName = nameMatchesExt?name:null;
1081                         
1082                         Button chooseFileButton = new Button(parent, SWT.PUSH);
1083                         chooseFileButton.setText("Select");
1084                         chooseFileButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BEGINNING, false, false));
1085                         chooseFileButton.addSelectionListener(new SelectionAdapter() {
1086                                 public void widgetSelected(SelectionEvent e) {
1087                                         String name = text.getText();
1088                                         if ( name == null || name.isEmpty() ) name = initialName;
1089                                         if ( name!=null ) fd.setFileName( name );
1090                                         String result = fd.open();
1091                                         if (result != null)
1092                                                 text.setText(result);
1093                                 }
1094                         });
1095                 }
1096
1097                 if (directory) {
1098                         final DirectoryDialog fd = new DirectoryDialog(parent.getShell(), 0);
1099                         String name = getName(ref);
1100                         fd.setMessage( name );
1101                         Button chooseDirButton = new Button(parent, SWT.PUSH);
1102                         chooseDirButton.setText("Select");
1103                         chooseDirButton.setLayoutData(new GridData(SWT.RIGHT, SWT.BEGINNING, false, false));
1104                         chooseDirButton.addSelectionListener(new SelectionAdapter() {
1105                                 public void widgetSelected(SelectionEvent e) {
1106                                         String dir = text.getText();                                    
1107                                         fd.setFilterPath(dir);
1108                                         String result = fd.open();
1109                                         if (result != null)
1110                                                 text.setText(result);
1111                                 }
1112                         });
1113                 }
1114         }
1115
1116         static String appendName(String ref, String name)
1117         {
1118                 return ref+"/"+URIUtil.encodeURI(name);
1119         }
1120         
1121         static String getName(String ref)
1122         {
1123                 if ( ref==null ) return null;
1124                 int i = ref.lastIndexOf('/');
1125                 if (i<0) return URIUtil.decodeURI(ref);
1126                 String namePart = ref.substring(i+1);
1127                 return URIUtil.decodeURI(namePart);
1128         }
1129         
1130         static int getDepth(String ref)
1131         {
1132                 int depth = 0;
1133                 for (int i=0; i<ref.length(); i++) if ( ref.charAt(i)=='/' ) depth++;
1134                 return depth;
1135         }
1136         
1137         static boolean allBooleans(RecordType rt) {
1138                 for (Component c : rt.getComponents())
1139                         if ( c.type instanceof BooleanType == false ) return false;
1140                 return true;
1141         }
1142         
1143         static String print(RecordBinding recordBinding, Object value) {                
1144                 if ( allBooleans(recordBinding.type() ) ) {
1145                         StringBuilder sb = new StringBuilder();
1146                         int j = 0;
1147                         for ( int i = 0; i<recordBinding.getComponentCount(); i++ ) {
1148                                 try {
1149                                         boolean b = recordBinding.getBoolean(value, i);
1150                                         if ( !b ) continue;
1151                                         if ( j>0 ) sb.append(", ");
1152                                         sb.append(recordBinding.type().getComponent(i).name);
1153                                         j++;
1154                                 } catch (BindingException e) {
1155                                         continue;
1156                                 }                               
1157                         }
1158                         return sb.toString();
1159                 } else 
1160                 try {
1161                         return recordBinding.toString(value, true);
1162                 } catch (BindingException e) {
1163                         return e.toString();
1164                 }
1165         }
1166         
1167         static Object parse(RecordBinding recordBinding, String txt) throws BindingException {
1168                 if ( allBooleans(recordBinding.type() ) ) {
1169                         Object result = recordBinding.createDefaultUnchecked();
1170                         String[] tokens = txt.split(", ");
1171                         for ( String token : tokens ) {
1172                                 if ( token.isEmpty() ) continue;
1173                                 int i = recordBinding.type().getComponentIndex2(token);
1174                                 if ( i>=0 ) {
1175                                         try {
1176                                                 recordBinding.setBoolean(result, i, true);
1177                                         } catch (BindingException e) {
1178                                                 throw e;
1179                                         }
1180                                 } else {
1181                                         throw new BindingException("There is no field \""+token+"\"");
1182                                 }
1183                         }
1184                         return result;
1185                 } else {
1186                         try {
1187                                 return recordBinding.parseValueDefinition(txt);
1188                         } catch (DataTypeSyntaxError e) {
1189                                 throw new BindingException(e);
1190                         }
1191                         
1192                 }
1193         }
1194         
1195         static {
1196             PASSWORD = new StringType();
1197             PASSWORD.metadata.put("style", "password");
1198             
1199             TEXTBOX = new StringType();
1200             TEXTBOX.metadata.put("style", "multi");
1201         }
1202                 
1203 }