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