Frames | No Frames |
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: }