001    /* BasicLabelUI.java
002     Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc.
003    
004     This file is part of GNU Classpath.
005    
006     GNU Classpath is free software; you can redistribute it and/or modify
007     it under the terms of the GNU General Public License as published by
008     the Free Software Foundation; either version 2, or (at your option)
009     any later version.
010    
011     GNU Classpath is distributed in the hope that it will be useful, but
012     WITHOUT ANY WARRANTY; without even the implied warranty of
013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     General Public License for more details.
015    
016     You should have received a copy of the GNU General Public License
017     along with GNU Classpath; see the file COPYING.  If not, write to the
018     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019     02110-1301 USA.
020    
021     Linking this library statically or dynamically with other modules is
022     making a combined work based on this library.  Thus, the terms and
023     conditions of the GNU General Public License cover the whole
024     combination.
025    
026     As a special exception, the copyright holders of this library give you
027     permission to link this library with independent modules to produce an
028     executable, regardless of the license terms of these independent
029     modules, and to copy and distribute the resulting executable under
030     terms of your choice, provided that you also meet, for each linked
031     independent module, the terms and conditions of the license of that
032     module.  An independent module is a module which is not derived from
033     or based on this library.  If you modify this library, you may extend
034     this exception to your version of the library, but you are not
035     obligated to do so.  If you do not wish to do so, delete this
036     exception statement from your version. */
037    
038    package javax.swing.plaf.basic;
039    
040    import java.awt.Component;
041    import java.awt.Dimension;
042    import java.awt.Font;
043    import java.awt.FontMetrics;
044    import java.awt.Graphics;
045    import java.awt.Insets;
046    import java.awt.Rectangle;
047    import java.awt.Toolkit;
048    import java.awt.event.ActionEvent;
049    import java.awt.event.KeyEvent;
050    import java.beans.PropertyChangeEvent;
051    import java.beans.PropertyChangeListener;
052    
053    import javax.swing.AbstractAction;
054    import javax.swing.ActionMap;
055    import javax.swing.Icon;
056    import javax.swing.InputMap;
057    import javax.swing.JComponent;
058    import javax.swing.JLabel;
059    import javax.swing.KeyStroke;
060    import javax.swing.LookAndFeel;
061    import javax.swing.SwingUtilities;
062    import javax.swing.UIManager;
063    import javax.swing.plaf.ComponentUI;
064    import javax.swing.plaf.LabelUI;
065    import javax.swing.text.View;
066    
067    /**
068     * This is the Basic Look and Feel class for the JLabel.  One BasicLabelUI
069     * object is used to paint all JLabels that utilize the Basic Look and Feel.
070     */
071    public class BasicLabelUI extends LabelUI implements PropertyChangeListener
072    {
073      /** The labelUI that is shared by all labels. */
074      protected static BasicLabelUI labelUI;
075    
076      /**
077       * These fields hold the rectangles for the whole label,
078       * the icon and the text.
079       */
080      private Rectangle vr;
081      private Rectangle ir;
082      private Rectangle tr;
083    
084      /**
085       * A cached Insets object for reuse in the label layout methods.
086       */
087      private Insets cachedInsets;
088    
089      /**
090       * Creates a new BasicLabelUI object.
091       */
092      public BasicLabelUI()
093      {
094        super();
095        vr = new Rectangle();
096        ir = new Rectangle();
097        tr = new Rectangle();
098      }
099    
100      /**
101       * Creates and returns a UI for the label. Since one UI is shared by  all
102       * labels, this means creating only if necessary and returning the  shared
103       * UI.
104       *
105       * @param c The {@link JComponent} that a UI is being created for.
106       *
107       * @return A label UI for the Basic Look and Feel.
108       */
109      public static ComponentUI createUI(JComponent c)
110      {
111        if (labelUI == null)
112          labelUI = new BasicLabelUI();
113        return labelUI;
114      }
115    
116      /**
117       * Returns the preferred size of this component as calculated by the
118       * {@link #layoutCL(JLabel, FontMetrics, String, Icon, Rectangle, Rectangle, 
119       * Rectangle)} method.
120       *
121       * @param c This {@link JComponent} to get a preferred size for.
122       *
123       * @return The preferred size.
124       */
125      public Dimension getPreferredSize(JComponent c)
126      {
127        JLabel lab = (JLabel) c;
128        Insets insets = lab.getInsets();
129        int insetsX = insets.left + insets.right;
130        int insetsY = insets.top + insets.bottom;
131        Icon icon = lab.getIcon();
132        String text = lab.getText();
133        Dimension ret;
134        if (icon == null && text == null)
135          ret = new Dimension(insetsX, insetsY);
136        else if (icon != null && text == null)
137          ret = new Dimension(icon.getIconWidth() + insetsX,
138                              icon.getIconHeight() + insetsY);
139        else
140          {
141            FontMetrics fm = getFontMetrics(lab);
142            ir.x = 0;
143            ir.y = 0;
144            ir.width = 0;
145            ir.height = 0;
146            tr.x = 0;
147            tr.y = 0;
148            tr.width = 0;
149            tr.height = 0;
150            vr.x = 0;
151            vr.y = 0;
152            vr.width = Short.MAX_VALUE;
153            vr.height = Short.MAX_VALUE;
154            layoutCL(lab, fm, text, icon, vr, ir, tr);
155            Rectangle cr = SwingUtilities.computeUnion(tr.x, tr.y, tr.width,
156                                                       tr.height, ir);
157            ret = new Dimension(cr.width + insetsX, cr.height + insetsY);
158          }
159        return ret;
160      }
161    
162      /**
163       * This method returns the minimum size of the {@link JComponent} given. If
164       * this method returns null, then it is up to the Layout Manager to give
165       * this component a minimum size.
166       *
167       * @param c The {@link JComponent} to get a minimum size for.
168       *
169       * @return The minimum size.
170       */
171      public Dimension getMinimumSize(JComponent c)
172      {
173        return getPreferredSize(c);
174      }
175    
176      /**
177       * This method returns the maximum size of the {@link JComponent} given. If
178       * this method returns null, then it is up to the Layout Manager to give
179       * this component a maximum size.
180       *
181       * @param c The {@link JComponent} to get a maximum size for.
182       *
183       * @return The maximum size.
184       */
185      public Dimension getMaximumSize(JComponent c)
186      {
187        return getPreferredSize(c);
188      }
189    
190      /**
191       * The method that paints the label according to its current state.
192       * 
193       * @param g The {@link Graphics} object to paint with.
194       * @param c The {@link JComponent} to paint.
195       */
196      public void paint(Graphics g, JComponent c)
197      {
198        JLabel b = (JLabel) c;
199        Icon icon = (b.isEnabled()) ? b.getIcon() : b.getDisabledIcon();
200        String text = b.getText();
201        if (icon != null || (text != null && ! text.equals("")))
202          {
203            FontMetrics fm = getFontMetrics(b);
204            Insets i = c.getInsets(cachedInsets);
205            vr.x = i.left;
206            vr.y = i.right;
207            vr.width = c.getWidth() - i.left - i.right;
208            vr.height = c.getHeight() - i.top - i.bottom;
209            ir.x = 0;
210            ir.y = 0;
211            ir.width = 0;
212            ir.height = 0;
213            tr.x = 0;
214            tr.y = 0;
215            tr.width = 0;
216            tr.height = 0;
217    
218            text = layoutCL(b, fm, text, icon, vr, ir, tr);
219    
220            if (icon != null)
221              icon.paintIcon(b, g, ir.x, ir.y);       
222    
223            if (text != null && ! text.equals(""))
224              {
225                Object htmlRenderer = b.getClientProperty(BasicHTML.propertyKey);
226                if (htmlRenderer == null)
227                  {
228                    if (b.isEnabled())
229                      paintEnabledText(b, g, text, tr.x, tr.y + fm.getAscent());
230                    else
231                      paintDisabledText(b, g, text, tr.x, tr.y + fm.getAscent());
232                  }
233                else
234                  {
235                    ((View) htmlRenderer).paint(g, tr);
236                  }
237              }
238          }
239      }
240    
241      /**
242       * This method is simply calls SwingUtilities's layoutCompoundLabel.
243       * 
244       * @param label The label to lay out.
245       * @param fontMetrics The FontMetrics for the font used.
246       * @param text The text to paint.
247       * @param icon The icon to draw.
248       * @param viewR The entire viewable rectangle.
249       * @param iconR The icon bounds rectangle.
250       * @param textR The text bounds rectangle.
251       * 
252       * @return A possibly clipped version of the text.
253       */
254      protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text,
255          Icon icon, Rectangle viewR, Rectangle iconR, Rectangle textR)
256      {
257        return SwingUtilities.layoutCompoundLabel(label, fontMetrics, text, icon,
258            label.getVerticalAlignment(), label.getHorizontalAlignment(), label
259                .getVerticalTextPosition(), label.getHorizontalTextPosition(),
260            viewR, iconR, textR, label.getIconTextGap());
261      }
262    
263      /**
264       * Paints the text if the label is disabled. By default, this paints the
265       * clipped text returned by layoutCompoundLabel using the
266       * background.brighter() color. It also paints the same text using the
267       * background.darker() color one pixel to the right and one pixel down.
268       *
269       * @param l The {@link JLabel} being painted.
270       * @param g The {@link Graphics} object to paint with.
271       * @param s The String to paint.
272       * @param textX The x coordinate of the start of the baseline.
273       * @param textY The y coordinate of the start of the baseline.
274       */
275      protected void paintDisabledText(JLabel l, Graphics g, String s, int textX,
276          int textY)
277      {
278        g.setColor(l.getBackground().brighter());
279    
280        int mnemIndex = l.getDisplayedMnemonicIndex();
281    
282        if (mnemIndex != -1)
283          BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX,
284              textY);
285        else
286          g.drawString(s, textX, textY);
287    
288        g.setColor(l.getBackground().darker());
289        if (mnemIndex != -1)
290          BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX + 1,
291              textY + 1);
292        else
293          g.drawString(s, textX + 1, textY + 1);
294      }
295    
296      /**
297       * Paints the text if the label is enabled. The text is painted using the
298       * foreground color.
299       *
300       * @param l The {@link JLabel} being painted.
301       * @param g The {@link Graphics} object to paint with.
302       * @param s The String to paint.
303       * @param textX The x coordinate of the start of the baseline.
304       * @param textY The y coordinate of the start of the baseline.
305       */
306      protected void paintEnabledText(JLabel l, Graphics g, String s, int textX,
307                                      int textY)
308      {
309        g.setColor(l.getForeground());
310    
311        int mnemIndex = l.getDisplayedMnemonicIndex();
312    
313        if (mnemIndex != -1)
314          BasicGraphicsUtils.drawStringUnderlineCharAt(g, s, mnemIndex, textX,
315              textY);
316        else
317          g.drawString(s, textX, textY);
318      }
319    
320      /**
321       * This method installs the UI for the given {@link JComponent}.  This
322       * method will install the component, defaults, listeners,  and keyboard
323       * actions.
324       *
325       * @param c The {@link JComponent} that this UI is being installed on.
326       */
327      public void installUI(JComponent c)
328      {
329        super.installUI(c);
330        if (c instanceof JLabel)
331        {
332          JLabel l = (JLabel) c;
333    
334          installComponents(l);
335          installDefaults(l);
336          installListeners(l);
337          installKeyboardActions(l);
338        }
339      }
340    
341      /**
342       * This method uninstalls the UI for the given {@link JComponent}. This
343       * method will uninstall the component, defaults, listeners,  and keyboard
344       * actions.
345       *
346       * @param c The {@link JComponent} that this UI is being installed on.
347       */
348      public void uninstallUI(JComponent c)
349      {
350        super.uninstallUI(c);
351        if (c instanceof JLabel)
352        {
353          JLabel l = (JLabel) c;
354    
355          uninstallKeyboardActions(l);
356          uninstallListeners(l);
357          uninstallDefaults(l);
358          uninstallComponents(l);
359        }
360      }
361    
362      /**
363       * This method installs the components for this {@link JLabel}.
364       *
365       * @param c The {@link JLabel} to install components for.
366       */
367      protected void installComponents(JLabel c)
368      {
369        BasicHTML.updateRenderer(c, c.getText());
370      }
371    
372      /**
373       * This method uninstalls the components for this {@link JLabel}.
374       *
375       * @param c The {@link JLabel} to uninstall components for.
376       */
377      protected void uninstallComponents(JLabel c)
378      {
379        c.putClientProperty(BasicHTML.propertyKey, null);
380        c.putClientProperty(BasicHTML.documentBaseKey, null);
381      }
382    
383      /**
384       * This method installs the defaults that are defined in  the Basic look and
385       * feel for this {@link JLabel}.
386       *
387       * @param c The {@link JLabel} to install defaults for.
388       */
389      protected void installDefaults(JLabel c)
390      {
391        LookAndFeel.installColorsAndFont(c, "Label.background", "Label.foreground",
392                                         "Label.font");
393        //XXX: There are properties we don't use called disabledForeground
394        //and disabledShadow.
395      }
396    
397      /**
398       * This method uninstalls the defaults that are defined in the Basic look
399       * and feel for this {@link JLabel}.
400       *
401       * @param c The {@link JLabel} to uninstall defaults for.
402       */
403      protected void uninstallDefaults(JLabel c)
404      {
405        c.setForeground(null);
406        c.setBackground(null);
407        c.setFont(null);
408      }
409    
410      /**
411       * Installs the keyboard actions for the given {@link JLabel}.
412       *
413       * @param l The {@link JLabel} to install keyboard actions for.
414       */
415      protected void installKeyboardActions(JLabel l)
416      {
417        Component c = l.getLabelFor();
418        if (c != null)
419          {
420            int mnemonic = l.getDisplayedMnemonic();
421            if (mnemonic > 0)
422              {
423                // add a keystroke for the given mnemonic mapping to 'press';
424                InputMap keyMap = new InputMap();
425                keyMap.put(KeyStroke.getKeyStroke(mnemonic, KeyEvent.VK_ALT), 
426                    "press");
427                SwingUtilities.replaceUIInputMap(l, 
428                    JComponent.WHEN_IN_FOCUSED_WINDOW, keyMap);
429                
430                // add an action to focus the component when 'press' happens
431                ActionMap map = new ActionMap();
432                map.put("press", new AbstractAction() {
433                  public void actionPerformed(ActionEvent event)
434                  {
435                    JLabel label = (JLabel) event.getSource();
436                    Component c = label.getLabelFor();
437                    if (c != null)
438                      c.requestFocus();
439                  }
440                });
441                SwingUtilities.replaceUIActionMap(l, map);
442              }
443          }   
444      }
445    
446      /**
447       * This method uninstalls the keyboard actions for the given {@link JLabel}.
448       *
449       * @param l The {@link JLabel} to uninstall keyboard actions for.
450       */
451      protected void uninstallKeyboardActions(JLabel l)
452      {
453        SwingUtilities.replaceUIActionMap(l, null);
454        SwingUtilities.replaceUIInputMap(l, JComponent.WHEN_IN_FOCUSED_WINDOW, 
455                                         null);
456      }
457    
458      /**
459       * This method installs the listeners for the  given {@link JLabel}. The UI
460       * delegate only listens to  the label.
461       *
462       * @param c The {@link JLabel} to install listeners for.
463       */
464      protected void installListeners(JLabel c)
465      {
466        c.addPropertyChangeListener(this);
467      }
468    
469      /**
470       * This method uninstalls the listeners for the given {@link JLabel}. The UI
471       * delegate only listens to the label.
472       *
473       * @param c The {@link JLabel} to uninstall listeners for.
474       */
475      protected void uninstallListeners(JLabel c)
476      {
477        c.removePropertyChangeListener(this);
478      }
479    
480      /**
481       * This method is called whenever any JLabel's that use this UI has one of
482       * their properties change.
483       *
484       * @param e The {@link PropertyChangeEvent} that describes the change.
485       */
486      public void propertyChange(PropertyChangeEvent e)
487      {
488        if (e.getPropertyName().equals("text"))
489          {
490            String text = (String) e.getNewValue();
491            JLabel l = (JLabel) e.getSource();
492            BasicHTML.updateRenderer(l, text);
493          }
494        else if (e.getPropertyName().equals("displayedMnemonic"))
495          {
496            // update the key to action mapping
497            JLabel label = (JLabel) e.getSource();
498            if (label.getLabelFor() != null)
499              {
500                int oldMnemonic = ((Integer) e.getOldValue()).intValue();
501                int newMnemonic = ((Integer) e.getNewValue()).intValue();
502                InputMap keyMap = label.getInputMap(
503                    JComponent.WHEN_IN_FOCUSED_WINDOW);
504                keyMap.put(KeyStroke.getKeyStroke(oldMnemonic, 
505                    KeyEvent.ALT_DOWN_MASK), null);
506                keyMap.put(KeyStroke.getKeyStroke(newMnemonic, 
507                    KeyEvent.ALT_DOWN_MASK), "press");
508              }
509          }
510        else if (e.getPropertyName().equals("labelFor"))
511          {
512            JLabel label = (JLabel) e.getSource();
513            InputMap keyMap = label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
514            int mnemonic = label.getDisplayedMnemonic();
515            if (mnemonic > 0)
516              keyMap.put(KeyStroke.getKeyStroke(mnemonic, KeyEvent.ALT_DOWN_MASK), 
517                  "press");       
518          }
519      }
520    
521      /**
522       * Fetches a font metrics object for the specified label. This first
523       * tries to get it from the label object itself by calling
524       * {@link Component#getFontMetrics(Font)}, and if that does not work
525       * (for instance, when we are in the initialization and have no parent yet),
526       * it asks the Toolkit for a font metrics object.
527       *
528       * @param l the label
529       *
530       * @return a suitable font metrics object
531       */
532      private FontMetrics getFontMetrics(JLabel l)
533      {
534        Font font = l.getFont();
535        FontMetrics fm = l.getFontMetrics(font);
536        if (fm == null)
537          {
538            Toolkit tk = Toolkit.getDefaultToolkit();
539            fm = tk.getFontMetrics(font);
540          }
541        return fm;
542      }
543    }