       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&amp;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 || "graphicsConfiguration" == 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 }