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