OSDN Git Service

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