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