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