001/* BasicMenuItemUI.java -- 002 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.plaf.basic; 040 041import gnu.classpath.SystemProperties; 042 043import java.awt.Color; 044import java.awt.Component; 045import java.awt.Container; 046import java.awt.Dimension; 047import java.awt.Font; 048import java.awt.FontMetrics; 049import java.awt.Graphics; 050import java.awt.Insets; 051import java.awt.Rectangle; 052import java.awt.event.ActionEvent; 053import java.awt.event.ItemEvent; 054import java.awt.event.ItemListener; 055import java.awt.event.KeyEvent; 056import java.awt.event.MouseEvent; 057import java.awt.font.FontRenderContext; 058import java.awt.font.TextLayout; 059import java.awt.geom.AffineTransform; 060import java.beans.PropertyChangeEvent; 061import java.beans.PropertyChangeListener; 062import java.util.ArrayList; 063 064import javax.swing.AbstractAction; 065import javax.swing.AbstractButton; 066import javax.swing.ActionMap; 067import javax.swing.ButtonModel; 068import javax.swing.Icon; 069import javax.swing.InputMap; 070import javax.swing.JCheckBoxMenuItem; 071import javax.swing.JComponent; 072import javax.swing.JMenu; 073import javax.swing.JMenuItem; 074import javax.swing.JPopupMenu; 075import javax.swing.KeyStroke; 076import javax.swing.LookAndFeel; 077import javax.swing.MenuElement; 078import javax.swing.MenuSelectionManager; 079import javax.swing.SwingConstants; 080import javax.swing.SwingUtilities; 081import javax.swing.UIDefaults; 082import javax.swing.UIManager; 083import javax.swing.event.MenuDragMouseEvent; 084import javax.swing.event.MenuDragMouseListener; 085import javax.swing.event.MenuKeyEvent; 086import javax.swing.event.MenuKeyListener; 087import javax.swing.event.MouseInputListener; 088import javax.swing.plaf.ActionMapUIResource; 089import javax.swing.plaf.ComponentInputMapUIResource; 090import javax.swing.plaf.ComponentUI; 091import javax.swing.plaf.MenuItemUI; 092import javax.swing.text.View; 093 094/** 095 * UI Delegate for JMenuItem. 096 */ 097public 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}