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.Insets;
  46: import java.awt.Rectangle;
  47: import java.beans.PropertyChangeEvent;
  48: import java.beans.PropertyChangeListener;
  49: 
  50: import javax.swing.AbstractButton;
  51: import javax.swing.ButtonModel;
  52: import javax.swing.Icon;
  53: import javax.swing.JButton;
  54: import javax.swing.JComponent;
  55: import javax.swing.LookAndFeel;
  56: import javax.swing.SwingUtilities;
  57: import javax.swing.UIManager;
  58: import javax.swing.plaf.ButtonUI;
  59: import javax.swing.plaf.ComponentUI;
  60: import javax.swing.plaf.UIResource;
  61: import javax.swing.text.View;
  62: 
  63: /**
  64:  * A UI delegate for the {@link JButton} component.
  65:  */
  66: public class BasicButtonUI extends ButtonUI
  67: {
  68:   /**
  69:    * Cached rectangle for layouting the label. Used in paint() and
  70:    * BasicGraphicsUtils.getPreferredButtonSize().
  71:    */
  72:   static Rectangle viewR = new Rectangle();
  73: 
  74:   /**
  75:    * Cached rectangle for layouting the label. Used in paint() and
  76:    * BasicGraphicsUtils.getPreferredButtonSize().
  77:    */
  78:   static Rectangle iconR = new Rectangle();
  79: 
  80:   /**
  81:    * Cached rectangle for layouting the label. Used in paint() and
  82:    * BasicGraphicsUtils.getPreferredButtonSize().
  83:    */
  84:   static Rectangle textR = new Rectangle();
  85: 
  86:   /**
  87:    * Cached Insets instance, used in paint().
  88:    */
  89:   static Insets cachedInsets;
  90: 
  91:   /**
  92:    * The shared button UI.
  93:    */
  94:   private static BasicButtonUI sharedUI;
  95: 
  96:   /**
  97:    * The shared BasicButtonListener.
  98:    */
  99:   private static BasicButtonListener sharedListener;
 100: 
 101:   /**
 102:    * A constant used to pad out elements in the button's layout and
 103:    * preferred size calculations.
 104:    */
 105:   protected int defaultTextIconGap = 4;
 106: 
 107:   /**
 108:    * A constant added to the defaultTextIconGap to adjust the text
 109:    * within this particular button.
 110:    */
 111:   protected int defaultTextShiftOffset;
 112: 
 113:   private int textShiftOffset;
 114: 
 115:   /**
 116:    * Factory method to create an instance of BasicButtonUI for a given
 117:    * {@link JComponent}, which should be an {@link AbstractButton}.
 118:    *
 119:    * @param c The component.
 120:    *
 121:    * @return A new UI capable of drawing the component
 122:    */
 123:   public static ComponentUI createUI(final JComponent c) 
 124:   {
 125:     if (sharedUI == null)
 126:       sharedUI = new BasicButtonUI();
 127:     return sharedUI;
 128:   }
 129: 
 130:   /**
 131:    * Returns the default gap between the button's text and icon (in pixels).
 132:    * 
 133:    * @param b  the button (ignored).
 134:    * 
 135:    * @return The gap.
 136:    */
 137:   public int getDefaultTextIconGap(AbstractButton b)
 138:   {
 139:     return defaultTextIconGap;
 140:   }
 141: 
 142:   /**
 143:    * Sets the text shift offset to zero.
 144:    * 
 145:    * @see #setTextShiftOffset()
 146:    */
 147:   protected void clearTextShiftOffset()
 148:   {
 149:     textShiftOffset = 0;
 150:   }
 151:   
 152:   /**
 153:    * Returns the text shift offset.
 154:    * 
 155:    * @return The text shift offset.
 156:    * 
 157:    * @see #clearTextShiftOffset()
 158:    * @see #setTextShiftOffset()
 159:    */
 160:   protected int getTextShiftOffset()
 161:   {
 162:     return textShiftOffset;
 163:   }
 164: 
 165:   /**
 166:    * Sets the text shift offset to the value in {@link #defaultTextShiftOffset}.
 167:    * 
 168:    * @see #clearTextShiftOffset()
 169:    */
 170:   protected void setTextShiftOffset()
 171:   {
 172:     textShiftOffset = defaultTextShiftOffset;
 173:   }
 174: 
 175:   /**
 176:    * Returns the prefix for the UI defaults property for this UI class.
 177:    * This is 'Button' for this class.
 178:    *
 179:    * @return the prefix for the UI defaults property
 180:    */
 181:   protected String getPropertyPrefix()
 182:   {
 183:     return "Button.";
 184:   }
 185: 
 186:   /**
 187:    * Installs the default settings.
 188:    * 
 189:    * @param b  the button (<code>null</code> not permitted).
 190:    */
 191:   protected void installDefaults(AbstractButton b)
 192:   {
 193:     String prefix = getPropertyPrefix();
 194:     // Install colors and font.
 195:     LookAndFeel.installColorsAndFont(b, prefix + "background",
 196:                                      prefix + "foreground", prefix + "font");
 197:     // Install border.
 198:     LookAndFeel.installBorder(b, prefix + "border");
 199: 
 200:     // Install margin property.
 201:     if (b.getMargin() == null || b.getMargin() instanceof UIResource)
 202:       b.setMargin(UIManager.getInsets(prefix + "margin"));
 203: 
 204:     // Install rollover property.
 205:     Object rollover = UIManager.get(prefix + "rollover");
 206:     if (rollover != null)
 207:       LookAndFeel.installProperty(b, "rolloverEnabled", rollover);
 208: 
 209:     // Fetch default textShiftOffset.
 210:     defaultTextShiftOffset = UIManager.getInt(prefix + "textShiftOffset");
 211: 
 212:     // Make button opaque if needed.
 213:     if (b.isContentAreaFilled())
 214:       LookAndFeel.installProperty(b, "opaque", Boolean.TRUE);
 215:     else
 216:       LookAndFeel.installProperty(b, "opaque", Boolean.FALSE);
 217:   }
 218: 
 219:   /**
 220:    * Removes the defaults added by {@link #installDefaults(AbstractButton)}.
 221:    * 
 222:    * @param b  the button (<code>null</code> not permitted).
 223:    */
 224:   protected void uninstallDefaults(AbstractButton b)
 225:   {
 226:     // The other properties aren't uninstallable.
 227:     LookAndFeel.uninstallBorder(b);
 228:   }
 229: 
 230:   /**
 231:    * Creates and returns a new instance of {@link BasicButtonListener}.  This
 232:    * method provides a hook to make it easy for subclasses to install a 
 233:    * different listener.
 234:    * 
 235:    * @param b  the button.
 236:    * 
 237:    * @return A new listener.
 238:    */
 239:   protected BasicButtonListener createButtonListener(AbstractButton b)
 240:   {
 241:     // Note: The RI always returns a new instance here. However,
 242:     // the BasicButtonListener class is perfectly suitable to be shared
 243:     // between multiple buttons, so we return a shared instance here
 244:     // for efficiency.
 245:     if (sharedListener == null)
 246:       sharedListener = new BasicButtonListener(b);
 247:     return sharedListener;
 248:   }
 249: 
 250:   /**
 251:    * Installs listeners for the button.
 252:    * 
 253:    * @param b  the button (<code>null</code> not permitted).
 254:    */
 255:   protected void installListeners(AbstractButton b)
 256:   {
 257:     BasicButtonListener listener = createButtonListener(b);
 258:     if (listener != null)
 259:       {
 260:         b.addChangeListener(listener);
 261:         b.addPropertyChangeListener(listener);
 262:         b.addFocusListener(listener);    
 263:         b.addMouseListener(listener);
 264:         b.addMouseMotionListener(listener);
 265:       }
 266:     // Fire synthetic property change event to let the listener update
 267:     // the TextLayout cache.
 268:     listener.propertyChange(new PropertyChangeEvent(b, "font", null,
 269:                                                     b.getFont()));
 270:   }
 271: 
 272:   /**
 273:    * Uninstalls listeners for the button.
 274:    * 
 275:    * @param b  the button (<code>null</code> not permitted).
 276:    */
 277:   protected void uninstallListeners(AbstractButton b)
 278:   {
 279:     BasicButtonListener listener = getButtonListener(b);
 280:     if (listener != null)
 281:       {
 282:         b.removeChangeListener(listener);
 283:         b.removePropertyChangeListener(listener);
 284:         b.removeFocusListener(listener);    
 285:         b.removeMouseListener(listener);
 286:         b.removeMouseMotionListener(listener);
 287:       }
 288:   }
 289: 
 290:   protected void installKeyboardActions(AbstractButton b)
 291:   {
 292:     BasicButtonListener listener = getButtonListener(b);
 293:     if (listener != null)
 294:       listener.installKeyboardActions(b);
 295:   }
 296: 
 297:   protected void uninstallKeyboardActions(AbstractButton b)
 298:   {
 299:     BasicButtonListener listener = getButtonListener(b);
 300:     if (listener != null)
 301:       listener.uninstallKeyboardActions(b);
 302:   }
 303: 
 304:   /**
 305:    * Install the BasicButtonUI as the UI for a particular component.
 306:    * This means registering all the UI's listeners with the component,
 307:    * and setting any properties of the button which are particular to 
 308:    * this look and feel.
 309:    *
 310:    * @param c The component to install the UI into
 311:    */
 312:   public void installUI(final JComponent c) 
 313:   {
 314:     super.installUI(c);
 315:     if (c instanceof AbstractButton)
 316:       {
 317:         AbstractButton b = (AbstractButton) c;
 318:         installDefaults(b);
 319:         // It is important to install the listeners before installing
 320:         // the keyboard actions, because the keyboard actions
 321:         // are actually installed on the listener instance.
 322:         installListeners(b);
 323:         installKeyboardActions(b);
 324:         BasicHTML.updateRenderer(b, b.getText());
 325:       }
 326:   }
 327: 
 328:   /**
 329:    * Uninstalls the UI from the component.
 330:    *
 331:    * @param c the component from which to uninstall the UI
 332:    */
 333:   public void uninstallUI(JComponent c)
 334:   {
 335:     if (c instanceof AbstractButton)
 336:       {
 337:         AbstractButton b = (AbstractButton) c;
 338:         uninstallKeyboardActions(b);
 339:         uninstallListeners(b);
 340:         uninstallDefaults(b);
 341:         BasicHTML.updateRenderer(b, "");
 342:         b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null);
 343:       }
 344:   }
 345: 
 346:   /**
 347:    * Calculates the minimum size for the specified component.
 348:    *
 349:    * @param c the component for which to compute the minimum size
 350:    *
 351:    * @return the minimum size for the specified component
 352:    */
 353:   public Dimension getMinimumSize(JComponent c)
 354:   {
 355:     Dimension size = getPreferredSize(c);
 356:     // When the HTML view has a minimum width different from the preferred
 357:     // width, then substract this here accordingly. The height is not
 358:     // affected by that.
 359:     View html = (View) c.getClientProperty(BasicHTML.propertyKey);
 360:     if (html != null)
 361:       {
 362:         size.width -= html.getPreferredSpan(View.X_AXIS)
 363:                       - html.getPreferredSpan(View.X_AXIS);
 364:       }
 365:     return size;
 366:   }
 367: 
 368:   /**
 369:    * Calculates the maximum size for the specified component.
 370:    *
 371:    * @param c the component for which to compute the maximum size
 372:    *
 373:    * @return the maximum size for the specified component
 374:    */
 375:   public Dimension getMaximumSize(JComponent c)
 376:   {
 377:     Dimension size = getPreferredSize(c);
 378:     // When the HTML view has a maximum width different from the preferred
 379:     // width, then add this here accordingly. The height is not
 380:     // affected by that.
 381:     View html = (View) c.getClientProperty(BasicHTML.propertyKey);
 382:     if (html != null)
 383:       {
 384:         size.width += html.getMaximumSpan(View.X_AXIS)
 385:                       - html.getPreferredSpan(View.X_AXIS);
 386:       }
 387:     return size;
 388:   }
 389: 
 390:   /**
 391:    * Calculate the preferred size of this component, by delegating to
 392:    * {@link BasicGraphicsUtils#getPreferredButtonSize}.
 393:    *
 394:    * @param c The component to measure
 395:    *
 396:    * @return The preferred dimensions of the component
 397:    */
 398:   public Dimension getPreferredSize(JComponent c) 
 399:   {
 400:     AbstractButton b = (AbstractButton) c;
 401:     Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b,
 402:                                                            b.getIconTextGap());
 403:     return d;
 404:   }
 405: 
 406:   static Icon currentIcon(AbstractButton b)
 407:   {
 408:     Icon i = b.getIcon();
 409:     ButtonModel model = b.getModel();
 410: 
 411:     if (model.isPressed() && b.getPressedIcon() != null && b.isEnabled())
 412:       i = b.getPressedIcon();
 413: 
 414:     else if (model.isRollover())
 415:       {
 416:         if (b.isSelected() && b.getRolloverSelectedIcon() != null)
 417:           i = b.getRolloverSelectedIcon();
 418:         else if (b.getRolloverIcon() != null)
 419:           i = b.getRolloverIcon();
 420:       }    
 421: 
 422:     else if (b.isSelected() && b.isEnabled())
 423:       {
 424:         if (b.isEnabled() && b.getSelectedIcon() != null)
 425:           i = b.getSelectedIcon();
 426:         else if (b.getDisabledSelectedIcon() != null)
 427:           i = b.getDisabledSelectedIcon();
 428:       }
 429: 
 430:     else if (! b.isEnabled() && b.getDisabledIcon() != null)
 431:       i = b.getDisabledIcon();
 432: 
 433:     return i;
 434:   }
 435: 
 436:   /**
 437:    * Paint the component, which is an {@link AbstractButton}, according to 
 438:    * its current state.
 439:    *
 440:    * @param g The graphics context to paint with
 441:    * @param c The component to paint the state of
 442:    */
 443:   public void paint(Graphics g, JComponent c)
 444:   {
 445:     AbstractButton b = (AbstractButton) c;
 446: 
 447:     Insets i = c.getInsets(cachedInsets);
 448:     viewR.x = i.left;
 449:     viewR.y = i.top;
 450:     viewR.width = c.getWidth() - i.left - i.right;
 451:     viewR.height = c.getHeight() - i.top - i.bottom;
 452:     textR.x = 0;
 453:     textR.y = 0;
 454:     textR.width = 0;
 455:     textR.height = 0;
 456:     iconR.x = 0;
 457:     iconR.y = 0;
 458:     iconR.width = 0;
 459:     iconR.height = 0;
 460: 
 461:     Font f = c.getFont();
 462:     g.setFont(f);
 463:     Icon icon = b.getIcon();
 464:     String text = b.getText();
 465:     text = SwingUtilities.layoutCompoundLabel(c, g.getFontMetrics(f), 
 466:                                               text, icon,
 467:                                               b.getVerticalAlignment(), 
 468:                                               b.getHorizontalAlignment(),
 469:                                               b.getVerticalTextPosition(), 
 470:                                               b.getHorizontalTextPosition(),
 471:                                               viewR, iconR, textR, 
 472:                                               text == null ? 0
 473:                                                          : b.getIconTextGap());
 474: 
 475:     ButtonModel model = b.getModel();
 476:     if (model.isArmed() && model.isPressed())
 477:       paintButtonPressed(g, b);
 478: 
 479:     if (icon != null)
 480:       paintIcon(g, c, iconR);
 481:     if (text != null)
 482:       {
 483:         View html = (View) b.getClientProperty(BasicHTML.propertyKey);
 484:         if (html != null)
 485:           html.paint(g, textR);
 486:         else
 487:           paintText(g, b, textR, text);
 488:       }
 489:     if (b.isFocusOwner() && b.isFocusPainted())
 490:       paintFocus(g, b, viewR, textR, iconR);
 491:   }
 492: 
 493:   /**
 494:    * Paint any focus decoration this {@link JComponent} might have.  The
 495:    * component, which in this case will be an {@link AbstractButton},
 496:    * should only have focus decoration painted if it has the focus, and its
 497:    * "focusPainted" property is <code>true</code>.
 498:    *
 499:    * @param g Graphics context to paint with
 500:    * @param b Button to paint the focus of
 501:    * @param vr Visible rectangle, the area in which to paint
 502:    * @param tr Text rectangle, contained in visible rectangle
 503:    * @param ir Icon rectangle, contained in visible rectangle
 504:    *
 505:    * @see AbstractButton#isFocusPainted()
 506:    * @see JComponent#hasFocus()
 507:    */
 508:   protected void paintFocus(Graphics g, AbstractButton b, Rectangle vr,
 509:                             Rectangle tr, Rectangle ir)
 510:   {
 511:     // In the BasicLookAndFeel no focus border is drawn. This can be
 512:     // overridden in subclasses to implement such behaviour.
 513:   }
 514: 
 515:   /**
 516:    * Paint the icon for this component. Depending on the state of the
 517:    * component and the availability of the button's various icon
 518:    * properties, this might mean painting one of several different icons.
 519:    *
 520:    * @param g Graphics context to paint with
 521:    * @param c Component to paint the icon of
 522:    * @param iconRect Rectangle in which the icon should be painted
 523:    */
 524:   protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect)
 525:   {
 526:     AbstractButton b = (AbstractButton) c;
 527:     Icon i = currentIcon(b);
 528: 
 529:     if (i != null)
 530:       {
 531:         ButtonModel m = b.getModel();
 532:         if (m.isPressed() && m.isArmed())
 533:           {
 534:             int offs = getTextShiftOffset();
 535:             i.paintIcon(c, g, iconRect.x + offs, iconRect.y + offs);
 536:           }
 537:         else
 538:           i.paintIcon(c, g, iconRect.x, iconRect.y);
 539:       }
 540:   }
 541: 
 542:   /**
 543:    * Paints the background area of an {@link AbstractButton} in the pressed
 544:    * state.  This means filling the supplied area with a darker than normal 
 545:    * background.
 546:    *
 547:    * @param g The graphics context to paint with
 548:    * @param b The button to paint the state of
 549:    */
 550:   protected void paintButtonPressed(Graphics g, AbstractButton b)
 551:   {
 552:     if (b.isContentAreaFilled() && b.isOpaque())
 553:       {
 554:         Rectangle area = new Rectangle();
 555:         SwingUtilities.calculateInnerArea(b, area);
 556:         g.setColor(UIManager.getColor(getPropertyPrefix() + "shadow"));
 557:         g.fillRect(area.x, area.y, area.width, area.height);
 558:       }
 559:   }
 560:     
 561:   /**
 562:    * Paints the "text" property of an {@link AbstractButton}.
 563:    *
 564:    * @param g The graphics context to paint with
 565:    * @param c The component to paint the state of
 566:    * @param textRect The area in which to paint the text
 567:    * @param text The text to paint
 568:    */
 569:   protected void paintText(Graphics g, JComponent c, Rectangle textRect,
 570:                            String text) 
 571:   {    
 572:     AbstractButton b = (AbstractButton) c;
 573:     Font f = b.getFont();
 574:     g.setFont(f);
 575:     FontMetrics fm = g.getFontMetrics(f);
 576: 
 577:     if (b.isEnabled())
 578:       {
 579:         g.setColor(b.getForeground());
 580:         // FIXME: Underline mnemonic.
 581:         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
 582:                                       textRect.y + fm.getAscent());
 583:       }
 584:     else
 585:       {
 586:         String prefix = getPropertyPrefix();
 587:         g.setColor(UIManager.getColor(prefix + "disabledText"));
 588:         // FIXME: Underline mnemonic.
 589:         BasicGraphicsUtils.drawString(b, g, text, -1, textRect.x,
 590:                                       textRect.y + fm.getAscent());
 591:       }
 592:   }
 593: 
 594:   /**
 595:    * Paints the "text" property of an {@link AbstractButton}.
 596:    *
 597:    * @param g The graphics context to paint with
 598:    * @param b The button to paint the state of
 599:    * @param textRect The area in which to paint the text
 600:    * @param text The text to paint
 601:    *
 602:    * @since 1.4
 603:    */
 604:   protected void paintText(Graphics g, AbstractButton b, Rectangle textRect,
 605:                String text)
 606:   {
 607:     paintText(g, (JComponent) b, textRect, text);
 608:   } 
 609: 
 610:   /**
 611:    * A helper method that finds the BasicButtonListener for the specified
 612:    * button. This is there because this UI class is stateless and
 613:    * shared for all buttons, and thus can't store the listener
 614:    * as instance field. (We store our shared instance in sharedListener,
 615:    * however, subclasses may override createButtonListener() and we would
 616:    * be lost in this case).
 617:    *
 618:    * @param b the button
 619:    *
 620:    * @return the UI event listener
 621:    */
 622:   private BasicButtonListener getButtonListener(AbstractButton b)
 623:   {
 624:     // The listener gets installed as PropertyChangeListener,
 625:     // so look for it in the list of property change listeners.
 626:     PropertyChangeListener[] listeners = b.getPropertyChangeListeners();
 627:     BasicButtonListener l = null;
 628:     for (int i = 0; listeners != null && l == null && i < listeners.length;
 629:            i++)
 630:       {
 631:         if (listeners[i] instanceof BasicButtonListener)
 632:           l = (BasicButtonListener) listeners[i];
 633:       }
 634:     return l;
 635:   }
 636: }