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