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