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
26package javax.swing.plaf.basic;
27
28import sun.swing.SwingUtilities2;
29import sun.swing.DefaultLookup;
30import sun.swing.UIAction;
31import sun.awt.AppContext;
32
33import javax.swing.*;
34import javax.swing.plaf.*;
35import javax.swing.text.View;
36
37import java.awt.event.ActionEvent;
38import java.awt.event.ActionListener;
39import java.awt.event.KeyEvent;
40import java.awt.Component;
41import java.awt.Container;
42import java.awt.Dimension;
43import java.awt.Rectangle;
44import java.awt.Insets;
45import java.awt.Color;
46import java.awt.Graphics;
47import java.awt.Font;
48import java.awt.FontMetrics;
49import java.beans.PropertyChangeEvent;
50import 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 */
59public 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            // remove the old html view client property if one
477            // existed, and install a new one if the text installed
478            // into the JLabel is html source.
479            JLabel lbl = ((JLabel) e.getSource());
480            String text = lbl.getText();
481            BasicHTML.updateRenderer(lbl, text);
482        }
483        else if (name == "labelFor" || name == "displayedMnemonic") {
484            installKeyboardActions((JLabel) e.getSource());
485        }
486    }
487
488    // When the accelerator is pressed, temporarily make the JLabel
489    // focusTraversable by registering a WHEN_FOCUSED action for the
490    // release of the accelerator.  Then give it focus so it can
491    // prevent unwanted keyTyped events from getting to other components.
492    private static class Actions extends UIAction {
493        private static final String PRESS = "press";
494        private static final String RELEASE = "release";
495
496        Actions(String key) {
497            super(key);
498        }
499
500        public void actionPerformed(ActionEvent e) {
501            JLabel label = (JLabel)e.getSource();
502            String key = getName();
503            if (key == PRESS) {
504                doPress(label);
505            }
506            else if (key == RELEASE) {
507                doRelease(label, e.getActionCommand() != null);
508            }
509        }
510
511        private void doPress(JLabel label) {
512            Component labelFor = label.getLabelFor();
513            if (labelFor != null && labelFor.isEnabled()) {
514                InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED);
515                if (inputMap == null) {
516                    inputMap = new InputMapUIResource();
517                    SwingUtilities.replaceUIInputMap(label, JComponent.WHEN_FOCUSED, inputMap);
518                }
519                int dka = label.getDisplayedMnemonic();
520                putOnRelease(inputMap, dka, BasicLookAndFeel
521                        .getFocusAcceleratorKeyMask());
522                // Need this when the sticky keys are enabled
523                putOnRelease(inputMap, dka, 0);
524                // Need this if ALT is released before the accelerator
525                putOnRelease(inputMap, KeyEvent.VK_ALT, 0);
526                label.requestFocus();
527            }
528        }
529
530        private void doRelease(JLabel label, boolean isCommand) {
531            Component labelFor = label.getLabelFor();
532            if (labelFor != null && labelFor.isEnabled()) {
533                if (label.hasFocus()) {
534                    InputMap inputMap = SwingUtilities.getUIInputMap(label,
535                            JComponent.WHEN_FOCUSED);
536                    if (inputMap != null) {
537                        // inputMap should never be null.
538                        int dka = label.getDisplayedMnemonic();
539                        removeOnRelease(inputMap, dka, BasicLookAndFeel
540                                .getFocusAcceleratorKeyMask());
541                        removeOnRelease(inputMap, dka, 0);
542                        removeOnRelease(inputMap, KeyEvent.VK_ALT, 0);
543                    }
544                    inputMap = SwingUtilities.getUIInputMap(label,
545                            JComponent.WHEN_IN_FOCUSED_WINDOW);
546                    if (inputMap == null) {
547                        inputMap = new InputMapUIResource();
548                        SwingUtilities.replaceUIInputMap(label,
549                                JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap);
550                    }
551                    int dka = label.getDisplayedMnemonic();
552                    if (isCommand) {
553                        putOnRelease(inputMap, KeyEvent.VK_ALT, 0);
554                    } else {
555                        putOnRelease(inputMap, dka, BasicLookAndFeel
556                                .getFocusAcceleratorKeyMask());
557                        // Need this when the sticky keys are enabled
558                        putOnRelease(inputMap, dka, 0);
559                    }
560                    if (labelFor instanceof Container &&
561                            ((Container) labelFor).isFocusCycleRoot()) {
562                        labelFor.requestFocus();
563                    } else {
564                        SwingUtilities2.compositeRequestFocus(labelFor);
565                    }
566                } else {
567                    InputMap inputMap = SwingUtilities.getUIInputMap(label,
568                            JComponent.WHEN_IN_FOCUSED_WINDOW);
569                    int dka = label.getDisplayedMnemonic();
570                    if (inputMap != null) {
571                        if (isCommand) {
572                            removeOnRelease(inputMap, dka, BasicLookAndFeel
573                                    .getFocusAcceleratorKeyMask());
574                            removeOnRelease(inputMap, dka, 0);
575                        } else {
576                            removeOnRelease(inputMap, KeyEvent.VK_ALT, 0);
577                        }
578                    }
579                }
580            }
581        }
582
583        private void putOnRelease(InputMap inputMap, int keyCode, int modifiers) {
584            inputMap.put(KeyStroke.getKeyStroke(keyCode, modifiers, true),
585                    RELEASE);
586        }
587
588        private void removeOnRelease(InputMap inputMap, int keyCode, int modifiers) {
589            inputMap.remove(KeyStroke.getKeyStroke(keyCode, modifiers, true));
590        }
591
592    }
593}
594