1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import sun.swing.SwingUtilities2; 29 import sun.awt.AppContext; 30 31 import java.awt.*; 32 import java.awt.event.*; 33 import java.io.Serializable; 34 import javax.swing.*; 35 import javax.swing.border.*; 36 import java.awt.*; 37 import java.awt.event.*; 38 import javax.swing.plaf.ButtonUI; 39 import javax.swing.plaf.UIResource; 40 import javax.swing.plaf.ComponentUI; 41 import javax.swing.plaf.TextUIDrawing; 42 import javax.swing.text.View; 43 44 /** 45 * BasicButton implementation 46 * 47 * @author Jeff Dinkins 48 */ 49 public class BasicButtonUI extends ButtonUI{ 50 // Visual constants 51 // NOTE: This is not used or set any where. Were we allowed to remove 52 // fields, this would be removed. 53 /** 54 * The default gap between a text and an icon. 55 */ 56 protected int defaultTextIconGap; 57 58 // Amount to offset text, the value of this comes from 59 // defaultTextShiftOffset once setTextShiftOffset has been invoked. 60 private int shiftOffset = 0; 61 // Value that is set in shiftOffset once setTextShiftOffset has been 62 // invoked. The value of this comes from the defaults table. 63 /** 64 * The default offset of a text. 65 */ 66 protected int defaultTextShiftOffset; 67 68 private TextUIDrawing textUIDrawing; 69 70 private static final String propertyPrefix = "Button" + "."; 71 72 private static final Object BASIC_BUTTON_UI_KEY = new Object(); 73 74 // ******************************** 75 // Create PLAF 76 // ******************************** 77 /** 78 * Returns an instance of {@code BasicButtonUI}. 79 * 80 * @param c a component 81 * @return an instance of {@code BasicButtonUI} 82 */ 83 public static ComponentUI createUI(JComponent c) { 84 AppContext appContext = AppContext.getAppContext(); 85 BasicButtonUI buttonUI = 86 (BasicButtonUI) appContext.get(BASIC_BUTTON_UI_KEY); 87 if (buttonUI == null) { 88 buttonUI = new BasicButtonUI(); 89 appContext.put(BASIC_BUTTON_UI_KEY, buttonUI); 90 } 91 return buttonUI; 92 } 93 94 /** 95 * Returns the property prefix. 96 * 97 * @return the property prefix 98 */ 99 protected String getPropertyPrefix() { 100 return propertyPrefix; 101 } 102 103 104 // ******************************** 105 // Install PLAF 106 // ******************************** 107 public void installUI(JComponent c) { 108 installDefaults((AbstractButton) c); 109 installListeners((AbstractButton) c); 110 installKeyboardActions((AbstractButton) c); 111 BasicHTML.updateRenderer(c, ((AbstractButton) c).getText()); 112 } 113 114 /** 115 * Installs default properties. 116 * 117 * @param b an abstract button 118 */ 119 protected void installDefaults(AbstractButton b) { 120 // load shared instance defaults 121 String pp = getPropertyPrefix(); 122 123 defaultTextShiftOffset = UIManager.getInt(pp + "textShiftOffset"); 124 125 // set the following defaults on the button 126 if (b.isContentAreaFilled()) { 127 LookAndFeel.installProperty(b, "opaque", Boolean.TRUE); 128 } else { 129 LookAndFeel.installProperty(b, "opaque", Boolean.FALSE); 130 } 131 132 if(b.getMargin() == null || (b.getMargin() instanceof UIResource)) { 133 b.setMargin(UIManager.getInsets(pp + "margin")); 134 } 135 136 LookAndFeel.installColorsAndFont(b, pp + "background", 137 pp + "foreground", pp + "font"); 138 LookAndFeel.installBorder(b, pp + "border"); 139 140 Object rollover = UIManager.get(pp + "rollover"); 141 if (rollover != null) { 142 LookAndFeel.installProperty(b, "rolloverEnabled", rollover); 143 } 144 145 LookAndFeel.installProperty(b, "iconTextGap", Integer.valueOf(4)); 146 textUIDrawing = SwingUtilities2.getTextUIDrawing(textUIDrawing); 147 } 148 149 /** 150 * Registers listeners. 151 * 152 * @param b an abstract button 153 */ 154 protected void installListeners(AbstractButton b) { 155 BasicButtonListener listener = createButtonListener(b); 156 if(listener != null) { 157 b.addMouseListener(listener); 158 b.addMouseMotionListener(listener); 159 b.addFocusListener(listener); 160 b.addPropertyChangeListener(listener); 161 b.addChangeListener(listener); 162 } 163 } 164 165 /** 166 * Registers keyboard actions. 167 * 168 * @param b an abstract button 169 */ 170 protected void installKeyboardActions(AbstractButton b){ 171 BasicButtonListener listener = getButtonListener(b); 172 173 if(listener != null) { 174 listener.installKeyboardActions(b); 175 } 176 } 177 178 179 // ******************************** 180 // Uninstall PLAF 181 // ******************************** 182 public void uninstallUI(JComponent c) { 183 uninstallKeyboardActions((AbstractButton) c); 184 uninstallListeners((AbstractButton) c); 185 uninstallDefaults((AbstractButton) c); 186 BasicHTML.updateRenderer(c, ""); 187 } 188 189 /** 190 * Unregisters keyboard actions. 191 * 192 * @param b an abstract button 193 */ 194 protected void uninstallKeyboardActions(AbstractButton b) { 195 BasicButtonListener listener = getButtonListener(b); 196 if(listener != null) { 197 listener.uninstallKeyboardActions(b); 198 } 199 } 200 201 /** 202 * Unregisters listeners. 203 * 204 * @param b an abstract button 205 */ 206 protected void uninstallListeners(AbstractButton b) { 207 BasicButtonListener listener = getButtonListener(b); 208 if(listener != null) { 209 b.removeMouseListener(listener); 210 b.removeMouseMotionListener(listener); 211 b.removeFocusListener(listener); 212 b.removeChangeListener(listener); 213 b.removePropertyChangeListener(listener); 214 } 215 } 216 217 /** 218 * Uninstalls default properties. 219 * 220 * @param b an abstract button 221 */ 222 protected void uninstallDefaults(AbstractButton b) { 223 LookAndFeel.uninstallBorder(b); 224 if (textUIDrawing != SwingUtilities2.DEFAULT_UI_TEXT_DRAWING 225 && textUIDrawing instanceof UIResource) { 226 textUIDrawing = SwingUtilities2.DEFAULT_UI_TEXT_DRAWING; 227 } 228 } 229 230 // ******************************** 231 // Create Listeners 232 // ******************************** 233 /** 234 * Returns a new instance of {@code BasicButtonListener}. 235 * 236 * @param b an abstract button 237 * @return a new instance of {@code BasicButtonListener} 238 */ 239 protected BasicButtonListener createButtonListener(AbstractButton b) { 240 return new BasicButtonListener(b); 241 } 242 243 /** 244 * Returns the default gap between a text and an icon. 245 * 246 * @param b an abstract button 247 * @return the default gap between text and an icon 248 */ 249 public int getDefaultTextIconGap(AbstractButton b) { 250 return defaultTextIconGap; 251 } 252 253 /* These rectangles/insets are allocated once for all 254 * ButtonUI.paint() calls. Re-using rectangles rather than 255 * allocating them in each paint call substantially reduced the time 256 * it took paint to run. Obviously, this method can't be re-entered. 257 */ 258 private static Rectangle viewRect = new Rectangle(); 259 private static Rectangle textRect = new Rectangle(); 260 private static Rectangle iconRect = new Rectangle(); 261 262 // ******************************** 263 // Paint Methods 264 // ******************************** 265 266 public void paint(Graphics g, JComponent c) 267 { 268 AbstractButton b = (AbstractButton) c; 269 ButtonModel model = b.getModel(); 270 271 String text = layout(b, SwingUtilities2.getFontMetrics(b, g), 272 b.getWidth(), b.getHeight()); 273 274 clearTextShiftOffset(); 275 276 // perform UI specific press action, e.g. Windows L&F shifts text 277 if (model.isArmed() && model.isPressed()) { 278 paintButtonPressed(g,b); 279 } 280 281 // Paint the Icon 282 if(b.getIcon() != null) { 283 paintIcon(g,c,iconRect); 284 } 285 286 if (text != null && !text.equals("")){ 287 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 288 if (v != null) { 289 v.paint(g, textRect); 290 } else { 291 paintText(g, b, textRect, text); 292 } 293 } 294 295 if (b.isFocusPainted() && b.hasFocus()) { 296 // paint UI specific focus 297 paintFocus(g,b,viewRect,textRect,iconRect); 298 } 299 } 300 301 /** 302 * Paints an icon of the current button. 303 * 304 * @param g an instance of {@code Graphics} 305 * @param c a component 306 * @param iconRect a bounding rectangle to render the icon 307 */ 308 protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect){ 309 AbstractButton b = (AbstractButton) c; 310 ButtonModel model = b.getModel(); 311 Icon icon = b.getIcon(); 312 Icon tmpIcon = null; 313 314 if(icon == null) { 315 return; 316 } 317 318 Icon selectedIcon = null; 319 320 /* the fallback icon should be based on the selected state */ 321 if (model.isSelected()) { 322 selectedIcon = b.getSelectedIcon(); 323 if (selectedIcon != null) { 324 icon = selectedIcon; 325 } 326 } 327 328 if(!model.isEnabled()) { 329 if(model.isSelected()) { 330 tmpIcon = b.getDisabledSelectedIcon(); 331 if (tmpIcon == null) { 332 tmpIcon = selectedIcon; 333 } 334 } 335 336 if (tmpIcon == null) { 337 tmpIcon = b.getDisabledIcon(); 338 } 339 } else if(model.isPressed() && model.isArmed()) { 340 tmpIcon = b.getPressedIcon(); 341 if(tmpIcon != null) { 342 // revert back to 0 offset 343 clearTextShiftOffset(); 344 } 345 } else if(b.isRolloverEnabled() && model.isRollover()) { 346 if(model.isSelected()) { 347 tmpIcon = b.getRolloverSelectedIcon(); 348 if (tmpIcon == null) { 349 tmpIcon = selectedIcon; 350 } 351 } 352 353 if (tmpIcon == null) { 354 tmpIcon = b.getRolloverIcon(); 355 } 356 } 357 358 if(tmpIcon != null) { 359 icon = tmpIcon; 360 } 361 362 if(model.isPressed() && model.isArmed()) { 363 icon.paintIcon(c, g, iconRect.x + getTextShiftOffset(), 364 iconRect.y + getTextShiftOffset()); 365 } else { 366 icon.paintIcon(c, g, iconRect.x, iconRect.y); 367 } 368 369 } 370 371 /** 372 * Method which renders the text of the current button. 373 * 374 * As of Java 2 platform v 1.4 this method should not be used or overriden. 375 * Use the paintText method which takes the AbstractButton argument. 376 * 377 * @param g an instance of {@code Graphics} 378 * @param c a component 379 * @param textRect a bounding rectangle to render the text 380 * @param text a string to render 381 */ 382 protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text) { 383 AbstractButton b = (AbstractButton) c; 384 ButtonModel model = b.getModel(); 385 FontMetrics fm = SwingUtilities2.getFontMetrics(c, g); 386 int mnemonicIndex = b.getDisplayedMnemonicIndex(); 387 388 /* Draw the Text */ 389 if(model.isEnabled()) { 390 /*** paint the text normally */ 391 g.setColor(b.getForeground()); 392 textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex, 393 textRect.x + getTextShiftOffset(), 394 textRect.y + fm.getAscent() + getTextShiftOffset()); 395 } 396 else { 397 /*** paint the text disabled ***/ 398 g.setColor(b.getBackground().brighter()); 399 textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex, 400 textRect.x, textRect.y + fm.getAscent()); 401 g.setColor(b.getBackground().darker()); 402 textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex, 403 textRect.x - 1, textRect.y + fm.getAscent() - 1); 404 } 405 } 406 407 /** 408 * Method which renders the text of the current button. 409 * 410 * @param g Graphics context 411 * @param b Current button to render 412 * @param textRect Bounding rectangle to render the text 413 * @param text String to render 414 * @since 1.4 415 */ 416 protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) { 417 paintText(g, (JComponent)b, textRect, text); 418 } 419 420 // Method signature defined here overriden in subclasses. 421 // Perhaps this class should be abstract? 422 /** 423 * Paints a focused button. 424 * 425 * @param g an instance of {@code Graphics} 426 * @param b an abstract button 427 * @param viewRect a bounding rectangle to render the button 428 * @param textRect a bounding rectangle to render the text 429 * @param iconRect a bounding rectangle to render the icon 430 */ 431 protected void paintFocus(Graphics g, AbstractButton b, 432 Rectangle viewRect, Rectangle textRect, Rectangle iconRect){ 433 } 434 435 436 /** 437 * Paints a pressed button. 438 * 439 * @param g an instance of {@code Graphics} 440 * @param b an abstract button 441 */ 442 protected void paintButtonPressed(Graphics g, AbstractButton b){ 443 } 444 445 /** 446 * Clears the offset of the text. 447 */ 448 protected void clearTextShiftOffset(){ 449 this.shiftOffset = 0; 450 } 451 452 /** 453 * Sets the offset of the text. 454 */ 455 protected void setTextShiftOffset(){ 456 this.shiftOffset = defaultTextShiftOffset; 457 } 458 459 /** 460 * Returns the offset of the text. 461 * 462 * @return the offset of the text 463 */ 464 protected int getTextShiftOffset() { 465 return shiftOffset; 466 } 467 468 // ******************************** 469 // Layout Methods 470 // ******************************** 471 public Dimension getMinimumSize(JComponent c) { 472 Dimension d = getPreferredSize(c); 473 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 474 if (v != null) { 475 d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS); 476 } 477 return d; 478 } 479 480 public Dimension getPreferredSize(JComponent c) { 481 AbstractButton b = (AbstractButton)c; 482 return BasicGraphicsUtils.getPreferredButtonSize(b, b.getIconTextGap()); 483 } 484 485 public Dimension getMaximumSize(JComponent c) { 486 Dimension d = getPreferredSize(c); 487 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 488 if (v != null) { 489 d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS); 490 } 491 return d; 492 } 493 494 /** 495 * Returns the baseline. 496 * 497 * @throws NullPointerException {@inheritDoc} 498 * @throws IllegalArgumentException {@inheritDoc} 499 * @see javax.swing.JComponent#getBaseline(int, int) 500 * @since 1.6 501 */ 502 public int getBaseline(JComponent c, int width, int height) { 503 super.getBaseline(c, width, height); 504 AbstractButton b = (AbstractButton)c; 505 String text = b.getText(); 506 if (text == null || "".equals(text)) { 507 return -1; 508 } 509 FontMetrics fm = b.getFontMetrics(b.getFont()); 510 layout(b, fm, width, height); 511 return BasicHTML.getBaseline(b, textRect.y, fm.getAscent(), 512 textRect.width, textRect.height); 513 } 514 515 /** 516 * Returns an enum indicating how the baseline of the component 517 * changes as the size changes. 518 * 519 * @throws NullPointerException {@inheritDoc} 520 * @see javax.swing.JComponent#getBaseline(int, int) 521 * @since 1.6 522 */ 523 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 524 JComponent c) { 525 super.getBaselineResizeBehavior(c); 526 if (c.getClientProperty(BasicHTML.propertyKey) != null) { 527 return Component.BaselineResizeBehavior.OTHER; 528 } 529 switch(((AbstractButton)c).getVerticalAlignment()) { 530 case AbstractButton.TOP: 531 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 532 case AbstractButton.BOTTOM: 533 return Component.BaselineResizeBehavior.CONSTANT_DESCENT; 534 case AbstractButton.CENTER: 535 return Component.BaselineResizeBehavior.CENTER_OFFSET; 536 } 537 return Component.BaselineResizeBehavior.OTHER; 538 } 539 540 private String layout(AbstractButton b, FontMetrics fm, 541 int width, int height) { 542 Insets i = b.getInsets(); 543 viewRect.x = i.left; 544 viewRect.y = i.top; 545 viewRect.width = width - (i.right + viewRect.x); 546 viewRect.height = height - (i.bottom + viewRect.y); 547 548 textRect.x = textRect.y = textRect.width = textRect.height = 0; 549 iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0; 550 551 // layout the text and icon 552 return SwingUtilities.layoutCompoundLabel( 553 b, fm, b.getText(), b.getIcon(), 554 b.getVerticalAlignment(), b.getHorizontalAlignment(), 555 b.getVerticalTextPosition(), b.getHorizontalTextPosition(), 556 viewRect, iconRect, textRect, 557 b.getText() == null ? 0 : b.getIconTextGap()); 558 } 559 560 /** 561 * Returns the ButtonListener for the passed in Button, or null if one 562 * could not be found. 563 */ 564 private BasicButtonListener getButtonListener(AbstractButton b) { 565 MouseMotionListener[] listeners = b.getMouseMotionListeners(); 566 567 if (listeners != null) { 568 for (MouseMotionListener listener : listeners) { 569 if (listener instanceof BasicButtonListener) { 570 return (BasicButtonListener) listener; 571 } 572 } 573 } 574 return null; 575 } 576 577 }