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