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