View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.ui.swing.components;
20  
21  import java.awt.AWTEventMulticaster;
22  import java.awt.BorderLayout;
23  import java.awt.Component;
24  import java.awt.Container;
25  import java.awt.FlowLayout;
26  import java.awt.Insets;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.ActionListener;
29  import java.awt.event.MouseEvent;
30  import java.awt.event.MouseMotionListener;
31  import java.beans.BeanInfo;
32  import java.beans.Introspector;
33  import java.beans.PropertyChangeEvent;
34  import java.beans.PropertyChangeListener;
35  import java.beans.PropertyDescriptor;
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.util.Enumeration;
39  import java.util.Vector;
40  
41  import javax.swing.AbstractButton;
42  import javax.swing.Action;
43  import javax.swing.JButton;
44  import javax.swing.JFrame;
45  import javax.swing.JPanel;
46  import javax.swing.UIManager;
47  
48  /***
49  * ButtonPanel handles display and event broadcasting of standard buttons like
50  * OK/Cancel/Save/etc. The constructor takes a list or array of strings, each
51  * representing a button to appear on the panel from left to right.
52  * Any button click will send an action event to all listeners with the action
53  * command containing the corresponding string.  Note action events are simply
54  * forwarded from the buttons themselves, so the source of the event will be
55  * the button, not the button panel.  The button panel is the source of the
56  * STATE_CHANGED events that notify about changes to the panel itself.<BR><BR>
57  *
58  * @author michael@mpowers.net
59  * @author $Author: cgruber $
60  * @version $Revision: 904 $
61  */
62  public class ButtonPanel extends JPanel implements ActionListener, MouseMotionListener
63  {
64      // TODO: Button text should be read from resources.
65  /***
66  *   Specifies a "OK" button.
67  *   This is also the action command sent by the OK button.
68  */
69      public static final String OK = "OK";
70  /***
71  *   Specifies a "Save" button.
72  *   This is also the action command sent by the Save button.
73  */
74      public static final String SAVE = "Save";
75  /***
76  *   Specifies a "Refresh" button.
77  *   This is also the action command sent by the Refresh button.
78  */
79      public static final String REFRESH = "Refresh";
80  /***
81  *   Specifies a "Clear All" button.
82  *   This is also the action command sent by the Clear All button.
83  */
84      public static final String CLEAR_ALL = "Clear All";
85  /***
86  *   Specifies a "Refresh" button.
87  *   This is also the action command sent by the Cancel button.
88  */
89      public static final String CANCEL = "Cancel";
90  /***
91  *   Specifies a "Yes" button.
92  *   This is also the action command sent by the Yes button.
93  */
94      public static final String YES = "Yes";
95  /***
96  *   Specifies a "No" button.
97  *   This is also the action command sent by the No button.
98  */
99      public static final String NO = "No";
100 /***
101 *   Specifies an "Add" button.
102 *   This is also the action command sent by the Add button.
103 */
104     public static final String ADD = "Add";
105 /***
106 *   Specifies a "Remove" button.
107 *   This is also the action command sent by the Remove button.
108 */
109     public static final String REMOVE = "Remove";
110 /***
111 *   This is the action command to all listeners when the button state is changed.
112 */
113     public static final String STATE_CHANGED = "STATE_CHANGED";
114 
115 /***
116 *   This is the container to which buttons are added.
117 */
118     protected Container buttonContainer = null; // useful for subclasses
119 /***
120 *   This is the list of all buttons on the panel.
121 */
122     protected Vector buttonList = null;
123 /***
124 *   The insets for this panel, so they can be modified.
125 */
126     protected Insets insets = new Insets( 5, 5, 5, 5 );
127 
128 /***
129 *   This is the layout manager - which must be a FlowLayout or subclass.
130 */
131     protected FlowLayout buttonPanelLayout = null;
132 
133     // for action multicasting
134     protected ActionListener actionListener = null;
135 
136 
137 /***
138 * Constructs a ButtonPanel.  Three buttons are created
139 * so the panel is filled when used in a GUI-builder environment.
140 */
141     public ButtonPanel()
142     {
143         buttonList = new Vector();
144         initLayout();
145 
146         // default labels for bean layout
147         setLabels( new String[] { "One", "Two", "Three" } );
148     }
149 
150 /***
151 * This method is responsible for the initial layout of the panel.
152 * Subclasses can implement different layouts, but this method
153 * is responsible for initializing buttonContainer and buttonPanelLayout
154 * and setting the container to use the layout.
155 */
156     protected void initLayout()
157     {
158         this.setInsets( super.getInsets() );
159         buttonContainer = this;
160         buttonPanelLayout = new BetterFlowLayout( BetterFlowLayout.RIGHT );
161         buttonContainer.setLayout(buttonPanelLayout);
162         ((BetterFlowLayout)buttonPanelLayout).setWidthUniform( true );
163 
164         // setBackground( Color.blue ); // useful for debugging
165     }
166 
167 /***
168 * Constructs a ButtonPanel using specified buttons.
169 * @param buttonList An array containing the strings to be used in labeling the buttons.
170 */
171     public ButtonPanel( String[] buttonList )
172     {
173         this();
174         setLabels( buttonList );
175     }
176 
177 /***
178 * Constructs a ButtonPane using specified actions.  For each action, a button
179 * is created, that when pressed the corresponding action is activated.  The
180 * "name" of the action is used as the title of the button.
181 * @param actionList An array of actions to be used to create buttons with.
182 */
183     public ButtonPanel( Action[] actionList )
184     {
185         this();
186         setLabels( actionList );
187     }
188 
189 /***
190 * Creates the buttons to appear on the panel.  Any existing buttons
191 * are replaced.  The labels are used as names and action commands
192 * in addition to labels.
193 * @param labels An array of strings to be used in labeling the buttons.
194 * If null, all buttons will be removed.
195 */
196     public void setLabels( String[] labels )
197     {
198         if ( labels == null )
199         {
200             labels = new String[] {};
201         }
202 
203         buttonContainer.removeAll();
204         this.buttonList = new Vector( labels.length );
205 
206         String item = null;
207         Component button;
208         for ( int i = 0; i < labels.length; i++ )
209         {
210             item = labels[i];
211             if ( item != null )
212             {
213                 button = createComponentWithLabel( item.toString() );
214                 this.buttonList.addElement( item );
215                 addComponentToPanel( button );
216                 button.setEnabled( this.isEnabled() );
217 /*
218                 if ( i == 0 )
219                 {
220                     JRootPane root = SwingUtilities.getRootPane( button );
221                     if ( root != null )
222                         root.setDefaultButton( button );
223                 }
224 */
225             }
226             else
227             {
228                 throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." );
229             }
230         }
231 
232         this.revalidate();
233         this.repaint();
234         broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) );
235     }
236 
237 /***
238 *
239 */
240     public void setLabels( Action[] actions )
241     {
242         if ( actions == null )
243         {
244             actions = new Action[] {};
245         }
246 
247         buttonContainer.removeAll();
248         this.buttonList = new Vector( actions.length );
249 
250         Action action = null;
251         Component button;
252         for ( int i = 0; i < actions.length; i++ )
253         {
254             action = actions[i];
255             if ( action != null )
256             {
257                 String name = ( String )action.getValue( Action.NAME );
258                 button = createComponentWithLabel( name );
259                 this.buttonList.addElement( name );
260                 addComponentToPanel( button );
261                 button.setEnabled( this.isEnabled() ? action.isEnabled() : false );
262 
263                 // Add the action to the "button" if it knows about action listeners.
264                 try
265                 {
266                     Method addActionListenerMethod =
267                         button.getClass().getMethod( "addActionListener", new Class[] { ActionListener.class } );
268                     addActionListenerMethod.invoke( button, new Object[] { action } );
269                 }
270                 catch ( NoSuchMethodException e ) { /* Do Nothing */ }
271                 catch ( IllegalAccessException e ) { e.printStackTrace(); /* TODO: Do Something? */ }
272                 catch ( InvocationTargetException e ) { e.printStackTrace(); /* TODO: Do Something? */ }
273 
274                 // Create a new listener for property change events and have
275                 // the action broadcast to that listener.
276                 PropertyChangeListener pcListener = new ActionChangeListener( button );
277                 action.addPropertyChangeListener( pcListener );
278             }
279             else
280             {
281                 throw new IllegalArgumentException( "ButtonPanel.setButtons: nulls are not allowed." );
282             }
283         }
284 
285         this.revalidate();
286         this.repaint();
287         broadcastEvent( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, STATE_CHANGED ) );
288     }
289 
290 
291 /***
292 * Gets the labels of the buttons that appear on the panel, ordered from left to right.
293 * @return A new list containing strings used in labeling the buttons.
294 */
295     public String[] getLabels()
296     {
297         String[] labels = new String[ buttonList.size() ];
298         int i = 0;
299         for ( Enumeration it = buttonList.elements(); it.hasMoreElements(); )
300         {
301             labels[i++] = it.nextElement().toString();
302         }
303         return labels;
304     }
305 
306 /***
307 * Gets the first component having the specified name.
308 * @return A component with the specified name, or null if none match.
309 */
310     public Component getButton( String aLabel )
311     {
312         if ( aLabel == null ) return null;
313         
314         Component c = null;
315         int count = buttonContainer.getComponentCount();
316         for ( int i = 0; i < count; i++ )
317         {
318             c = buttonContainer.getComponent( i );
319             if ( aLabel.equals( c.getName() ) )
320             {
321                 return c;
322             }
323         }
324         return null;
325     }
326 
327 /***
328 * Creates a new component with the specified label.
329 * The label is also used for the component's name
330 * and action command, if any.
331 * (This implementation returns a JButton.)
332 * @param aLabel The label for the component that will be created.
333 * @return The newly created component.
334 */
335     protected Component createComponentWithLabel( String aLabel )
336     {
337         String buttonLabel = aLabel; // TODO: get string from resource
338         JButton newButton = new JButton(); // might allow other types in future
339         newButton.setName( aLabel );
340         newButton.setText( buttonLabel );
341         newButton.setActionCommand( aLabel );
342         newButton.addActionListener( this );
343         return newButton;
344     }
345 
346 /***
347 * Adds a component to the right-most side of the layout.
348 * @param aComponent The component to be added to the layout.
349 */
350     protected void addComponentToPanel( Component aComponent )
351     {
352         buttonContainer.add( aComponent );
353     }
354 
355 
356 /***
357 * Changes the alignment of the buttons in the panel.  Defaults to right-justified.
358 * @param alignment A valid alignment code, per BetterFlowLayout implementation.
359 * @see BetterFlowLayout
360 */
361     public void setAlignment( int alignment )
362     {
363         buttonPanelLayout.setAlignment(alignment);
364         buttonContainer.doLayout();
365     }
366 /***
367 * Gets the alignment of the buttons in the panel.
368 * @return An alignment code, per FlowLayout implementation.
369 * @see FlowLayout
370 */
371     public int getAlignment()
372     {
373         return buttonPanelLayout.getAlignment();
374     }
375 
376 /***
377 * Changes the horizontal spacing between components in the panel.
378 * @param newHgap the new spacing, in pixels.  May not be negative.
379 */
380     public void setHgap( int newHgap )
381     {
382         if ( newHgap < 0 ) return; // may not be negative
383         buttonPanelLayout.setHgap( newHgap );
384     }
385 
386 /***
387 * Gets the current horizontal spacing between components.
388 * @return the current horizontal spacing, in pixels.
389 */
390     public int getHgap()
391     {
392         return buttonPanelLayout.getHgap();
393     }
394 
395 /***
396 * Changes the vertical spacing between components in the panel.
397 * @param newVgap the new spacing, in pixels.  May not be negative.
398 */
399     public void setVgap( int newVgap )
400     {
401         if ( newVgap < 0 ) return; // may not be negative
402         buttonPanelLayout.setVgap( newVgap );
403     }
404 
405 /***
406 * Gets the current vertical spacing between components.
407 * @return the current vertical spacing, in pixels.
408 */
409     public int getVgap()
410     {
411         return buttonPanelLayout.getVgap();
412     }
413 
414 /***
415 * Changes the insets for this panel.
416 * @param newInsets the new insets.
417 */
418     public void setInsets( Insets newInsets )
419     {
420         insets = newInsets;
421     }
422 
423 /***
424 * Overridden to return the user-specified insets for this panel.
425 * @return the current insets for this panel.
426 */
427     public Insets getInsets()
428     {
429         return insets;
430     }
431 
432 /***
433 * Overridden to call setEnabled on all components on panel.
434 * @param isEnabled whether to enable the panel and all components on it.
435 */
436     public void setEnabled( boolean isEnabled )
437     {
438         super.setEnabled( isEnabled );
439         int count = buttonContainer.getComponentCount();
440         for ( int i = 0; i < count; i++ )
441         {
442             buttonContainer.getComponent( i ).setEnabled( isEnabled );
443         }
444     }
445 
446     // Action Multicast methods
447 
448 /***
449 * Adds an action listener to the list that will be
450 * notified by button events and changes in button state.
451 * @param l An action listener to be notified.
452 */
453      public void addActionListener(ActionListener l)
454      {
455            actionListener = AWTEventMulticaster.add(actionListener, l);
456      }
457 /***
458 * Removes an action listener from the list that will be
459 * notified by button events and changes in button state.
460 * @param l An action listener to be removed.
461 */
462      public void removeActionListener(ActionListener l)
463      {
464            actionListener = AWTEventMulticaster.remove(actionListener, l);
465      }
466 /***
467 * Notifies all registered action listeners of a pending Action Event.
468 * @param e An action event to be broadcast.
469 */
470      protected void broadcastEvent(ActionEvent e)
471      {
472          if (actionListener != null)
473          {
474              actionListener.actionPerformed(e);
475          }
476      }
477 
478     // interface ActionListener
479 
480 /***
481 * Called by buttons on panel and by other components that
482 * might be set to broadcast events to this listener.
483 * @param e An action event to be received.
484 */
485      public void actionPerformed(ActionEvent e)
486      {
487             broadcastEvent(e);
488      }
489 
490 /***
491 * A property change listener that listens specifically for property changes
492 * from action objects.  This is the class that ties in the action to the
493 * button.  This class is added to an action as a property change listener.
494 * The corresponding component is referenced by this class toe easily handle
495 * updates to the component caused by changes to the action.
496 */
497      public class ActionChangeListener implements PropertyChangeListener
498      {
499         /*** The UI component that is affected by the action's changes. */
500         Component theComponent;
501 
502         /***
503         * Constructs an ActionChangeListener with the given component being
504         * the recipient of the action's changes.
505         * @param The component to bind with the action.
506         */
507         public ActionChangeListener( Component aComponent )
508         {
509             super();
510             theComponent = aComponent;
511         }
512 
513         /***
514         * Called whenever a property changes on the action object.
515         * @pram e The property change event generated by the action.
516         */
517         public void propertyChange( PropertyChangeEvent e )
518         {
519             String propertyName = e.getPropertyName();
520             if ( propertyName.equals( Action.NAME ) )
521             {
522                 String name = ( String )e.getNewValue();
523                 if ( theComponent instanceof AbstractButton )
524                 {
525                     AbstractButton button = ( AbstractButton )theComponent;
526                     String oldName = button.getName();
527                     button.setText( name );
528                     button.setName( name );
529                     button.setActionCommand( name );
530 
531                     // Replace the old name of the component with the new name
532                     // in the ButtonPanel's list of components.
533                     buttonList.setElementAt( name, buttonList.indexOf( oldName ) );
534                 }
535 
536                 // TODO: If component is not a button (or doesn't define the getText()
537                 // then what should be done.
538             }
539             else if ( propertyName.equals( "enabled" ) )
540             {
541                 Boolean enabled = ( Boolean )e.getNewValue();
542                 theComponent.setEnabled( ButtonPanel.this.isEnabled() ? enabled.booleanValue() : false );
543             }
544 
545             // TODO: Icon?
546         }
547      }
548 
549 
550     // for testing
551 
552     public static void main( String[] argv )
553     {
554         try
555         {
556             UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
557         }
558         catch (Exception exc)
559         {
560 
561         }
562 
563         JFrame dialog = new JFrame();
564         BorderLayout bl = new BorderLayout( 20, 20 );
565 
566         ButtonPanel panel = new ButtonPanel();
567 //        ButtonPanel panel = new ButtonPanel( new String[] { "OkayOkay", "CancelCancel" } );
568 
569         dialog.getContentPane().setLayout( bl );
570         dialog.getContentPane().add( panel, BorderLayout.CENTER );
571         dialog.setLocation( 50, 50 );
572         // dialog.setSize( 450, 150 );
573 
574         panel.setAlignment( BetterFlowLayout.CENTER_VERTICAL );
575         panel.getButton( "One" ).setEnabled( false );
576 
577         dialog.pack();
578         dialog.setVisible( true );
579 
580         try
581         {
582             BeanInfo info = Introspector.getBeanInfo( ButtonPanel.class );
583             PropertyDescriptor[] props = info.getPropertyDescriptors();
584             for ( int i = 0; i < props.length; i++ )
585             {
586                 System.out.println( props[i].getName() );
587             }
588         }
589         catch (Exception exc)
590         {
591             System.out.println( exc );
592         }
593 
594 
595 
596 
597     }
598 
599     public void mouseDragged(MouseEvent e)
600     {
601     }
602 
603     public void mouseMoved(MouseEvent e)
604     {
605     }
606 
607 
608 
609 }
610