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 }