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