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 "ancestor" == name || "graphicsConfig" == name) { 477 // remove the old html view client property if one 478 // existed, and install a new one if the text installed 479 // into the JLabel is html source. 480 JLabel lbl = ((JLabel) e.getSource()); 481 String text = lbl.getText(); 482 BasicHTML.updateRenderer(lbl, text); 483 } 484 else if (name == "labelFor" || name == "displayedMnemonic") { 485 installKeyboardActions((JLabel) e.getSource()); 486 } 487 } 488 489 // When the accelerator is pressed, temporarily make the JLabel 490 // focusTraversable by registering a WHEN_FOCUSED action for the 491 // release of the accelerator. Then give it focus so it can 492 // prevent unwanted keyTyped events from getting to other components. 493 private static class Actions extends UIAction { 494 private static final String PRESS = "press"; 495 private static final String RELEASE = "release"; 496 497 Actions(String key) { 498 super(key); 499 } 500 501 public void actionPerformed(ActionEvent e) { 502 JLabel label = (JLabel)e.getSource(); 503 String key = getName(); 504 if (key == PRESS) { 505 doPress(label); 506 } 507 else if (key == RELEASE) { 508 doRelease(label, e.getActionCommand() != null); 509 } 510 } 511 512 private void doPress(JLabel label) { 513 Component labelFor = label.getLabelFor(); 514 if (labelFor != null && labelFor.isEnabled()) { 515 InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED); 516 if (inputMap == null) { 517 inputMap = new InputMapUIResource(); 518 SwingUtilities.replaceUIInputMap(label, JComponent.WHEN_FOCUSED, inputMap); 519 } 520 int dka = label.getDisplayedMnemonic(); 521 putOnRelease(inputMap, dka, BasicLookAndFeel 522 .getFocusAcceleratorKeyMask()); 523 // Need this when the sticky keys are enabled 524 putOnRelease(inputMap, dka, 0); 525 // Need this if ALT is released before the accelerator 526 putOnRelease(inputMap, KeyEvent.VK_ALT, 0); 527 label.requestFocus(); 528 } 529 } 530 531 private void doRelease(JLabel label, boolean isCommand) { 532 Component labelFor = label.getLabelFor(); 533 if (labelFor != null && labelFor.isEnabled()) { 534 if (label.hasFocus()) { 535 InputMap inputMap = SwingUtilities.getUIInputMap(label, 536 JComponent.WHEN_FOCUSED); 537 if (inputMap != null) { 538 // inputMap should never be null. 539 int dka = label.getDisplayedMnemonic(); 540 removeOnRelease(inputMap, dka, BasicLookAndFeel 541 .getFocusAcceleratorKeyMask()); 542 removeOnRelease(inputMap, dka, 0); 543 removeOnRelease(inputMap, KeyEvent.VK_ALT, 0); 544 } 545 inputMap = SwingUtilities.getUIInputMap(label, 546 JComponent.WHEN_IN_FOCUSED_WINDOW); 547 if (inputMap == null) { 548 inputMap = new InputMapUIResource(); 549 SwingUtilities.replaceUIInputMap(label, 550 JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap); 551 } 552 int dka = label.getDisplayedMnemonic(); 553 if (isCommand) { 554 putOnRelease(inputMap, KeyEvent.VK_ALT, 0); 555 } else { 556 putOnRelease(inputMap, dka, BasicLookAndFeel 557 .getFocusAcceleratorKeyMask()); 558 // Need this when the sticky keys are enabled 559 putOnRelease(inputMap, dka, 0); 560 } 561 if (labelFor instanceof Container && 562 ((Container) labelFor).isFocusCycleRoot()) { 563 labelFor.requestFocus(); 564 } else { 565 SwingUtilities2.compositeRequestFocus(labelFor); 566 } 567 } else { 568 InputMap inputMap = SwingUtilities.getUIInputMap(label, 569 JComponent.WHEN_IN_FOCUSED_WINDOW); 570 int dka = label.getDisplayedMnemonic(); 571 if (inputMap != null) { 572 if (isCommand) { 573 removeOnRelease(inputMap, dka, BasicLookAndFeel 574 .getFocusAcceleratorKeyMask()); 575 removeOnRelease(inputMap, dka, 0); 576 } else { 577 removeOnRelease(inputMap, KeyEvent.VK_ALT, 0); 578 } 579 } 580 } 581 } 582 } 583 584 private void putOnRelease(InputMap inputMap, int keyCode, int modifiers) { 585 inputMap.put(KeyStroke.getKeyStroke(keyCode, modifiers, true), 586 RELEASE); 587 } 588 589 private void removeOnRelease(InputMap inputMap, int keyCode, int modifiers) { 590 inputMap.remove(KeyStroke.getKeyStroke(keyCode, modifiers, true)); 591 } 592 593 } 594 }