This should improve memory footprint and initialization performance for JButton and derived classes: - The listeners in AbstractButton are combined into one class, thus only one instance needs to be instantiated per button (no sharing possible unfortunately). The protected ButtonChangeListener class delegates to this combined handler for compatibility. - The UI classes are shared between between all button instances. I thought they were shared before, but obviously weren't. - The BasicButtonListener is now shared between all buttons. This is where we are a little different from the RI, which installs a new BasicButtonListener on each button. However, the implementation of BasicButtonListener is perfectly suitable for sharing and should be compatible to the RI still. - The keyboard actions are now shared between buttons, and updated to use the InputMap/ActionMap model like I already did for other components.

2006-08-22  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/AbstractButton.java
        (ButtonChangeListener.stateChanged): Delegate to combined
        handler.
        (EventHandler): New inner class. Handles all three types
        of events on the model.
        (eventHandler): New field. Stores the combined event
        handler.
        (AbstractButton): Moved listener initialization to
        setModel().
        (createActionListener): Return combined handler.
        (createChangeListener): Return combined handler.
        (createItemListener): Return combined handler.
        (getEventHandler): New helper method for creating the combined
        handler.
        (setModel): Initialize listeners here.
        * javax/swing/plaf/basic/BasicButtonListener.java
        (ButtonAction): New class. Implements the keyboard action
        for buttons.
        (checkOpacity): Implemented.
        (createDefaultActionMap): New helper method.
        (installKeyboardActions): Rewritten to install InputMap
        and ActionMap according to 'new' keyboard input method.
        (mouseClicked): Commented as no-op.
        (mouseDragged): Commented as no-op.
        (mouseMoved): Commented as no-op.
        (propertyChange): Check for contentAreaFilled change and
        update opacity. Pull handling of HTLM in font and text handler.
        (stateChanged): Repaint button.
        (uninstallKeyboardActions): Properly uninstall keyboard actions.
        * javax/swing/plaf/basic/BasicButtonUI.java
        (listener): Removed.
        (sharedListener): New static field. Stores the shared listener.
        (sharedUI): New static field. Stores the shared UI.
        (createButtonListener): Return shared instance here.
        (createUI): Return shared instance here.
        (getButtonListener): New helper method. Looks for the
        BasicButtonListener installed on a button and returns it.
        (installDefaults): Correctly install rollover property here.
        Fetch defaultTextShiftOffset. Initialize opaqueness correctly.
        (installKeyboardActions): Fetch listener with new helper method.
        (installListeners): Don't use removed field. Check for null.
        (installUI): Added comment about order of method invocations.
        (uninstallDefaults): Don't uninstall non-uninstallable properties.
        (uninstallKeyboardActions): Fetch listener with new helper method.
        (uninstallListeners): Fetch listener with new helper method.
        (paintIcon): Paint icon offset when pressed and armed.
        * javax/swing/plaf/metal/MetalButtonListener.java: Removed.
        * javax/swing/plaf/metal/MetalButtonUI.java
        (sharedUI): New field. Stores the shared UI.
        (MetalButtonUI): Don't initialize fields here.
        (createButtonListener): Removed method. Use super impl.
        (createUI): Return shared instance.
        (getDisabledTextColor): Update field here.
        (getFocusColor): Update field here.
        (getSelectColor): Update field here.
        (installDefaults): Don't handle rollover property here.
        (uninstallDefaults): Don't handle rollover property here.
        (paintButtonPressed): Use accessor method to update the
        field value.


/Roman
Index: javax/swing/AbstractButton.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/AbstractButton.java,v
retrieving revision 1.65
diff -u -1 -2 -r1.65 AbstractButton.java
--- javax/swing/AbstractButton.java	11 Jul 2006 15:35:29 -0000	1.65
+++ javax/swing/AbstractButton.java	22 Aug 2006 11:23:15 -0000
@@ -178,27 +178,50 @@
     ButtonChangeListener()
     {
       // Nothing to do here.
     }
 
     /**
      * Notified when the target of the listener changes its state.
      *
      * @param ev the ChangeEvent describing the change
      */
     public void stateChanged(ChangeEvent ev)
     {
-      AbstractButton.this.fireStateChanged();
+      getEventHandler().stateChanged(ev);
+    }
+  }
+
+  /**
+   * The combined event handler for ActionEvent, ChangeEvent and
+   * ItemEvent. This combines ButtonChangeListener, ActionListener
+   */
+  private class EventHandler
+    implements ActionListener, ChangeListener, ItemListener
+  {
+    public void actionPerformed(ActionEvent ev)
+    {
+      fireActionPerformed(ev);
+    }
+
+    public void stateChanged(ChangeEvent ev)
+    {
+      fireStateChanged();
       repaint();
     }
+
+    public void itemStateChanged(ItemEvent ev)
+    {
+      fireItemStateChanged(ev);
+    }
   }
 
   /** The icon displayed by default. */
   Icon default_icon;
 
   /** The icon displayed when the button is pressed. */
   Icon pressed_icon;
 
   /** The icon displayed when the button is disabled. */
   Icon disabledIcon;
 
   /** The icon displayed when the button is selected. */
@@ -255,34 +278,47 @@
   /** The button's current state. */
   protected ButtonModel model;
 
   /** The margin between the button's border and its label. */
   Insets margin;
 
   /**
    * A hint to the look and feel class, suggesting which character in the
    * button's label should be underlined when drawing the label.
    */
   int mnemonicIndex;
 
-  /** Listener the button uses to receive ActionEvents from its model.  */
+  /**
+   * Listener the button uses to receive ActionEvents from its model.
+   */
   protected ActionListener actionListener;
 
-  /** Listener the button uses to receive ItemEvents from its model.  */
+  /**
+   * Listener the button uses to receive ItemEvents from its model.
+   */
   protected ItemListener itemListener;
 
-  /** Listener the button uses to receive ChangeEvents from its model.  */  
+  /**
+   * Listener the button uses to receive ChangeEvents from its model.
+   */  
   protected ChangeListener changeListener;
 
   /**
+   * The event handler for ActionEvent, ItemEvent and ChangeEvent.
+   * This replaces the above three handlers and combines them
+   * into one for efficiency.
+   */
+  private EventHandler eventHandler;
+
+  /**
    * The time in milliseconds in which clicks get coalesced into a single
    * <code>ActionEvent</code>.
    */
   long multiClickThreshhold;
   
   /**
    * Listener the button uses to receive PropertyChangeEvents from its
    * Action.
    */
   PropertyChangeListener actionPropertyChangeListener;
   
   /** ChangeEvent that is fired to button's ChangeEventListeners  */  
@@ -846,28 +882,24 @@
    * <pre>
    * super();
    * init(text, icon);
    * </pre>
    *
    * The [EMAIL PROTECTED] #init(String, Icon)} method is not called automatically by this
    * constructor.
    *
    * @see #init(String, Icon)
    */
   public AbstractButton()
   {
-    actionListener = createActionListener();
-    changeListener = createChangeListener();
-    itemListener = createItemListener();
-
     horizontalAlignment = CENTER;
     horizontalTextPosition = TRAILING;
     verticalAlignment = CENTER;
     verticalTextPosition = CENTER;
     borderPainted = true;
     contentAreaFilled = true;
     focusPainted = true;
     setFocusable(true);
     setAlignmentX(CENTER_ALIGNMENT);
     setAlignmentY(CENTER_ALIGNMENT);
     setDisplayedMnemonicIndex(-1);
     setOpaque(true);
@@ -891,33 +923,39 @@
    * with the new model.
    *
    * @param newModel The new model
    */
   public void setModel(ButtonModel newModel)
   {
     if (newModel == model)
       return;
 
     if (model != null)
       {
         model.removeActionListener(actionListener);
+        actionListener = null;
         model.removeChangeListener(changeListener);
+        changeListener = null;
         model.removeItemListener(itemListener);
+        itemListener = null;
       }
     ButtonModel old = model;
     model = newModel;
     if (model != null)
       {
+        actionListener = createActionListener();
         model.addActionListener(actionListener);
+        changeListener = createChangeListener();
         model.addChangeListener(changeListener);
+        itemListener = createItemListener();
         model.addItemListener(itemListener);
       }
     firePropertyChange(MODEL_CHANGED_PROPERTY, old, model);
     revalidate();
     repaint();
   }
 
  protected void init(String text, Icon icon) 
  {
     // If text is null, we fall back to the empty
     // string (which is set using AbstractButton's
     // constructor).
@@ -1914,31 +1952,25 @@
    * of the incoming model [EMAIL PROTECTED] ActionEvent}.</p>
    *
    * <p>The button calls this method during construction, stores the
    * resulting ActionListener in its <code>actionListener</code> member
    * field, and subscribes it to the button's model. If the button's model
    * is changed, this listener is unsubscribed from the old model and
    * subscribed to the new one.</p>
    *
    * @return A new ActionListener 
    */
   protected  ActionListener createActionListener()
   {
-    return new ActionListener()
-      {
-        public void actionPerformed(ActionEvent e)
-        {
-          AbstractButton.this.fireActionPerformed(e);
-        }
-      };
+    return getEventHandler();
   }
 
   /**
    * <p>A factory method which should return a [EMAIL PROTECTED] PropertyChangeListener}
    * that accepts changes to the specified [EMAIL PROTECTED] Action} and reconfigure
    * the [EMAIL PROTECTED] AbstractButton}, by default using the [EMAIL PROTECTED]
    * #configurePropertiesFromAction} method.</p>
    *
    * <p>The button calls this method whenever a new Action is assigned to
    * the button's "action" property, via [EMAIL PROTECTED] #setAction}, and stores the
    * resulting PropertyChangeListener in its
    * <code>actionPropertyChangeListener</code> member field. The button
@@ -1986,25 +2018,25 @@
    * AbstractButton#fireStateChanged} method.</p>
    *
    * <p>The button calls this method during construction, stores the
    * resulting ChangeListener in its <code>changeListener</code> member
    * field, and subscribes it to the button's model. If the button's model
    * is changed, this listener is unsubscribed from the old model and
    * subscribed to the new one.</p>
    *
    * @return The new ChangeListener
    */
   protected ChangeListener createChangeListener()
   {
-    return new ButtonChangeListener();
+    return getEventHandler();
   }
 
   /**
    * <p>Factory method which creates a [EMAIL PROTECTED] ItemListener}, used to
    * subscribe to ItemEvents from the button's model. Subclasses of
    * AbstractButton may wish to override the listener used to subscribe to
    * such ItemEvents. By default, the listener just propagates the
    * [EMAIL PROTECTED] ItemEvent} to the button's ItemListeners, via the [EMAIL PROTECTED]
    * AbstractButton#fireItemStateChanged} method.</p>
    *
    * <p>The button calls this method during construction, stores the
    * resulting ItemListener in its <code>changeListener</code> member
@@ -2012,31 +2044,25 @@
    * is changed, this listener is unsubscribed from the old model and
    * subscribed to the new one.</p>
    *
    * <p>Note that ItemEvents are only generated from the button's model
    * when the model's <em>selected</em> property changes. If you want to
    * subscribe to other properties of the model, you must subscribe to
    * ChangeEvents.
    *
    * @return The new ItemListener
    */
   protected  ItemListener createItemListener()
   {
-    return new ItemListener()
-      {
-        public void itemStateChanged(ItemEvent e)
-        {
-          AbstractButton.this.fireItemStateChanged(e);
-        }
-      };
+    return getEventHandler();
   }
 
   /**
    * Programmatically perform a "click" on the button: arming, pressing,
    * waiting, un-pressing, and disarming the model.
    */
   public void doClick()
   {
     doClick(100);
   }
 
   /**
@@ -2481,13 +2507,26 @@
       {
         if (! clientContentAreaFilledSet)
           {
             setContentAreaFilled(((Boolean) value).booleanValue());
             clientContentAreaFilledSet = false;
           }
       }
     else
       {
         super.setUIProperty(propertyName, value);
       }
   }
+
+  /**
+   * Returns the combined event handler. The instance is created if
+   * necessary.
+   *
+   * @return the combined event handler
+   */
+  EventHandler getEventHandler()
+  {
+    if (eventHandler == null)
+      eventHandler = new EventHandler();
+    return eventHandler;
+  }
 }
Index: javax/swing/plaf/basic/BasicButtonListener.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicButtonListener.java,v
retrieving revision 1.17
diff -u -1 -2 -r1.17 BasicButtonListener.java
--- javax/swing/plaf/basic/BasicButtonListener.java	4 Aug 2006 11:09:11 -0000	1.17
+++ javax/swing/plaf/basic/BasicButtonListener.java	22 Aug 2006 11:23:15 -0000
@@ -45,90 +45,183 @@
 import java.awt.event.FocusListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.font.FontRenderContext;
 import java.awt.font.TextLayout;
 import java.awt.geom.AffineTransform;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 
 import javax.swing.AbstractAction;
 import javax.swing.AbstractButton;
+import javax.swing.Action;
+import javax.swing.ActionMap;
 import javax.swing.ButtonModel;
+import javax.swing.InputMap;
 import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ActionMapUIResource;
+import javax.swing.plaf.ButtonUI;
 
-public class BasicButtonListener implements MouseListener, MouseMotionListener,
-  FocusListener, ChangeListener, PropertyChangeListener
+public class BasicButtonListener
+  implements MouseListener, MouseMotionListener, FocusListener, ChangeListener,
+             PropertyChangeListener
 {
+  /**
+   * Implements the keyboard action for Swing buttons.
+   */
+  private class ButtonAction
+    extends AbstractAction
+  {
+    /**
+     * The key for pressed action.
+     */
+    static final String PRESSED = "pressed";
+
+    /**
+     * The key for released action.
+     */
+    static final String RELEASED = "released";
+
+    /**
+     * Performs the action.
+     */
+    public void actionPerformed(ActionEvent event)
+    {
+      Object cmd = getValue("__command__");
+      AbstractButton b = (AbstractButton) event.getSource();
+      ButtonModel m = b.getModel();
+      if (PRESSED.equals(cmd))
+        {
+          m.setArmed(true);
+          m.setPressed(true);
+          if (! b.isFocusOwner())
+            b.requestFocus();
+        }
+      else if (RELEASED.equals(cmd))
+        {
+          m.setPressed(false);
+          m.setArmed(false);
+        }
+    }
+
+    /**
+     * Indicates if this action is enabled.
+     *
+     * @param source the source of the action
+     *
+     * @return <code>true</code> when enabled, <code>false</code> otherwise
+     */
+    public boolean isEnabled(Object source)
+    {
+      boolean enabled = true;
+      if (source instanceof AbstractButton)
+        {
+          AbstractButton b = (AbstractButton) source;
+          enabled = b.isEnabled();
+        }
+      return enabled;
+    }
+  }
+
   public BasicButtonListener(AbstractButton b)
   {
     // Do nothing here.
   }
   
   public void propertyChange(PropertyChangeEvent e)
   {
     // Store the TextLayout for this in a client property for speed-up
     // painting of the label.
     String property = e.getPropertyName();
     AbstractButton b = (AbstractButton) e.getSource();
     if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)
          || property.equals("font"))
         && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")
         == null)
       {
         String text = b.getText();
         if (text == null)
           text = "";
         FontRenderContext frc = new FontRenderContext(new AffineTransform(),
                                                       false, false);
         TextLayout layout = new TextLayout(text, b.getFont(), frc);
         b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout);
+
+        // Update HTML renderer.
+        BasicHTML.updateRenderer(b, b.getText());
       }
-    if (property.equals(AbstractButton.TEXT_CHANGED_PROPERTY))
+    else if (property.equals(AbstractButton.CONTENT_AREA_FILLED_CHANGED_PROPERTY))
       {
-        BasicHTML.updateRenderer(b, b.getText());
+        checkOpacity(b);
       }
   }
-  
+
+  /**
+   * Checks the <code>contentAreaFilled</code> property and updates the
+   * opaque property of the button.
+   *
+   * @param b the button to check
+   */
   protected void checkOpacity(AbstractButton b) 
   {    
-    // TODO: What should be done here?
+    b.setOpaque(b.isContentAreaFilled());
   }
   
   public void focusGained(FocusEvent e) 
   {    
     if (e.getSource() instanceof AbstractButton)
       {
         AbstractButton button = (AbstractButton) e.getSource();
         if (button.isFocusPainted())
           button.repaint();   
       }
   }
   
   public void focusLost(FocusEvent e)
   {
     if (e.getSource() instanceof AbstractButton)
       {
         AbstractButton button = (AbstractButton) e.getSource();
         if (button.isFocusPainted())
           button.repaint();   
       }
   }
   
   public void installKeyboardActions(JComponent c)
   {
+    ButtonUI ui = ((AbstractButton) c).getUI();
+    if (ui instanceof BasicButtonUI)
+      {
+        // Install InputMap.
+        BasicButtonUI basicUI = (BasicButtonUI) ui;
+        String prefix = basicUI.getPropertyPrefix(); 
+        InputMap focusInputMap =
+          (InputMap) UIManager.get(prefix + "focusInputMap");
+        SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED,
+                                         focusInputMap);
+
+        ActionMap am = (ActionMap) UIManager.get(prefix + "actionMap");
+        if (am == null)
+          {
+            am = createDefaultActionMap();
+            UIManager.put(prefix + "actionMap", am);
+          }
+        SwingUtilities.replaceUIActionMap(c, am);
+      }
+    
     c.getActionMap().put("pressed", 
                          new AbstractAction() 
                          {
                            public void actionPerformed(ActionEvent e)          
                            {
                              AbstractButton button = (AbstractButton) e.getSource();
                              ButtonModel model = button.getModel();
                              // It is important that these transitions happen in this order.
                              model.setArmed(true);
                              model.setPressed(true);
                            }
                          });
@@ -137,49 +230,64 @@
                          new AbstractAction() 
                          {
                            public void actionPerformed(ActionEvent e)          
                            {
                              AbstractButton button = (AbstractButton) e.getSource();
                              ButtonModel model = button.getModel();
                              // It is important that these transitions happen in this order.
                              model.setPressed(false);
                              model.setArmed(false);
                            }
                        });    
   }
-  
+
+  /**
+   * Creates and returns the default action map for Swing buttons.
+   *
+   * @return the default action map for Swing buttons
+   */
+  private ActionMap createDefaultActionMap()
+  {
+    Action action = new ButtonAction();
+    ActionMapUIResource am = new ActionMapUIResource();
+    am.put(ButtonAction.PRESSED, action);
+    am.put(ButtonAction.RELEASED, action);
+    return am;
+  }
+
   public void uninstallKeyboardActions(JComponent c)
   {
-    c.getActionMap().put("pressed", null);
-    c.getActionMap().put("released", null);
+    SwingUtilities.replaceUIActionMap(c, null);
+    SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
   }
   
   public void stateChanged(ChangeEvent e)
   {
-    // TODO: What should be done here, if anything?
+    // Need to repaint when the button state changes.
+    ((AbstractButton) e.getSource()).repaint();
   }
   
   public void mouseMoved(MouseEvent e)
   {
-    // TODO: What should be done here, if anything?
+    // Nothing to do here.
   }
   
   public void mouseDragged(MouseEvent e)
   {
-    // TODO: What should be done here, if anything?
+    // Nothing to do here.
   }
   
   public void mouseClicked(MouseEvent e)
   {
-    // TODO: What should be done here, if anything?
+    // Nothing to do here.
   }
 
   /**
    * Accept a mouse press event and arm the button.
    *
    * @param e The mouse press event to accept
    */
   public void mousePressed(MouseEvent e)
   {
     if (e.getSource() instanceof AbstractButton)
       {
         AbstractButton button = (AbstractButton) e.getSource();
Index: javax/swing/plaf/basic/BasicButtonUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicButtonUI.java,v
retrieving revision 1.41
diff -u -1 -2 -r1.41 BasicButtonUI.java
--- javax/swing/plaf/basic/BasicButtonUI.java	17 Aug 2006 14:45:46 -0000	1.41
+++ javax/swing/plaf/basic/BasicButtonUI.java	22 Aug 2006 11:23:15 -0000
@@ -35,29 +35,29 @@
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package javax.swing.plaf.basic;
 
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
 import java.awt.Insets;
 import java.awt.Rectangle;
+import java.beans.PropertyChangeListener;
 
 import javax.swing.AbstractButton;
 import javax.swing.ButtonModel;
 import javax.swing.Icon;
-import javax.swing.InputMap;
 import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.LookAndFeel;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
 import javax.swing.plaf.ButtonUI;
 import javax.swing.plaf.ComponentUI;
 import javax.swing.plaf.UIResource;
 import javax.swing.text.View;
 
 /**
  * A UI delegate for the [EMAIL PROTECTED] JButton} component.
@@ -74,48 +74,60 @@
    * Cached rectangle for layouting the label. Used in paint() and
    * BasicGraphicsUtils.getPreferredButtonSize().
    */
   static Rectangle iconR = new Rectangle();
 
   /**
    * Cached rectangle for layouting the label. Used in paint() and
    * BasicGraphicsUtils.getPreferredButtonSize().
    */
   static Rectangle textR = new Rectangle();
 
   /**
+   * The shared button UI.
+   */
+  private static BasicButtonUI sharedUI;
+
+  /**
+   * The shared BasicButtonListener.
+   */
+  private static BasicButtonListener sharedListener;
+
+  /**
    * A constant used to pad out elements in the button's layout and
    * preferred size calculations.
    */
   protected int defaultTextIconGap = 4;
 
   /**
    * A constant added to the defaultTextIconGap to adjust the text
    * within this particular button.
    */
   protected int defaultTextShiftOffset;
 
   private int textShiftOffset;
 
   /**
    * Factory method to create an instance of BasicButtonUI for a given
    * [EMAIL PROTECTED] JComponent}, which should be an [EMAIL PROTECTED] AbstractButton}.
    *
    * @param c The component.
    *
    * @return A new UI capable of drawing the component
    */
   public static ComponentUI createUI(final JComponent c) 
   {
-    return new BasicButtonUI();
+    if (sharedUI == null)
+      sharedUI = new BasicButtonUI();
+    return sharedUI;
   }
 
   /**
    * Returns the default gap between the button's text and icon (in pixels).
    * 
    * @param b  the button (ignored).
    * 
    * @return The gap.
    */
   public int getDefaultTextIconGap(AbstractButton b)
   {
     return defaultTextIconGap;
@@ -164,124 +176,148 @@
   {
     return "Button.";
   }
 
   /**
    * Installs the default settings.
    * 
    * @param b  the button (<code>null</code> not permitted).
    */
   protected void installDefaults(AbstractButton b)
   {
     String prefix = getPropertyPrefix();
+    // Install colors and font.
     LookAndFeel.installColorsAndFont(b, prefix + "background",
                                      prefix + "foreground", prefix + "font");
+    // Install border.
     LookAndFeel.installBorder(b, prefix + "border");
+
+    // Install margin property.
     if (b.getMargin() == null || b.getMargin() instanceof UIResource)
       b.setMargin(UIManager.getInsets(prefix + "margin"));
-    b.setIconTextGap(UIManager.getInt(prefix + "textIconGap"));
-    b.setInputMap(JComponent.WHEN_FOCUSED, 
-                  (InputMap) UIManager.get(prefix + "focusInputMap"));
+
+    // Install rollover property.
+    Object rollover = UIManager.get(prefix + "rollover");
+    if (rollover != null)
+      LookAndFeel.installProperty(b, "rolloverEnabled", rollover);
+
+    // Fetch default textShiftOffset.
+    defaultTextShiftOffset = UIManager.getInt(prefix + "textShiftOffset");
+
+    // Make button opaque if needed.
+    if (b.isContentAreaFilled())
+      LookAndFeel.installProperty(b, "opaque", Boolean.TRUE);
+    else
+      LookAndFeel.installProperty(b, "opaque", Boolean.FALSE);
   }
 
   /**
    * Removes the defaults added by [EMAIL PROTECTED] #installDefaults(AbstractButton)}.
    * 
    * @param b  the button (<code>null</code> not permitted).
    */
   protected void uninstallDefaults(AbstractButton b)
   {
-    if (b.getFont() instanceof UIResource)
-      b.setFont(null);
-    if (b.getForeground() instanceof UIResource)
-      b.setForeground(null);
-    if (b.getBackground() instanceof UIResource)
-      b.setBackground(null);
-    if (b.getBorder() instanceof UIResource)
-      b.setBorder(null);
-    b.setIconTextGap(defaultTextIconGap);
-    if (b.getMargin() instanceof UIResource)
-      b.setMargin(null);
+    // The other properties aren't uninstallable.
+    LookAndFeel.uninstallBorder(b);
   }
 
-  protected BasicButtonListener listener;
-
   /**
    * Creates and returns a new instance of [EMAIL PROTECTED] BasicButtonListener}.  This
    * method provides a hook to make it easy for subclasses to install a 
    * different listener.
    * 
    * @param b  the button.
    * 
    * @return A new listener.
    */
   protected BasicButtonListener createButtonListener(AbstractButton b)
   {
-    return new BasicButtonListener(b);
+    // Note: The RI always returns a new instance here. However,
+    // the BasicButtonListener class is perfectly suitable to be shared
+    // between multiple buttons, so we return a shared instance here
+    // for efficiency.
+    if (sharedListener == null)
+      sharedListener = new BasicButtonListener(b);
+    return sharedListener;
   }
 
   /**
    * Installs listeners for the button.
    * 
    * @param b  the button (<code>null</code> not permitted).
    */
   protected void installListeners(AbstractButton b)
   {
-    listener = createButtonListener(b);
-    b.addChangeListener(listener);
-    b.addPropertyChangeListener(listener);
-    b.addFocusListener(listener);    
-    b.addMouseListener(listener);
-    b.addMouseMotionListener(listener);
+    BasicButtonListener listener = createButtonListener(b);
+    if (listener != null)
+      {
+        b.addChangeListener(listener);
+        b.addPropertyChangeListener(listener);
+        b.addFocusListener(listener);    
+        b.addMouseListener(listener);
+        b.addMouseMotionListener(listener);
+      }
   }
 
   /**
    * Uninstalls listeners for the button.
    * 
    * @param b  the button (<code>null</code> not permitted).
    */
   protected void uninstallListeners(AbstractButton b)
   {
-    b.removeChangeListener(listener);
-    b.removePropertyChangeListener(listener);
-    b.removeFocusListener(listener);    
-    b.removeMouseListener(listener);
-    b.removeMouseMotionListener(listener);
+    BasicButtonListener listener = getButtonListener(b);
+    if (listener != null)
+      {
+        b.removeChangeListener(listener);
+        b.removePropertyChangeListener(listener);
+        b.removeFocusListener(listener);    
+        b.removeMouseListener(listener);
+        b.removeMouseMotionListener(listener);
+      }
   }
 
   protected void installKeyboardActions(AbstractButton b)
   {
-    listener.installKeyboardActions(b);
+    BasicButtonListener listener = getButtonListener(b);
+    if (listener != null)
+      listener.installKeyboardActions(b);
   }
 
   protected void uninstallKeyboardActions(AbstractButton b)
   {
-    listener.uninstallKeyboardActions(b);
+    BasicButtonListener listener = getButtonListener(b);
+    if (listener != null)
+      listener.uninstallKeyboardActions(b);
   }
 
   /**
    * Install the BasicButtonUI as the UI for a particular component.
    * This means registering all the UI's listeners with the component,
    * and setting any properties of the button which are particular to 
    * this look and feel.
    *
    * @param c The component to install the UI into
    */
   public void installUI(final JComponent c) 
   {
     super.installUI(c);
     if (c instanceof AbstractButton)
       {
         AbstractButton b = (AbstractButton) c;
         installDefaults(b);
+        // It is important to install the listeners before installing
+        // the keyboard actions, because the keyboard actions
+        // are actually installed on the listener instance.
         installListeners(b);
         installKeyboardActions(b);
         BasicHTML.updateRenderer(b, b.getText());
       }
   }
 
   /**
    * Uninstalls the UI from the component.
    *
    * @param c the component from which to uninstall the UI
    */
   public void uninstallUI(JComponent c)
@@ -471,25 +507,34 @@
    * properties, this might mean painting one of several different icons.
    *
    * @param g Graphics context to paint with
    * @param c Component to paint the icon of
    * @param iconRect Rectangle in which the icon should be painted
    */
   protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect)
   {
     AbstractButton b = (AbstractButton) c;
     Icon i = currentIcon(b);
 
     if (i != null)
-      i.paintIcon(c, g, iconRect.x, iconRect.y);
+      {
+        ButtonModel m = b.getModel();
+        if (m.isPressed() && m.isArmed())
+          {
+            int offs = getTextShiftOffset();
+            i.paintIcon(c, g, iconRect.x + offs, iconRect.y + offs);
+          }
+        else
+          i.paintIcon(c, g, iconRect.x, iconRect.y);
+      }
   }
 
   /**
    * Paints the background area of an [EMAIL PROTECTED] AbstractButton} in the pressed
    * state.  This means filling the supplied area with a darker than normal 
    * background.
    *
    * @param g The graphics context to paint with
    * @param b The button to paint the state of
    */
   protected void paintButtonPressed(Graphics g, AbstractButton b)
   {
@@ -540,13 +585,40 @@
         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
                                       textRect.y + fm.getAscent());
       }
     else
       {
         String prefix = getPropertyPrefix();
         g.setColor(UIManager.getColor(prefix + "disabledText"));
         // FIXME: Underline mnemonic.
         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
                                       textRect.y + fm.getAscent());
       }
   } 
+
+  /**
+   * A helper method that finds the BasicButtonListener for the specified
+   * button. This is there because this UI class is stateless and
+   * shared for all buttons, and thus can't store the listener
+   * as instance field. (We store our shared instance in sharedListener,
+   * however, subclasses may override createButtonListener() and we would
+   * be lost in this case).
+   *
+   * @param b the button
+   *
+   * @return the UI event listener
+   */
+  private BasicButtonListener getButtonListener(AbstractButton b)
+  {
+    // The listener gets installed as PropertyChangeListener,
+    // so look for it in the list of property change listeners.
+    PropertyChangeListener[] listeners = b.getPropertyChangeListeners();
+    BasicButtonListener l = null;
+    for (int i = 0; listeners != null && l == null && i < listeners.length;
+           i++)
+      {
+        if (listeners[i] instanceof BasicButtonListener)
+          l = (BasicButtonListener) listeners[i];
+      }
+    return l;
+  }
 }
Index: javax/swing/plaf/metal/MetalButtonListener.java
===================================================================
RCS file: javax/swing/plaf/metal/MetalButtonListener.java
diff -N javax/swing/plaf/metal/MetalButtonListener.java
--- javax/swing/plaf/metal/MetalButtonListener.java	10 Mar 2006 22:40:47 -0000	1.4
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,74 +0,0 @@
-/* MetalButtonListener.java
-   Copyright (C) 2005 Free Software Foundation, Inc.
-
-This file is part of GNU Classpath.
-
-GNU Classpath is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-GNU Classpath is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Classpath; see the file COPYING.  If not, write to the
-Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301 USA.
-
-Linking this library statically or dynamically with other modules is
-making a combined work based on this library.  Thus, the terms and
-conditions of the GNU General Public License cover the whole
-combination.
-
-As a special exception, the copyright holders of this library give you
-permission to link this library with independent modules to produce an
-executable, regardless of the license terms of these independent
-modules, and to copy and distribute the resulting executable under
-terms of your choice, provided that you also meet, for each linked
-independent module, the terms and conditions of the license of that
-module.  An independent module is a module which is not derived from
-or based on this library.  If you modify this library, you may extend
-this exception to your version of the library, but you are not
-obligated to do so.  If you do not wish to do so, delete this
-exception statement from your version. */
-
-
-package javax.swing.plaf.metal;
-
-import java.beans.PropertyChangeEvent;
-
-import javax.swing.AbstractButton;
-import javax.swing.plaf.basic.BasicButtonListener;
-
-/**
- * A listener for buttons under the [EMAIL PROTECTED] MetalLookAndFeel}.
- * 
- * @see MetalButtonUI#createButtonListener
- */
-class MetalButtonListener extends BasicButtonListener 
-{
-  
-  /**
-   * Creates a new instance.
-   * 
-   * @param button  the button.
-   */
-  public MetalButtonListener(AbstractButton button)
-  {
-    super(button);
-  }
-  
-  /**
-   * Handles property change events.
-   * 
-   * @param e  the event.
-   */
-  public void propertyChange(PropertyChangeEvent e)
-  {
-    super.propertyChange(e);
-    // TODO: What should be done here?
-  }
-}
Index: javax/swing/plaf/metal/MetalButtonUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/metal/MetalButtonUI.java,v
retrieving revision 1.19
diff -u -1 -2 -r1.19 MetalButtonUI.java
--- javax/swing/plaf/metal/MetalButtonUI.java	10 Jun 2006 07:49:12 -0000	1.19
+++ javax/swing/plaf/metal/MetalButtonUI.java	22 Aug 2006 11:23:15 -0000
@@ -45,146 +45,146 @@
 import java.awt.Graphics;
 import java.awt.Rectangle;
 
 import javax.swing.AbstractButton;
 import javax.swing.ButtonModel;
 import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JToolBar;
 import javax.swing.SwingConstants;
 import javax.swing.UIManager;
 import javax.swing.plaf.ComponentUI;
 import javax.swing.plaf.UIResource;
-import javax.swing.plaf.basic.BasicButtonListener;
 import javax.swing.plaf.basic.BasicButtonUI;
 
 /**
  * A UI delegate for the [EMAIL PROTECTED] JButton} component.
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  */
 public class MetalButtonUI
   extends BasicButtonUI
 {
 
-  /** The color used to draw the focus rectangle around the text and/or icon. */
+  /**
+   * The shared button UI.
+   */
+  private static MetalButtonUI sharedUI;
+
+  /**
+   * The color used to draw the focus rectangle around the text and/or icon.
+   */
   protected Color focusColor;
     
-  /** The background color for the button when it is pressed. */
+  /**
+   * The background color for the button when it is pressed.
+   */
   protected Color selectColor;
 
-  /** The color for disabled button labels. */
+  /**
+   * The color for disabled button labels.
+   */
   protected Color disabledTextColor;
 
   /**
+   * Returns a UI delegate for the specified component.
+   * 
+   * @param c  the component (should be a subclass of [EMAIL PROTECTED] AbstractButton}).
+   * 
+   * @return A new instance of <code>MetalButtonUI</code>.
+   */
+  public static ComponentUI createUI(JComponent c) 
+  {
+    if (sharedUI == null)
+      sharedUI = new MetalButtonUI();
+    return sharedUI;
+  }
+
+  /**
    * Creates a new instance.
    */
   public MetalButtonUI()
   {
     super();
-    focusColor = UIManager.getColor(getPropertyPrefix() + "focus");
-    selectColor = UIManager.getColor(getPropertyPrefix() + "select");
-    disabledTextColor = UIManager.getColor(getPropertyPrefix() + "disabledText");
   }
 
   /**
    * Returns the color for the focus border.
    *
    * @return the color for the focus border
    */
   protected Color getFocusColor()
   {
+    focusColor = UIManager.getColor(getPropertyPrefix() + "focus");
     return focusColor;
   }
 
   /**
    * Returns the color that indicates a selected button.
    *
    * @return the color that indicates a selected button
    */
   protected Color getSelectColor()
   {
+    selectColor = UIManager.getColor(getPropertyPrefix() + "select");
     return selectColor;
   }
 
   /**
    * Returns the color for the text label of disabled buttons.
    *
    * @return the color for the text label of disabled buttons
    */
   protected Color getDisabledTextColor()
   {
+    disabledTextColor = UIManager.getColor(getPropertyPrefix()
+                                           + "disabledText");
     return disabledTextColor;
   }
 
   /**
-   * Returns a UI delegate for the specified component.
-   * 
-   * @param c  the component (should be a subclass of [EMAIL PROTECTED] AbstractButton}).
-   * 
-   * @return A new instance of <code>MetalButtonUI</code>.
-   */
-  public static ComponentUI createUI(JComponent c) 
-  {
-    return new MetalButtonUI();
-  }
-
-  /**
    * Installs the default settings for the specified button.
    * 
    * @param button  the button.
    * 
    * @see #uninstallDefaults(AbstractButton)
    */
   public void installDefaults(AbstractButton button)
   {
+    // This is overridden to be public, for whatever reason.
     super.installDefaults(button);
-    button.setRolloverEnabled(UIManager.getBoolean(
-                                            getPropertyPrefix() + "rollover"));
   }
-    
+
   /**
    * Removes the defaults added by [EMAIL PROTECTED] #installDefaults(AbstractButton)}.
    */
   public void uninstallDefaults(AbstractButton button) 
   {
+    // This is overridden to be public, for whatever reason.
     super.uninstallDefaults(button);
-    button.setRolloverEnabled(false);
   }
 
   /**
-   * Returns a button listener for the specified button.
-   * 
-   * @param button  the button.
-   * 
-   * @return A button listener.
-   */
-  protected BasicButtonListener createButtonListener(AbstractButton button) 
-  {
-    return new MetalButtonListener(button);
-  }    
-
-  /**
    * Paints the background of the button to indicate that it is in the
    * "pressed" state.
    * 
    * @param g  the graphics context.
    * @param b  the button.
    */
   protected void paintButtonPressed(Graphics g, AbstractButton b) 
   { 
     if (b.isContentAreaFilled())
     {
       Rectangle area = b.getVisibleRect();
-      g.setColor(selectColor);
+      g.setColor(getSelectColor());
       g.fillRect(area.x, area.y, area.width, area.height);
     }
   }
     
   /** 
    * Paints the focus rectangle around the button text and/or icon.
    * 
    * @param g  the graphics context.
    * @param b  the button.
    * @param viewRect  the button bounds.
    * @param textRect  the text bounds.
    * @param iconRect  the icon bounds.

Reply via email to