1 /*
2  * Copyright (c) 1997, 2019, 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&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 
loadActionMap(LazyActionMap map)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      */
layoutCL( JLabel label, FontMetrics fontMetrics, String text, Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR)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      */
paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY)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      */
paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY)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      */
paint(Graphics g, JComponent c)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 
layout(JLabel label, FontMetrics fm, int width, int height)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 
getPreferredSize(JComponent c)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      */
getMinimumSize(JComponent c)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      */
getMaximumSize(JComponent c)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      */
getBaseline(JComponent c, int width, int height)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 || text.isEmpty() || 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      */
getBaselineResizeBehavior( JComponent c)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 
installUI(JComponent c)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 
uninstallUI(JComponent c)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      */
installDefaults(JLabel c)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      */
installListeners(JLabel c)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      */
installComponents(JLabel c)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      */
installKeyboardActions(JLabel l)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                     SwingUtilities2.setAltGraphMask (
408                             BasicLookAndFeel.getFocusAcceleratorKeyMask()),
409                     false), "press");
410         }
411         else {
412             InputMap inputMap = SwingUtilities.getUIInputMap
413                             (l, JComponent.WHEN_IN_FOCUSED_WINDOW);
414             if (inputMap != null) {
415                 inputMap.clear();
416             }
417         }
418     }
419 
420     /**
421      * Uninstalls default properties.
422      *
423      * @param c an instance of {@code JLabel}
424      */
uninstallDefaults(JLabel c)425     protected void uninstallDefaults(JLabel c){
426     }
427 
428     /**
429      * Unregisters listeners.
430      *
431      * @param c an instance of {@code JLabel}
432      */
uninstallListeners(JLabel c)433     protected void uninstallListeners(JLabel c){
434         c.removePropertyChangeListener(this);
435     }
436 
437     /**
438      * Unregisters components.
439      *
440      * @param c an instance of {@code JLabel}
441      */
uninstallComponents(JLabel c)442     protected void uninstallComponents(JLabel c){
443         BasicHTML.updateRenderer(c, "");
444     }
445 
446     /**
447      * Unregisters keyboard actions.
448      *
449      * @param c an instance of {@code JLabel}
450      */
uninstallKeyboardActions(JLabel c)451     protected void uninstallKeyboardActions(JLabel c) {
452         SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
453         SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW,
454                                        null);
455         SwingUtilities.replaceUIActionMap(c, null);
456     }
457 
458     /**
459      * Returns an instance of {@code BasicLabelUI}.
460      *
461      * @param c a component
462      * @return an instance of {@code BasicLabelUI}
463      */
createUI(JComponent c)464     public static ComponentUI createUI(JComponent c) {
465         if (System.getSecurityManager() != null) {
466             AppContext appContext = AppContext.getAppContext();
467             BasicLabelUI safeBasicLabelUI =
468                     (BasicLabelUI) appContext.get(BASIC_LABEL_UI_KEY);
469             if (safeBasicLabelUI == null) {
470                 safeBasicLabelUI = new BasicLabelUI();
471                 appContext.put(BASIC_LABEL_UI_KEY, safeBasicLabelUI);
472             }
473             return safeBasicLabelUI;
474         }
475         return labelUI;
476     }
477 
propertyChange(PropertyChangeEvent e)478     public void propertyChange(PropertyChangeEvent e) {
479         String name = e.getPropertyName();
480         if (name == "text" || "font" == name || "foreground" == name
481                 || SwingUtilities2.isScaleChanged(e)) {
482             // remove the old html view client property if one
483             // existed, and install a new one if the text installed
484             // into the JLabel is html source.
485             JLabel lbl = ((JLabel) e.getSource());
486             String text = lbl.getText();
487             BasicHTML.updateRenderer(lbl, text);
488         }
489         else if (name == "labelFor" || name == "displayedMnemonic") {
490             installKeyboardActions((JLabel) e.getSource());
491         }
492     }
493 
494     // When the accelerator is pressed, temporarily make the JLabel
495     // focusTraversable by registering a WHEN_FOCUSED action for the
496     // release of the accelerator.  Then give it focus so it can
497     // prevent unwanted keyTyped events from getting to other components.
498     private static class Actions extends UIAction {
499         private static final String PRESS = "press";
500         private static final String RELEASE = "release";
501 
Actions(String key)502         Actions(String key) {
503             super(key);
504         }
505 
actionPerformed(ActionEvent e)506         public void actionPerformed(ActionEvent e) {
507             JLabel label = (JLabel)e.getSource();
508             String key = getName();
509             if (key == PRESS) {
510                 doPress(label);
511             }
512             else if (key == RELEASE) {
513                 doRelease(label, e.getActionCommand() != null);
514             }
515         }
516 
doPress(JLabel label)517         private void doPress(JLabel label) {
518             Component labelFor = label.getLabelFor();
519             if (labelFor != null && labelFor.isEnabled()) {
520                 InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED);
521                 if (inputMap == null) {
522                     inputMap = new InputMapUIResource();
523                     SwingUtilities.replaceUIInputMap(label, JComponent.WHEN_FOCUSED, inputMap);
524                 }
525                 int dka = label.getDisplayedMnemonic();
526                 putOnRelease(inputMap, dka, BasicLookAndFeel
527                         .getFocusAcceleratorKeyMask());
528                 putOnRelease(inputMap, dka, SwingUtilities2.setAltGraphMask (
529                         BasicLookAndFeel.getFocusAcceleratorKeyMask()));
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 
doRelease(JLabel label, boolean isCommand)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,
550                                 SwingUtilities2.setAltGraphMask (
551                                 BasicLookAndFeel.getFocusAcceleratorKeyMask()));
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,
569                                 SwingUtilities2.setAltGraphMask (
570                                 BasicLookAndFeel.getFocusAcceleratorKeyMask()));
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,
589                                     SwingUtilities2.setAltGraphMask (
590                                     BasicLookAndFeel.getFocusAcceleratorKeyMask()));
591                             removeOnRelease(inputMap, dka, 0);
592                         } else {
593                             removeOnRelease(inputMap, KeyEvent.VK_ALT, 0);
594                         }
595                     }
596                 }
597             }
598         }
599 
putOnRelease(InputMap inputMap, int keyCode, int modifiers)600         private void putOnRelease(InputMap inputMap, int keyCode, int modifiers) {
601             inputMap.put(KeyStroke.getKeyStroke(keyCode, modifiers, true),
602                     RELEASE);
603         }
604 
removeOnRelease(InputMap inputMap, int keyCode, int modifiers)605         private void removeOnRelease(InputMap inputMap, int keyCode, int modifiers) {
606             inputMap.remove(KeyStroke.getKeyStroke(keyCode, modifiers, true));
607         }
608 
609     }
610 }
611