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