1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.7. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2009 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.option;
15  
16  import y.option.AbstractItemEditor;
17  import y.option.ChildChangeReporter;
18  import y.option.ColorOptionItem;
19  import y.option.CompoundEditor;
20  import y.option.ConstraintManager;
21  import y.option.DefaultEditorFactory;
22  import y.option.Editor;
23  import y.option.FileOptionItem;
24  import y.option.GuiFactory;
25  import y.option.ItemEditor;
26  import y.option.ObjectOptionItem;
27  import y.option.OptionGroup;
28  import y.option.OptionHandler;
29  import y.option.OptionItem;
30  import y.option.ResourceBundleGuiFactory;
31  import y.option.StringOptionItem;
32  import y.option.TableEditorFactory;
33  
34  import java.awt.BorderLayout;
35  import java.awt.Color;
36  import java.awt.Dimension;
37  import java.awt.GridBagConstraints;
38  import java.awt.GridBagLayout;
39  import java.awt.GridLayout;
40  import java.awt.Toolkit;
41  import java.awt.Window;
42  import java.awt.EventQueue;
43  import java.awt.event.ActionEvent;
44  import java.awt.event.ActionListener;
45  import java.awt.event.FocusAdapter;
46  import java.awt.event.FocusEvent;
47  import java.awt.event.KeyAdapter;
48  import java.awt.event.KeyEvent;
49  import java.io.File;
50  import java.beans.PropertyChangeListener;
51  import java.beans.PropertyChangeEvent;
52  import java.beans.VetoableChangeListener;
53  import java.beans.PropertyVetoException;
54  import java.text.MessageFormat;
55  import java.text.SimpleDateFormat;
56  import java.text.ParseException;
57  import java.util.Date;
58  import java.util.Iterator;
59  import java.util.Locale;
60  import java.util.Map;
61  import java.util.MissingResourceException;
62  import javax.swing.BorderFactory;
63  import javax.swing.JButton;
64  import javax.swing.JCheckBox;
65  import javax.swing.JComponent;
66  import javax.swing.JFileChooser;
67  import javax.swing.JFrame;
68  import javax.swing.JPanel;
69  import javax.swing.JScrollPane;
70  import javax.swing.JSplitPane;
71  import javax.swing.JTextArea;
72  import javax.swing.JTextField;
73  import javax.swing.SwingUtilities;
74  import javax.swing.UIManager;
75  import javax.swing.JRootPane;
76  import javax.swing.filechooser.FileFilter;
77  
78  
79  /**
80   * Demonstrates how to create an OptionHandler whose values are
81   * editable by multiple editor components. The demo also shows
82   * how to localize and customize these editors, and how to register listeners
83   * for <code>PropertyChange</code> events.
84   * <br><br>
85   * Usage Note:
86   * <br>
87   * Each editor is controlled by a set of buttons and check boxes:
88   * <ul>
89   *   <li><code>Commit</code><br>
90   *       As the name implies, clicking this buttons commits the
91   *       displayed values to the corresponding option items.</li>
92   *   <li><code>Auto Commit</code><br>
93   *       If this option is checked, changes to displayed values are
94   *       automatically committed to the corresponding option items, without
95   *       having to click <code>Commit</code> first.</li>
96   *   <li><code>Reset</code><br>
97   *       The standard option item implementations provided by the
98   *       {@link y.option} package all support the notion of a backup value.
99   *       The backup value is (usually) the value with which an option item was
100  *       initialized. The only exception is the {@link y.option.EnumOptionItem},
101  *       which allows users to explicitly set its backup value.
102  *       Clicking this button (re-) sets the displayed values to the
103  *       backup values of the corresponding option items.</li>
104  *   <li><code>Adopt</code><br>
105  *       Clicking this button sets the displayed values to the values currently
106  *       stored in the corresponding option items.</li>
107  *   <li><code>Auto Adopt</code><br>
108  *       If this option is checked, the displayed values will be automatically
109  *       updated on changes to the values of the corresponding option items,
110  *       without having to click <code>Adopt</code> first.</li>
111  * </ul>
112  *
113  */
114 public class OptionHandlerDemo implements Runnable
115 {
116   /**
117    * Custom Date option item that allows only values of type
118    * <code>java.util.Date</code>.
119    */
120   private static final class DateOptionItem extends ObjectOptionItem
121   {
122     /**
123      * Creates a new instance of DateOptionItem.
124      * The initial value is the current date.
125      * @param name    the name of the item
126      */
127     public DateOptionItem( final String name )
128     {
129       super(name, new java.util.Date());
130     }
131 
132     /**
133      * Creates a new instance of DateOptionItem.
134      * @param name    the name of the item
135      * @param value   the initial date of the item
136      */
137     public DateOptionItem( final String name, final Date value )
138     {
139       super(name, value);
140     }
141 
142     /**
143      * Returns "Date".
144      */
145     public String getType()
146     {
147       return "Date";
148     }
149 
150     /**
151      * Sets the value of this option item.
152      *
153      * @throws IllegalArgumentException if the specified <code>value</code> is
154      *         not of type {@link java.util.Date}
155      */
156     public void setValue( final Object value )
157     {
158       if ( value instanceof Date || value == null )
159       {
160         super.setValue( value );
161       }
162       else
163       {
164         final String message = "argument type mismatch";
165         throw new IllegalArgumentException( message );
166       }
167     }
168   }
169 
170   /**
171    * Custom <code>ItemEditor</code> implementation for
172    * <code>DateOptionItem</code>.
173    *
174    * The editor component displays three <code>JTextField</code>s to enter
175    * day, month, and year of a date.
176    *
177    * No validation checks are performed on the user input.
178    */
179   private static final class DateItemEditor extends AbstractItemEditor
180   {
181     // value
182     private Date date;
183 
184     // editor components
185     private final JPanel panel;
186     private final JTextField day;
187     private final JTextField month;
188     private final JTextField year;
189 
190     // utilities for Date <-> String conversions
191     private final SimpleDateFormat parser;
192     private final SimpleDateFormat formatDay;
193     private final SimpleDateFormat formatMonth;
194     private final SimpleDateFormat formatYear;
195 
196 
197     /**
198      * Creates a new instance of DateItemEditor.
199      */
200     public DateItemEditor( final DateOptionItem item )
201     {
202       super(item);
203 
204       panel = new JPanel(new GridBagLayout());
205       day   = new JTextField(2);
206       month = new JTextField(2);
207       year  = new JTextField(4);
208 
209       parser = new SimpleDateFormat("dd MM yyyy");
210       formatDay   = new SimpleDateFormat("dd");
211       formatMonth = new SimpleDateFormat("MM");
212       formatYear  = new SimpleDateFormat("yyyy");
213 
214 
215       // Adopt the text value as editor value when <ENTER> is pressed.
216       final KeyAdapter keyAdapter = new KeyAdapter()
217       {
218         public void keyPressed(final KeyEvent e)
219         {
220           if (KeyEvent.VK_ENTER == e.getKeyCode())
221           {
222             parseDateAndSetValue(day.getText(), month.getText(), year.getText());
223           }
224         }
225       };
226 
227       // Adopt the text value as editor value when a modified textfield looses
228       // focus.
229       final FocusAdapter focusAdapter = new FocusAdapter()
230       {
231         public void focusLost(final FocusEvent e)
232         {
233           if (!isValueUndefined() && !e.isTemporary())
234           {
235             parseDateAndSetValue(day.getText(), month.getText(), year.getText());
236           }
237         }
238       };
239 
240       day.addKeyListener(keyAdapter);
241       day.addFocusListener(focusAdapter);
242       month.addKeyListener(keyAdapter);
243       month.addFocusListener(focusAdapter);
244       year.addKeyListener(keyAdapter);
245       year.addFocusListener(focusAdapter);
246 
247 
248       final GridBagConstraints gbc = new GridBagConstraints();
249       gbc.anchor = GridBagConstraints.WEST;
250       gbc.fill = GridBagConstraints.NONE;
251       panel.add(day,   gbc);
252       panel.add(month, gbc);
253       panel.add(year,  gbc);
254       gbc.weightx = 1.0;
255       panel.add(new JPanel(), gbc);
256 
257 
258       // display initial item value
259       adoptItemValue();
260     }
261 
262     public void commitValue() {
263       parseDateAndSetValue(day.getText(), month.getText(), year.getText());
264       super.commitValue();
265     }
266 
267     public Object getValue()
268     {
269       return date;
270     }
271 
272     /**
273      * Sets the value of this editor.
274      */
275     public void setValue( final Object value )
276     {
277       setValueImpl(value);
278     }
279 
280     public boolean isEnabled()
281     {
282       return panel.isEnabled();
283     }
284 
285     public void setEnabled( final boolean enabled )
286     {
287       final boolean oldEnabled = isEnabled();
288       if (oldEnabled != enabled)
289       {
290         panel.setEnabled(enabled);
291         day.setEnabled(enabled);
292         month.setEnabled(enabled);
293         year.setEnabled(enabled);
294 
295         // notify interested parties
296         publishEnabledChange(oldEnabled, enabled);
297       }
298     }
299 
300     /**
301      * Returns always "false" - no value undefined support.
302      */
303     public boolean isValueUndefined()
304     {
305       // no value undefined support
306       return false;
307     }
308 
309     /**
310      * Does nothing - no value undefined support.
311      */
312     public void setValueUndefined( final boolean b )
313     {
314       // no value undefined support
315     }
316 
317     /**
318      * Returns the editor component.
319      */
320     public JComponent getComponent()
321     {
322       return panel;
323     }
324 
325     /**
326      * Sets the value of this editor.
327      * Supports only values of type <code>java.util.Date</code>.
328      */
329     private void setValueImpl(final Object value)
330     {
331       if ( null != date ? !date.equals(value) : null != value )
332       {
333         final Date oldValue = date;
334         try
335         {
336           // notify interested parties
337           fireVetoableChange(PROPERTY_VALUE, oldValue, value);
338         }
339         catch ( PropertyVetoException pve )
340         {
341           // rejected
342           return;
343         }
344 
345         date = (Date)value;
346 
347         day.setText(formatDay.format(date));
348         month.setText(formatMonth.format(date));
349         year.setText(formatYear.format(date));
350 
351         // notify interested parties
352         publishValueChange(oldValue, value);
353       }
354     }
355 
356     /**
357      * Tries to parse the specified data into a <code>Date</code> instance.
358      * No validation checks are performed on the data.
359      *
360      * @throws RuntimeException if the specified data cannot be parsed into
361      * a <code>Date</code> instance.
362      */
363     private void parseDateAndSetValue( final String day,
364                                        final String month,
365                                        final String year )
366     {
367       try
368       {
369         // parseDateAndSetValue is only called on user input,
370         // so we neither want nor need to update the editor components
371         setValueImpl(parser.parse(day + " " + month + " " + year));
372       }
373       catch (ParseException pe)
374       {
375         throw new RuntimeException(pe);
376       }
377     }
378   }
379 
380   /**
381    * Custom editor factory that supports <code>DateOptionItem</code>.
382    */
383   private static final class CustomEditorFactory extends DefaultEditorFactory
384   {
385     /**
386      * Overwritten to support <code>DateOptionItem</code> instances.
387      */
388     public ItemEditor createEditor( final OptionItem item, final Map attributes )
389     {
390       if (item instanceof DateOptionItem)
391       {
392         final ItemEditor editor =  new DateItemEditor((DateOptionItem)item);
393 
394         // IMPORTANT:
395         // Register the new editor on the item. If this is not done,
396         // features like automatic adoption of item values or constraint
397         // handling will not work
398         item.addEditor(editor);
399 
400         return editor;
401       }
402       else
403       {
404         return super.createEditor(item, attributes);
405       }
406     }
407   }
408 
409 
410   private final GuiFactory i18n;
411 
412   /**
413    * Private ctor to prevent external instantiation.
414    */
415   public OptionHandlerDemo()
416   {
417     // setup a guifactory
418     ResourceBundleGuiFactory gf = null;
419     try
420     {
421       gf = new ResourceBundleGuiFactory();
422       gf.addBundle( OptionHandlerDemo.class.getName() );
423     }
424     catch ( final MissingResourceException mre )
425     {
426       System.err.println( "Could not find resources! " + mre );
427     }
428     i18n = gf;
429   }
430 
431   /**
432    * Creates an OptionHandler.
433    */
434   private OptionHandler createHandler()
435   {
436     /*
437      * Ok, let's create an OptionHandler and add some items.
438      * Nothing new so far.
439      */
440     final OptionHandler op = new OptionHandler("Grid");
441 
442     op.useSection("Misc");
443     op.addItem(new DateOptionItem("Date"));
444     op.addInt("Rows",5);
445     op.addInt("Columns",5,1,100);
446     op.addCommentItem(getI18nString("Grid.Misc.Comment1"));
447     OptionItem col = op.addColor("Color", Color.blue, true);
448     col.setAttribute( ColorOptionItem.ATTRIBUTE_SHOW_ALPHA, Boolean.TRUE);
449     op.addFile("Open","blafasel");
450     op.addFile("Save","");
451     op.addEnum("Model",new String[]{"Random","Deterministic","Buba"},2);
452     op.addBool("Invert",true);
453 
454     JFileChooser chooser = new JFileChooser( System.getProperty( "user.dir" ) );
455     chooser.setDialogType( JFileChooser.SAVE_DIALOG );
456     chooser.addChoosableFileFilter( new FileFilter()
457     {
458       public boolean accept( final File f )
459       {
460         return f.getName().toLowerCase().endsWith( ".txt" );
461       }
462 
463       public String getDescription()
464       {
465         return "*.txt";
466       }
467     } );
468 
469     /*
470      * Register the custom file chooser for one of our file items.
471      */
472     OptionItem item;
473     item = op.getItem( "Misc", "Save" );
474     item.setAttribute( FileOptionItem.ATTRIBUTE_FILE_CHOOSER, chooser );
475 
476     op.useSection( "Enums" );
477 
478     op.addEnum( "ComboBox", new String[]{"val1", "val2", "val3"}, 0 );
479     op.addEnum( "RadioHorizontal", new String[]{"Button1", "Button2", "Button3"}, 0 );
480     op.addEnum( "RadioVertical", new String[]{"Button1", "Button2", "Button3"}, 0 );
481     op.addEnum( "NoI18n", new String[]{"English", "Deutsch", "Francais"}, 0 );
482 
483     op.addString( "Input", "lalala" );
484     op.addInt( "Rows", 10 );
485     op.addCommentItem( getI18nString("Grid.Enums.Comment1") );
486     op.addInt( "Columns", 10 );
487 
488     op.addEnum( "Layout", new String[]{"Rows", "Columns"}, 0 );
489 
490 
491     /*
492      * Let's pep it up:
493      * Different styles of enumeration items
494      */
495     item = op.getItem( "Enums", "RadioHorizontal" );
496     item.setAttribute( DefaultEditorFactory.ATTRIBUTE_ENUM_STYLE,
497                        DefaultEditorFactory.STYLE_RADIO_BUTTONS );
498     item.setAttribute( DefaultEditorFactory.ATTRIBUTE_ENUM_ALIGNMENT,
499                        DefaultEditorFactory.ALIGNMENT_HORIZONTAL );
500     item = op.getItem( "Enums", "RadioVertical" );
501     item.setAttribute( DefaultEditorFactory.ATTRIBUTE_ENUM_STYLE,
502                        DefaultEditorFactory.STYLE_RADIO_BUTTONS );
503     item.setAttribute( DefaultEditorFactory.ATTRIBUTE_ENUM_ALIGNMENT,
504                        DefaultEditorFactory.ALIGNMENT_VERTICAL );
505 
506     op.useSection( "Groups" );
507     op.addBool("Options",true);
508     op.addDouble("Quality", 0.5, 0.0, 1.0, 2);
509     op.addEnum( "Layout", new String[]{"rows", "columns"}, 0 );
510     op.addInt("Rows",5,1,20);
511     op.addColor("RowsColor", Color.blue, true);
512     op.addInt("Columns",5,1,20);
513     op.addColor("ColumnsColor", Color.blue, true);
514 
515     op.useSection( "Strings" );
516     op.addString( "TextField", "bla blubber" );
517     op.addString( "MultiLine", "bla bla bla\nblubber blubbber" );
518     op.addString( "OneLineEmpty", "" );
519     op.addString( "MultiLineEmpty", "" );
520 
521     item = op.getItem( "Strings", "MultiLine" );
522     item.setAttribute( StringOptionItem.ATTRIBUTE_ROWS, new Integer( 5 ) );
523     item.setAttribute( StringOptionItem.ATTRIBUTE_POPUP_ROWS, new Integer( 20 ) );
524     item.setAttribute( StringOptionItem.ATTRIBUTE_COLUMNS, new Integer( 15 ) );
525     item.setAttribute( StringOptionItem.ATTRIBUTE_POPUP_COLUMNS, new Integer( 40 ) );
526     item = op.getItem( "Strings", "OneLineEmpty" );
527     item.setAttribute( DefaultEditorFactory.ATTRIBUTE_STRING_STYLE,
528                        DefaultEditorFactory.STYLE_TEXT_AREA );
529     item = op.getItem( "Strings", "MultiLineEmpty" );
530     item.setAttribute( StringOptionItem.ATTRIBUTE_ROWS, new Integer( 5 ) );
531     item.setAttribute( StringOptionItem.ATTRIBUTE_COLUMNS, new Integer( 15 ) );
532 
533     op.useSection("Info");
534     op.addCommentItem(getI18nString("Grid.Info.Comment1"));
535 
536     final OptionHandler op2 = new OptionHandler("Innerhandler");
537     op2.useSection("Inner");
538     op2.addInt("Rows",5,1,300);
539     op2.addInt("Columns",5,1,20);
540     op2.addString("String","value asdf asdf asdf asdf",4).setAttribute(DefaultEditorFactory.FILL_SPACE_WEIGHT, new Double(2));
541     op2.addString("String2","value asdf asdf asdf asdf");
542 
543     op.addOptionHandler(op2, "Innerhandler");
544 
545     /*
546      * Now let's see something new:
547      * We create a constraint that ensures that the quality slider is disabled
548      * when the options checkbox is unchecked.
549      */
550     ConstraintManager cm = new ConstraintManager( op );
551     cm.setEnabledOnValueEquals( "Options", Boolean.TRUE,
552                                 "Quality" );
553 
554     /*
555      * Another new feature: grouping items
556      */
557     OptionGroup og;
558     og = new OptionGroup();
559     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, "OPTIONS_AND_QUALITY" );
560     og.addItem( op.getItem( "Groups", "Options" ) );
561     og.addItem( op.getItem( "Groups", "Quality" ) );
562 
563     og = new OptionGroup();
564     cm.setEnabledOnValueEquals( "Layout", "rows", og );
565     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, "ROWS" );
566     og.addItem( op.getItem( "Groups", "Rows" ) );
567     og.addItem( op.getItem( "Groups", "RowsColor" ) );
568 
569     og = new OptionGroup();
570     cm.setEnabledOnValueEquals( "Layout", "columns", og );
571     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, "COLUMNS" );
572     og.addItem( op.getItem( "Groups", "Columns" ) );
573     og.addItem( op.getItem( "Groups", "ColumnsColor" ) );
574 
575     /*
576      * This is the way to create cards ...
577      * First we need a controller id.
578      */
579     final Object ctrId = new Object();
580 
581     /*
582      * Now, let's set up a group specifying what goes to the first card.
583      */
584     og = new OptionGroup();
585     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, "ROWS" );
586     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
587     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, "Rows" );
588     og.addItem( op.getItem( "Enums", "Input" ) );
589     og.addItem( op.getItem( "Enums", "Rows" ) );
590 
591     /*
592      * The second card ...
593      */
594     og = new OptionGroup();
595     og.setAttribute( OptionGroup.ATTRIBUTE_TITLE, "COLUMNS" );
596     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
597     og.setAttribute( DefaultEditorFactory.ATTRIBUTE_CARD_ID, "Columns" );
598     og.addItem( op.getItem( "Enums", "_COMMENT" ) );
599     og.addItem( op.getItem( "Enums", "Columns" ) );
600 
601     /*
602      * And finally, we need to specify which item controlls the cards.
603      */
604     op.getItem( "Enums", "Layout" )
605       .setAttribute( DefaultEditorFactory.ATTRIBUTE_CONTROLLER_ID, ctrId );
606 
607     /*
608      * Hmmm, descriptions might be handy, too ...
609      */
610     op.section( "Misc" )
611       .setAttribute( "OptionSection.longDescription",
612                      getI18nString( "Grid.Misc.longDescription" ) );
613     op.getItem( "Misc", "Rows" )
614       .setAttribute( "OptionItem.longDescription",
615                      getI18nString( "Grid.Misc.Rows.longDescription" ) );
616     op.getItem( "Misc", "Columns" )
617       .setAttribute( "OptionItem.longDescription",
618                      getI18nString( "Grid.Misc.Columns.longDescription" ) );
619     op.getItem( "Groups", "Rows" )
620       .setAttribute( "OptionItem.longDescription",
621                      getI18nString( "Grid.Groups.Rows.longDescription" ) );
622     op.getItem( "Groups", "Columns" )
623       .setAttribute( "OptionItem.longDescription",
624                      getI18nString( "Grid.Groups.Columns.longDescription" ) );
625     op.section( "Info" )
626       .setAttribute( "OptionSection.longDescription",
627                      getI18nString( "Grid.Info.longDescription" ) );
628     op2.section( "Inner" )
629        .setAttribute( "OptionSection.longDescription",
630                       getI18nString( "Grid.Inner.longDescription" ) );
631 
632     return op;
633   }
634 
635   /**
636    * Creates the GUI.
637    */
638   private JComponent createGUI( final OptionHandler handler )
639   {
640     /*
641      * First we want to create a view we already know and love,
642      * so we instantiate a CustomEditorFactory and create an editor.
643      */
644     DefaultEditorFactory defaultFactory = new CustomEditorFactory();
645     defaultFactory.setGuiFactory( i18n );
646     Editor editor1 = defaultFactory.createEditor( handler );
647 
648     /*
649      * Now we want to see something new: a table view.
650      * Same procedure as before: instantiate the appropriate factory
651      * and create an editor.
652      *
653      * Note:
654      * We used the same OptionHandler instance as before!
655      */
656     TableEditorFactory tableFactory = new TableEditorFactory();
657 
658     // we want to support our custom DateOptionItem in the table view, too.
659     tableFactory.setItemFactory(defaultFactory);
660 
661     tableFactory.setGuiFactory( i18n );
662     Editor editor2 = tableFactory.createEditor( handler );
663     final JPanel editorPane = new JPanel( new BorderLayout() );
664     editorPane.add( createEditorPane( editor1, getI18nString( "Editor.title.Default" ),
665                                       false, true ),
666                     BorderLayout.WEST );
667     editorPane.add( createEditorPane( editor2, getI18nString( "Editor.title.Table"  ),
668                                       true, true ),
669                     BorderLayout.CENTER );
670 
671 
672     /*
673      * We set up property change listeners to print onto the console when
674      * a property change occurs.
675      */
676     final JTextArea console = new JTextArea();
677     console.setEditable( false );
678     console.setBackground( Color.white );
679 
680     final PropertyChangeListener itemListener = new PropertyChangeListener()
681     {
682       final StringBuffer buffer = new StringBuffer();
683       final Object[] args = new Object[5];
684       final MessageFormat formatter = new MessageFormat( getI18nString( "Demo.ItemListener.Format" ) );
685       public void propertyChange( final PropertyChangeEvent evt )
686       {
687         args[0] = evt.getSource().getClass().getName();
688         args[1] = ((OptionItem)evt.getSource()).getName();
689         args[2] = evt.getPropertyName();
690         args[3] = evt.getOldValue();
691         args[4] = evt.getNewValue();
692         buffer.setLength( 0 );
693         formatter.format( args, buffer, null );
694         buffer.append( "\n" );
695         console.append( buffer.toString() );
696       }
697     };
698 
699     final PropertyChangeListener editorListener = new PropertyChangeListener()
700     {
701       final StringBuffer buffer = new StringBuffer();
702       final Object[] args = new Object[5];
703       final MessageFormat formatter = new MessageFormat( getI18nString( "Demo.EditorListener.Format" ) );
704       public void propertyChange( final PropertyChangeEvent evt )
705       {
706         args[0] = evt.getSource().getClass().getName();
707         args[1] = ((ItemEditor)evt.getSource()).getItem().getName();
708         args[2] = evt.getPropertyName();
709         args[3] = evt.getOldValue();
710         args[4] = evt.getNewValue();
711         buffer.setLength( 0 );
712         formatter.format( args, buffer, null );
713         buffer.append( "\n" );
714         console.append( buffer.toString() );
715       }
716     };
717 
718     // register the listener on all items
719     handler.addChildPropertyChangeListener( itemListener );
720 
721     // register the listener on all item editors
722     ((ChildChangeReporter)editor1).addChildPropertyChangeListener( editorListener );
723     ((ChildChangeReporter)editor2).addChildPropertyChangeListener( editorListener );
724 
725 
726     VetoableChangeListener editorVeto = new VetoableChangeListener()
727     {
728       final StringBuffer buffer = new StringBuffer();
729       final Object[] args = new Object[5];
730       final MessageFormat formatter = new MessageFormat( getI18nString( "Demo.EditorVeto.Format" ) );
731       final Integer ten = new Integer(10);
732       public void vetoableChange( final PropertyChangeEvent evt )
733               throws PropertyVetoException
734       {
735         final ItemEditor editor = (ItemEditor)evt.getSource();
736         final String itemName = editor.getItem().getName();
737         if ("Color".equals(itemName))
738         {
739           if (Color.yellow.equals(evt.getNewValue()))
740           {
741             args[0] = evt.getSource().getClass().getName();
742             args[1] = editor.getItem().getName();
743             args[2] = evt.getPropertyName();
744             args[3] = evt.getOldValue();
745             args[4] = evt.getNewValue();
746             buffer.setLength( 0 );
747             formatter.format( args, buffer, null );
748             buffer.append( "\n" );
749             console.append( buffer.toString() );
750             throw new PropertyVetoException("RevertColor", evt);
751           }
752         }
753       }
754     };
755     ((ChildChangeReporter)editor1).addChildVetoableChangeListener( editorVeto );
756     ((ChildChangeReporter)editor2).addChildVetoableChangeListener( editorVeto );
757 
758     VetoableChangeListener itemVeto = new VetoableChangeListener()
759     {
760       final StringBuffer buffer = new StringBuffer();
761       final Object[] args = new Object[5];
762       final MessageFormat formatter = new MessageFormat( getI18nString( "Demo.ItemVeto.Format" ) );
763       final Integer two = new Integer(2);
764       public void vetoableChange( final PropertyChangeEvent evt )
765               throws PropertyVetoException
766       {
767         final OptionItem item = (OptionItem)evt.getSource();
768         if ("Color".equals(item.getName()))
769         {
770           if (Color.red.equals(evt.getNewValue()))
771           {
772             args[0] = evt.getSource().getClass().getName();
773             args[1] = item.getName();
774             args[2] = evt.getPropertyName();
775             args[3] = evt.getOldValue();
776             args[4] = evt.getNewValue();
777             buffer.setLength( 0 );
778             formatter.format( args, buffer, null );
779             buffer.append( "\n" );
780             console.append( buffer.toString() );
781             throw new PropertyVetoException("RevertColor", evt);
782           }
783         }
784       }
785     };
786     handler.addChildVetoableChangeListener( itemVeto );
787 
788     /*
789      * Some rather uninteresting stuff:
790      * Putting everything into frame and displaying that.
791      */
792     final JButton clearConsole = new JButton( getI18nString( "CLEAR_ACTION" ) );
793     clearConsole.addActionListener( new ActionListener()
794     {
795       public void actionPerformed( final ActionEvent e )
796       {
797         console.setText( "" );
798       }
799     });
800 
801     final JScrollPane jsp = new JScrollPane( console );
802     final Dimension d = jsp.getPreferredSize();
803     d.height = 100;
804     jsp.setPreferredSize( d );
805 
806     GridBagConstraints gbc = new GridBagConstraints();
807     final JPanel consolePane = new JPanel( new GridBagLayout() );
808 
809     gbc.fill = GridBagConstraints.BOTH;
810     gbc.gridx = 0;
811     gbc.gridy = 0;
812     gbc.weightx = 1.0;
813     gbc.weighty = 1.0;
814     consolePane.add( jsp, gbc );
815 
816     gbc.anchor = GridBagConstraints.EAST;
817     gbc.fill = GridBagConstraints.VERTICAL;
818     gbc.gridx = 1;
819     gbc.gridy = 0;
820     gbc.weightx = 0.0;
821     gbc.weighty = 0.0;
822     consolePane.add( clearConsole, gbc );
823 
824 
825     final JPanel contentPane = new JPanel( new GridLayout( 1, 1 ) );
826     contentPane.setBorder( BorderFactory.createEmptyBorder( 5,5,5,5 ) );
827     contentPane.add( new JSplitPane(JSplitPane.VERTICAL_SPLIT,
828                                     true,
829                                     editorPane,
830                                     consolePane ) );
831     return contentPane;
832   }
833 
834   /**
835    * Creates a component displaying an editor view with some controls.
836    */
837   private JPanel createEditorPane( final Editor editor, final String title,
838                                    final boolean autoCommit,
839                                    final boolean autoAdopt )
840   {
841     final JPanel ep1 = new JPanel( new BorderLayout() );
842     ep1.setBorder( BorderFactory.createTitledBorder( title ) );
843     ep1.add( editor.getComponent(), BorderLayout.CENTER );
844     ep1.add( createControlPane( editor, autoCommit, autoAdopt ),
845              BorderLayout.SOUTH );
846     return ep1;
847   }
848 
849   /**
850    * Creates controls for the specified editor.
851    */
852   private JComponent createControlPane( final Editor editor,
853                                         final boolean autoCommitFlag,
854                                         final boolean autoAdoptFlag )
855   {
856     final JCheckBox autoCommit = new JCheckBox( getI18nString( "AUTO_COMMIT_ACTION" ) );
857     final JCheckBox autoAdopt = new JCheckBox( getI18nString( "AUTO_ADOPT_ACTION" ) );
858     final JButton commit = new JButton( getI18nString( "COMMIT_ACTION" ) );
859     final JButton adopt = new JButton( getI18nString( "ADOPT_ACTION" ) );
860     final JButton reset = new JButton( getI18nString( "RESET_ACTION" ) );
861 
862     autoCommit.addActionListener( new ActionListener()
863     {
864       public void actionPerformed( ActionEvent e )
865       {
866         final boolean state = autoCommit.isSelected(); 
867         commit.setEnabled( !state );
868         setAutoCommit( state, editor );
869       }
870     });
871 
872     autoAdopt.addActionListener( new ActionListener()
873     {
874       public void actionPerformed( ActionEvent e )
875       {
876         final boolean state = autoAdopt.isSelected();
877         adopt.setEnabled( !state );
878         setAutoAdopt( state, editor );
879       }
880     });
881 
882     commit.setToolTipText( getI18nString( "COMMIT_ACTION.TOOLTIP" ) );
883     commit.addActionListener( new ActionListener()
884     {
885       public void actionPerformed( ActionEvent e )
886       {
887         editor.commitValue();
888       }
889     });
890 
891     adopt.setToolTipText( getI18nString( "ADOPT_ACTION.TOOLTIP" ) );
892     adopt.addActionListener( new ActionListener()
893     {
894       public void actionPerformed( ActionEvent e )
895       {
896         editor.adoptItemValue();
897       }
898     });
899 
900     reset.setToolTipText( getI18nString( "RESET_ACTION.TOOLTIP" ) );
901     reset.addActionListener( new ActionListener()
902     {
903       public void actionPerformed( ActionEvent e )
904       {
905         editor.resetValue();
906       }
907     });
908 
909     if ( !autoCommitFlag )
910     {
911       autoCommit.setSelected( true );
912     }
913     autoCommit.doClick();
914     if ( !autoAdoptFlag )
915     {
916       autoAdopt.setSelected( true );
917     }
918     autoAdopt.doClick();
919 
920     final JPanel controlPane = new JPanel( new GridBagLayout() );
921     final GridBagConstraints gbc = new GridBagConstraints();
922     gbc.anchor = GridBagConstraints.WEST;
923     gbc.fill = GridBagConstraints.HORIZONTAL;
924     gbc.insets.left = 4;
925     gbc.insets.right = 4;
926     gbc.gridx = 0;
927     gbc.gridy = 0;
928     controlPane.add( commit, gbc );
929     gbc.gridx++;
930     controlPane.add( reset, gbc );
931     gbc.gridx++;
932     controlPane.add( adopt, gbc );
933     gbc.insets.left = 0;
934     gbc.gridx = 0;
935     gbc.gridy++;
936     controlPane.add( autoCommit, gbc );
937     gbc.gridx+=2;
938     controlPane.add( autoAdopt, gbc );
939     return controlPane;
940   }
941 
942   /**
943    * Sets the <code>autoCommit</code> property to the specified value,
944    * if the specified editor support setting said property.
945    */
946   private static void setAutoCommit( final boolean autoCommit,
947                                      final Editor editor )
948   {
949     if ( editor instanceof CompoundEditor )
950     {
951       for ( Iterator it = ((CompoundEditor)editor).editors(); it.hasNext(); )
952       {
953         setAutoCommit( autoCommit, (Editor)it.next() );
954       }
955     }
956     if ( editor instanceof ItemEditor )
957     {
958       ((ItemEditor)editor).setAutoCommit( autoCommit );
959     }
960   }
961 
962   /**
963    * Sets the <code>autoAdopt</code> property for all items of the specified
964    * option handler.
965    */
966   private static void setAutoAdopt( final boolean autoAdopt,
967                                     final Editor editor )
968   {
969     if ( editor instanceof CompoundEditor )
970     {
971       for ( Iterator it = ((CompoundEditor)editor).editors(); it.hasNext(); )
972       {
973         setAutoAdopt( autoAdopt, (Editor)it.next() );
974       }
975     }
976     if ( editor instanceof ItemEditor )
977     {
978       ((ItemEditor)editor).setAutoAdopt( autoAdopt );
979     }
980   }
981 
982   /**
983    * Centers the specified window.
984    */
985   private static void centerOnScreen( final Window w )
986   {
987     final Dimension wd = w.getSize();
988     final Dimension sd = Toolkit.getDefaultToolkit().getScreenSize();
989 
990     int x = sd.width - wd.width;
991     x = (x > 0) ? x/2 : 0;
992     int y = sd.height - wd.height;
993     y = (y > 0) ? y/3 : 0;
994 
995     w.setLocation( x, y );
996   }
997 
998   /**
999    * Creates an OptionHandler, creates a GUI for the handler, and displays it.
1000   */
1001  public void run()
1002  {
1003    final JFrame frame = new JFrame( getI18nString( "Demo.title" ) );
1004    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
1005    addContentTo( frame.getRootPane() );
1006    frame.pack();
1007    centerOnScreen( frame );
1008    frame.setVisible( true );
1009  }
1010
1011  public void addContentTo( final JRootPane rootPane )
1012  {
1013    rootPane.setContentPane( createGUI( createHandler() ) );
1014  }
1015
1016  /**
1017   * Convenience method, so we do not have to check for <code>null</code>
1018   * when doing i18n.
1019   */
1020  private String getI18nString( final String key )
1021  {
1022    return i18n != null ? i18n.getString( key ) : key;
1023  }
1024
1025
1026  /**
1027   * Initializes to a "nice" look and feel.
1028   */
1029  public static void initLnF()
1030  {
1031    try
1032    {
1033      if(!UIManager.getSystemLookAndFeelClassName().equals(
1034        "com.sun.java.swing.plaf.motif.MotifLookAndFeel") &&
1035        !UIManager.getSystemLookAndFeelClassName().equals(
1036        "com.sun.java.swing.plaf.gtk.GTKLookAndFeel") &&
1037         !UIManager.getSystemLookAndFeelClassName().equals(
1038           UIManager.getLookAndFeel().getClass().getName()))
1039      {
1040        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
1041      }
1042    }
1043    catch (Exception e)
1044    {
1045      e.printStackTrace();
1046    }
1047  }
1048
1049  /**
1050   * The main method.
1051   */
1052  public static void main( final String[] args )
1053  {
1054    // set the locale as given from the arguments
1055    if (args.length > 1)
1056    {
1057      Locale.setDefault(new Locale(args[0],args[1]));
1058    }
1059    else if (args.length > 0)
1060    {
1061      Locale.setDefault(new Locale(args[0],""));
1062    }
1063
1064    EventQueue.invokeLater(new Runnable() {
1065      public void run() {
1066        initLnF();
1067        (new OptionHandlerDemo()).run();
1068      }
1069    });
1070  }
1071}
1072