001    /* BasicMenuItemUI.java --
002       Copyright (C) 2002, 2004, 2005  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    
039    package javax.swing.plaf.basic;
040    
041    import gnu.classpath.SystemProperties;
042    
043    import java.awt.Color;
044    import java.awt.Component;
045    import java.awt.Container;
046    import java.awt.Dimension;
047    import java.awt.Font;
048    import java.awt.FontMetrics;
049    import java.awt.Graphics;
050    import java.awt.Insets;
051    import java.awt.Rectangle;
052    import java.awt.event.ActionEvent;
053    import java.awt.event.ItemEvent;
054    import java.awt.event.ItemListener;
055    import java.awt.event.KeyEvent;
056    import java.awt.event.MouseEvent;
057    import java.awt.font.FontRenderContext;
058    import java.awt.font.TextLayout;
059    import java.awt.geom.AffineTransform;
060    import java.beans.PropertyChangeEvent;
061    import java.beans.PropertyChangeListener;
062    import java.util.ArrayList;
063    
064    import javax.swing.AbstractAction;
065    import javax.swing.AbstractButton;
066    import javax.swing.ActionMap;
067    import javax.swing.ButtonModel;
068    import javax.swing.Icon;
069    import javax.swing.InputMap;
070    import javax.swing.JCheckBoxMenuItem;
071    import javax.swing.JComponent;
072    import javax.swing.JMenu;
073    import javax.swing.JMenuItem;
074    import javax.swing.JPopupMenu;
075    import javax.swing.KeyStroke;
076    import javax.swing.LookAndFeel;
077    import javax.swing.MenuElement;
078    import javax.swing.MenuSelectionManager;
079    import javax.swing.SwingConstants;
080    import javax.swing.SwingUtilities;
081    import javax.swing.UIDefaults;
082    import javax.swing.UIManager;
083    import javax.swing.event.MenuDragMouseEvent;
084    import javax.swing.event.MenuDragMouseListener;
085    import javax.swing.event.MenuKeyEvent;
086    import javax.swing.event.MenuKeyListener;
087    import javax.swing.event.MouseInputListener;
088    import javax.swing.plaf.ActionMapUIResource;
089    import javax.swing.plaf.ComponentInputMapUIResource;
090    import javax.swing.plaf.ComponentUI;
091    import javax.swing.plaf.MenuItemUI;
092    import javax.swing.text.View;
093    
094    /**
095     * UI Delegate for JMenuItem.
096     */
097    public class BasicMenuItemUI extends MenuItemUI
098    {
099      /**
100       * Font to be used when displaying menu item's accelerator.
101       */
102      protected Font acceleratorFont;
103    
104      /**
105       * Color to be used when displaying menu item's accelerator.
106       */
107      protected Color acceleratorForeground;
108    
109      /**
110       * Color to be used when displaying menu item's accelerator when menu item is
111       * selected.
112       */
113      protected Color acceleratorSelectionForeground;
114    
115      /**
116       * Icon that is displayed after the text to indicated that this menu contains
117       * submenu.
118       */
119      protected Icon arrowIcon;
120    
121      /**
122       * Icon that is displayed before the text. This icon is only used in
123       * JCheckBoxMenuItem or JRadioBoxMenuItem.
124       */
125      protected Icon checkIcon;
126    
127      /**
128       * Number of spaces between icon and text.
129       */
130      protected int defaultTextIconGap = 4;
131    
132      /**
133       * Color of the text when menu item is disabled
134       */
135      protected Color disabledForeground;
136    
137      /**
138       * The menu Drag mouse listener listening to the menu item.
139       */
140      protected MenuDragMouseListener menuDragMouseListener;
141    
142      /**
143       * The menu item itself
144       */
145      protected JMenuItem menuItem;
146    
147      /**
148       * Menu Key listener listening to the menu item.
149       */
150      protected MenuKeyListener menuKeyListener;
151    
152      /**
153       * mouse input listener listening to menu item.
154       */
155      protected MouseInputListener mouseInputListener;
156    
157      /**
158       * Indicates if border should be painted
159       */
160      protected boolean oldBorderPainted;
161    
162      /**
163       * Color of text that is used when menu item is selected
164       */
165      protected Color selectionBackground;
166    
167      /**
168       * Color of the text that is used when menu item is selected.
169       */
170      protected Color selectionForeground;
171    
172      /**
173       * String that separates description of the modifiers and the key
174       */
175      private String acceleratorDelimiter;
176    
177      /**
178       * ItemListener to listen for item changes in the menu item
179       */
180      private ItemListener itemListener;
181    
182      /**
183       * A PropertyChangeListener to make UI updates after property changes.
184       */
185      private PropertyChangeHandler propertyChangeListener;
186    
187      /**
188       * The view rectangle used for layout of the menu item.
189       */
190      private Rectangle viewRect;
191    
192      /**
193       * The rectangle that holds the area of the label.
194       */
195      private Rectangle textRect;
196    
197      /**
198       * The rectangle that holds the area of the accelerator.
199       */
200      private Rectangle accelRect;
201    
202      /**
203       * The rectangle that holds the area of the icon.
204       */
205      private Rectangle iconRect;
206    
207      /**
208       * The rectangle that holds the area of the icon.
209       */
210      private Rectangle arrowIconRect;
211    
212      /**
213       * The rectangle that holds the area of the check icon.
214       */
215      private Rectangle checkIconRect;
216    
217      /**
218       * A rectangle used for temporary storage to avoid creation of new
219       * rectangles.
220       */
221      private Rectangle cachedRect;
222    
223      /**
224       * A class to handle PropertChangeEvents for the JMenuItem
225       * @author Anthony Balkissoon abalkiss at redhat dot com.
226       */
227      class PropertyChangeHandler implements PropertyChangeListener
228      {
229        /**
230         * This method is called when a property of the menuItem is changed.
231         * Currently it is only used to update the accelerator key bindings.
232         *
233         * @param e
234         *          the PropertyChangeEvent
235         */
236        public void propertyChange(PropertyChangeEvent e)
237        {
238          String property = e.getPropertyName();
239          if (property.equals("accelerator"))
240            {
241              InputMap map = SwingUtilities.getUIInputMap(menuItem,
242                  JComponent.WHEN_IN_FOCUSED_WINDOW);
243              if (map != null)
244                map.remove((KeyStroke) e.getOldValue());
245              else
246                map = new ComponentInputMapUIResource(menuItem);
247    
248              KeyStroke accelerator = (KeyStroke) e.getNewValue();
249              if (accelerator != null)
250                map.put(accelerator, "doClick");
251            }
252          // TextLayout caching for speed-up drawing of text.
253          else if ((property.equals(AbstractButton.TEXT_CHANGED_PROPERTY)
254                    || property.equals("font"))
255                   && SystemProperties.getProperty("gnu.javax.swing.noGraphics2D")
256                   == null)
257            {
258              AbstractButton b = (AbstractButton) e.getSource();
259              String text = b.getText();
260              if (text == null)
261                text = "";
262              FontRenderContext frc = new FontRenderContext(new AffineTransform(),
263                                                            false, false);
264              TextLayout layout = new TextLayout(text, b.getFont(), frc);
265              b.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, layout);
266            }
267        }
268      }
269    
270      /**
271       * A class to handle accelerator keys.  This is the Action we will
272       * perform when the accelerator key for this JMenuItem is pressed.
273       * @author Anthony Balkissoon abalkiss at redhat dot com
274       *
275       */
276      class ClickAction extends AbstractAction
277      {
278        /**
279         * This is what is done when the accelerator key for the JMenuItem is
280         * pressed.
281         */
282        public void actionPerformed(ActionEvent event)
283        {
284          doClick(MenuSelectionManager.defaultManager());
285        }
286      }
287    
288      /**
289       * Creates a new BasicMenuItemUI object.
290       */
291      public BasicMenuItemUI()
292      {
293        mouseInputListener = createMouseInputListener(menuItem);
294        menuDragMouseListener = createMenuDragMouseListener(menuItem);
295        menuKeyListener = createMenuKeyListener(menuItem);
296        itemListener = new ItemHandler();
297        propertyChangeListener = new PropertyChangeHandler();
298    
299        // Initialize rectangles for layout.
300        viewRect = new Rectangle();
301        textRect = new Rectangle();
302        iconRect = new Rectangle();
303        arrowIconRect = new Rectangle();
304        checkIconRect = new Rectangle();
305        accelRect = new Rectangle();
306        cachedRect = new Rectangle();
307      }
308    
309      /**
310       * Create MenuDragMouseListener to listen for mouse dragged events.
311       *
312       * @param c
313       *          menu item to listen to
314       * @return The MenuDragMouseListener
315       */
316      protected MenuDragMouseListener createMenuDragMouseListener(JComponent c)
317      {
318        return new MenuDragMouseHandler();
319      }
320    
321      /**
322       * Creates MenuKeyListener to listen to key events occuring when menu item is
323       * visible on the screen.
324       *
325       * @param c
326       *          menu item to listen to
327       * @return The MenuKeyListener
328       */
329      protected MenuKeyListener createMenuKeyListener(JComponent c)
330      {
331        return new MenuKeyHandler();
332      }
333    
334      /**
335       * Handles mouse input events occuring for this menu item
336       *
337       * @param c
338       *          menu item to listen to
339       * @return The MouseInputListener
340       */
341      protected MouseInputListener createMouseInputListener(JComponent c)
342      {
343        return new MouseInputHandler();
344      }
345    
346      /**
347       * Factory method to create a BasicMenuItemUI for the given {@link
348       * JComponent}, which should be a {@link JMenuItem}.
349       *
350       * @param c
351       *          The {@link JComponent} a UI is being created for.
352       * @return A BasicMenuItemUI for the {@link JComponent}.
353       */
354      public static ComponentUI createUI(JComponent c)
355      {
356        return new BasicMenuItemUI();
357      }
358    
359      /**
360       * Programatically clicks menu item.
361       *
362       * @param msm
363       *          MenuSelectionManager for the menu hierarchy
364       */
365      protected void doClick(MenuSelectionManager msm)
366      {
367        menuItem.doClick(0);
368        msm.clearSelectedPath();
369      }
370    
371      /**
372       * Returns maximum size for the specified menu item
373       *
374       * @param c
375       *          component for which to get maximum size
376       * @return Maximum size for the specified menu item.
377       */
378      public Dimension getMaximumSize(JComponent c)
379      {
380        return null;
381      }
382    
383      /**
384       * Returns minimum size for the specified menu item
385       *
386       * @param c
387       *          component for which to get minimum size
388       * @return Minimum size for the specified menu item.
389       */
390      public Dimension getMinimumSize(JComponent c)
391      {
392        return null;
393      }
394    
395      /**
396       * Returns path to this menu item.
397       *
398       * @return $MenuElement[]$ Returns array of menu elements that constitute a
399       *         path to this menu item.
400       */
401      public MenuElement[] getPath()
402      {
403        ArrayList path = new ArrayList();
404    
405        Component c = menuItem;
406        while (c instanceof MenuElement)
407          {
408            path.add(0, c);
409    
410            if (c instanceof JPopupMenu)
411              c = ((JPopupMenu) c).getInvoker();
412            else
413              c = c.getParent();
414          }
415    
416        MenuElement[] pathArray = new MenuElement[path.size()];
417        path.toArray(pathArray);
418        return pathArray;
419      }
420    
421      /**
422       * Returns preferred size for the given menu item.
423       *
424       * @param c
425       *          menu item for which to get preferred size
426       * @param checkIcon
427       *          check icon displayed in the given menu item
428       * @param arrowIcon
429       *          arrow icon displayed in the given menu item
430       * @param defaultTextIconGap
431       *          space between icon and text in the given menuItem
432       * @return $Dimension$ preferred size for the given menu item
433       */
434      protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
435                                                   Icon arrowIcon,
436                                                   int defaultTextIconGap)
437      {
438        JMenuItem m = (JMenuItem) c;
439        String accelText = getAcceleratorString(m);
440    
441        // Layout the menu item. The result gets stored in the rectangle
442        // fields of this class.
443        resetRectangles(null);
444        layoutMenuItem(m, accelText);
445    
446        // The union of the text and icon areas is the label area.
447        cachedRect.setBounds(textRect);
448        Rectangle pref = SwingUtilities.computeUnion(iconRect.x, iconRect.y,
449                                                     iconRect.width,
450                                                     iconRect.height,
451                                                     cachedRect);
452    
453        // Find the widest menu item text and accelerator and store it in
454        // client properties of the parent, so that we can align the accelerators
455        // properly. Of course, we only need can do this, if the parent is
456        // a JComponent and this menu item is not a toplevel menu.
457        Container parent = m.getParent();
458        if (parent != null && parent instanceof JComponent
459            && !(m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
460          {
461            JComponent p = (JComponent) parent;
462    
463            // The widest text so far.
464            Integer maxTextWidth = (Integer) p.getClientProperty("maxTextWidth");
465            int maxTextValue = maxTextWidth == null ? 0 : maxTextWidth.intValue();
466            if (pref.width < maxTextValue)
467              pref.width = maxTextValue;
468            else
469              p.putClientProperty("maxTextWidth", new Integer(pref.width));
470    
471            // The widest accelerator so far.
472            Integer maxAccelWidth = (Integer) p.getClientProperty("maxAccelWidth");
473            int maxAccelValue = maxAccelWidth == null ? 0
474                                                      : maxAccelWidth.intValue();
475            if (accelRect.width > maxAccelValue)
476              {
477                maxAccelValue = accelRect.width;
478                p.putClientProperty("maxAccelWidth", new Integer(accelRect.width));
479              }
480            pref.width += maxAccelValue;
481            pref.width += defaultTextIconGap;
482          }
483    
484        // Add arrow and check size if appropriate.
485        if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
486          {
487            pref.width += checkIconRect.width;
488            pref.width += defaultTextIconGap;
489            pref.width += arrowIconRect.width;
490            pref.width += defaultTextIconGap;
491          }
492    
493        // Add a gap ~2 times as wide as the defaultTextIconGap.
494        pref.width += 2 * defaultTextIconGap;
495    
496        // Respect the insets of the menu item.
497        Insets i = m.getInsets();
498        pref.width += i.left + i.right;
499        pref.height += i.top + i.bottom;
500    
501        // Return a copy, so that nobody messes with our textRect.
502        return pref.getSize();
503      }
504    
505      /**
506       * Returns preferred size of the given component
507       *
508       * @param c
509       *          component for which to return preferred size
510       * @return $Dimension$ preferred size for the given component
511       */
512      public Dimension getPreferredSize(JComponent c)
513      {
514        return getPreferredMenuItemSize(c, checkIcon, arrowIcon,
515                                        defaultTextIconGap);
516      }
517    
518      /**
519       * Returns the prefix for entries in the {@link UIDefaults} table.
520       *
521       * @return "MenuItem"
522       */
523      protected String getPropertyPrefix()
524      {
525        return "MenuItem";
526      }
527    
528      /**
529       * This method installs the components for this {@link JMenuItem}.
530       *
531       * @param menuItem
532       *          The {@link JMenuItem} to install components for.
533       */
534      protected void installComponents(JMenuItem menuItem)
535      {
536        // FIXME: Need to implement
537      }
538    
539      /**
540       * This method installs the defaults that are defined in the Basic look and
541       * feel for this {@link JMenuItem}.
542       */
543      protected void installDefaults()
544      {
545        String prefix = getPropertyPrefix();
546        LookAndFeel.installBorder(menuItem, prefix + ".border");
547        LookAndFeel.installColorsAndFont(menuItem, prefix + ".background",
548                                         prefix + ".foreground", prefix + ".font");
549        menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
550        acceleratorFont = UIManager.getFont(prefix + ".acceleratorFont");
551        acceleratorForeground = UIManager.getColor(prefix
552            + ".acceleratorForeground");
553        acceleratorSelectionForeground = UIManager.getColor(prefix
554            + ".acceleratorSelectionForeground");
555        selectionBackground = UIManager.getColor(prefix + ".selectionBackground");
556        selectionForeground = UIManager.getColor(prefix + ".selectionForeground");
557        acceleratorDelimiter = UIManager.getString(prefix + ".acceleratorDelimiter");
558        checkIcon = UIManager.getIcon(prefix + ".checkIcon");
559    
560        menuItem.setHorizontalTextPosition(SwingConstants.TRAILING);
561        menuItem.setHorizontalAlignment(SwingConstants.LEADING);
562      }
563    
564      /**
565       * This method installs the keyboard actions for this {@link JMenuItem}.
566       */
567      protected void installKeyboardActions()
568      {
569        InputMap focusedWindowMap = SwingUtilities.getUIInputMap(menuItem,
570            JComponent.WHEN_IN_FOCUSED_WINDOW);
571        if (focusedWindowMap == null)
572          focusedWindowMap = new ComponentInputMapUIResource(menuItem);
573        KeyStroke accelerator = menuItem.getAccelerator();
574        if (accelerator != null)
575          focusedWindowMap.put(accelerator, "doClick");
576        SwingUtilities.replaceUIInputMap(menuItem,
577            JComponent.WHEN_IN_FOCUSED_WINDOW, focusedWindowMap);
578    
579        ActionMap UIActionMap = SwingUtilities.getUIActionMap(menuItem);
580        if (UIActionMap == null)
581          UIActionMap = new ActionMapUIResource();
582        UIActionMap.put("doClick", new ClickAction());
583        SwingUtilities.replaceUIActionMap(menuItem, UIActionMap);
584      }
585    
586      /**
587       * This method installs the listeners for the {@link JMenuItem}.
588       */
589      protected void installListeners()
590      {
591        menuItem.addMouseListener(mouseInputListener);
592        menuItem.addMouseMotionListener(mouseInputListener);
593        menuItem.addMenuDragMouseListener(menuDragMouseListener);
594        menuItem.addMenuKeyListener(menuKeyListener);
595        menuItem.addItemListener(itemListener);
596        menuItem.addPropertyChangeListener(propertyChangeListener);
597        // Fire synthetic property change event to let the listener update
598        // the TextLayout cache.
599        propertyChangeListener.propertyChange(new PropertyChangeEvent(menuItem,
600                                                                      "font", null,
601                                                              menuItem.getFont()));
602      }
603    
604      /**
605       * Installs and initializes all fields for this UI delegate. Any properties of
606       * the UI that need to be initialized and/or set to defaults will be done now.
607       * It will also install any listeners necessary.
608       *
609       * @param c
610       *          The {@link JComponent} that is having this UI installed.
611       */
612      public void installUI(JComponent c)
613      {
614        super.installUI(c);
615        menuItem = (JMenuItem) c;
616        installDefaults();
617        installComponents(menuItem);
618        installListeners();
619        installKeyboardActions();
620      }
621    
622      /**
623       * Paints given menu item using specified graphics context
624       *
625       * @param g
626       *          The graphics context used to paint this menu item
627       * @param c
628       *          Menu Item to paint
629       */
630      public void paint(Graphics g, JComponent c)
631      {
632        paintMenuItem(g, c, checkIcon, arrowIcon, selectionBackground,
633                      c.getForeground(), defaultTextIconGap);
634      }
635    
636      /**
637       * Paints background of the menu item
638       *
639       * @param g
640       *          The graphics context used to paint this menu item
641       * @param menuItem
642       *          menu item to paint
643       * @param bgColor
644       *          Background color to use when painting menu item
645       */
646      protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor)
647      {
648        // Menu item is considered to be highlighted when it is selected.
649        // But we don't want to paint the background of JCheckBoxMenuItems
650        ButtonModel mod = menuItem.getModel();
651        Color saved = g.getColor();
652        if (mod.isArmed() || ((menuItem instanceof JMenu) && mod.isSelected()))
653          {
654            g.setColor(bgColor);
655            g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
656          }
657        else if (menuItem.isOpaque())
658          {
659            g.setColor(menuItem.getBackground());
660            g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
661          }
662        g.setColor(saved);
663      }
664    
665      /**
666       * Paints specified menu item
667       *
668       * @param g
669       *          The graphics context used to paint this menu item
670       * @param c
671       *          menu item to paint
672       * @param checkIcon
673       *          check icon to use when painting menu item
674       * @param arrowIcon
675       *          arrow icon to use when painting menu item
676       * @param background
677       *          Background color of the menu item
678       * @param foreground
679       *          Foreground color of the menu item
680       * @param defaultTextIconGap
681       *          space to use between icon and text when painting menu item
682       */
683      protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
684                                   Icon arrowIcon, Color background,
685                                   Color foreground, int defaultTextIconGap)
686      {
687        JMenuItem m = (JMenuItem) c;
688    
689        // Fetch fonts.
690        Font oldFont = g.getFont();
691        Font font = c.getFont();
692        g.setFont(font);
693        FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
694    
695        // Create accelerator string.
696        String accelText = getAcceleratorString(m);
697    
698        // Layout menu item. The result gets stored in the rectangle fields
699        // of this class.
700        resetRectangles(m);
701    
702        layoutMenuItem(m, accelText);
703    
704        // Paint the background.
705        paintBackground(g, m, background);
706    
707        Color oldColor = g.getColor();
708    
709        // Paint the check icon.
710        if (checkIcon != null)
711          {
712            checkIcon.paintIcon(m, g, checkIconRect.x, checkIconRect.y);
713          }
714    
715        // Paint the icon.
716        ButtonModel model = m.getModel();
717        if (m.getIcon() != null)
718          {
719            // Determine icon depending on the menu item
720            // state (normal/disabled/pressed).
721            Icon icon;
722            if (! m.isEnabled())
723              {
724                icon = m.getDisabledIcon();
725              }
726            else if (model.isPressed() && model.isArmed())
727              {
728                icon = m.getPressedIcon();
729                if (icon == null)
730                  {
731                    icon = m.getIcon();
732                  }
733              }
734            else
735              {
736                icon = m.getIcon();
737              }
738    
739            if (icon != null)
740              {
741                icon.paintIcon(m, g, iconRect.x, iconRect.y);
742              }
743          }
744    
745        // Paint the text.
746        String text = m.getText();
747        if (text != null)
748          {
749            // Handle HTML.
750            View html = (View) m.getClientProperty(BasicHTML.propertyKey);
751            if (html != null)
752              {
753                html.paint(g, textRect);
754              }
755            else
756              {
757                paintText(g, m, textRect, text);
758              }
759          }
760    
761        // Paint accelerator text.
762        if (! accelText.equals(""))
763          {
764            // Align the accelerator text. In getPreferredMenuItemSize() we
765            // store a client property 'maxAccelWidth' in the parent which holds
766            // the maximum accelerator width for the children of this parent.
767            // We use this here to align the accelerators properly.
768            int accelOffset = 0;
769            Container parent = m.getParent();
770            if (parent != null && parent instanceof JComponent)
771              {
772                JComponent p = (JComponent) parent;
773                Integer maxAccelWidth =
774                  (Integer) p.getClientProperty("maxAccelWidth");
775                int maxAccelValue = maxAccelWidth == null ? 0
776                                                        : maxAccelWidth.intValue();
777                accelOffset = maxAccelValue - accelRect.width;
778              }
779    
780            g.setFont(acceleratorFont);
781            if (! m.isEnabled())
782              {
783                // Paint accelerator disabled.
784                g.setColor(disabledForeground);
785              }
786            else
787              {
788                if (m.isArmed() || (m instanceof JMenu && m.isSelected()))
789                  g.setColor(acceleratorSelectionForeground);
790                else
791                  g.setColor(acceleratorForeground);
792              }
793            g.drawString(accelText, accelRect.x - accelOffset,
794                         accelRect.y + accelFm.getAscent());
795          }
796    
797        // Paint arrow.
798        if (arrowIcon != null
799            && ! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
800          {
801            arrowIcon.paintIcon(m, g, arrowIconRect.x, arrowIconRect.y);
802          }
803    
804        g.setFont(oldFont);
805        g.setColor(oldColor);
806    
807      }
808    
809      /**
810       * Paints label for the given menu item
811       *
812       * @param g
813       *          The graphics context used to paint this menu item
814       * @param menuItem
815       *          menu item for which to draw its label
816       * @param textRect
817       *          rectangle specifiying position of the text relative to the given
818       *          menu item
819       * @param text
820       *          label of the menu item
821       */
822      protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect,
823                               String text)
824      {
825        Font f = menuItem.getFont();
826        g.setFont(f);
827        FontMetrics fm = g.getFontMetrics(f);
828    
829        if (text != null && !text.equals(""))
830          {
831            if (menuItem.isEnabled())
832              {
833                // Menu item is considered to be highlighted when it is selected.
834                // But not if it's a JCheckBoxMenuItem
835                ButtonModel mod = menuItem.getModel();
836                if ((menuItem.isSelected() && checkIcon == null)
837                    || (mod != null && mod.isArmed())
838                    && (menuItem.getParent() instanceof MenuElement))
839                  g.setColor(selectionForeground);
840                else
841                  g.setColor(menuItem.getForeground());
842              }
843            else
844              // FIXME: should fix this to use 'disabledForeground', but its
845              // default value in BasicLookAndFeel is null.
846    
847              // FIXME: should there be different foreground colours for selected
848              // or deselected, when disabled?
849              g.setColor(Color.gray);
850    
851            int mnemonicIndex = menuItem.getDisplayedMnemonicIndex();
852    
853            if (mnemonicIndex != -1)
854              BasicGraphicsUtils.drawStringUnderlineCharAt(menuItem, g, text,
855                                                           mnemonicIndex,
856                                                           textRect.x,
857                                                           textRect.y
858                                                               + fm.getAscent());
859            else
860              BasicGraphicsUtils.drawString(menuItem, g, text, 0, textRect.x,
861                                            textRect.y + fm.getAscent());
862          }
863      }
864    
865      /**
866       * This method uninstalls the components for this {@link JMenuItem}.
867       *
868       * @param menuItem
869       *          The {@link JMenuItem} to uninstall components for.
870       */
871      protected void uninstallComponents(JMenuItem menuItem)
872      {
873        // FIXME: need to implement
874      }
875    
876      /**
877       * This method uninstalls the defaults and sets any objects created during
878       * install to null
879       */
880      protected void uninstallDefaults()
881      {
882        menuItem.setForeground(null);
883        menuItem.setBackground(null);
884        menuItem.setBorder(null);
885        menuItem.setMargin(null);
886        menuItem.setBackground(null);
887        menuItem.setBorder(null);
888        menuItem.setFont(null);
889        menuItem.setForeground(null);
890        menuItem.setMargin(null);
891        acceleratorFont = null;
892        acceleratorForeground = null;
893        acceleratorSelectionForeground = null;
894        arrowIcon = null;
895        selectionBackground = null;
896        selectionForeground = null;
897        acceleratorDelimiter = null;
898      }
899    
900      /**
901       * Uninstalls any keyboard actions.
902       */
903      protected void uninstallKeyboardActions()
904      {
905        SwingUtilities.replaceUIInputMap(menuItem,
906                                         JComponent.WHEN_IN_FOCUSED_WINDOW, null);
907      }
908    
909      /**
910       * Unregisters all the listeners that this UI delegate was using.
911       */
912      protected void uninstallListeners()
913      {
914        menuItem.removeMouseListener(mouseInputListener);
915        menuItem.removeMenuDragMouseListener(menuDragMouseListener);
916        menuItem.removeMenuKeyListener(menuKeyListener);
917        menuItem.removeItemListener(itemListener);
918        menuItem.removePropertyChangeListener(propertyChangeListener);
919      }
920    
921      /**
922       * Performs the opposite of installUI. Any properties or resources that need
923       * to be cleaned up will be done now. It will also uninstall any listeners it
924       * has. In addition, any properties of this UI will be nulled.
925       *
926       * @param c
927       *          The {@link JComponent} that is having this UI uninstalled.
928       */
929      public void uninstallUI(JComponent c)
930      {
931        uninstallListeners();
932        uninstallDefaults();
933        uninstallComponents(menuItem);
934        c.putClientProperty(BasicGraphicsUtils.CACHED_TEXT_LAYOUT, null);
935        menuItem = null;
936      }
937    
938      /**
939       * This method calls paint.
940       *
941       * @param g
942       *          The graphics context used to paint this menu item
943       * @param c
944       *          The menu item to paint
945       */
946      public void update(Graphics g, JComponent c)
947      {
948        paint(g, c);
949      }
950    
951      /**
952       * This class handles mouse events occuring inside the menu item. Most of the
953       * events are forwarded for processing to MenuSelectionManager of the current
954       * menu hierarchy.
955       */
956      protected class MouseInputHandler implements MouseInputListener
957      {
958        /**
959         * Creates a new MouseInputHandler object.
960         */
961        protected MouseInputHandler()
962        {
963          // Nothing to do here.
964        }
965    
966        /**
967         * This method is called when mouse is clicked on the menu item. It forwards
968         * this event to MenuSelectionManager.
969         *
970         * @param e
971         *          A {@link MouseEvent}.
972         */
973        public void mouseClicked(MouseEvent e)
974        {
975          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
976          manager.processMouseEvent(e);
977        }
978    
979        /**
980         * This method is called when mouse is dragged inside the menu item. It
981         * forwards this event to MenuSelectionManager.
982         *
983         * @param e
984         *          A {@link MouseEvent}.
985         */
986        public void mouseDragged(MouseEvent e)
987        {
988          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
989          manager.processMouseEvent(e);
990        }
991    
992        /**
993         * This method is called when mouse enters menu item. When this happens menu
994         * item is considered to be selected and selection path in
995         * MenuSelectionManager is set. This event is also forwarded to
996         * MenuSelection Manager for further processing.
997         *
998         * @param e
999         *          A {@link MouseEvent}.
1000         */
1001        public void mouseEntered(MouseEvent e)
1002        {
1003          Component source = (Component) e.getSource();
1004          if (source.getParent() instanceof MenuElement)
1005            {
1006              MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1007              manager.setSelectedPath(getPath());
1008              manager.processMouseEvent(e);
1009            }
1010        }
1011    
1012        /**
1013         * This method is called when mouse exits menu item. The event is forwarded
1014         * to MenuSelectionManager for processing.
1015         *
1016         * @param e
1017         *          A {@link MouseEvent}.
1018         */
1019        public void mouseExited(MouseEvent e)
1020        {
1021          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1022          manager.processMouseEvent(e);
1023        }
1024    
1025        /**
1026         * This method is called when mouse is inside the menu item. This event is
1027         * forwarder to MenuSelectionManager for further processing.
1028         *
1029         * @param e
1030         *          A {@link MouseEvent}.
1031         */
1032        public void mouseMoved(MouseEvent e)
1033        {
1034          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1035          manager.processMouseEvent(e);
1036        }
1037    
1038        /**
1039         * This method is called when mouse is pressed. This event is forwarded to
1040         * MenuSelectionManager for further processing.
1041         *
1042         * @param e
1043         *          A {@link MouseEvent}.
1044         */
1045        public void mousePressed(MouseEvent e)
1046        {
1047          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1048          manager.processMouseEvent(e);
1049        }
1050    
1051        /**
1052         * This method is called when mouse is released. If the mouse is released
1053         * inside this menuItem, then this menu item is considered to be chosen and
1054         * the menu hierarchy should be closed.
1055         *
1056         * @param e
1057         *          A {@link MouseEvent}.
1058         */
1059        public void mouseReleased(MouseEvent e)
1060        {
1061          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1062          int x = e.getX();
1063          int y = e.getY();
1064          if (x > 0 && x < menuItem.getWidth() && y > 0
1065              && y < menuItem.getHeight())
1066            {
1067              doClick(manager);
1068            }
1069          else
1070            manager.processMouseEvent(e);
1071        }
1072      }
1073    
1074      /**
1075       * This class handles mouse dragged events.
1076       */
1077      private class MenuDragMouseHandler implements MenuDragMouseListener
1078      {
1079        /**
1080         * Tbis method is invoked when mouse is dragged over the menu item.
1081         *
1082         * @param e
1083         *          The MenuDragMouseEvent
1084         */
1085        public void menuDragMouseDragged(MenuDragMouseEvent e)
1086        {
1087          MenuSelectionManager manager = e.getMenuSelectionManager();
1088          manager.setSelectedPath(e.getPath());
1089        }
1090    
1091        /**
1092         * Tbis method is invoked when mouse enters the menu item while it is being
1093         * dragged.
1094         *
1095         * @param e
1096         *          The MenuDragMouseEvent
1097         */
1098        public void menuDragMouseEntered(MenuDragMouseEvent e)
1099        {
1100          MenuSelectionManager manager = e.getMenuSelectionManager();
1101          manager.setSelectedPath(e.getPath());
1102        }
1103    
1104        /**
1105         * Tbis method is invoked when mouse exits the menu item while it is being
1106         * dragged
1107         *
1108         * @param e the MenuDragMouseEvent
1109         */
1110        public void menuDragMouseExited(MenuDragMouseEvent e)
1111        {
1112          // Nothing to do here yet.
1113        }
1114    
1115        /**
1116         * Tbis method is invoked when mouse was dragged and released inside the
1117         * menu item.
1118         *
1119         * @param e
1120         *          The MenuDragMouseEvent
1121         */
1122        public void menuDragMouseReleased(MenuDragMouseEvent e)
1123        {
1124          MenuSelectionManager manager = e.getMenuSelectionManager();
1125          int x = e.getX();
1126          int y = e.getY();
1127          if (x >= 0 && x < menuItem.getWidth() && y >= 0
1128              && y < menuItem.getHeight())
1129            doClick(manager);
1130          else
1131            manager.clearSelectedPath();
1132        }
1133      }
1134    
1135      /**
1136       * This class handles key events occuring when menu item is visible on the
1137       * screen.
1138       */
1139      private class MenuKeyHandler implements MenuKeyListener
1140      {
1141        /**
1142         * This method is invoked when key has been pressed
1143         *
1144         * @param e
1145         *          A {@link MenuKeyEvent}.
1146         */
1147        public void menuKeyPressed(MenuKeyEvent e)
1148        {
1149          // TODO: What should be done here, if anything?
1150        }
1151    
1152        /**
1153         * This method is invoked when key has been pressed
1154         *
1155         * @param e
1156         *          A {@link MenuKeyEvent}.
1157         */
1158        public void menuKeyReleased(MenuKeyEvent e)
1159        {
1160          // TODO: What should be done here, if anything?
1161        }
1162    
1163        /**
1164         * This method is invoked when key has been typed It handles the mnemonic
1165         * key for the menu item.
1166         *
1167         * @param e
1168         *          A {@link MenuKeyEvent}.
1169         */
1170        public void menuKeyTyped(MenuKeyEvent e)
1171        {
1172          // TODO: What should be done here, if anything?
1173        }
1174      }
1175    
1176      /**
1177       * Helper class that listens for item changes to the properties of the {@link
1178       * JMenuItem}.
1179       */
1180      private class ItemHandler implements ItemListener
1181      {
1182        /**
1183         * This method is called when one of the menu item changes.
1184         *
1185         * @param evt A {@link ItemEvent}.
1186         */
1187        public void itemStateChanged(ItemEvent evt)
1188        {
1189          boolean state = false;
1190          if (menuItem instanceof JCheckBoxMenuItem)
1191            {
1192              if (evt.getStateChange() == ItemEvent.SELECTED)
1193                state = true;
1194              ((JCheckBoxMenuItem) menuItem).setState(state);
1195            }
1196          menuItem.revalidate();
1197          menuItem.repaint();
1198        }
1199      }
1200    
1201      /**
1202       * A helper method to create the accelerator string from the menu item's
1203       * accelerator property. The returned string is empty if there is
1204       * no accelerator defined.
1205       *
1206       * @param m the menu item
1207       *
1208       * @return the accelerator string, not null
1209       */
1210      private String getAcceleratorString(JMenuItem m)
1211      {
1212        // Create accelerator string.
1213        KeyStroke accel = m.getAccelerator();
1214        String accelText = "";
1215        if (accel != null)
1216          {
1217            int mods = accel.getModifiers();
1218            if (mods > 0)
1219              {
1220                accelText = KeyEvent.getKeyModifiersText(mods);
1221                accelText += acceleratorDelimiter;
1222              }
1223            int keycode = accel.getKeyCode();
1224            if (keycode != 0)
1225              accelText += KeyEvent.getKeyText(keycode);
1226            else
1227              accelText += accel.getKeyChar();
1228          }
1229        return accelText;
1230      }
1231    
1232      /**
1233       * Resets the cached layout rectangles. If <code>i</code> is not null, then
1234       * the view rectangle is set to the inner area of the component, otherwise
1235       * it is set to (0, 0, Short.MAX_VALUE, Short.MAX_VALUE), this is needed
1236       * for layouting.
1237       *
1238       * @param i the component for which to initialize the rectangles
1239       */
1240      private void resetRectangles(JMenuItem i)
1241      {
1242        // Reset rectangles.
1243        iconRect.setBounds(0, 0, 0, 0);
1244        textRect.setBounds(0, 0, 0, 0);
1245        accelRect.setBounds(0, 0, 0, 0);
1246        checkIconRect.setBounds(0, 0, 0, 0);
1247        arrowIconRect.setBounds(0, 0, 0, 0);
1248        if (i == null)
1249          viewRect.setBounds(0, 0, Short.MAX_VALUE, Short.MAX_VALUE);
1250        else
1251          {
1252            Insets insets = i.getInsets();
1253            viewRect.setBounds(insets.left, insets.top,
1254                               i.getWidth() - insets.left - insets.right,
1255                               i.getHeight() - insets.top - insets.bottom);
1256          }
1257      }
1258    
1259      /**
1260       * A helper method that lays out the menu item. The layout is stored
1261       * in the fields of this class.
1262       *
1263       * @param m the menu item to layout
1264       * @param accelText the accelerator text
1265       */
1266      private void layoutMenuItem(JMenuItem m, String accelText)
1267      {
1268        // Fetch the fonts.
1269        Font font = m.getFont();
1270        FontMetrics fm = m.getFontMetrics(font);
1271        FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
1272    
1273        String text = m.getText();
1274        SwingUtilities.layoutCompoundLabel(m, fm, text, m.getIcon(),
1275                                           m.getVerticalAlignment(),
1276                                           m.getHorizontalAlignment(),
1277                                           m.getVerticalTextPosition(),
1278                                           m.getHorizontalTextPosition(),
1279                                           viewRect, iconRect, textRect,
1280                                           defaultTextIconGap);
1281    
1282        // Initialize accelerator width and height.
1283        if (! accelText.equals(""))
1284          {
1285            accelRect.width = accelFm.stringWidth(accelText);
1286            accelRect.height = accelFm.getHeight();
1287          }
1288    
1289        // Initialize check and arrow icon width and height.
1290        if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1291          {
1292            if (checkIcon != null)
1293              {
1294                checkIconRect.width = checkIcon.getIconWidth();
1295                checkIconRect.height = checkIcon.getIconHeight();
1296              }
1297            if (arrowIcon != null)
1298              {
1299                arrowIconRect.width = arrowIcon.getIconWidth();
1300                arrowIconRect.height = arrowIcon.getIconHeight();
1301              }
1302          }
1303    
1304        // The union of the icon and text of the menu item is the 'label area'.
1305        cachedRect.setBounds(textRect);
1306        Rectangle labelRect = SwingUtilities.computeUnion(iconRect.x,
1307                                                          iconRect.y,
1308                                                          iconRect.width,
1309                                                          iconRect.height,
1310                                                          cachedRect);
1311        textRect.x += defaultTextIconGap;
1312        iconRect.x += defaultTextIconGap;
1313    
1314        // Layout accelerator rect.
1315        accelRect.x = viewRect.x + viewRect.width - arrowIconRect.width
1316          - defaultTextIconGap - accelRect.width;
1317        // Layout check and arrow icons only when not in toplevel menu.
1318        if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1319          {
1320            checkIconRect.x = viewRect.x + defaultTextIconGap;
1321            textRect.x += defaultTextIconGap + checkIconRect.width;
1322            iconRect.x += defaultTextIconGap + checkIconRect.width;
1323            arrowIconRect.x = viewRect.x + viewRect.width - defaultTextIconGap
1324              - arrowIconRect.width;
1325          }
1326    
1327        // Align the accelerator text and all the icons vertically centered to
1328        // the menu text.
1329        accelRect.y = labelRect.y + (labelRect.height / 2)
1330          - (accelRect.height / 2);
1331        if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1332          {
1333            arrowIconRect.y = labelRect.y + (labelRect.height / 2)
1334              - (arrowIconRect.height / 2);
1335            checkIconRect.y = labelRect.y + (labelRect.height / 2)
1336              - (checkIconRect.height / 2);
1337          }
1338      }
1339    }