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.Dimension;
26  import java.awt.GridBagConstraints;
27  import java.awt.GridBagLayout;
28  import java.awt.GridLayout;
29  import java.awt.Insets;
30  import java.awt.event.ActionEvent;
31  import java.awt.event.ActionListener;
32  import java.beans.BeanInfo;
33  import java.beans.Introspector;
34  import java.beans.MethodDescriptor;
35  import java.lang.reflect.Method;
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Collections;
39  import java.util.HashMap;
40  import java.util.Iterator;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.Map;
44  
45  import javax.swing.Box;
46  import javax.swing.JLabel;
47  import javax.swing.JPanel;
48  import javax.swing.JTextField;
49  import javax.swing.SwingConstants;
50  
51  /***
52  * InfoPanel uses labels and textfields (or any other component - see below)
53  * to display a list of keys and values in a well-aligned and consistent manner,
54  * conforming to alignment and pixel spacing in the java look and feel 
55  * <a href="http://java.sun.com/products/jlf/dg/higg.htm#55417">design guidelines</a>. 
56  * <BR><BR>
57  *
58  * Each key is displayed in a label to the left of the component that contains
59  * the corresponding value.  Each row is displayed starting at the top of the
60  * component's available area.  Each row's height is the maximum preferred
61  * height of its components and the field itself gets as much of the width as
62  * it can, dependent on the length of the longest label. <BR><BR>
63  *
64  * The values in the fields can be editable, and the
65  * current value can be retrieved using the key - for this reason, unique keys
66  * are recommended. <BR><BR>
67  *
68  * As a convenience, push buttons may be placed across the
69  * bottom of the panel in a manner similar to ButtonPanel. <BR><BR>
70  *
71  * The panel forwards any ActionEvents generated by the components and
72  * buttons on it to all registered listeners. <BR><BR>
73  *
74  * Optionally, any component can be used instead of a textfield.
75  * However, <code>get/setValueForKey()</code> and <code>get/setEditable()</code>
76  * may not work for those components.  Use <code>getComponentForKey()</code> to
77  * access them instead.
78  *
79  * @author michael@mpowers.net
80  * @author $Author: cgruber $
81  * @version $Revision: 904 $
82  * $Date: 2006-02-18 23:19:05 +0000 (Sat, 18 Feb 2006) $
83  */
84  public class InfoPanel extends JPanel implements ActionListener
85  {
86  /***
87  * Special label for an empty pair - a label and component
88  * that take up space but are hidden from view.  This might
89  * be useful for achieving certain layouts.
90  */
91      public static final String HIDDEN = "(hidden)";
92  
93      /*** Cache for the introspectComponent method */
94      private static Map _method_cache =
95        Collections.synchronizedMap( new HashMap(30) );
96  
97      protected Container listContainer = null;
98      protected int hgap;    // set in constructor
99      protected int vgap;    // set in constructor
100     protected int margin;  // set in constructor
101     protected int columns; // set in constructor
102     protected List fields = null;
103     protected List labels = null;
104     protected List fieldSpacers = null; 
105     protected ButtonPanel buttonPanel = null;
106     protected boolean isEditable = true;
107     protected String prefix;
108     protected String postfix;
109 	protected int labelAnchor;
110 	protected int labelAlign;
111 //    protected Component marginStrut = null;
112 
113     // for action multicasting
114     protected ActionListener actionListener = null;
115     
116 /***
117 * Constructs an empty InfoPanel.
118 */
119     public InfoPanel()
120     {
121         hgap = 12;   // per java l&f guidelines
122         vgap = 6;    // java l&f says 11
123         columns = 1; // default columns
124         margin = 0;  // default margin: none
125         prefix = ""; // default prefix: none
126         postfix = ":"; // per java l&f guidelines
127         fields = new ArrayList();
128         labels = new ArrayList();
129 		labelAnchor = GridBagConstraints.NORTHWEST; 
130 			// per java l&f guidelines (CENTER is nicer)
131 		labelAlign = SwingConstants.LEFT; 
132 			// per java l&f guidelines
133             
134         doInitialLayout();
135     }
136 
137 /***
138 * Constructs an InfoPanel with the specified labels
139 * each paired with a blank textfield.
140 * @param labelArray An Array containing the labels in the
141 * order in which they should appear from top to bottom.
142 * A null value produces an empty panel.
143 */
144     public InfoPanel( String[] labelArray )
145     {
146         this();
147         setLabels( labelArray );
148     }
149 
150 /***
151 * Creates a set of labels and empty textfields after first
152 * clearing all existing components on the panel.
153 * @param labelArray An Array containing the labels in the order
154 * in which they should appear from top to bottom.  A null
155 * value will clear the panel.
156 */
157     public void setLabels( String[] labelArray )
158     {
159         removeAll();
160         if ( labelArray == null ) return; // null clears panel
161         for ( int i = 0; i < labelArray.length; i++ )
162         {
163             addPair( labelArray[i], new JTextField() );
164         }
165     }
166 
167 /***
168 * Retrieves the labls for the components on the panel
169 * in the order in which they are displayed from top WIDTH bottom.
170 * These are the keys used to reference values or to reference
171 * the components directly.
172 * @return An Array of Strings containing the labels.
173 */
174     public String[] getLabels()
175     {
176         int length = fields.size();
177         String[] labelArray = new String[ length ];
178         for ( int i = 0; i < length; i++ )
179         {
180             labelArray[i] = ((Component)fields.get(i)).getName();
181         }
182         return labelArray;
183     }
184 	
185 /***
186 * Retrieves the constant used to anchor the labels in place.
187 * The default value is GridBagConstraints.NORTHWEST.
188 */
189 	public int getLabelAnchor()
190 	{
191 		return labelAnchor;
192 	}
193 
194 /***
195 * Sets the constant used to anchor the labels in place
196 * and reflows the layout.
197 * @param anAnchorConstant An anchor constant from
198 * GridBagConstraints.
199 */
200 	public void setLabelAnchor( int anAnchorConstant )
201 	{
202 		labelAnchor = anAnchorConstant;	
203 		updateLabels();
204 	}
205 
206 /***
207 * Retrieves the constant used to align the labels in place.
208 * The default value is GridBagConstraints.CENTER.
209 */
210 	public int getLabelAlignment()
211 	{
212 		return labelAlign;
213 	}
214 
215 /***
216 * Sets the constant used to align the labels in place
217 * and reflows the layout.
218 * @param anAlignmentConstant LEFT, CENTER, or RIGHT constants
219 * from SwingUtilities.
220 */
221 	public void setLabelAlignment( int anAlignmentConstant )
222 	{
223 		labelAlign = anAlignmentConstant;	
224 		updateLabels();
225 	}
226     
227 /***
228 * Factory method for creating panel spacers.
229 * This implementation returns a JPanel with
230 * opaque set to false.  Override to customize.
231 */    
232     public JPanel createPanel()
233     {
234         JPanel result = new JPanel();
235         result.setOpaque( false );
236         return result;
237     }
238 
239 /***
240 * This method is responsible for the initial layout of the panel.
241 * All labels and textfields will later added to listContainer.
242 * This method is responsible for initializing listContainer.
243 */
244     protected void doInitialLayout()
245     {
246         listContainer = createPanel();
247         listContainer.setLayout( new BetterGridBagLayout() );
248         this.setLayout( new BorderLayout() ); 
249         this.add( listContainer, BorderLayout.NORTH ); 
250 
251         //listContainer.setBackground( Color.blue ); // useful for testing
252         //this.setBackground( Color.red );
253     }
254 
255 /***
256 * Changes the horizontal spacing between the label and the components in the panel.
257 * Note: Assumes listContainer uses a GridBagLayout.
258 * @param newHgap the new spacing, in pixels.  May not be negative.
259 */
260     public void setHgap( int newHgap )
261     {
262         if ( newHgap < 0 ) return; // may not be negative
263         this.hgap = newHgap;
264         updateGaps();
265         this.revalidate();
266         this.repaint();
267 
268     }
269 
270 /***
271 * Gets the current horizontal spacing between components.
272 * @return the current horizontal spacing, in pixels.
273 */
274     public int getHgap()
275     {
276         return this.hgap;
277     }
278 
279 /***
280 * Changes the vertical spacing between components in the panel.
281 * Note: Assumes listContainer uses a GridBagLayout.
282 * @param newVgap the new spacing, in pixels.  May not be negative.
283 */
284     public void setVgap( int newVgap )
285     {
286         if ( newVgap < 0 ) return; // may not be negative
287         this.vgap = newVgap;
288         updateGaps();
289         this.revalidate();
290         this.repaint();
291 
292     }
293 
294 /***
295 * Gets the current vertical spacing between components.
296 * @return the current vertical spacing, in pixels.
297 */
298     public int getVgap()
299     {
300         return this.vgap;
301     }
302 
303 /***
304 * Sets the minimum width for the labels column.
305 * This left margin will grow if one of the labels
306 * is wider than this value.
307 * Note: assumes GridBagLayout.
308 * @param newMargin the new minimum margin in pixels.  May not be negative.
309 */
310     public void setMargin( int newMargin )
311     {
312         if ( newMargin < 0 ) return; // may not be negative
313         this.margin = newMargin;
314 
315         if ( listContainer.getLayout() instanceof GridBagLayout )
316         {
317             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
318             GridBagConstraints constraints = null;
319             Component c = null;
320             int count = listContainer.getComponentCount();
321             for ( int i = 0; i < count; i++ )
322             {
323                 c = listContainer.getComponent( i );
324                 constraints = gridBag.getConstraints( c );
325                 if ( constraints.gridy == 0 && constraints.gridx % 2 == 0 )
326                 { // if this is a label spacer 
327                     // replace it with an appropriately sized box
328                     listContainer.remove( c );
329                     listContainer.add( Box.createHorizontalStrut( this.margin ), constraints );
330                 }
331             }
332         }
333 
334         this.revalidate();
335         this.repaint();
336 
337     }
338 
339 /***
340 * Gets the current minimum margin for the labels column.
341 * @return the current minimum margin in pixels.
342 */
343     public int getMargin()
344     {
345         return this.margin;
346     }
347 
348 /***
349 * Sets the number of columns for the panel.
350 * Label/Component pairs will start from the top left
351 * and fill in to the right before wrapping to the
352 * next row.  The default number of columns is one.
353 * Note: assumes GridBagLayout.
354 * @param newColumns the new number of columns.  May not be less than one.
355 */
356     public void setColumns( int newColumns )
357     {
358         if ( newColumns < 1 ) return; // may not be less than one.
359         int oldColumns = this.columns;
360         this.columns = newColumns;
361 
362         if ( listContainer.getLayout() instanceof GridBagLayout )
363         {
364             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
365             int count = listContainer.getComponentCount();
366             Component[] components = listContainer.getComponents();
367             GridBagConstraints[] constraints = new GridBagConstraints[ components.length ];
368             for ( int i = 0; i < components.length; i++ )
369             {
370                 constraints[i] = gridBag.getConstraints( components[i] );
371             }
372             listContainer.removeAll();
373             for ( int i = 0; i < components.length; i++ )
374             {
375                 if ( constraints[i].gridy != 0 )
376                 { // ignore first row which is reserved for spacers.
377 
378                     // translate component to new position
379                     // (columns*2 accounts for two grid columns for one "actual" column)
380                     int index = ( constraints[i].gridy - 1 ) * oldColumns*2 + constraints[i].gridx;
381                     constraints[i].gridy = ( index / (newColumns*2) ) + 1;
382                     constraints[i].gridx = index % (newColumns*2) ;
383                     listContainer.add( components[i], constraints[i] );
384                 }
385             }
386             createSpacers(); // replace the spacers
387             updateGaps();
388         }
389 
390         this.revalidate();
391         this.repaint();
392 
393     }
394 
395 /***
396 * Sets the vertical weight used for determining how to distribute additional
397 * vertical space in the component. 
398 * @param aComponent Key that exists in the layout.
399 * @return weighty The weight of the component, or -1.0 if not found.
400 */
401     public double getVerticalWeightForKey( String key )
402     {
403         Container c = getCompositeComponentForKey( key );
404         if ( c == null ) return -1.0;
405         if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return -1.0;
406         GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
407         GridBagConstraints gbc = layout.getConstraints( c );
408         return gbc.weighty;
409     }
410     
411 /***
412 * Sets the vertical weight used for determining how to distribute additional
413 * vertical space in the component.  By default, all weights are zero, so each
414 * component gets its preferred height.  If any weights are specified, then
415 * additional space is allocated to those components proportionately.
416 * @param aComponent Key that exists in the layout.
417 * @param weighty The new weight.
418 */
419     public void setVerticalWeightForKey( String key, double weighty )
420     {
421         Container c = getCompositeComponentForKey( key );
422         if ( c == null ) return;
423         if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return;
424         GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
425         GridBagConstraints gbc = layout.getConstraints( c );
426         gbc.weighty = weighty;
427         layout.setConstraints( c, gbc );
428         // handle adding on-the-fly
429         updateGaps();
430         this.revalidate();
431         this.repaint();
432     }
433     
434 /***
435 * Gets the current number of columns.
436 * @return the current number of columns.
437 */
438     public int getColumns()
439     {
440         return this.columns;
441     }
442 
443 /***
444 * Appends a label containing a key and the specified component
445 * to the bottom of the panel.  Any registered action listeners
446 * will receive action events from the component - the key corresponding
447 * to the component will be used as the action command.
448 * @param key A string that will be displayed in a label, preferrably unique.
449 * @param component A component that will be placed next to the label.
450 * If null, a blank JPanel will be used.
451 */
452 	public void addPair( String key, Component component )
453 	{
454 		addRow( key, new Component[] { component } );
455 	}
456 	
457 /***
458 * Appends a label containing a key and the specified component
459 * to the bottom of the panel.  Any registered action listeners
460 * will receive action events from the component - the key corresponding
461 * to the component will be used as the action command.
462 * @param key A string that will be displayed in a label, preferrably unique.
463 * @param component A component that will be placed next to the label.
464 * If null, a blank JPanel will appear.
465 */
466 	public void addRow( String key, Component component )
467 	{
468 		addRow( key, new Component[] { component } );
469 	}
470 	
471 /***
472 * Appends a label containing a key and the specified components
473 * to the bottom of the panel.  Any registered action listeners
474 * will receive action events from the component - the key corresponding
475 * to the component will be used as the action command.
476 * @param key A string that will be displayed in a label, preferrably unique.
477 * @param components An array of components that will be placed next to the label.
478 * Any nulls in the list will be replaced with blank JPanels.
479 */
480 	public void addRow( 
481 		String key, Component[] components )
482 	{
483 		addCompositeComponent( key, makeCompositeComponent( key, components ) );
484 	}
485 
486 /***
487 * Appends a label containing a key and the specified components
488 * to the bottom of the panel.  Any registered action listeners
489 * will receive action events from the components - the key corresponding
490 * to the component will be used as the action command.
491 * @param key A string that will be displayed in a label, preferrably unique.
492 * @param west A component that will appear to the left of the other components,
493 * as wide as its preferred width and as tall as the tallest of the other components.
494 * A null will be replaced with a blank JPanel.
495 * @param center A component that will appear between the other components, 
496 * taking up available space.
497 * A null will be replaced with a blank JPanel.
498 * @param east A component that will appear to the right of the other components,
499 * as wide as its preferred width and as tall as the tallest of the other components.
500 * A null will be replaced with a blank JPanel.
501 */
502 	public void addRow( 
503 		String key, Component west, Component center, Component east )
504 	{
505 		addCompositeComponent( key, 
506             makeCompositeComponent( key, 
507                 west, center, east ) );
508 	}
509 
510 /***
511 * Appends a label containing a key and the specified components
512 * to the bottom of the panel.  Any registered action listeners
513 * will receive action events from the components - the key corresponding
514 * to the component will be used as the action command.
515 * @param key A string that will be displayed in a label, preferrably unique.
516 * @param west A component that will appear to the left of the other components,
517 * as wide as its preferred width and as tall as the tallest of the other components.
518 * A null will be replaced with a blank JPanel.
519 * @param north A component that will appear above all the other components,
520 * as tall as its preferred height and as wide as the info panel itself.
521 * @param center A component that will appear between the other components, 
522 * taking up available space.  A null will be replaced with a blank JPanel.
523 * @param south A component that will appear below all the other components,
524 * as tall as its preferred height and as wide as the info panel itself.
525 * @param east A component that will appear to the right of the other components,
526 * as wide as its preferred width and as tall as the tallest of the other components.
527 * A null will be replaced with a blank JPanel.
528 */
529 	public void addRow( 
530 		String key, Component west, Component north, 
531         Component center, Component south, Component east )
532 	{
533 		addCompositeComponent( key, 
534             makeCompositeComponent( key, 
535                 west, north, center, south, east ) );
536 	}
537 
538 /***
539 * Produces a container that contains the specified components,
540 * using GridLayout.  Nulls are ignored.
541 * This implementation returns a JPanel.
542 */
543 	protected Container makeCompositeComponent( 
544 		String key, Component[] components )
545 	{
546 		JPanel panel = createPanel();
547 		if ( components.length != 0 ) 
548 		{
549 			panel.setLayout( new GridLayout( 1, components.length, hgap, vgap ) );			
550 
551 			Component c;
552 			for ( int i = 0; i < components.length; i++ )
553 			{		
554 				c = components[i];
555 				if ( c != null )
556 				{
557 					introspectComponent( c, key );
558 					panel.add( c );
559 				}
560 			}
561 		}
562 		return panel;
563 	}
564 
565 /***
566 * Produces a container that contains the specified components,
567 * using BorderLayout.  Nulls are ignored.
568 * This implementation returns a JPanel.
569 */
570 	protected Container makeCompositeComponent( 
571 		String key, Component west, Component center, Component east )
572 	{
573 		JPanel panel = createPanel();
574 		panel.setLayout( new BorderLayout( hgap, vgap ) );
575 
576 		if ( west != null )
577 		{
578 			introspectComponent( west, key );
579 			panel.add( west, BorderLayout.WEST );
580 		}
581 		
582 		if ( center != null )
583 		{
584 			introspectComponent( center, key );
585 			panel.add( center, BorderLayout.CENTER );
586 		}
587 		
588 		if ( east != null )
589 		{
590 			introspectComponent( east, key );
591 			panel.add( east, BorderLayout.EAST );
592 		}
593 		
594 		return panel;
595 	}
596 
597 /***
598 * Produces a container that contains the specified components,
599 * using BorderLayout.  Nulls are ignored.
600 * This implementation returns a JPanel.
601 */
602 	protected Container makeCompositeComponent( 
603 		String key,  Component west, Component north, 
604 		Component center, Component south, Component east )
605 	{
606 		JPanel panel = createPanel();
607 		panel.setLayout( new BorderLayout( hgap, vgap ) );
608 
609 		if ( west != null )
610 		{
611 			introspectComponent( west, key );
612 			panel.add( west, BorderLayout.WEST );
613 		}
614 		
615 		if ( north != null )
616 		{
617 			introspectComponent( north, key );
618 			panel.add( north, BorderLayout.WEST );
619 		}
620 		
621 		if ( center != null )
622 		{
623 			introspectComponent( center, key );
624 			panel.add( center, BorderLayout.CENTER );
625 		}
626 		
627 		if ( south != null )
628 		{
629 			introspectComponent( south, key );
630 			panel.add( south, BorderLayout.CENTER );
631 		}
632 		
633 		if ( east != null )
634 		{
635 			introspectComponent( east, key );
636 			panel.add( east, BorderLayout.EAST );
637 		}
638 		
639 		return panel;
640 	}
641 
642 /***
643 * Override to return a specific component to be used
644 * as a label.  This implementation calls createLabel().
645 */
646     protected Component createLabelForKey( String aKey )
647 	{
648 		return createLabel();	
649 	}
650 
651 /***
652 * Provided for backwards compatibility, and called by
653 * the default implementation of createLabelForKey.
654 * This implementation returns a JLabel.
655 */
656     protected JLabel createLabel()
657 	{
658 		return new JLabel();	
659 	}
660 
661 /***
662 * Appends a label containing a key and the specified component
663 * to the bottom of the panel.  Any registered action listeners
664 * will receive action events from the component - the key corresponding
665 * to the component will be used as the action command.
666 * @param key A string that will be displayed in a label, preferrably unique.
667 * @param component A component that will be placed next to the label.
668 * If null, a stock JTextField will be used.
669 */
670     protected void addCompositeComponent( String key, Component component )
671     {
672         if ( key == null )
673         {
674             key = "";
675         }
676         Component label = createLabelForKey( key );
677         Component field = component;
678         if ( field == null )
679         {
680             field = new JTextField( 15 ); // default to 15 columns
681         }
682         field.setName( key ); // for association and reference
683         label.setName( key ); // ditto
684         if ( label instanceof JLabel )
685         {
686             ((JLabel)label).setHorizontalAlignment( labelAlign );
687             ((JLabel)label).setLabelFor( field ); // for accessibility
688         }
689         if ( "".equals( key ) )
690         {
691             setText( label, "" );
692         }
693         else
694         {
695             setText( label, prefix + key + postfix );
696         }
697         field.setEnabled( this.isEditable ); // was: setEditable
698 
699         GridBagConstraints gbc = new GridBagConstraints();
700 
701         if ( listContainer.getComponentCount() == 0 )
702         { // we've just initialized or called removeAll
703             createSpacers();
704         }
705 
706         gbc.gridx = ( fields.size() % this.columns ) * 2;
707         gbc.gridy = ( fields.size() / this.columns ) + 1; // spacer is at index zero
708         gbc.weightx = 0.0;
709         gbc.weighty = 0.0;
710         gbc.anchor = this.labelAnchor; 
711         gbc.fill = GridBagConstraints.HORIZONTAL;
712         listContainer.add( label, gbc );
713 
714 		gbc.fill = GridBagConstraints.BOTH;
715         gbc.gridx = gbc.gridx + 1;
716 		//FIXME: components default to the labelAnchor - should be different?
717         gbc.weightx = 1.0;
718         gbc.weighty = 0.0;
719         
720         listContainer.add( field, gbc );
721         
722         if ( key.equals( HIDDEN ) )
723         { // these components are not to be shown
724             setText( label, "     " );
725             field.setVisible( false );
726         }
727 
728         fields.add( field ); // using list not map to allow for duplicate keys
729         labels.add( label ); // ditto
730 
731         // handle adding on-the-fly
732         updateGaps();
733         this.revalidate();
734         this.repaint();
735     }
736 
737 /***
738 * Introspects a component to set the action command and to add the
739 * InfoPanel to its list of ActionListeners.
740 * @param aComponent The Component to be introspected.
741 * @param aKey The action command to be set.
742 */
743     protected void introspectComponent( Component aComponent, String aKey )
744     {
745         // try to set properties of whatever component this might be
746         try {
747             Method [] methods =
748                 (Method []) _method_cache.get( aComponent.getClass() );
749             if (methods == null) {
750                 Class componentClass = aComponent.getClass();
751                 BeanInfo info =
752                     Introspector.getBeanInfo( componentClass );
753 
754                 MethodDescriptor[] descriptors =
755                     info.getMethodDescriptors();
756                 Method setMethod = null;
757                 Method addMethod = null;
758                 for ( int i = 0;
759                       ((setMethod == null || addMethod == null) &&
760                        i < descriptors.length);
761                       i++ )
762                 {
763                     Method m = descriptors[i].getMethod();
764                     String name = m.getName ();
765                     if ( setMethod == null &&
766                          name.equals( "setActionCommand" ) )
767                     {
768                         setMethod = m;
769                     }
770                     else if ( addMethod == null &&
771                               name.equals( "addActionListener" ) )
772                     {
773                         addMethod = m;
774                     }
775                 }
776 
777                 methods = new Method [] {setMethod, addMethod};
778                 _method_cache.put (componentClass, methods);
779             }
780             if (methods [0] != null) {
781               methods [0].invoke( aComponent, new Object[] { aKey } );
782             }
783             if (methods [1] != null) {
784               methods [1].invoke( aComponent, new Object[] { this } );
785               listenedToComponents.add( aComponent );
786             }
787         }
788         catch ( Exception exc )
789         { // error occured while introspecting... move along.
790               System.out.println( "InfoPanel.introspectComponent: " + exc );
791         }
792     }
793     
794 /***
795 * Called to populate a label component with the specified text.
796 * This implementation attempts to call setText(String) on the component.
797 * Override to customize.
798 */
799     protected void setText( Component c, String text )
800     {
801         try
802         {
803             Method m = c.getClass().getMethod( "setText", new Class[] { String.class } );
804             if ( m != null )
805             {
806                 m.invoke( c, new Object[] { text } );
807             }
808         }
809         catch ( Exception exc )
810         {
811             // no such method: ignore
812         }
813     }
814 
815 /***
816 * Creates spacer components on the reserved first grid row
817 * for each column of labels and fields.  
818 * This allows us to set the margin for those label columns,
819 * and set the preferred width of the field columns.
820 * A list containing the field spacers should be assigned to
821 * the fieldSpacers instance variable.
822 */
823     private void createSpacers()
824     {
825         if ( listContainer.getLayout() instanceof GridBagLayout )
826         {
827             // insert spacers for labels column
828             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
829             GridBagConstraints constraints = new GridBagConstraints();
830             constraints.gridy = 0;
831             constraints.fill = GridBagConstraints.HORIZONTAL;
832 
833             fieldSpacers = new LinkedList();
834             Component fieldSpacer;
835             for ( int i = 0; i < this.columns; i++ )
836             {
837                 constraints.gridx = i * 2;
838                 listContainer.add( Box.createHorizontalStrut( this.margin ), constraints );
839 
840                 constraints.gridx = i * 2 + 1;
841                 fieldSpacer = Box.createHorizontalStrut( 0 ); 
842                 fieldSpacers.add( fieldSpacer );
843                 listContainer.add( fieldSpacer, constraints );
844             }
845         }
846     }
847 
848 /***
849 * Updates the insets for all components.
850 */
851     protected void updateGaps()
852     {
853         if ( listContainer.getLayout() instanceof GridBagLayout )
854         {
855             GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
856             Component c = null;
857             GridBagConstraints gbc = null;
858             double totalWeightY = 0.0;
859             int count = listContainer.getComponentCount();
860             int i;
861             for ( i = 0; i < count; i++ )
862             {
863                 c = listContainer.getComponent( i );
864                 gbc = layout.getConstraints( c );
865                 totalWeightY += gbc.weighty;
866                 if ( (gbc.gridx + 1) % ( this.columns * 2 ) == 0 )
867                 { // if last component in row
868                     gbc.insets = new Insets( 0, 0, this.vgap, 0 );
869                 }
870                 else
871                 {
872                     if ( gbc.gridx % 2 == 0 )
873                     { // is a label column - NOTE: uses eleven pixels before component, per l&f guide
874                         gbc.insets = new Insets( 0, 0, this.vgap, 11 );
875                     }
876                     else
877                     { // is a component column
878                         if ( gbc.gridy != 0 ) 
879                         {
880                             if ( c instanceof JPanel ) ((JPanel)c).setPreferredSize( null );
881                             gbc.insets = new Insets( 0, 0, this.vgap, this.hgap );
882                         }
883                     }
884                 }
885                 layout.setConstraints( c, gbc );
886             }
887             
888             //hack: gridbag clumps components in center if weighty is zero
889             // if sum of weighty is zero, top-justify the list container
890             this.remove( listContainer );
891             if ( totalWeightY == 0.0 )
892             {
893                 this.add( listContainer, BorderLayout.NORTH );
894             } 
895             else // put list container in center so it will grow
896             {
897                 this.add( listContainer, BorderLayout.CENTER );
898             }
899         }
900     }
901 
902 /***
903 * Updates the label alignment.
904 */
905     protected void updateLabels()
906     {
907         if ( listContainer.getLayout() instanceof GridBagLayout )
908         {
909             GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
910             Component c = null;
911             GridBagConstraints gbc = null;
912 			Iterator it = labels.iterator();
913 			while ( it.hasNext() )
914             {
915                 c = (Component) it.next();
916                 if ( c instanceof JLabel )
917                 {
918 				    ((JLabel)c).setHorizontalAlignment( labelAlign );
919                 }
920                 gbc = layout.getConstraints( c );
921                 gbc.anchor = this.labelAnchor;
922                 layout.setConstraints( c, gbc );
923             }
924         }
925     }
926 
927 /***
928 * Convenience method that uses a stock JTextField.
929 * @param key A string that will be displayed in a label, preferrably unique.
930 * @param value A string that will be displayed in a textfield.
931 */
932     public void addPair( String key, String value )
933     {
934         addPair( key, value, null );
935     }
936 
937 /***
938 * Convenience method that uses the specified JTextField or subclass
939 * and sets it to the specified value.
940 * @param key A string that will be displayed in a label, preferrably unique.
941 * @param value A string that will be displayed in a textfield.
942 * @param textField A JTextField or subclass that will be used to display the value.
943 * If null, a stock JTextField will be used.
944 */
945     public void addPair( String key, String value, JTextField textField )
946     {
947         if ( value == null )
948         {
949             value = "";
950         }
951         JTextField field = textField;
952         if ( field == null )
953         {
954             field = new JTextField( 15 ); // default to 15 columns
955         }
956         else
957         {
958             field = textField;
959         }
960         field.setText( value );
961 
962         addPair( key, (Component) field );
963     }
964 
965 /***
966 * Removes all components from the list.  Buttons, if any,
967 * will remain unchanged - use setButtons( null ) to remove 
968 * them.  NOTE: does not call super.removeAll().
969 */
970     public void removeAll()
971     {
972         Object component;
973         Method method;
974         Class[] paramClasses = new Class[] { ActionListener.class };
975         Object[] paramObjects = new Object[] { this };
976 
977         Iterator iterator = listenedToComponents.iterator();
978         while ( iterator.hasNext() )
979         {
980             component = iterator.next();
981             try
982             {
983                 method = component.getClass().getMethod( "removeActionListener", paramClasses );
984                 if ( method != null )
985                 {
986                     method.invoke( component, paramObjects );
987                 }
988             }
989             catch ( Exception exception )
990             {
991                 // No removeActionListener() method, move along.
992             }
993         }
994 
995         listenedToComponents.clear();
996         
997         listContainer.removeAll();
998         fields.clear();
999         labels.clear();
1000         this.revalidate();
1001         this.repaint();
1002         
1003         //FIXME: It is very confusing that this 
1004         // implementation does not call super.removeAll().
1005     }
1006 
1007 /***
1008 * Adds one or buttons to the bottom of the panel with the specified labels
1009 * from left to right.  Any action listeners will receive action events
1010 * from clicks on these buttons - the supplied label will be used as the action command.
1011 * @param buttons A string array containing the strings to be used for the button labels
1012 * and action commands.  A null value will remove the button panel.
1013 * @see ButtonPanel
1014 */
1015     public void setButtons( String[] buttons )
1016     {
1017         if ( buttonPanel == null )
1018         {
1019             buttonPanel = new ButtonPanel();
1020             buttonPanel.setInsets( new Insets( 6, 0, 0, 0 ) ); 
1021 				// button panel has a 11-pixel top inset
1022 				// and java l&f guide says 17-pixels before command buttons
1023             buttonPanel.addActionListener( this );
1024             this.add( buttonPanel, BorderLayout.SOUTH );
1025         }
1026         if ( buttons == null )
1027         {
1028             this.remove( buttonPanel );
1029             buttonPanel = null;
1030         }
1031         else
1032         {
1033             buttonPanel.setLabels( buttons );
1034         }
1035 
1036         this.revalidate();
1037         this.repaint();
1038     }
1039     protected Collection listenedToComponents = new LinkedList();
1040 
1041 /***
1042 * Retrieves the names of the buttons that are displayed, if any.
1043 * @return A string array containing the strings used for the button labels
1044 * and action commands, or null if no buttons have been created.
1045 * @see ButtonPanel
1046 */
1047     public String[] getButtons()
1048     {
1049         if ( buttonPanel == null )
1050         {
1051             return null; // none created
1052         }
1053 
1054         return buttonPanel.getLabels();
1055     }
1056 
1057 /***
1058 * Retrieves the actual button panel, if any.
1059 * @return A button panel, or null if none has been created.
1060 * @see ButtonPanel
1061 */
1062     public ButtonPanel getButtonPanel()
1063     {
1064         return buttonPanel;
1065     }
1066 
1067 
1068 /***
1069 * Sets whether the values displayed in the panel should be editable.  Defaults to true.
1070 * @param isEditable Whether the values should be editable.
1071 */
1072     public void setEditable( boolean isEditable )
1073     {
1074         this.isEditable = isEditable;
1075         Iterator enumeration = fields.iterator();
1076         while ( enumeration.hasNext() )
1077         {
1078             ( (Component) enumeration.next() ).setEnabled( isEditable );
1079         }
1080     }
1081 
1082 /***
1083 * Gets whether the values displayed in the panel are editable.
1084 * @return Whether the values should be editable.
1085 */
1086     public boolean isEditable()
1087     {
1088         return this.isEditable;
1089     }
1090 
1091 /***
1092 * Sets the field associated with the key to the specified value.
1093 * Note: If the component does not respond to setText() or setString()
1094 * or setValue() the value will not be set.  JTextFields and the like will work.
1095 * @param key A string representing the key associated with the field.  Nulls are converted to an empty string.
1096 * @param value A object to be displayed in the specified field.  Nulls are converted to an empty string.
1097 */
1098     public void setValueForKey( String key, Object value )
1099     {
1100 		setValueForKey( key, value, 0 );	
1101 	}
1102 	
1103 /***
1104 * Sets the field associated with the key to the specified value.
1105 * Note: If the component does not respond to setText() or setString()
1106 * or setValue() the value will not be set.  JTextFields and the like will work.
1107 * @param key A string representing the key associated with the field.  Nulls are converted to an empty string.
1108 * @param value A object to be displayed in the specified field.  Nulls are converted to an empty string.
1109 */
1110     public void setValueForKey( String key, Object value, int index )
1111     {
1112         if ( key == null )
1113         {
1114             key = "";
1115         }
1116 
1117         Container field = null;
1118 		for ( int i = 0; i < fields.size(); i++ )
1119 		{
1120             field = (Container) fields.get(i);
1121             if ( key.equals( field.getName() ) )
1122             {
1123 				setValueForIndex( index, i, value );
1124 				return;
1125             }
1126 		}
1127         // else not found - ignore
1128     }
1129 	
1130 /***
1131 * Sets the first field at the specified row index to the specified value.
1132 * Note: If the component does not respond to setText() or setString()
1133 * or setValue() the value will not be set.  JTextFields and the like will work.
1134 * @param row The row index of the component.
1135 * @param value A object to be displayed in the specified field.  
1136 * Nulls are converted to an empty string.
1137 */
1138     public void setValueForIndex( int row, Object value )
1139     {
1140 		setValueForIndex( row, 0, value );	
1141 	}
1142 	
1143 /***
1144 * Sets the field at the specified row index and column index to the specified value.
1145 * Note: If the component does not respond to setText() or setString()
1146 * or setValue() the value will not be set.  JTextFields and the like will work.
1147 * @param row The row index of the component.
1148 * @param index The column index of the component.
1149 * @param value A object to be displayed in the specified field.  
1150 * Nulls are converted to an empty string.
1151 */
1152     public void setValueForIndex( int row, int col, Object value )
1153     {
1154 		Container field = (Container) fields.get( row );
1155 		Component c = field.getComponent( col );
1156 		setValueForComponent( c, value );
1157 	}
1158 	
1159 
1160 
1161 /***
1162 * Sets the value in the field at the specified index.
1163 * Note: If the component does not respond to setText() or setString()
1164 * or setValue() this method will return null.  JTextFields and the like will work.
1165 * @param A valid index.
1166 * @param value A object to be displayed in the specified field.
1167 */
1168     protected void setValueForComponent( Component aComponent, Object value )
1169     {
1170         // try to set a text or string property
1171         try {
1172             BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() );
1173             MethodDescriptor[] methods = info.getMethodDescriptors();
1174             for ( int i = 0; i < methods.length; i++ )
1175             {
1176                 Method m = methods[i].getMethod();
1177                 Class[] paramTypes = m.getParameterTypes();
1178                 if ( paramTypes.length == 1 )
1179                 {
1180                     if ( m.getName().equals( "setText" ) )
1181                     {
1182                         if ( paramTypes[0].getName().equals( String.class.getName() ) )
1183                         {
1184                             m.invoke( aComponent, new Object[] { value } );
1185                         }
1186                     }
1187                     if ( m.getName().equals( "setString" ) )
1188                     {
1189                         if ( paramTypes[0].getName().equals( String.class.getName() ) )
1190                         {
1191                             m.invoke( aComponent, new Object[] { value } );
1192                         }
1193                     }
1194                     if ( m.getName().equals( "setValue" ) )
1195                     {
1196                         if ( paramTypes[0].getName().equals( Object.class.getName() ) )
1197                         {
1198                             m.invoke( aComponent, new Object[] { value } );
1199                         }
1200                     }
1201                 }
1202             }
1203         }
1204         catch ( Exception exc )
1205         { // error occured while introspecting... move along.
1206           // FIXME: should log error in ErrorManager
1207           System.out.println( "InfoPanel.setValueForComponent: " + exc );
1208         }
1209     }
1210 
1211 /***
1212 * Gets the value in the field at the specified index.
1213 * Note: If the component does not respond to getText() or getString()
1214 * or getSelectedItem() this method will return null.  JTextFields and the like will work.
1215 * @param A valid index.
1216 * @return An object representing the value in the field at the specified index,
1217 * or null if the component does not have a text property or if the index is out of bounds.
1218 */
1219     public Object getValueForIndex( int anIndex )
1220     {
1221         return getValueForIndex( anIndex, 0 );
1222     }
1223 
1224 /***
1225 * Gets the value in the field at the specified row and column.
1226 * Note: If the component does not respond to getText() or getString()
1227 * or getSelectedItem() this method will return null.  JTextFields and the like will work.
1228 * @param A valid index.
1229 * @return An object representing the value in the field at the specified index,
1230 * or null if the component does not have a text property or if the index is out of bounds.
1231 */
1232     public Object getValueForIndex( int row, int col )
1233     {
1234         if ( ( row >= fields.size() ) || ( row < 0 ) )
1235         { // out of bounds
1236             return null;
1237         }
1238 		
1239 		Container field = (Container) fields.get( row );
1240 		Component c = field.getComponent( col );
1241         return getValueForComponent( c );
1242     }
1243 
1244 /***
1245 * Gets the value in the field associated with the key.
1246 * Note: If the component does not respond to getText() or getString()
1247 * or getSelectedItem() this method will return null.  JTextFields and the like will work.
1248 * @param key An string representing the key associated with the field.  Nulls are converted to an empty string.
1249 * @return An object representing the value in the field associated with the key,
1250 * or null if the key does not exist or if the component does not have a text property.
1251 */
1252     public Object getValueForKey( String key )
1253     {
1254 		return getValueForKey( key, 0 );	
1255 	}
1256 
1257 /***
1258 * Gets the value in the field associated with the key.
1259 * Note: If the component does not respond to getText() or getString()
1260 * or getSelectedItem() this method will return null.  JTextFields and the like will work.
1261 * @param key An string representing the key associated with the field.  Nulls are converted to an empty string.
1262 * @return An object representing the value in the field associated with the key,
1263 * or null if the key does not exist or if the component does not have a text property.
1264 */
1265     public Object getValueForKey( String key, int index )
1266     {
1267         if ( key == null )
1268         {
1269             key = "";
1270         }
1271 
1272         Container field = null;
1273         Iterator enumeration = fields.iterator();
1274         while ( enumeration.hasNext() )
1275         { // finds first value in list with specified key
1276             field = (Container) enumeration.next();
1277             if ( key.equals( field.getName() ) )
1278             {
1279 				Component c = field.getComponent( index );
1280 				if ( c != null )
1281 				{
1282 					return getValueForComponent( c );
1283 				}
1284             }
1285         }
1286         // else not found
1287         return null;
1288     }
1289 
1290 /***
1291 * Gets the value in the specified component.
1292 * Note: If the component does not respond to getText() or getString()
1293 * or getSelectedItem() this method will return null.  JTextFields and the like will work.
1294 * @param aComponent The specified component.
1295 * @return An object representing the value in the component.
1296 * or null if the component does not have a text property.
1297 */
1298     protected Object getValueForComponent( Component aComponent )
1299     {
1300         // try to get a text or string property
1301         try
1302         {
1303             BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() );
1304             MethodDescriptor[] methods = info.getMethodDescriptors();
1305             for ( int i = 0; i < methods.length; i++ )
1306             {
1307                 Method m = methods[i].getMethod();
1308                 Class[] paramTypes = m.getParameterTypes();
1309                 if ( m.getName().equals( "getText" ) )
1310                 {
1311                     if ( paramTypes.length == 0 )
1312                     {
1313                         return m.invoke( aComponent, new Object[] {} );
1314                     }
1315                 }
1316                 if ( m.getName().equals( "getString" ) )
1317                 {
1318                     if ( paramTypes.length == 0 )
1319                     {
1320                         return m.invoke( aComponent, new Object[] {} );
1321                     }
1322                 }
1323                 if ( m.getName().equals( "getSelectedItem" ) )
1324                 {
1325                     if ( paramTypes.length == 0 )
1326                     {
1327                         return m.invoke( aComponent, new Object[] {} );
1328                     }
1329                 }
1330                 // TODO: should also handle variants of setValue()
1331             }
1332         }
1333         catch ( Exception exc )
1334         { // error occured while introspecting... move along.
1335           System.out.println( "InfoPanel.getValueFromComponent: " + exc );
1336         }
1337 
1338         // not found
1339         return null;
1340     }
1341 
1342 /***
1343 * Gets the component associated with the key as a JTextField, for backwards compatibility.
1344 * @param key A string representing the key associated with the component.  Nulls are converted to an empty string.
1345 * @return A JTextField that contains the value associated with the key,
1346 * or null if the key does not exist or if the component is not a JTextField.
1347 */
1348     public JTextField getFieldForKey( String key )
1349     {
1350         Component c = getComponentForKey( key );
1351         if ( c instanceof JTextField )
1352         {
1353             return (JTextField) c;
1354         }
1355         return null;
1356     }
1357 
1358 /***
1359 * Gets the component associated with the key.  If more than one component is associated
1360 * with the key, returns the first such component.
1361 * @param key A string representing the key associated with the component.  
1362 * Nulls are converted to an empty string.
1363 * @return A component that contains the value associated with the key,
1364 * or null if the key does not exist.
1365 */
1366     public Component getComponentForKey( String key )
1367     {
1368 		return getComponentForKey( key, 0 );
1369 	}
1370 
1371 /***
1372 * Gets the component associated with the key and index.
1373 * @param key A string representing the key associated with the component.  
1374 * Nulls are converted to an empty string.
1375 * @return A component that contains the value associated with the key,
1376 * or null if the key does not exist.
1377 */
1378     public Component getComponentForKey( String key, int index )
1379     {
1380 		Container c = getCompositeComponentForKey( key );
1381 		if ( c == null ) return null;
1382 		return c.getComponent( index );
1383     }
1384 
1385 /***
1386 * Gets the component at the specified row.  If more than one component exists
1387 * on that row, returns the first such component.
1388 * @return A component or null if the row does not exist.
1389 */
1390     public Object getComponentForIndex( int row )
1391     {
1392         return getComponentForIndex( row, 0 );
1393     }
1394 
1395 /***
1396 * Gets the component at the specified row and column.
1397 * @return A component or null if the index is out of bounds.
1398 */
1399     public Object getComponentForIndex( int row, int col )
1400     {
1401         if ( ( row > fields.size() ) || ( row < 0 ) )
1402         { // out of bounds
1403             return null;
1404         }
1405 		
1406 		Container field = (Container) fields.get( row );
1407 		return field.getComponent( col );
1408     }
1409 
1410 /***
1411 * Gets the container associated with the key.
1412 * @param key A string representing the key associated with the component.  
1413 * Nulls are converted to an empty string.
1414 * @return A component that contains the value associated with the key,
1415 * or null if the key does not exist.
1416 */
1417     protected Container getCompositeComponentForKey( String key )
1418     {
1419         if ( key == null )
1420         {
1421             key = "";
1422         }
1423 
1424         JPanel field = null;
1425         Iterator enumeration = fields.iterator();
1426         while ( enumeration.hasNext() )
1427         { // finds first value in list with specified key
1428             field = (JPanel) enumeration.next();
1429             if ( key.equals( field.getName() ) )
1430             {
1431                 return field;
1432             }
1433         }
1434 
1435         // else not found
1436         return null;
1437     }
1438 
1439 /***
1440 * Provided for backwards compatibility: calls getLabelComponentForKey.
1441 * @param key A string representing the key associated with the compoent.
1442 *            Nulls are converted to an empty string.
1443 * @return Component label object associated with the key, or null if the key does not exist
1444 * or if the label component is not an instance of JLabel.
1445 */
1446     public JLabel getLabelForKey( String key )
1447     {
1448         Component result = getLabelComponentForKey( key );
1449         if ( result instanceof JLabel ) return (JLabel) result;
1450         return null;
1451     }
1452     
1453 /***
1454 * Get the label component associated with the key.
1455 * @param key A string representing the key associated with the compoent.
1456 *            Nulls are converted to an empty string.
1457 * @return Component label object associated with the key, or null if the key does not exist.
1458 */
1459     public Component getLabelComponentForKey( String key )
1460     {
1461         if ( key == null )
1462         {
1463             key = "";
1464         }
1465 
1466         Component label = null;
1467         Iterator enumeration = labels.iterator();
1468         while ( enumeration.hasNext() )
1469         { // finds first value in list with specified key
1470             label = (Component) enumeration.next();
1471             if ( key.equals( label.getName() ) )
1472             {
1473                 return label;
1474             }
1475         }
1476 
1477         // else not found
1478         return null;
1479     }
1480 
1481 /***
1482 * Replaces the first component associated with the key.  Any value in the existing
1483 * component will be copied to the new component.
1484 * @param key A string representing the key to be associated with the component.  
1485 * Nulls are converted to an empty string.
1486 * @param c A component to be placed next to the label corresponding to the key.  
1487 * Nulls are converted to a JTextField.
1488 */
1489     public void setComponentForKey( String key, Component c )
1490     {
1491 		setComponentForKey( key, c, 0 );	
1492 	}
1493 	
1494 /***
1495 * Replaces the component associated with the key.  Any value in the existing
1496 * component will be copied to the new component.
1497 * @param key A string representing the key to be associated with the component.  
1498 * Nulls are converted to an empty string.
1499 * @param c A component to be placed next to the label corresponding to the key.  
1500 * Nulls are converted to a JTextField.
1501 */
1502     public void setComponentForKey( String key, Component c, int index )
1503     {
1504         if ( c == null )
1505         {
1506             c = new JTextField( 15 );
1507         }
1508         if ( key == null )
1509         {
1510             key = "";
1511         }
1512 
1513         Container container = this.getCompositeComponentForKey( key );
1514 		Component field = container.getComponent( index );
1515         Object value = this.getValueForKey( key, index );
1516         if ( field != null )
1517         {
1518 			container.remove( index );
1519 			container.add( c, index );
1520 			c.setEnabled( this.isEditable );
1521 			introspectComponent( c, key );
1522             setValueForComponent( c, value );
1523         }
1524     }
1525 
1526 /***
1527 * Replaces the first component in the specified row.  Any value in the existing
1528 * component will be copied to the new component.
1529 * @param row A valid index.
1530 * @param c A component to be placed next to the label corresponding to the key.  
1531 */
1532     public void setComponentForIndex( int row, Component c )
1533     {
1534 		setComponentForIndex( row, 0, c );	
1535 	}
1536 	
1537 /***
1538 * Replaces the component associated with the key.  Any value in the existing
1539 * component will be copied to the new component.
1540 * @param row A valid index.
1541 * @param c A component to be placed next to the label corresponding to the key.  
1542 */
1543     public void setComponentForIndex( int row, int col, Component c )
1544     {
1545 		setComponentForKey( getLabels()[row], c, col );
1546     }
1547 
1548 /***
1549 * Sets the string that appears before each label's text on the panel.
1550 * @param aString A String to be used as the label prefix.
1551 */
1552     public void setLabelPrefix( String aString )
1553     {
1554         prefix = aString;
1555         setLabels( getLabels() ); // force refresh
1556     }
1557 
1558 /***
1559 * Gets the string that appears before each label's text on the panel.
1560 * Defaults to "", an empty string.
1561 * @return A String that is currently used as the label prefix.
1562 */
1563     public String getLabelPrefix()
1564     {
1565         return prefix;
1566     }
1567 
1568 /***
1569 * Sets the string that appears after each label's text on the panel.
1570 * Defaults to ": ", a colon followed by a space.
1571 * @param aString A String to be used as the label postfix.
1572 */
1573     public void setLabelPostfix( String aString )
1574     {
1575         postfix = aString;
1576         setLabels( getLabels() ); // force refresh
1577     }
1578 
1579 /***
1580 * Gets the string that appears after each label's text on the panel.
1581 * @return A String that is currently used as the label postfix.
1582 */
1583     public String getLabelPostfix()
1584     {
1585         return postfix;
1586     }
1587 
1588 /***
1589 * Adds an action listener to the list that will be
1590 * notified by events occurring in the panel.
1591 * @param l An action listener to be notified.
1592 */
1593      public void addActionListener(ActionListener l)
1594      {
1595            actionListener = AWTEventMulticaster.add(actionListener, l);
1596      }
1597 /***
1598 * Removes an action listener from the list that will be
1599 * notified by events occurring in the panel.
1600 * @param l An action listener to be removed.
1601 */
1602      public void removeActionListener(ActionListener l)
1603      {
1604            actionListener = AWTEventMulticaster.remove(actionListener, l);
1605      }
1606 /***
1607 * Notifies all registered action listeners of a pending Action Event.
1608 * @param e An action event to be broadcast.
1609 */
1610      protected void broadcastEvent(ActionEvent e)
1611      {
1612          if (actionListener != null)
1613          {
1614              actionListener.actionPerformed(e);
1615          }
1616      }
1617 
1618     // interface ActionListener
1619 
1620 /***
1621 * Called by buttons on panel and by other components that
1622 * might be set to broadcast events to this listener.
1623 * Simply forwards the action event unchanged.
1624 * @param e An action event to be received.
1625 */
1626      public void actionPerformed(ActionEvent e)
1627      {
1628 //        if ( e.getSource() instanceof AbstractButton )
1629 //        {
1630             broadcastEvent(e);
1631 //        }
1632      }
1633      
1634     /***
1635     * GridBagLayout allocates weightx only after considering
1636     * the preferred width of the components in a column.
1637     * We'd prefer that preferred width wasn't considered,
1638     * so that the layout worked more like a html-table.
1639     * GridBagLayout is poorly factored for subclassing,
1640     * so this code is going to get a little bit ugly.
1641     * Really, what good is a protected method that returns
1642     * a private class?  Would have liked to just override
1643     * getLayoutInfo and be done with it.
1644     */
1645     private class BetterGridBagLayout extends GridBagLayout
1646     {
1647         public Dimension preferredLayoutSize(Container parent) 
1648         {
1649             preprocess();
1650             return super.preferredLayoutSize( parent );
1651         }
1652         
1653         public Dimension minimumLayoutSize(Container parent) 
1654         {
1655             preprocess();
1656             return super.minimumLayoutSize( parent );
1657         }
1658 
1659         
1660         public void layoutContainer(Container parent) 
1661         {
1662             preprocess();
1663             super.layoutContainer( parent );
1664         }
1665 
1666         protected void preprocess() 
1667         {
1668             if ( fieldSpacers == null ) return;
1669             Iterator i;
1670             
1671             // find the field with the widest preferred size
1672             Component c;
1673             int maxWidth = 0;
1674             i = fields.iterator();
1675             while ( i.hasNext() )
1676             {
1677                 c = (Component) i.next();
1678                 maxWidth = Math.max( maxWidth, 
1679                     Math.max( c.getPreferredSize().width, c.getMinimumSize().width ) );
1680             }
1681             
1682             // set each column's spacers to that preferred size
1683             Dimension min = new Dimension( 0, 0 );
1684             Dimension pref = new Dimension( maxWidth, 0 );
1685             i = fieldSpacers.iterator();
1686             while ( i.hasNext() )
1687             {
1688                 ((Box.Filler)i.next()).changeShape( min, pref, pref );
1689             }
1690         }
1691     }
1692 }
1693