Source for javax.swing.plaf.basic.BasicButtonUI

   1: /* BasicButtonUI.java --
   2:    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import java.awt.Dimension;
  42: import java.awt.Font;
  43: import java.awt.FontMetrics;
  44: import java.awt.Graphics;
  45: import java.awt.Rectangle;
  46: 
  47: import javax.swing.AbstractButton;
  48: import javax.swing.ButtonModel;
  49: import javax.swing.Icon;
  50: import javax.swing.InputMap;
  51: import javax.swing.JButton;
  52: import javax.swing.JComponent;
  53: import javax.swing.LookAndFeel;
  54: import javax.swing.SwingUtilities;
  55: import javax.swing.UIManager;
  56: import javax.swing.plaf.ButtonUI;
  57: import javax.swing.plaf.ComponentUI;
  58: import javax.swing.plaf.UIResource;
  59: 
  60: /**
  61:  * A UI delegate for the {@link JButton} component.
  62:  */
  63: public class BasicButtonUI extends ButtonUI
  64: {
  65:   /**
  66:    * A constant used to pad out elements in the button's layout and
  67:    * preferred size calculations.
  68:    */
  69:   protected int defaultTextIconGap = 4;
  70: 
  71:   /**
  72:    * A constant added to the defaultTextIconGap to adjust the text
  73:    * within this particular button.
  74:    */
  75:   protected int defaultTextShiftOffset;
  76: 
  77:   private int textShiftOffset;
  78: 
  79:   /**
  80:    * Factory method to create an instance of BasicButtonUI for a given
  81:    * {@link JComponent}, which should be an {@link AbstractButton}.
  82:    *
  83:    * @param c The component.
  84:    *
  85:    * @return A new UI capable of drawing the component
  86:    */
  87:   public static ComponentUI createUI(final JComponent c) 
  88:   {
  89:     return new BasicButtonUI();
  90:   }
  91: 
  92:   /**
  93:    * Returns the default gap between the button's text and icon (in pixels).
  94:    * 
  95:    * @param b  the button (ignored).
  96:    * 
  97:    * @return The gap.
  98:    */
  99:   public int getDefaultTextIconGap(AbstractButton b)
 100:   {
 101:     return defaultTextIconGap;
 102:   }
 103: 
 104:   /**
 105:    * Sets the text shift offset to zero.
 106:    * 
 107:    * @see #setTextShiftOffset()
 108:    */
 109:   protected void clearTextShiftOffset()
 110:   {
 111:     textShiftOffset = 0;
 112:   }
 113:   
 114:   /**
 115:    * Returns the text shift offset.
 116:    * 
 117:    * @return The text shift offset.
 118:    * 
 119:    * @see #clearTextShiftOffset()
 120:    * @see #setTextShiftOffset()
 121:    */
 122:   protected int getTextShiftOffset()
 123:   {
 124:     return textShiftOffset;
 125:   }
 126: 
 127:   /**
 128:    * Sets the text shift offset to the value in {@link #defaultTextShiftOffset}.
 129:    * 
 130:    * @see #clearTextShiftOffset()
 131:    */
 132:   protected void setTextShiftOffset()
 133:   {
 134:     textShiftOffset = defaultTextShiftOffset;
 135:   }
 136: 
 137:   /**
 138:    * Returns the prefix for the UI defaults property for this UI class.
 139:    * This is 'Button' for this class.
 140:    *
 141:    * @return the prefix for the UI defaults property
 142:    */
 143:   protected String getPropertyPrefix()
 144:   {
 145:     return "Button.";
 146:   }
 147: 
 148:   /**
 149:    * Installs the default settings.
 150:    * 
 151:    * @param b  the button (<code>null</code> not permitted).
 152:    */
 153:   protected void installDefaults(AbstractButton b)
 154:   {
 155:     String prefix = getPropertyPrefix();
 156:     LookAndFeel.installColorsAndFont(b, prefix + "background",
 157:                                      prefix + "foreground", prefix + "font");
 158:     LookAndFeel.installBorder(b, prefix + "border");
 159:     if (b.getMargin() == null || b.getMargin() instanceof UIResource)
 160:       b.setMargin(UIManager.getInsets(prefix + "margin"));
 161:     b.setIconTextGap(UIManager.getInt(prefix + "textIconGap"));
 162:     b.setInputMap(JComponent.WHEN_FOCUSED, 
 163:                   (InputMap) UIManager.get(prefix + "focusInputMap"));
 164:   }
 165: 
 166:   /**
 167:    * Removes the defaults added by {@link #installDefaults(AbstractButton)}.
 168:    * 
 169:    * @param b  the button (<code>null</code> not permitted).
 170:    */
 171:   protected void uninstallDefaults(AbstractButton b)
 172:   {
 173:     if (b.getFont() instanceof UIResource)
 174:       b.setFont(null);
 175:     if (b.getForeground() instanceof UIResource)
 176:       b.setForeground(null);
 177:     if (b.getBackground() instanceof UIResource)
 178:       b.setBackground(null);
 179:     if (b.getBorder() instanceof UIResource)
 180:       b.setBorder(null);
 181:     b.setIconTextGap(defaultTextIconGap);
 182:     if (b.getMargin() instanceof UIResource)
 183:       b.setMargin(null);
 184:   }
 185: 
 186:   protected BasicButtonListener listener;
 187: 
 188:   /**
 189:    * Creates and returns a new instance of {@link BasicButtonListener}.  This
 190:    * method provides a hook to make it easy for subclasses to install a 
 191:    * different listener.
 192:    * 
 193:    * @param b  the button.
 194:    * 
 195:    * @return A new listener.
 196:    */
 197:   protected BasicButtonListener createButtonListener(AbstractButton b)
 198:   {
 199:     return new BasicButtonListener(b);
 200:   }
 201: 
 202:   /**
 203:    * Installs listeners for the button.
 204:    * 
 205:    * @param b  the button (<code>null</code> not permitted).
 206:    */
 207:   protected void installListeners(AbstractButton b)
 208:   {
 209:     listener = createButtonListener(b);
 210:     b.addChangeListener(listener);
 211:     b.addPropertyChangeListener(listener);
 212:     b.addFocusListener(listener);    
 213:     b.addMouseListener(listener);
 214:     b.addMouseMotionListener(listener);
 215:   }
 216: 
 217:   /**
 218:    * Uninstalls listeners for the button.
 219:    * 
 220:    * @param b  the button (<code>null</code> not permitted).
 221:    */
 222:   protected void uninstallListeners(AbstractButton b)
 223:   {
 224:     b.removeChangeListener(listener);
 225:     b.removePropertyChangeListener(listener);
 226:     b.removeFocusListener(listener);    
 227:     b.removeMouseListener(listener);
 228:     b.removeMouseMotionListener(listener);
 229:   }
 230: 
 231:   protected void installKeyboardActions(AbstractButton b)
 232:   {
 233:     listener.installKeyboardActions(b);
 234:   }
 235: 
 236:   protected void uninstallKeyboardActions(AbstractButton b)
 237:   {
 238:     listener.uninstallKeyboardActions(b);
 239:   }
 240: 
 241:   /**
 242:    * Install the BasicButtonUI as the UI for a particular component.
 243:    * This means registering all the UI's listeners with the component,
 244:    * and setting any properties of the button which are particular to 
 245:    * this look and feel.
 246:    *
 247:    * @param c The component to install the UI into
 248:    */
 249:   public void installUI(final JComponent c) 
 250:   {
 251:     super.installUI(c);
 252:     if (c instanceof AbstractButton)
 253:       {
 254:         AbstractButton b = (AbstractButton) c;
 255:         installDefaults(b);
 256:         installListeners(b);
 257:         installKeyboardActions(b);
 258:       }
 259:   }
 260: 
 261:   /**
 262:    * Calculate the preferred size of this component, by delegating to
 263:    * {@link BasicGraphicsUtils#getPreferredButtonSize}.
 264:    *
 265:    * @param c The component to measure
 266:    *
 267:    * @return The preferred dimensions of the component
 268:    */
 269:   public Dimension getPreferredSize(JComponent c) 
 270:   {
 271:     AbstractButton b = (AbstractButton) c;
 272:     Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, 
 273:         defaultTextIconGap + defaultTextShiftOffset);
 274:     return d;
 275:   }
 276: 
 277:   static Icon currentIcon(AbstractButton b)
 278:   {
 279:     Icon i = b.getIcon();
 280:     ButtonModel model = b.getModel();
 281: 
 282:     if (model.isPressed() && b.getPressedIcon() != null && b.isEnabled())
 283:       i = b.getPressedIcon();
 284: 
 285:     else if (model.isRollover())
 286:       {
 287:         if (b.isSelected() && b.getRolloverSelectedIcon() != null)
 288:           i = b.getRolloverSelectedIcon();
 289:         else if (b.getRolloverIcon() != null)
 290:           i = b.getRolloverIcon();
 291:       }    
 292: 
 293:     else if (b.isSelected() && b.isEnabled())
 294:       {
 295:         if (b.isEnabled() && b.getSelectedIcon() != null)
 296:           i = b.getSelectedIcon();
 297:         else if (b.getDisabledSelectedIcon() != null)
 298:           i = b.getDisabledSelectedIcon();
 299:       }
 300: 
 301:     else if (! b.isEnabled() && b.getDisabledIcon() != null)
 302:       i = b.getDisabledIcon();
 303: 
 304:     return i;
 305:   }
 306: 
 307:   /**
 308:    * Paint the component, which is an {@link AbstractButton}, according to 
 309:    * its current state.
 310:    *
 311:    * @param g The graphics context to paint with
 312:    * @param c The component to paint the state of
 313:    */
 314:   public void paint(Graphics g, JComponent c)
 315:   {
 316:     AbstractButton b = (AbstractButton) c;
 317: 
 318:     Rectangle tr = new Rectangle();
 319:     Rectangle ir = new Rectangle();
 320:     Rectangle vr = new Rectangle();
 321: 
 322:     Font f = c.getFont();
 323: 
 324:     g.setFont(f);
 325: 
 326:     if (b.isBorderPainted())
 327:       SwingUtilities.calculateInnerArea(b, vr);
 328:     else
 329:       vr = SwingUtilities.getLocalBounds(b);
 330:     String text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), 
 331:                                                      b.getText(),
 332:                                                      currentIcon(b),
 333:                                                      b.getVerticalAlignment(), 
 334:                                                      b.getHorizontalAlignment(),
 335:                                                      b.getVerticalTextPosition(), 
 336:                                                      b.getHorizontalTextPosition(),
 337:                                                      vr, ir, tr, 
 338:                                                      b.getIconTextGap() 
 339:                                                      + defaultTextShiftOffset);
 340:     
 341:     if ((b.getModel().isArmed() && b.getModel().isPressed()) 
 342:         || b.isSelected())
 343:       paintButtonPressed(g, b);
 344:     
 345:     paintIcon(g, c, ir);
 346:     if (text != null)
 347:       paintText(g, b, tr, text);
 348:     if (b.isFocusOwner() && b.isFocusPainted())
 349:       paintFocus(g, b, vr, tr, ir);
 350:   }
 351: 
 352:   /**
 353:    * Paint any focus decoration this {@link JComponent} might have.  The
 354:    * component, which in this case will be an {@link AbstractButton},
 355:    * should only have focus decoration painted if it has the focus, and its
 356:    * "focusPainted" property is <code>true</code>.
 357:    *
 358:    * @param g Graphics context to paint with
 359:    * @param b Button to paint the focus of
 360:    * @param vr Visible rectangle, the area in which to paint
 361:    * @param tr Text rectangle, contained in visible rectangle
 362:    * @param ir Icon rectangle, contained in visible rectangle
 363:    *
 364:    * @see AbstractButton#isFocusPainted()
 365:    * @see JComponent#hasFocus()
 366:    */
 367:   protected void paintFocus(Graphics g, AbstractButton b, Rectangle vr,
 368:                             Rectangle tr, Rectangle ir)
 369:   {
 370:     // In the BasicLookAndFeel no focus border is drawn. This can be
 371:     // overridden in subclasses to implement such behaviour.
 372:   }
 373: 
 374:   /**
 375:    * Paint the icon for this component. Depending on the state of the
 376:    * component and the availability of the button's various icon
 377:    * properties, this might mean painting one of several different icons.
 378:    *
 379:    * @param g Graphics context to paint with
 380:    * @param c Component to paint the icon of
 381:    * @param iconRect Rectangle in which the icon should be painted
 382:    */
 383:   protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect)
 384:   {
 385:     AbstractButton b = (AbstractButton) c;
 386:     Icon i = currentIcon(b);
 387: 
 388:     if (i != null)
 389:       i.paintIcon(c, g, iconRect.x, iconRect.y);
 390:   }
 391: 
 392:   /**
 393:    * Paints the background area of an {@link AbstractButton} in the pressed
 394:    * state.  This means filling the supplied area with a darker than normal 
 395:    * background.
 396:    *
 397:    * @param g The graphics context to paint with
 398:    * @param b The button to paint the state of
 399:    */
 400:   protected void paintButtonPressed(Graphics g, AbstractButton b)
 401:   {
 402:     if (b.isContentAreaFilled() && b.isOpaque())
 403:       {
 404:         Rectangle area = new Rectangle();
 405:         SwingUtilities.calculateInnerArea(b, area);
 406:         g.setColor(UIManager.getColor(getPropertyPrefix() + "shadow"));
 407:         g.fillRect(area.x, area.y, area.width, area.height);
 408:       }
 409:   }
 410:     
 411:   /**
 412:    * Paints the "text" property of an {@link AbstractButton}.
 413:    *
 414:    * @param g The graphics context to paint with
 415:    * @param c The component to paint the state of
 416:    * @param textRect The area in which to paint the text
 417:    * @param text The text to paint
 418:    */
 419:   protected void paintText(Graphics g, JComponent c, Rectangle textRect,
 420:                            String text) 
 421:   {    
 422:     paintText(g, (AbstractButton) c, textRect, text);
 423:   }
 424: 
 425:   /**
 426:    * Paints the "text" property of an {@link AbstractButton}.
 427:    *
 428:    * @param g The graphics context to paint with
 429:    * @param b The button to paint the state of
 430:    * @param textRect The area in which to paint the text
 431:    * @param text The text to paint
 432:    *
 433:    * @since 1.4
 434:    */
 435:   protected void paintText(Graphics g, AbstractButton b, Rectangle textRect,
 436:                String text)
 437:   {
 438:     Font f = b.getFont();
 439:     g.setFont(f);
 440:     FontMetrics fm = g.getFontMetrics(f);
 441: 
 442:     if (b.isEnabled())
 443:       {
 444:         g.setColor(b.getForeground());
 445:         // FIXME: Underline mnemonic.
 446:         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
 447:                                       textRect.y + fm.getAscent());
 448:       }
 449:     else
 450:       {
 451:         String prefix = getPropertyPrefix();
 452:         g.setColor(UIManager.getColor(prefix + "disabledText"));
 453:         // FIXME: Underline mnemonic.
 454:         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
 455:                                       textRect.y + fm.getAscent());
 456:       }
 457:   } 
 458: }