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.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: }