1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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;
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
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
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
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
219
220
221
222
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
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 ) {
271 catch ( IllegalAccessException e ) { e.printStackTrace();
272 catch ( InvocationTargetException e ) { e.printStackTrace();
273
274
275
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;
338 JButton newButton = new JButton();
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;
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;
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
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
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
532
533 buttonList.setElementAt( name, buttonList.indexOf( oldName ) );
534 }
535
536
537
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
546 }
547 }
548
549
550
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
568
569 dialog.getContentPane().setLayout( bl );
570 dialog.getContentPane().add( panel, BorderLayout.CENTER );
571 dialog.setLocation( 50, 50 );
572
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