Coverage Report - net.wotonomy.ui.swing.components.InfoPanel
 
Classes in this File Line Coverage Branch Coverage Complexity
InfoPanel
0% 
0% 
2.654
 
 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  0
 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  0
     private static Map _method_cache =
 95  0
       Collections.synchronizedMap( new HashMap(30) );
 96  
 
 97  0
     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  0
     protected List fields = null;
 103  0
     protected List labels = null;
 104  0
     protected List fieldSpacers = null; 
 105  0
     protected ButtonPanel buttonPanel = null;
 106  0
     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  0
     protected ActionListener actionListener = null;
 115  
     
 116  
 /**
 117  
 * Constructs an empty InfoPanel.
 118  
 */
 119  0
     public InfoPanel()
 120  0
     {
 121  0
         hgap = 12;   // per java l&f guidelines
 122  0
         vgap = 6;    // java l&f says 11
 123  0
         columns = 1; // default columns
 124  0
         margin = 0;  // default margin: none
 125  0
         prefix = ""; // default prefix: none
 126  0
         postfix = ":"; // per java l&f guidelines
 127  0
         fields = new ArrayList();
 128  0
         labels = new ArrayList();
 129  0
                 labelAnchor = GridBagConstraints.NORTHWEST; 
 130  
                         // per java l&f guidelines (CENTER is nicer)
 131  0
                 labelAlign = SwingConstants.LEFT; 
 132  
                         // per java l&f guidelines
 133  
             
 134  0
         doInitialLayout();
 135  0
     }
 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  0
         this();
 147  0
         setLabels( labelArray );
 148  0
     }
 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  0
         removeAll();
 160  0
         if ( labelArray == null ) return; // null clears panel
 161  0
         for ( int i = 0; i < labelArray.length; i++ )
 162  
         {
 163  0
             addPair( labelArray[i], new JTextField() );
 164  
         }
 165  0
     }
 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  0
         int length = fields.size();
 177  0
         String[] labelArray = new String[ length ];
 178  0
         for ( int i = 0; i < length; i++ )
 179  
         {
 180  0
             labelArray[i] = ((Component)fields.get(i)).getName();
 181  
         }
 182  0
         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  0
                 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  0
                 labelAnchor = anAnchorConstant;        
 203  0
                 updateLabels();
 204  0
         }
 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  0
                 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  0
                 labelAlign = anAlignmentConstant;        
 224  0
                 updateLabels();
 225  0
         }
 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  0
         JPanel result = new JPanel();
 235  0
         result.setOpaque( false );
 236  0
         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  0
         listContainer = createPanel();
 247  0
         listContainer.setLayout( new BetterGridBagLayout() );
 248  0
         this.setLayout( new BorderLayout() ); 
 249  0
         this.add( listContainer, BorderLayout.NORTH ); 
 250  
 
 251  
         //listContainer.setBackground( Color.blue ); // useful for testing
 252  
         //this.setBackground( Color.red );
 253  0
     }
 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  0
         if ( newHgap < 0 ) return; // may not be negative
 263  0
         this.hgap = newHgap;
 264  0
         updateGaps();
 265  0
         this.revalidate();
 266  0
         this.repaint();
 267  
 
 268  0
     }
 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  0
         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  0
         if ( newVgap < 0 ) return; // may not be negative
 287  0
         this.vgap = newVgap;
 288  0
         updateGaps();
 289  0
         this.revalidate();
 290  0
         this.repaint();
 291  
 
 292  0
     }
 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  0
         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  0
         if ( newMargin < 0 ) return; // may not be negative
 313  0
         this.margin = newMargin;
 314  
 
 315  0
         if ( listContainer.getLayout() instanceof GridBagLayout )
 316  
         {
 317  0
             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
 318  0
             GridBagConstraints constraints = null;
 319  0
             Component c = null;
 320  0
             int count = listContainer.getComponentCount();
 321  0
             for ( int i = 0; i < count; i++ )
 322  
             {
 323  0
                 c = listContainer.getComponent( i );
 324  0
                 constraints = gridBag.getConstraints( c );
 325  0
                 if ( constraints.gridy == 0 && constraints.gridx % 2 == 0 )
 326  
                 { // if this is a label spacer 
 327  
                     // replace it with an appropriately sized box
 328  0
                     listContainer.remove( c );
 329  0
                     listContainer.add( Box.createHorizontalStrut( this.margin ), constraints );
 330  
                 }
 331  
             }
 332  
         }
 333  
 
 334  0
         this.revalidate();
 335  0
         this.repaint();
 336  
 
 337  0
     }
 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  0
         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  0
         if ( newColumns < 1 ) return; // may not be less than one.
 359  0
         int oldColumns = this.columns;
 360  0
         this.columns = newColumns;
 361  
 
 362  0
         if ( listContainer.getLayout() instanceof GridBagLayout )
 363  
         {
 364  0
             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
 365  0
             int count = listContainer.getComponentCount();
 366  0
             Component[] components = listContainer.getComponents();
 367  0
             GridBagConstraints[] constraints = new GridBagConstraints[ components.length ];
 368  0
             for ( int i = 0; i < components.length; i++ )
 369  
             {
 370  0
                 constraints[i] = gridBag.getConstraints( components[i] );
 371  
             }
 372  0
             listContainer.removeAll();
 373  0
             for ( int i = 0; i < components.length; i++ )
 374  
             {
 375  0
                 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  0
                     int index = ( constraints[i].gridy - 1 ) * oldColumns*2 + constraints[i].gridx;
 381  0
                     constraints[i].gridy = ( index / (newColumns*2) ) + 1;
 382  0
                     constraints[i].gridx = index % (newColumns*2) ;
 383  0
                     listContainer.add( components[i], constraints[i] );
 384  
                 }
 385  
             }
 386  0
             createSpacers(); // replace the spacers
 387  0
             updateGaps();
 388  
         }
 389  
 
 390  0
         this.revalidate();
 391  0
         this.repaint();
 392  
 
 393  0
     }
 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  0
         Container c = getCompositeComponentForKey( key );
 404  0
         if ( c == null ) return -1.0;
 405  0
         if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return -1.0;
 406  0
         GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
 407  0
         GridBagConstraints gbc = layout.getConstraints( c );
 408  0
         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  0
         Container c = getCompositeComponentForKey( key );
 422  0
         if ( c == null ) return;
 423  0
         if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return;
 424  0
         GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
 425  0
         GridBagConstraints gbc = layout.getConstraints( c );
 426  0
         gbc.weighty = weighty;
 427  0
         layout.setConstraints( c, gbc );
 428  
         // handle adding on-the-fly
 429  0
         updateGaps();
 430  0
         this.revalidate();
 431  0
         this.repaint();
 432  0
     }
 433  
     
 434  
 /**
 435  
 * Gets the current number of columns.
 436  
 * @return the current number of columns.
 437  
 */
 438  
     public int getColumns()
 439  
     {
 440  0
         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  0
                 addRow( key, new Component[] { component } );
 455  0
         }
 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  0
                 addRow( key, new Component[] { component } );
 469  0
         }
 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  0
                 addCompositeComponent( key, makeCompositeComponent( key, components ) );
 484  0
         }
 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  0
                 addCompositeComponent( key, 
 506  0
             makeCompositeComponent( key, 
 507  0
                 west, center, east ) );
 508  0
         }
 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  0
                 addCompositeComponent( key, 
 534  0
             makeCompositeComponent( key, 
 535  0
                 west, north, center, south, east ) );
 536  0
         }
 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  0
                 JPanel panel = createPanel();
 547  0
                 if ( components.length != 0 ) 
 548  
                 {
 549  0
                         panel.setLayout( new GridLayout( 1, components.length, hgap, vgap ) );                        
 550  
 
 551  
                         Component c;
 552  0
                         for ( int i = 0; i < components.length; i++ )
 553  
                         {                
 554  0
                                 c = components[i];
 555  0
                                 if ( c != null )
 556  
                                 {
 557  0
                                         introspectComponent( c, key );
 558  0
                                         panel.add( c );
 559  
                                 }
 560  
                         }
 561  
                 }
 562  0
                 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  0
                 JPanel panel = createPanel();
 574  0
                 panel.setLayout( new BorderLayout( hgap, vgap ) );
 575  
 
 576  0
                 if ( west != null )
 577  
                 {
 578  0
                         introspectComponent( west, key );
 579  0
                         panel.add( west, BorderLayout.WEST );
 580  
                 }
 581  
                 
 582  0
                 if ( center != null )
 583  
                 {
 584  0
                         introspectComponent( center, key );
 585  0
                         panel.add( center, BorderLayout.CENTER );
 586  
                 }
 587  
                 
 588  0
                 if ( east != null )
 589  
                 {
 590  0
                         introspectComponent( east, key );
 591  0
                         panel.add( east, BorderLayout.EAST );
 592  
                 }
 593  
                 
 594  0
                 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  0
                 JPanel panel = createPanel();
 607  0
                 panel.setLayout( new BorderLayout( hgap, vgap ) );
 608  
 
 609  0
                 if ( west != null )
 610  
                 {
 611  0
                         introspectComponent( west, key );
 612  0
                         panel.add( west, BorderLayout.WEST );
 613  
                 }
 614  
                 
 615  0
                 if ( north != null )
 616  
                 {
 617  0
                         introspectComponent( north, key );
 618  0
                         panel.add( north, BorderLayout.WEST );
 619  
                 }
 620  
                 
 621  0
                 if ( center != null )
 622  
                 {
 623  0
                         introspectComponent( center, key );
 624  0
                         panel.add( center, BorderLayout.CENTER );
 625  
                 }
 626  
                 
 627  0
                 if ( south != null )
 628  
                 {
 629  0
                         introspectComponent( south, key );
 630  0
                         panel.add( south, BorderLayout.CENTER );
 631  
                 }
 632  
                 
 633  0
                 if ( east != null )
 634  
                 {
 635  0
                         introspectComponent( east, key );
 636  0
                         panel.add( east, BorderLayout.EAST );
 637  
                 }
 638  
                 
 639  0
                 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  0
                 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  0
                 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  0
         if ( key == null )
 673  
         {
 674  0
             key = "";
 675  
         }
 676  0
         Component label = createLabelForKey( key );
 677  0
         Component field = component;
 678  0
         if ( field == null )
 679  
         {
 680  0
             field = new JTextField( 15 ); // default to 15 columns
 681  
         }
 682  0
         field.setName( key ); // for association and reference
 683  0
         label.setName( key ); // ditto
 684  0
         if ( label instanceof JLabel )
 685  
         {
 686  0
             ((JLabel)label).setHorizontalAlignment( labelAlign );
 687  0
             ((JLabel)label).setLabelFor( field ); // for accessibility
 688  
         }
 689  0
         if ( "".equals( key ) )
 690  
         {
 691  0
             setText( label, "" );
 692  0
         }
 693  
         else
 694  
         {
 695  0
             setText( label, prefix + key + postfix );
 696  
         }
 697  0
         field.setEnabled( this.isEditable ); // was: setEditable
 698  
 
 699  0
         GridBagConstraints gbc = new GridBagConstraints();
 700  
 
 701  0
         if ( listContainer.getComponentCount() == 0 )
 702  
         { // we've just initialized or called removeAll
 703  0
             createSpacers();
 704  
         }
 705  
 
 706  0
         gbc.gridx = ( fields.size() % this.columns ) * 2;
 707  0
         gbc.gridy = ( fields.size() / this.columns ) + 1; // spacer is at index zero
 708  0
         gbc.weightx = 0.0;
 709  0
         gbc.weighty = 0.0;
 710  0
         gbc.anchor = this.labelAnchor; 
 711  0
         gbc.fill = GridBagConstraints.HORIZONTAL;
 712  0
         listContainer.add( label, gbc );
 713  
 
 714  0
                 gbc.fill = GridBagConstraints.BOTH;
 715  0
         gbc.gridx = gbc.gridx + 1;
 716  
                 //FIXME: components default to the labelAnchor - should be different?
 717  0
         gbc.weightx = 1.0;
 718  0
         gbc.weighty = 0.0;
 719  
         
 720  0
         listContainer.add( field, gbc );
 721  
         
 722  0
         if ( key.equals( HIDDEN ) )
 723  
         { // these components are not to be shown
 724  0
             setText( label, "     " );
 725  0
             field.setVisible( false );
 726  
         }
 727  
 
 728  0
         fields.add( field ); // using list not map to allow for duplicate keys
 729  0
         labels.add( label ); // ditto
 730  
 
 731  
         // handle adding on-the-fly
 732  0
         updateGaps();
 733  0
         this.revalidate();
 734  0
         this.repaint();
 735  0
     }
 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  0
             Method [] methods =
 748  0
                 (Method []) _method_cache.get( aComponent.getClass() );
 749  0
             if (methods == null) {
 750  0
                 Class componentClass = aComponent.getClass();
 751  0
                 BeanInfo info =
 752  0
                     Introspector.getBeanInfo( componentClass );
 753  
 
 754  0
                 MethodDescriptor[] descriptors =
 755  0
                     info.getMethodDescriptors();
 756  0
                 Method setMethod = null;
 757  0
                 Method addMethod = null;
 758  0
                 for ( int i = 0;
 759  0
                       ((setMethod == null || addMethod == null) &&
 760  0
                        i < descriptors.length);
 761  0
                       i++ )
 762  
                 {
 763  0
                     Method m = descriptors[i].getMethod();
 764  0
                     String name = m.getName ();
 765  0
                     if ( setMethod == null &&
 766  0
                          name.equals( "setActionCommand" ) )
 767  
                     {
 768  0
                         setMethod = m;
 769  0
                     }
 770  0
                     else if ( addMethod == null &&
 771  0
                               name.equals( "addActionListener" ) )
 772  
                     {
 773  0
                         addMethod = m;
 774  
                     }
 775  
                 }
 776  
 
 777  0
                 methods = new Method [] {setMethod, addMethod};
 778  0
                 _method_cache.put (componentClass, methods);
 779  
             }
 780  0
             if (methods [0] != null) {
 781  0
               methods [0].invoke( aComponent, new Object[] { aKey } );
 782  
             }
 783  0
             if (methods [1] != null) {
 784  0
               methods [1].invoke( aComponent, new Object[] { this } );
 785  0
               listenedToComponents.add( aComponent );
 786  
             }
 787  
         }
 788  0
         catch ( Exception exc )
 789  
         { // error occured while introspecting... move along.
 790  0
               System.out.println( "InfoPanel.introspectComponent: " + exc );
 791  0
         }
 792  0
     }
 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  0
             Method m = c.getClass().getMethod( "setText", new Class[] { String.class } );
 804  0
             if ( m != null )
 805  
             {
 806  0
                 m.invoke( c, new Object[] { text } );
 807  
             }
 808  
         }
 809  0
         catch ( Exception exc )
 810  
         {
 811  
             // no such method: ignore
 812  0
         }
 813  0
     }
 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  0
         if ( listContainer.getLayout() instanceof GridBagLayout )
 826  
         {
 827  
             // insert spacers for labels column
 828  0
             GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout();
 829  0
             GridBagConstraints constraints = new GridBagConstraints();
 830  0
             constraints.gridy = 0;
 831  0
             constraints.fill = GridBagConstraints.HORIZONTAL;
 832  
 
 833  0
             fieldSpacers = new LinkedList();
 834  
             Component fieldSpacer;
 835  0
             for ( int i = 0; i < this.columns; i++ )
 836  
             {
 837  0
                 constraints.gridx = i * 2;
 838  0
                 listContainer.add( Box.createHorizontalStrut( this.margin ), constraints );
 839  
 
 840  0
                 constraints.gridx = i * 2 + 1;
 841  0
                 fieldSpacer = Box.createHorizontalStrut( 0 ); 
 842  0
                 fieldSpacers.add( fieldSpacer );
 843  0
                 listContainer.add( fieldSpacer, constraints );
 844  
             }
 845  
         }
 846  0
     }
 847  
 
 848  
 /**
 849  
 * Updates the insets for all components.
 850  
 */
 851  
     protected void updateGaps()
 852  
     {
 853  0
         if ( listContainer.getLayout() instanceof GridBagLayout )
 854  
         {
 855  0
             GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
 856  0
             Component c = null;
 857  0
             GridBagConstraints gbc = null;
 858  0
             double totalWeightY = 0.0;
 859  0
             int count = listContainer.getComponentCount();
 860  
             int i;
 861  0
             for ( i = 0; i < count; i++ )
 862  
             {
 863  0
                 c = listContainer.getComponent( i );
 864  0
                 gbc = layout.getConstraints( c );
 865  0
                 totalWeightY += gbc.weighty;
 866  0
                 if ( (gbc.gridx + 1) % ( this.columns * 2 ) == 0 )
 867  
                 { // if last component in row
 868  0
                     gbc.insets = new Insets( 0, 0, this.vgap, 0 );
 869  0
                 }
 870  
                 else
 871  
                 {
 872  0
                     if ( gbc.gridx % 2 == 0 )
 873  
                     { // is a label column - NOTE: uses eleven pixels before component, per l&f guide
 874  0
                         gbc.insets = new Insets( 0, 0, this.vgap, 11 );
 875  0
                     }
 876  
                     else
 877  
                     { // is a component column
 878  0
                         if ( gbc.gridy != 0 ) 
 879  
                         {
 880  0
                             if ( c instanceof JPanel ) ((JPanel)c).setPreferredSize( null );
 881  0
                             gbc.insets = new Insets( 0, 0, this.vgap, this.hgap );
 882  
                         }
 883  
                     }
 884  
                 }
 885  0
                 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  0
             this.remove( listContainer );
 891  0
             if ( totalWeightY == 0.0 )
 892  
             {
 893  0
                 this.add( listContainer, BorderLayout.NORTH );
 894  0
             } 
 895  
             else // put list container in center so it will grow
 896  
             {
 897  0
                 this.add( listContainer, BorderLayout.CENTER );
 898  
             }
 899  
         }
 900  0
     }
 901  
 
 902  
 /**
 903  
 * Updates the label alignment.
 904  
 */
 905  
     protected void updateLabels()
 906  
     {
 907  0
         if ( listContainer.getLayout() instanceof GridBagLayout )
 908  
         {
 909  0
             GridBagLayout layout = (GridBagLayout) listContainer.getLayout();
 910  0
             Component c = null;
 911  0
             GridBagConstraints gbc = null;
 912  0
                         Iterator it = labels.iterator();
 913  0
                         while ( it.hasNext() )
 914  
             {
 915  0
                 c = (Component) it.next();
 916  0
                 if ( c instanceof JLabel )
 917  
                 {
 918  0
                                     ((JLabel)c).setHorizontalAlignment( labelAlign );
 919  
                 }
 920  0
                 gbc = layout.getConstraints( c );
 921  0
                 gbc.anchor = this.labelAnchor;
 922  0
                 layout.setConstraints( c, gbc );
 923  0
             }
 924  
         }
 925  0
     }
 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  0
         addPair( key, value, null );
 935  0
     }
 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  0
         if ( value == null )
 948  
         {
 949  0
             value = "";
 950  
         }
 951  0
         JTextField field = textField;
 952  0
         if ( field == null )
 953  
         {
 954  0
             field = new JTextField( 15 ); // default to 15 columns
 955  0
         }
 956  
         else
 957  
         {
 958  0
             field = textField;
 959  
         }
 960  0
         field.setText( value );
 961  
 
 962  0
         addPair( key, (Component) field );
 963  0
     }
 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  0
         Class[] paramClasses = new Class[] { ActionListener.class };
 975  0
         Object[] paramObjects = new Object[] { this };
 976  
 
 977  0
         Iterator iterator = listenedToComponents.iterator();
 978  0
         while ( iterator.hasNext() )
 979  
         {
 980  0
             component = iterator.next();
 981  
             try
 982  
             {
 983  0
                 method = component.getClass().getMethod( "removeActionListener", paramClasses );
 984  0
                 if ( method != null )
 985  
                 {
 986  0
                     method.invoke( component, paramObjects );
 987  
                 }
 988  
             }
 989  0
             catch ( Exception exception )
 990  
             {
 991  
                 // No removeActionListener() method, move along.
 992  0
             }
 993  0
         }
 994  
 
 995  0
         listenedToComponents.clear();
 996  
         
 997  0
         listContainer.removeAll();
 998  0
         fields.clear();
 999  0
         labels.clear();
 1000  0
         this.revalidate();
 1001  0
         this.repaint();
 1002  
         
 1003  
         //FIXME: It is very confusing that this 
 1004  
         // implementation does not call super.removeAll().
 1005  0
     }
 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  0
         if ( buttonPanel == null )
 1018  
         {
 1019  0
             buttonPanel = new ButtonPanel();
 1020  0
             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  0
             buttonPanel.addActionListener( this );
 1024  0
             this.add( buttonPanel, BorderLayout.SOUTH );
 1025  
         }
 1026  0
         if ( buttons == null )
 1027  
         {
 1028  0
             this.remove( buttonPanel );
 1029  0
             buttonPanel = null;
 1030  0
         }
 1031  
         else
 1032  
         {
 1033  0
             buttonPanel.setLabels( buttons );
 1034  
         }
 1035  
 
 1036  0
         this.revalidate();
 1037  0
         this.repaint();
 1038  0
     }
 1039  0
     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  0
         if ( buttonPanel == null )
 1050  
         {
 1051  0
             return null; // none created
 1052  
         }
 1053  
 
 1054  0
         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  0
         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  0
         this.isEditable = isEditable;
 1075  0
         Iterator enumeration = fields.iterator();
 1076  0
         while ( enumeration.hasNext() )
 1077  
         {
 1078  0
             ( (Component) enumeration.next() ).setEnabled( isEditable );
 1079  0
         }
 1080  0
     }
 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  0
         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  0
                 setValueForKey( key, value, 0 );        
 1101  0
         }
 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  0
         if ( key == null )
 1113  
         {
 1114  0
             key = "";
 1115  
         }
 1116  
 
 1117  0
         Container field = null;
 1118  0
                 for ( int i = 0; i < fields.size(); i++ )
 1119  
                 {
 1120  0
             field = (Container) fields.get(i);
 1121  0
             if ( key.equals( field.getName() ) )
 1122  
             {
 1123  0
                                 setValueForIndex( index, i, value );
 1124  0
                                 return;
 1125  
             }
 1126  
                 }
 1127  
         // else not found - ignore
 1128  0
     }
 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  0
                 setValueForIndex( row, 0, value );        
 1141  0
         }
 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  0
                 Container field = (Container) fields.get( row );
 1155  0
                 Component c = field.getComponent( col );
 1156  0
                 setValueForComponent( c, value );
 1157  0
         }
 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  0
             BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() );
 1173  0
             MethodDescriptor[] methods = info.getMethodDescriptors();
 1174  0
             for ( int i = 0; i < methods.length; i++ )
 1175  
             {
 1176  0
                 Method m = methods[i].getMethod();
 1177  0
                 Class[] paramTypes = m.getParameterTypes();
 1178  0
                 if ( paramTypes.length == 1 )
 1179  
                 {
 1180  0
                     if ( m.getName().equals( "setText" ) )
 1181  
                     {
 1182  0
                         if ( paramTypes[0].getName().equals( String.class.getName() ) )
 1183  
                         {
 1184  0
                             m.invoke( aComponent, new Object[] { value } );
 1185  
                         }
 1186  
                     }
 1187  0
                     if ( m.getName().equals( "setString" ) )
 1188  
                     {
 1189  0
                         if ( paramTypes[0].getName().equals( String.class.getName() ) )
 1190  
                         {
 1191  0
                             m.invoke( aComponent, new Object[] { value } );
 1192  
                         }
 1193  
                     }
 1194  0
                     if ( m.getName().equals( "setValue" ) )
 1195  
                     {
 1196  0
                         if ( paramTypes[0].getName().equals( Object.class.getName() ) )
 1197  
                         {
 1198  0
                             m.invoke( aComponent, new Object[] { value } );
 1199  
                         }
 1200  
                     }
 1201  
                 }
 1202  
             }
 1203  
         }
 1204  0
         catch ( Exception exc )
 1205  
         { // error occured while introspecting... move along.
 1206  
           // FIXME: should log error in ErrorManager
 1207  0
           System.out.println( "InfoPanel.setValueForComponent: " + exc );
 1208  0
         }
 1209  0
     }
 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  0
         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  0
         if ( ( row >= fields.size() ) || ( row < 0 ) )
 1235  
         { // out of bounds
 1236  0
             return null;
 1237  
         }
 1238  
                 
 1239  0
                 Container field = (Container) fields.get( row );
 1240  0
                 Component c = field.getComponent( col );
 1241  0
         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  0
                 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  0
         if ( key == null )
 1268  
         {
 1269  0
             key = "";
 1270  
         }
 1271  
 
 1272  0
         Container field = null;
 1273  0
         Iterator enumeration = fields.iterator();
 1274  0
         while ( enumeration.hasNext() )
 1275  
         { // finds first value in list with specified key
 1276  0
             field = (Container) enumeration.next();
 1277  0
             if ( key.equals( field.getName() ) )
 1278  
             {
 1279  0
                                 Component c = field.getComponent( index );
 1280  0
                                 if ( c != null )
 1281  
                                 {
 1282  0
                                         return getValueForComponent( c );
 1283  
                                 }
 1284  0
             }
 1285  
         }
 1286  
         // else not found
 1287  0
         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  0
             BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() );
 1304  0
             MethodDescriptor[] methods = info.getMethodDescriptors();
 1305  0
             for ( int i = 0; i < methods.length; i++ )
 1306  
             {
 1307  0
                 Method m = methods[i].getMethod();
 1308  0
                 Class[] paramTypes = m.getParameterTypes();
 1309  0
                 if ( m.getName().equals( "getText" ) )
 1310  
                 {
 1311  0
                     if ( paramTypes.length == 0 )
 1312  
                     {
 1313  0
                         return m.invoke( aComponent, new Object[] {} );
 1314  
                     }
 1315  
                 }
 1316  0
                 if ( m.getName().equals( "getString" ) )
 1317  
                 {
 1318  0
                     if ( paramTypes.length == 0 )
 1319  
                     {
 1320  0
                         return m.invoke( aComponent, new Object[] {} );
 1321  
                     }
 1322  
                 }
 1323  0
                 if ( m.getName().equals( "getSelectedItem" ) )
 1324  
                 {
 1325  0
                     if ( paramTypes.length == 0 )
 1326  
                     {
 1327  0
                         return m.invoke( aComponent, new Object[] {} );
 1328  
                     }
 1329  
                 }
 1330  
                 // TODO: should also handle variants of setValue()
 1331  
             }
 1332  
         }
 1333  0
         catch ( Exception exc )
 1334  
         { // error occured while introspecting... move along.
 1335  0
           System.out.println( "InfoPanel.getValueFromComponent: " + exc );
 1336  0
         }
 1337  
 
 1338  
         // not found
 1339  0
         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  0
         Component c = getComponentForKey( key );
 1351  0
         if ( c instanceof JTextField )
 1352  
         {
 1353  0
             return (JTextField) c;
 1354  
         }
 1355  0
         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  0
                 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  0
                 Container c = getCompositeComponentForKey( key );
 1381  0
                 if ( c == null ) return null;
 1382  0
                 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  0
         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  0
         if ( ( row > fields.size() ) || ( row < 0 ) )
 1402  
         { // out of bounds
 1403  0
             return null;
 1404  
         }
 1405  
                 
 1406  0
                 Container field = (Container) fields.get( row );
 1407  0
                 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  0
         if ( key == null )
 1420  
         {
 1421  0
             key = "";
 1422  
         }
 1423  
 
 1424  0
         JPanel field = null;
 1425  0
         Iterator enumeration = fields.iterator();
 1426  0
         while ( enumeration.hasNext() )
 1427  
         { // finds first value in list with specified key
 1428  0
             field = (JPanel) enumeration.next();
 1429  0
             if ( key.equals( field.getName() ) )
 1430  
             {
 1431  0
                 return field;
 1432  
             }
 1433  
         }
 1434  
 
 1435  
         // else not found
 1436  0
         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  0
         Component result = getLabelComponentForKey( key );
 1449  0
         if ( result instanceof JLabel ) return (JLabel) result;
 1450  0
         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  0
         if ( key == null )
 1462  
         {
 1463  0
             key = "";
 1464  
         }
 1465  
 
 1466  0
         Component label = null;
 1467  0
         Iterator enumeration = labels.iterator();
 1468  0
         while ( enumeration.hasNext() )
 1469  
         { // finds first value in list with specified key
 1470  0
             label = (Component) enumeration.next();
 1471  0
             if ( key.equals( label.getName() ) )
 1472  
             {
 1473  0
                 return label;
 1474  
             }
 1475  
         }
 1476  
 
 1477  
         // else not found
 1478  0
         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  0
                 setComponentForKey( key, c, 0 );        
 1492  0
         }
 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  0
         if ( c == null )
 1505  
         {
 1506  0
             c = new JTextField( 15 );
 1507  
         }
 1508  0
         if ( key == null )
 1509  
         {
 1510  0
             key = "";
 1511  
         }
 1512  
 
 1513  0
         Container container = this.getCompositeComponentForKey( key );
 1514  0
                 Component field = container.getComponent( index );
 1515  0
         Object value = this.getValueForKey( key, index );
 1516  0
         if ( field != null )
 1517  
         {
 1518  0
                         container.remove( index );
 1519  0
                         container.add( c, index );
 1520  0
                         c.setEnabled( this.isEditable );
 1521  0
                         introspectComponent( c, key );
 1522  0
             setValueForComponent( c, value );
 1523  
         }
 1524  0
     }
 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  0
                 setComponentForIndex( row, 0, c );        
 1535  0
         }
 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  0
                 setComponentForKey( getLabels()[row], c, col );
 1546  0
     }
 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  0
         prefix = aString;
 1555  0
         setLabels( getLabels() ); // force refresh
 1556  0
     }
 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  0
         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  0
         postfix = aString;
 1576  0
         setLabels( getLabels() ); // force refresh
 1577  0
     }
 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  0
         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  0
            actionListener = AWTEventMulticaster.add(actionListener, l);
 1596  0
      }
 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  0
            actionListener = AWTEventMulticaster.remove(actionListener, l);
 1605  0
      }
 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  0
          if (actionListener != null)
 1613  
          {
 1614  0
              actionListener.actionPerformed(e);
 1615  
          }
 1616  0
      }
 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  0
             broadcastEvent(e);
 1631  
 //        }
 1632  0
      }
 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  0
     private class BetterGridBagLayout extends GridBagLayout
 1646  
     {
 1647  
         public Dimension preferredLayoutSize(Container parent) 
 1648  
         {
 1649  0
             preprocess();
 1650  0
             return super.preferredLayoutSize( parent );
 1651  
         }
 1652  
         
 1653  
         public Dimension minimumLayoutSize(Container parent) 
 1654  
         {
 1655  0
             preprocess();
 1656  0
             return super.minimumLayoutSize( parent );
 1657  
         }
 1658  
 
 1659  
         
 1660  
         public void layoutContainer(Container parent) 
 1661  
         {
 1662  0
             preprocess();
 1663  0
             super.layoutContainer( parent );
 1664  0
         }
 1665  
 
 1666  
         protected void preprocess() 
 1667  
         {
 1668  0
             if ( fieldSpacers == null ) return;
 1669  
             Iterator i;
 1670  
             
 1671  
             // find the field with the widest preferred size
 1672  
             Component c;
 1673  0
             int maxWidth = 0;
 1674  0
             i = fields.iterator();
 1675  0
             while ( i.hasNext() )
 1676  
             {
 1677  0
                 c = (Component) i.next();
 1678  0
                 maxWidth = Math.max( maxWidth, 
 1679  0
                     Math.max( c.getPreferredSize().width, c.getMinimumSize().width ) );
 1680  0
             }
 1681  
             
 1682  
             // set each column's spacers to that preferred size
 1683  0
             Dimension min = new Dimension( 0, 0 );
 1684  0
             Dimension pref = new Dimension( maxWidth, 0 );
 1685  0
             i = fieldSpacers.iterator();
 1686  0
             while ( i.hasNext() )
 1687  
             {
 1688  0
                 ((Box.Filler)i.next()).changeShape( min, pref, pref );
 1689  0
             }
 1690  0
         }
 1691  
     }
 1692  
 }
 1693