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         getTextUIDrawing().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         getTextUIDrawing().drawStringUnderlineCharAt(l, g, s, accChar,
 155                                                    textX + 1, textY + 1);
 156         g.setColor(background.darker());
 157         getTextUIDrawing().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         super.installUI(c);
 343         installDefaults((JLabel)c);
 344         installComponents((JLabel)c);
 345         installListeners((JLabel)c);
 346         installKeyboardActions((JLabel)c);
 347     }
 348 
 349 
 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      */
 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      */
 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      */
 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      */
 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         }
 407         else {
 408             InputMap inputMap = SwingUtilities.getUIInputMap
 409                             (l, JComponent.WHEN_IN_FOCUSED_WINDOW);
 410             if (inputMap != null) {
 411                 inputMap.clear();
 412             }
 413         }
 414     }
 415 
 416     /**
 417      * Uninstalls default properties.
 418      *
 419      * @param c an instance of {@code JLabel}
 420      */
 421     protected void uninstallDefaults(JLabel c){
 422     }
 423 
 424     /**
 425      * Unregisters listeners.
 426      *
 427      * @param c an instance of {@code JLabel}
 428      */
 429     protected void uninstallListeners(JLabel c){
 430         c.removePropertyChangeListener(this);
 431     }
 432 
 433     /**
 434      * Unregisters components.
 435      *
 436      * @param c an instance of {@code JLabel}
 437      */
 438     protected void uninstallComponents(JLabel c){
 439         BasicHTML.updateRenderer(c, "");
 440     }
 441 
 442     /**
 443      * Unregisters keyboard actions.
 444      *
 445      * @param c an instance of {@code JLabel}
 446      */
 447     protected void uninstallKeyboardActions(JLabel c) {
 448         SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
 449         SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW,
 450                                        null);
 451         SwingUtilities.replaceUIActionMap(c, null);
 452     }
 453 
 454     /**
 455      * Returns an instance of {@code BasicLabelUI}.
 456      *
 457      * @param c a component
 458      * @return an instance of {@code BasicLabelUI}
 459      */
 460     public static ComponentUI createUI(JComponent c) {
 461         if (System.getSecurityManager() != null) {
 462             AppContext appContext = AppContext.getAppContext();
 463             BasicLabelUI safeBasicLabelUI =
 464                     (BasicLabelUI) appContext.get(BASIC_LABEL_UI_KEY);
 465             if (safeBasicLabelUI == null) {
 466                 safeBasicLabelUI = new BasicLabelUI();
 467                 appContext.put(BASIC_LABEL_UI_KEY, safeBasicLabelUI);
 468             }
 469             return safeBasicLabelUI;
 470         }
 471         return labelUI;
 472     }
 473 
 474     public void propertyChange(PropertyChangeEvent e) {
 475         String name = e.getPropertyName();
 476         if (name == "text" || "font" == name || "foreground" == 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 }