OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicMenuItemUI.java
1 /* BasicMenuItemUI.java --
2    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10  
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import gnu.classpath.SystemProperties;
42
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Container;
46 import java.awt.Dimension;
47 import java.awt.Font;
48 import java.awt.FontMetrics;
49 import java.awt.Graphics;
50 import java.awt.Insets;
51 import java.awt.Rectangle;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.KeyEvent;
56 import java.awt.event.MouseEvent;
57 import java.awt.font.FontRenderContext;
58 import java.awt.font.TextLayout;
59 import java.awt.geom.AffineTransform;
60 import java.beans.PropertyChangeEvent;
61 import java.beans.PropertyChangeListener;
62 import java.util.ArrayList;
63
64 import javax.swing.AbstractAction;
65 import javax.swing.AbstractButton;
66 import javax.swing.ActionMap;
67 import javax.swing.ButtonModel;
68 import javax.swing.Icon;
69 import javax.swing.InputMap;
70 import javax.swing.JCheckBoxMenuItem;
71 import javax.swing.JComponent;
72 import javax.swing.JMenu;
73 import javax.swing.JMenuItem;
74 import javax.swing.JPopupMenu;
75 import javax.swing.KeyStroke;
76 import javax.swing.LookAndFeel;
77 import javax.swing.MenuElement;
78 import javax.swing.MenuSelectionManager;
79 import javax.swing.SwingConstants;
80 import javax.swing.SwingUtilities;
81 import javax.swing.UIDefaults;
82 import javax.swing.UIManager;
83 import javax.swing.event.MenuDragMouseEvent;
84 import javax.swing.event.MenuDragMouseListener;
85 import javax.swing.event.MenuKeyEvent;
86 import javax.swing.event.MenuKeyListener;
87 import javax.swing.event.MouseInputListener;
88 import javax.swing.plaf.ActionMapUIResource;
89 import javax.swing.plaf.ComponentInputMapUIResource;
90 import javax.swing.plaf.ComponentUI;
91 import javax.swing.plaf.MenuItemUI;
92 import javax.swing.text.View;
93
94 /**
95  * UI Delegate for JMenuItem.
96  */
97 public class BasicMenuItemUI extends MenuItemUI
98 {
99   /**
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 }