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.swing.DefaultLookup; 30 import sun.swing.UIAction; 31 import sun.awt.AppContext; 32 33 import javax.swing.*; 34 import javax.swing.plaf.*; 35 import javax.swing.text.View; 36 37 import java.awt.event.ActionEvent; 38 import java.awt.event.ActionListener; 39 import java.awt.event.InputEvent; 40 import java.awt.event.KeyEvent; 41 import java.awt.Component; 42 import java.awt.Container; 43 import java.awt.Dimension; 44 import java.awt.Rectangle; 45 import java.awt.Insets; 46 import java.awt.Color; 47 import java.awt.Graphics; 48 import java.awt.Font; 49 import java.awt.FontMetrics; 50 import java.beans.PropertyChangeEvent; 51 import java.beans.PropertyChangeListener; 52 53 /** 54 * A Windows L&F implementation of LabelUI. This implementation 55 * is completely static, i.e. there's only one UIView implementation 56 * that's shared by all JLabel objects. 57 * 58 * @author Hans Muller 59 */ 60 public class BasicLabelUI extends LabelUI implements PropertyChangeListener 61 { 62 /** 63 * The default <code>BasicLabelUI</code> instance. This field might 64 * not be used. To change the default instance use a subclass which 65 * overrides the <code>createUI</code> method, and place that class 66 * name in defaults table under the key "LabelUI". 67 */ 68 protected static BasicLabelUI labelUI = new BasicLabelUI(); 69 private static final Object BASIC_LABEL_UI_KEY = new Object(); 70 71 private Rectangle paintIconR = new Rectangle(); 72 private Rectangle paintTextR = new Rectangle(); 73 loadActionMap(LazyActionMap map)74 static void loadActionMap(LazyActionMap map) { 75 map.put(new Actions(Actions.PRESS)); 76 map.put(new Actions(Actions.RELEASE)); 77 } 78 79 /** 80 * Forwards the call to SwingUtilities.layoutCompoundLabel(). 81 * This method is here so that a subclass could do Label specific 82 * layout and to shorten the method name a little. 83 * 84 * @param label an instance of {@code JLabel} 85 * @param fontMetrics a font metrics 86 * @param text a text 87 * @param icon an icon 88 * @param viewR a bounding rectangle to lay out label 89 * @param iconR a bounding rectangle to lay out icon 90 * @param textR a bounding rectangle to lay out text 91 * @return a possibly clipped version of the compound labels string 92 * @see SwingUtilities#layoutCompoundLabel 93 */ layoutCL( JLabel label, FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR)94 protected String layoutCL( 95 JLabel label, 96 FontMetrics fontMetrics, 97 String text, 98 Icon icon, 99 Rectangle viewR, 100 Rectangle iconR, 101 Rectangle textR) 102 { 103 return SwingUtilities.layoutCompoundLabel( 104 (JComponent) label, 105 fontMetrics, 106 text, 107 icon, 108 label.getVerticalAlignment(), 109 label.getHorizontalAlignment(), 110 label.getVerticalTextPosition(), 111 label.getHorizontalTextPosition(), 112 viewR, 113 iconR, 114 textR, 115 label.getIconTextGap()); 116 } 117 118 /** 119 * Paint clippedText at textX, textY with the labels foreground color. 120 * 121 * @param l an instance of {@code JLabel} 122 * @param g an instance of {@code Graphics} 123 * @param s a text 124 * @param textX an X coordinate 125 * @param textY an Y coordinate 126 * @see #paint 127 * @see #paintDisabledText 128 */ paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY)129 protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY) 130 { 131 int mnemIndex = l.getDisplayedMnemonicIndex(); 132 g.setColor(l.getForeground()); 133 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, mnemIndex, 134 textX, textY); 135 } 136 137 138 /** 139 * Paint clippedText at textX, textY with background.lighter() and then 140 * shifted down and to the right by one pixel with background.darker(). 141 * 142 * @param l an instance of {@code JLabel} 143 * @param g an instance of {@code Graphics} 144 * @param s a text 145 * @param textX an X coordinate 146 * @param textY an Y coordinate 147 * @see #paint 148 * @see #paintEnabledText 149 */ paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY)150 protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY) 151 { 152 int accChar = l.getDisplayedMnemonicIndex(); 153 Color background = l.getBackground(); 154 g.setColor(background.brighter()); 155 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, accChar, 156 textX + 1, textY + 1); 157 g.setColor(background.darker()); 158 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, accChar, 159 textX, textY); 160 } 161 162 /** 163 * Paints the label text with the foreground color, if the label is opaque 164 * then paints the entire background with the background color. The Label 165 * text is drawn by {@link #paintEnabledText} or {@link #paintDisabledText}. 166 * The locations of the label parts are computed by {@link #layoutCL}. 167 * 168 * @see #paintEnabledText 169 * @see #paintDisabledText 170 * @see #layoutCL 171 */ paint(Graphics g, JComponent c)172 public void paint(Graphics g, JComponent c) 173 { 174 JLabel label = (JLabel)c; 175 String text = label.getText(); 176 Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon(); 177 178 if ((icon == null) && (text == null)) { 179 return; 180 } 181 182 FontMetrics fm = SwingUtilities2.getFontMetrics(label, g); 183 String clippedText = layout(label, fm, c.getWidth(), c.getHeight()); 184 185 if (icon != null) { 186 icon.paintIcon(c, g, paintIconR.x, paintIconR.y); 187 } 188 189 if (text != null) { 190 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 191 if (v != null) { 192 v.paint(g, paintTextR); 193 } else { 194 int textX = paintTextR.x; 195 int textY = paintTextR.y + fm.getAscent(); 196 197 if (label.isEnabled()) { 198 paintEnabledText(label, g, clippedText, textX, textY); 199 } 200 else { 201 paintDisabledText(label, g, clippedText, textX, textY); 202 } 203 } 204 } 205 } 206 layout(JLabel label, FontMetrics fm, int width, int height)207 private String layout(JLabel label, FontMetrics fm, 208 int width, int height) { 209 Insets insets = label.getInsets(null); 210 String text = label.getText(); 211 Icon icon = (label.isEnabled()) ? label.getIcon() : 212 label.getDisabledIcon(); 213 Rectangle paintViewR = new Rectangle(); 214 paintViewR.x = insets.left; 215 paintViewR.y = insets.top; 216 paintViewR.width = width - (insets.left + insets.right); 217 paintViewR.height = height - (insets.top + insets.bottom); 218 paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0; 219 paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0; 220 return layoutCL(label, fm, text, icon, paintViewR, paintIconR, 221 paintTextR); 222 } 223 getPreferredSize(JComponent c)224 public Dimension getPreferredSize(JComponent c) 225 { 226 JLabel label = (JLabel)c; 227 String text = label.getText(); 228 Icon icon = (label.isEnabled()) ? label.getIcon() : 229 label.getDisabledIcon(); 230 Insets insets = label.getInsets(null); 231 Font font = label.getFont(); 232 233 int dx = insets.left + insets.right; 234 int dy = insets.top + insets.bottom; 235 236 if ((icon == null) && 237 ((text == null) || 238 ((text != null) && (font == null)))) { 239 return new Dimension(dx, dy); 240 } 241 else if ((text == null) || ((icon != null) && (font == null))) { 242 return new Dimension(icon.getIconWidth() + dx, 243 icon.getIconHeight() + dy); 244 } 245 else { 246 FontMetrics fm = label.getFontMetrics(font); 247 Rectangle iconR = new Rectangle(); 248 Rectangle textR = new Rectangle(); 249 Rectangle viewR = new Rectangle(); 250 251 iconR.x = iconR.y = iconR.width = iconR.height = 0; 252 textR.x = textR.y = textR.width = textR.height = 0; 253 viewR.x = dx; 254 viewR.y = dy; 255 viewR.width = viewR.height = Short.MAX_VALUE; 256 257 layoutCL(label, fm, text, icon, viewR, iconR, textR); 258 int x1 = Math.min(iconR.x, textR.x); 259 int x2 = Math.max(iconR.x + iconR.width, textR.x + textR.width); 260 int y1 = Math.min(iconR.y, textR.y); 261 int y2 = Math.max(iconR.y + iconR.height, textR.y + textR.height); 262 Dimension rv = new Dimension(x2 - x1, y2 - y1); 263 264 rv.width += dx; 265 rv.height += dy; 266 return rv; 267 } 268 } 269 270 271 /** 272 * @return getPreferredSize(c) 273 */ getMinimumSize(JComponent c)274 public Dimension getMinimumSize(JComponent c) { 275 Dimension d = getPreferredSize(c); 276 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 277 if (v != null) { 278 d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS); 279 } 280 return d; 281 } 282 283 /** 284 * @return getPreferredSize(c) 285 */ getMaximumSize(JComponent c)286 public Dimension getMaximumSize(JComponent c) { 287 Dimension d = getPreferredSize(c); 288 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 289 if (v != null) { 290 d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS); 291 } 292 return d; 293 } 294 295 /** 296 * Returns the baseline. 297 * 298 * @throws NullPointerException {@inheritDoc} 299 * @throws IllegalArgumentException {@inheritDoc} 300 * @see javax.swing.JComponent#getBaseline(int, int) 301 * @since 1.6 302 */ getBaseline(JComponent c, int width, int height)303 public int getBaseline(JComponent c, int width, int height) { 304 super.getBaseline(c, width, height); 305 JLabel label = (JLabel)c; 306 String text = label.getText(); 307 if (text == null || text.isEmpty() || label.getFont() == null) { 308 return -1; 309 } 310 FontMetrics fm = label.getFontMetrics(label.getFont()); 311 layout(label, fm, width, height); 312 return BasicHTML.getBaseline(label, paintTextR.y, fm.getAscent(), 313 paintTextR.width, paintTextR.height); 314 } 315 316 /** 317 * Returns an enum indicating how the baseline of the component 318 * changes as the size changes. 319 * 320 * @throws NullPointerException {@inheritDoc} 321 * @see javax.swing.JComponent#getBaseline(int, int) 322 * @since 1.6 323 */ getBaselineResizeBehavior( JComponent c)324 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 325 JComponent c) { 326 super.getBaselineResizeBehavior(c); 327 if (c.getClientProperty(BasicHTML.propertyKey) != null) { 328 return Component.BaselineResizeBehavior.OTHER; 329 } 330 switch(((JLabel)c).getVerticalAlignment()) { 331 case JLabel.TOP: 332 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 333 case JLabel.BOTTOM: 334 return Component.BaselineResizeBehavior.CONSTANT_DESCENT; 335 case JLabel.CENTER: 336 return Component.BaselineResizeBehavior.CENTER_OFFSET; 337 } 338 return Component.BaselineResizeBehavior.OTHER; 339 } 340 341 installUI(JComponent c)342 public void installUI(JComponent c) { 343 installDefaults((JLabel)c); 344 installComponents((JLabel)c); 345 installListeners((JLabel)c); 346 installKeyboardActions((JLabel)c); 347 } 348 349 uninstallUI(JComponent c)350 public void uninstallUI(JComponent c) { 351 uninstallDefaults((JLabel) c); 352 uninstallComponents((JLabel) c); 353 uninstallListeners((JLabel) c); 354 uninstallKeyboardActions((JLabel) c); 355 } 356 357 /** 358 * Installs default properties. 359 * 360 * @param c an instance of {@code JLabel} 361 */ installDefaults(JLabel c)362 protected void installDefaults(JLabel c){ 363 LookAndFeel.installColorsAndFont(c, "Label.background", "Label.foreground", "Label.font"); 364 LookAndFeel.installProperty(c, "opaque", Boolean.FALSE); 365 } 366 367 /** 368 * Registers listeners. 369 * 370 * @param c an instance of {@code JLabel} 371 */ installListeners(JLabel c)372 protected void installListeners(JLabel c){ 373 c.addPropertyChangeListener(this); 374 } 375 376 /** 377 * Registers components. 378 * 379 * @param c an instance of {@code JLabel} 380 */ installComponents(JLabel c)381 protected void installComponents(JLabel c){ 382 BasicHTML.updateRenderer(c, c.getText()); 383 c.setInheritsPopupMenu(true); 384 } 385 386 /** 387 * Registers keyboard actions. 388 * 389 * @param l an instance of {@code JLabel} 390 */ installKeyboardActions(JLabel l)391 protected void installKeyboardActions(JLabel l) { 392 int dka = l.getDisplayedMnemonic(); 393 Component lf = l.getLabelFor(); 394 if ((dka != 0) && (lf != null)) { 395 LazyActionMap.installLazyActionMap(l, BasicLabelUI.class, 396 "Label.actionMap"); 397 InputMap inputMap = SwingUtilities.getUIInputMap 398 (l, JComponent.WHEN_IN_FOCUSED_WINDOW); 399 if (inputMap == null) { 400 inputMap = new ComponentInputMapUIResource(l); 401 SwingUtilities.replaceUIInputMap(l, 402 JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap); 403 } 404 inputMap.clear(); 405 inputMap.put(KeyStroke.getKeyStroke(dka, BasicLookAndFeel.getFocusAcceleratorKeyMask(), false), "press"); 406 inputMap.put(KeyStroke.getKeyStroke(dka, 407 SwingUtilities2.setAltGraphMask ( 408 BasicLookAndFeel.getFocusAcceleratorKeyMask()), 409 false), "press"); 410 } 411 else { 412 InputMap inputMap = SwingUtilities.getUIInputMap 413 (l, JComponent.WHEN_IN_FOCUSED_WINDOW); 414 if (inputMap != null) { 415 inputMap.clear(); 416 } 417 } 418 } 419 420 /** 421 * Uninstalls default properties. 422 * 423 * @param c an instance of {@code JLabel} 424 */ uninstallDefaults(JLabel c)425 protected void uninstallDefaults(JLabel c){ 426 } 427 428 /** 429 * Unregisters listeners. 430 * 431 * @param c an instance of {@code JLabel} 432 */ uninstallListeners(JLabel c)433 protected void uninstallListeners(JLabel c){ 434 c.removePropertyChangeListener(this); 435 } 436 437 /** 438 * Unregisters components. 439 * 440 * @param c an instance of {@code JLabel} 441 */ uninstallComponents(JLabel c)442 protected void uninstallComponents(JLabel c){ 443 BasicHTML.updateRenderer(c, ""); 444 } 445 446 /** 447 * Unregisters keyboard actions. 448 * 449 * @param c an instance of {@code JLabel} 450 */ uninstallKeyboardActions(JLabel c)451 protected void uninstallKeyboardActions(JLabel c) { 452 SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null); 453 SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW, 454 null); 455 SwingUtilities.replaceUIActionMap(c, null); 456 } 457 458 /** 459 * Returns an instance of {@code BasicLabelUI}. 460 * 461 * @param c a component 462 * @return an instance of {@code BasicLabelUI} 463 */ createUI(JComponent c)464 public static ComponentUI createUI(JComponent c) { 465 if (System.getSecurityManager() != null) { 466 AppContext appContext = AppContext.getAppContext(); 467 BasicLabelUI safeBasicLabelUI = 468 (BasicLabelUI) appContext.get(BASIC_LABEL_UI_KEY); 469 if (safeBasicLabelUI == null) { 470 safeBasicLabelUI = new BasicLabelUI(); 471 appContext.put(BASIC_LABEL_UI_KEY, safeBasicLabelUI); 472 } 473 return safeBasicLabelUI; 474 } 475 return labelUI; 476 } 477 propertyChange(PropertyChangeEvent e)478 public void propertyChange(PropertyChangeEvent e) { 479 String name = e.getPropertyName(); 480 if (name == "text" || "font" == name || "foreground" == name 481 || SwingUtilities2.isScaleChanged(e)) { 482 // remove the old html view client property if one 483 // existed, and install a new one if the text installed 484 // into the JLabel is html source. 485 JLabel lbl = ((JLabel) e.getSource()); 486 String text = lbl.getText(); 487 BasicHTML.updateRenderer(lbl, text); 488 } 489 else if (name == "labelFor" || name == "displayedMnemonic") { 490 installKeyboardActions((JLabel) e.getSource()); 491 } 492 } 493 494 // When the accelerator is pressed, temporarily make the JLabel 495 // focusTraversable by registering a WHEN_FOCUSED action for the 496 // release of the accelerator. Then give it focus so it can 497 // prevent unwanted keyTyped events from getting to other components. 498 private static class Actions extends UIAction { 499 private static final String PRESS = "press"; 500 private static final String RELEASE = "release"; 501 Actions(String key)502 Actions(String key) { 503 super(key); 504 } 505 actionPerformed(ActionEvent e)506 public void actionPerformed(ActionEvent e) { 507 JLabel label = (JLabel)e.getSource(); 508 String key = getName(); 509 if (key == PRESS) { 510 doPress(label); 511 } 512 else if (key == RELEASE) { 513 doRelease(label, e.getActionCommand() != null); 514 } 515 } 516 doPress(JLabel label)517 private void doPress(JLabel label) { 518 Component labelFor = label.getLabelFor(); 519 if (labelFor != null && labelFor.isEnabled()) { 520 InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED); 521 if (inputMap == null) { 522 inputMap = new InputMapUIResource(); 523 SwingUtilities.replaceUIInputMap(label, JComponent.WHEN_FOCUSED, inputMap); 524 } 525 int dka = label.getDisplayedMnemonic(); 526 putOnRelease(inputMap, dka, BasicLookAndFeel 527 .getFocusAcceleratorKeyMask()); 528 putOnRelease(inputMap, dka, SwingUtilities2.setAltGraphMask ( 529 BasicLookAndFeel.getFocusAcceleratorKeyMask())); 530 // Need this when the sticky keys are enabled 531 putOnRelease(inputMap, dka, 0); 532 // Need this if ALT is released before the accelerator 533 putOnRelease(inputMap, KeyEvent.VK_ALT, 0); 534 label.requestFocus(); 535 } 536 } 537 doRelease(JLabel label, boolean isCommand)538 private void doRelease(JLabel label, boolean isCommand) { 539 Component labelFor = label.getLabelFor(); 540 if (labelFor != null && labelFor.isEnabled()) { 541 if (label.hasFocus()) { 542 InputMap inputMap = SwingUtilities.getUIInputMap(label, 543 JComponent.WHEN_FOCUSED); 544 if (inputMap != null) { 545 // inputMap should never be null. 546 int dka = label.getDisplayedMnemonic(); 547 removeOnRelease(inputMap, dka, BasicLookAndFeel 548 .getFocusAcceleratorKeyMask()); 549 removeOnRelease(inputMap, dka, 550 SwingUtilities2.setAltGraphMask ( 551 BasicLookAndFeel.getFocusAcceleratorKeyMask())); 552 removeOnRelease(inputMap, dka, 0); 553 removeOnRelease(inputMap, KeyEvent.VK_ALT, 0); 554 } 555 inputMap = SwingUtilities.getUIInputMap(label, 556 JComponent.WHEN_IN_FOCUSED_WINDOW); 557 if (inputMap == null) { 558 inputMap = new InputMapUIResource(); 559 SwingUtilities.replaceUIInputMap(label, 560 JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap); 561 } 562 int dka = label.getDisplayedMnemonic(); 563 if (isCommand) { 564 putOnRelease(inputMap, KeyEvent.VK_ALT, 0); 565 } else { 566 putOnRelease(inputMap, dka, BasicLookAndFeel 567 .getFocusAcceleratorKeyMask()); 568 putOnRelease(inputMap, dka, 569 SwingUtilities2.setAltGraphMask ( 570 BasicLookAndFeel.getFocusAcceleratorKeyMask())); 571 // Need this when the sticky keys are enabled 572 putOnRelease(inputMap, dka, 0); 573 } 574 if (labelFor instanceof Container && 575 ((Container) labelFor).isFocusCycleRoot()) { 576 labelFor.requestFocus(); 577 } else { 578 SwingUtilities2.compositeRequestFocus(labelFor); 579 } 580 } else { 581 InputMap inputMap = SwingUtilities.getUIInputMap(label, 582 JComponent.WHEN_IN_FOCUSED_WINDOW); 583 int dka = label.getDisplayedMnemonic(); 584 if (inputMap != null) { 585 if (isCommand) { 586 removeOnRelease(inputMap, dka, BasicLookAndFeel 587 .getFocusAcceleratorKeyMask()); 588 removeOnRelease(inputMap, dka, 589 SwingUtilities2.setAltGraphMask ( 590 BasicLookAndFeel.getFocusAcceleratorKeyMask())); 591 removeOnRelease(inputMap, dka, 0); 592 } else { 593 removeOnRelease(inputMap, KeyEvent.VK_ALT, 0); 594 } 595 } 596 } 597 } 598 } 599 putOnRelease(InputMap inputMap, int keyCode, int modifiers)600 private void putOnRelease(InputMap inputMap, int keyCode, int modifiers) { 601 inputMap.put(KeyStroke.getKeyStroke(keyCode, modifiers, true), 602 RELEASE); 603 } 604 removeOnRelease(InputMap inputMap, int keyCode, int modifiers)605 private void removeOnRelease(InputMap inputMap, int keyCode, int modifiers) { 606 inputMap.remove(KeyStroke.getKeyStroke(keyCode, modifiers, true)); 607 } 608 609 } 610 } 611