1. /*
  2. * @(#)BasicMenuItemUI.java 1.118 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.PropertyChangeEvent;
  11. import java.beans.PropertyChangeListener;
  12. import javax.swing.*;
  13. import javax.swing.event.*;
  14. import javax.swing.border.*;
  15. import javax.swing.plaf.*;
  16. import javax.swing.text.View;
  17. /**
  18. * BasicMenuItem implementation
  19. *
  20. * @version 1.118 01/23/03
  21. * @author Georges Saab
  22. * @author David Karlton
  23. * @author Arnaud Weber
  24. * @author Fredrik Lagerblad
  25. */
  26. public class BasicMenuItemUI extends MenuItemUI
  27. {
  28. protected JMenuItem menuItem = null;
  29. protected Color selectionBackground;
  30. protected Color selectionForeground;
  31. protected Color disabledForeground;
  32. protected Color acceleratorForeground;
  33. protected Color acceleratorSelectionForeground;
  34. private String acceleratorDelimiter;
  35. protected int defaultTextIconGap;
  36. protected Font acceleratorFont;
  37. protected MouseInputListener mouseInputListener;
  38. protected MenuDragMouseListener menuDragMouseListener;
  39. protected MenuKeyListener menuKeyListener;
  40. private PropertyChangeListener propertyChangeListener;
  41. protected Icon arrowIcon = null;
  42. protected Icon checkIcon = null;
  43. protected boolean oldBorderPainted;
  44. /** Used for accelerator binding, lazily created. */
  45. InputMap windowInputMap;
  46. /* diagnostic aids -- should be false for production builds. */
  47. private static final boolean TRACE = false; // trace creates and disposes
  48. private static final boolean VERBOSE = false; // show reuse hits/misses
  49. private static final boolean DEBUG = false; // show bad params, misc.
  50. /* Client Property keys for text and accelerator text widths */
  51. static final String MAX_TEXT_WIDTH = "maxTextWidth";
  52. static final String MAX_ACC_WIDTH = "maxAccWidth";
  53. public static ComponentUI createUI(JComponent c) {
  54. return new BasicMenuItemUI();
  55. }
  56. public void installUI(JComponent c) {
  57. menuItem = (JMenuItem) c;
  58. installDefaults();
  59. installComponents(menuItem);
  60. installListeners();
  61. installKeyboardActions();
  62. }
  63. protected void installDefaults() {
  64. String prefix = getPropertyPrefix();
  65. acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont");
  66. menuItem.setOpaque(true);
  67. if(menuItem.getMargin() == null ||
  68. (menuItem.getMargin() instanceof UIResource)) {
  69. menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
  70. }
  71. defaultTextIconGap = 4; // Should be from table
  72. LookAndFeel.installBorder(menuItem, prefix + ".border");
  73. oldBorderPainted = menuItem.isBorderPainted();
  74. menuItem.setBorderPainted( ( (Boolean) (UIManager.get(prefix + ".borderPainted")) ).booleanValue() );
  75. LookAndFeel.installColorsAndFont(menuItem,
  76. prefix + ".background",
  77. prefix + ".foreground",
  78. prefix + ".font");
  79. // MenuItem specific defaults
  80. if (selectionBackground == null ||
  81. selectionBackground instanceof UIResource) {
  82. selectionBackground =
  83. UIManager.getColor(prefix + ".selectionBackground");
  84. }
  85. if (selectionForeground == null ||
  86. selectionForeground instanceof UIResource) {
  87. selectionForeground =
  88. UIManager.getColor(prefix + ".selectionForeground");
  89. }
  90. if (disabledForeground == null ||
  91. disabledForeground instanceof UIResource) {
  92. disabledForeground =
  93. UIManager.getColor(prefix + ".disabledForeground");
  94. }
  95. if (acceleratorForeground == null ||
  96. acceleratorForeground instanceof UIResource) {
  97. acceleratorForeground =
  98. UIManager.getColor(prefix + ".acceleratorForeground");
  99. }
  100. if (acceleratorSelectionForeground == null ||
  101. acceleratorSelectionForeground instanceof UIResource) {
  102. acceleratorSelectionForeground =
  103. UIManager.getColor(prefix + ".acceleratorSelectionForeground");
  104. }
  105. // Get accelerator delimiter
  106. acceleratorDelimiter =
  107. UIManager.getString("MenuItem.acceleratorDelimiter");
  108. if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; }
  109. // Icons
  110. if (arrowIcon == null ||
  111. arrowIcon instanceof UIResource) {
  112. arrowIcon = UIManager.getIcon(prefix + ".arrowIcon");
  113. }
  114. if (checkIcon == null ||
  115. checkIcon instanceof UIResource) {
  116. checkIcon = UIManager.getIcon(prefix + ".checkIcon");
  117. }
  118. }
  119. /**
  120. * @since 1.3
  121. */
  122. protected void installComponents(JMenuItem menuItem){
  123. BasicHTML.updateRenderer(menuItem, menuItem.getText());
  124. }
  125. protected String getPropertyPrefix() {
  126. return "MenuItem";
  127. }
  128. protected void installListeners() {
  129. if ((mouseInputListener = createMouseInputListener(menuItem)) != null) {
  130. menuItem.addMouseListener(mouseInputListener);
  131. menuItem.addMouseMotionListener(mouseInputListener);
  132. }
  133. if ((menuDragMouseListener = createMenuDragMouseListener(menuItem)) != null) {
  134. menuItem.addMenuDragMouseListener(menuDragMouseListener);
  135. }
  136. if ((menuKeyListener = createMenuKeyListener(menuItem)) != null) {
  137. menuItem.addMenuKeyListener(menuKeyListener);
  138. }
  139. if ((propertyChangeListener = createPropertyChangeListener(menuItem)) != null) {
  140. menuItem.addPropertyChangeListener(propertyChangeListener);
  141. }
  142. }
  143. protected void installKeyboardActions() {
  144. ActionMap actionMap = getActionMap();
  145. SwingUtilities.replaceUIActionMap(menuItem, actionMap);
  146. updateAcceleratorBinding();
  147. }
  148. public void uninstallUI(JComponent c) {
  149. menuItem = (JMenuItem)c;
  150. uninstallDefaults();
  151. uninstallComponents(menuItem);
  152. uninstallListeners();
  153. uninstallKeyboardActions();
  154. //Remove the textWidth and accWidth values from the parent's Client Properties.
  155. Container parent = menuItem.getParent();
  156. if ( (parent != null && parent instanceof JComponent) &&
  157. !(menuItem instanceof JMenu && ((JMenu) menuItem).isTopLevelMenu())) {
  158. JComponent p = (JComponent) parent;
  159. p.putClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH, null );
  160. p.putClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH, null );
  161. }
  162. menuItem = null;
  163. }
  164. protected void uninstallDefaults() {
  165. LookAndFeel.uninstallBorder(menuItem);
  166. menuItem.setBorderPainted( oldBorderPainted );
  167. if (menuItem.getMargin() instanceof UIResource)
  168. menuItem.setMargin(null);
  169. if (arrowIcon instanceof UIResource)
  170. arrowIcon = null;
  171. if (checkIcon instanceof UIResource)
  172. checkIcon = null;
  173. }
  174. /**
  175. * @since 1.3
  176. */
  177. protected void uninstallComponents(JMenuItem menuItem){
  178. BasicHTML.updateRenderer(menuItem, "");
  179. }
  180. protected void uninstallListeners() {
  181. if (mouseInputListener != null) {
  182. menuItem.removeMouseListener(mouseInputListener);
  183. menuItem.removeMouseMotionListener(mouseInputListener);
  184. }
  185. if (menuDragMouseListener != null) {
  186. menuItem.removeMenuDragMouseListener(menuDragMouseListener);
  187. }
  188. if (menuKeyListener != null) {
  189. menuItem.removeMenuKeyListener(menuKeyListener);
  190. }
  191. if (propertyChangeListener != null) {
  192. menuItem.removePropertyChangeListener(propertyChangeListener);
  193. }
  194. mouseInputListener = null;
  195. menuDragMouseListener = null;
  196. menuKeyListener = null;
  197. propertyChangeListener = null;
  198. }
  199. protected void uninstallKeyboardActions() {
  200. SwingUtilities.replaceUIActionMap(menuItem, null);
  201. if (windowInputMap != null) {
  202. SwingUtilities.replaceUIInputMap(menuItem, JComponent.
  203. WHEN_IN_FOCUSED_WINDOW, null);
  204. windowInputMap = null;
  205. }
  206. }
  207. protected MouseInputListener createMouseInputListener(JComponent c) {
  208. return new MouseInputHandler();
  209. }
  210. protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
  211. return new MenuDragMouseHandler();
  212. }
  213. protected MenuKeyListener createMenuKeyListener(JComponent c) {
  214. return new MenuKeyHandler();
  215. }
  216. private PropertyChangeListener createPropertyChangeListener(JComponent c) {
  217. return new PropertyChangeHandler();
  218. }
  219. ActionMap getActionMap() {
  220. String propertyPrefix = getPropertyPrefix();
  221. String uiKey = propertyPrefix + ".actionMap";
  222. ActionMap am = (ActionMap)UIManager.get(uiKey);
  223. if (am == null) {
  224. am = createActionMap();
  225. UIManager.getLookAndFeelDefaults().put(uiKey, am);
  226. }
  227. return am;
  228. }
  229. ActionMap createActionMap() {
  230. ActionMap map = new ActionMapUIResource();
  231. map.put("doClick", new ClickAction());
  232. // Set the ActionMap's parent to the Auditory Feedback Action Map
  233. BasicLookAndFeel lf = (BasicLookAndFeel)UIManager.getLookAndFeel();
  234. ActionMap audioMap = lf.getAudioActionMap();
  235. map.setParent(audioMap);
  236. return map;
  237. }
  238. InputMap createInputMap(int condition) {
  239. if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
  240. return new ComponentInputMapUIResource(menuItem);
  241. }
  242. return null;
  243. }
  244. void updateAcceleratorBinding() {
  245. KeyStroke accelerator = menuItem.getAccelerator();
  246. if (windowInputMap != null) {
  247. windowInputMap.clear();
  248. }
  249. if (accelerator != null) {
  250. if (windowInputMap == null) {
  251. windowInputMap = createInputMap(JComponent.
  252. WHEN_IN_FOCUSED_WINDOW);
  253. SwingUtilities.replaceUIInputMap(menuItem,
  254. JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
  255. }
  256. windowInputMap.put(accelerator, "doClick");
  257. }
  258. }
  259. public Dimension getMinimumSize(JComponent c) {
  260. Dimension d = null;
  261. View v = (View) c.getClientProperty(BasicHTML.propertyKey);
  262. if (v != null) {
  263. d = getPreferredSize(c);
  264. d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
  265. }
  266. return d;
  267. }
  268. public Dimension getPreferredSize(JComponent c) {
  269. return getPreferredMenuItemSize(c,
  270. checkIcon,
  271. arrowIcon,
  272. defaultTextIconGap);
  273. }
  274. public Dimension getMaximumSize(JComponent c) {
  275. Dimension d = null;
  276. View v = (View) c.getClientProperty(BasicHTML.propertyKey);
  277. if (v != null) {
  278. d = getPreferredSize(c);
  279. d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
  280. }
  281. return d;
  282. }
  283. // these rects are used for painting and preferredsize calculations.
  284. // they used to be regenerated constantly. Now they are reused.
  285. static Rectangle zeroRect = new Rectangle(0,0,0,0);
  286. static Rectangle iconRect = new Rectangle();
  287. static Rectangle textRect = new Rectangle();
  288. static Rectangle acceleratorRect = new Rectangle();
  289. static Rectangle checkIconRect = new Rectangle();
  290. static Rectangle arrowIconRect = new Rectangle();
  291. static Rectangle viewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
  292. static Rectangle r = new Rectangle();
  293. private void resetRects() {
  294. iconRect.setBounds(zeroRect);
  295. textRect.setBounds(zeroRect);
  296. acceleratorRect.setBounds(zeroRect);
  297. checkIconRect.setBounds(zeroRect);
  298. arrowIconRect.setBounds(zeroRect);
  299. viewRect.setBounds(0,0,Short.MAX_VALUE, Short.MAX_VALUE);
  300. r.setBounds(zeroRect);
  301. }
  302. protected Dimension getPreferredMenuItemSize(JComponent c,
  303. Icon checkIcon,
  304. Icon arrowIcon,
  305. int defaultTextIconGap) {
  306. JMenuItem b = (JMenuItem) c;
  307. Icon icon = (Icon) b.getIcon();
  308. String text = b.getText();
  309. KeyStroke accelerator = b.getAccelerator();
  310. String acceleratorText = "";
  311. if (accelerator != null) {
  312. int modifiers = accelerator.getModifiers();
  313. if (modifiers > 0) {
  314. acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
  315. //acceleratorText += "-";
  316. acceleratorText += acceleratorDelimiter;
  317. }
  318. int keyCode = accelerator.getKeyCode();
  319. if (keyCode != 0) {
  320. acceleratorText += KeyEvent.getKeyText(keyCode);
  321. } else {
  322. acceleratorText += accelerator.getKeyChar();
  323. }
  324. }
  325. Font font = b.getFont();
  326. FontMetrics fm = b.getToolkit().getFontMetrics(font);
  327. FontMetrics fmAccel = b.getToolkit().getFontMetrics( acceleratorFont );
  328. resetRects();
  329. layoutMenuItem(
  330. fm, text, fmAccel, acceleratorText, icon, checkIcon, arrowIcon,
  331. b.getVerticalAlignment(), b.getHorizontalAlignment(),
  332. b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
  333. viewRect, iconRect, textRect, acceleratorRect, checkIconRect, arrowIconRect,
  334. text == null ? 0 : defaultTextIconGap,
  335. defaultTextIconGap
  336. );
  337. // find the union of the icon and text rects
  338. r.setBounds(textRect);
  339. r = SwingUtilities.computeUnion(iconRect.x,
  340. iconRect.y,
  341. iconRect.width,
  342. iconRect.height,
  343. r);
  344. // r = iconRect.union(textRect);
  345. // To make the accelerator texts appear in a column, find the widest MenuItem text
  346. // and the widest accelerator text.
  347. //Get the parent, which stores the information.
  348. Container parent = menuItem.getParent();
  349. //Check the parent, and see that it is not a top-level menu.
  350. if (parent != null && parent instanceof JComponent &&
  351. !(menuItem instanceof JMenu && ((JMenu) menuItem).isTopLevelMenu())) {
  352. JComponent p = (JComponent) parent;
  353. //Get widest text so far from parent, if no one exists null is returned.
  354. Integer maxTextWidth = (Integer) p.getClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH);
  355. Integer maxAccWidth = (Integer) p.getClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH);
  356. int maxTextValue = maxTextWidth!=null ? maxTextWidth.intValue() : 0;
  357. int maxAccValue = maxAccWidth!=null ? maxAccWidth.intValue() : 0;
  358. //Compare the text widths, and adjust the r.width to the widest.
  359. if (r.width < maxTextValue) {
  360. r.width = maxTextValue;
  361. } else {
  362. p.putClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH, new Integer(r.width) );
  363. }
  364. //Compare the accelarator widths.
  365. if (acceleratorRect.width > maxAccValue) {
  366. maxAccValue = acceleratorRect.width;
  367. p.putClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH, new Integer(acceleratorRect.width) );
  368. }
  369. //Add on the widest accelerator
  370. r.width += maxAccValue;
  371. r.width += defaultTextIconGap;
  372. }
  373. if( useCheckAndArrow() ) {
  374. // Add in the checkIcon
  375. r.width += checkIconRect.width;
  376. r.width += defaultTextIconGap;
  377. // Add in the arrowIcon
  378. r.width += defaultTextIconGap;
  379. r.width += arrowIconRect.width;
  380. }
  381. r.width += 2*defaultTextIconGap;
  382. Insets insets = b.getInsets();
  383. if(insets != null) {
  384. r.width += insets.left + insets.right;
  385. r.height += insets.top + insets.bottom;
  386. }
  387. // if the width is even, bump it up one. This is critical
  388. // for the focus dash line to draw properly
  389. if(r.width%2 == 0) {
  390. r.width++;
  391. }
  392. // if the height is even, bump it up one. This is critical
  393. // for the text to center properly
  394. if(r.height%2 == 0) {
  395. r.height++;
  396. }
  397. /*
  398. if(!(b instanceof JMenu && ((JMenu) b).isTopLevelMenu()) ) {
  399. // Container parent = menuItem.getParent();
  400. JComponent p = (JComponent) parent;
  401. System.out.println("MaxText: "+p.getClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH));
  402. System.out.println("MaxACC"+p.getClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH));
  403. System.out.println("returning pref.width: " + r.width);
  404. System.out.println("Current getSize: " + b.getSize() + "\n");
  405. }*/
  406. return r.getSize();
  407. }
  408. /**
  409. * We draw the background in paintMenuItem()
  410. * so override update (which fills the background of opaque
  411. * components by default) to just call paint().
  412. *
  413. */
  414. public void update(Graphics g, JComponent c) {
  415. paint(g, c);
  416. }
  417. public void paint(Graphics g, JComponent c) {
  418. paintMenuItem(g, c, checkIcon, arrowIcon,
  419. selectionBackground, selectionForeground,
  420. defaultTextIconGap);
  421. }
  422. protected void paintMenuItem(Graphics g, JComponent c,
  423. Icon checkIcon, Icon arrowIcon,
  424. Color background, Color foreground,
  425. int defaultTextIconGap) {
  426. JMenuItem b = (JMenuItem) c;
  427. ButtonModel model = b.getModel();
  428. // Dimension size = b.getSize();
  429. int menuWidth = b.getWidth();
  430. int menuHeight = b.getHeight();
  431. Insets i = c.getInsets();
  432. resetRects();
  433. viewRect.setBounds( 0, 0, menuWidth, menuHeight );
  434. viewRect.x += i.left;
  435. viewRect.y += i.top;
  436. viewRect.width -= (i.right + viewRect.x);
  437. viewRect.height -= (i.bottom + viewRect.y);
  438. Font holdf = g.getFont();
  439. Font f = c.getFont();
  440. g.setFont( f );
  441. FontMetrics fm = g.getFontMetrics( f );
  442. FontMetrics fmAccel = g.getFontMetrics( acceleratorFont );
  443. // get Accelerator text
  444. KeyStroke accelerator = b.getAccelerator();
  445. String acceleratorText = "";
  446. if (accelerator != null) {
  447. int modifiers = accelerator.getModifiers();
  448. if (modifiers > 0) {
  449. acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
  450. //acceleratorText += "-";
  451. acceleratorText += acceleratorDelimiter;
  452. }
  453. int keyCode = accelerator.getKeyCode();
  454. if (keyCode != 0) {
  455. acceleratorText += KeyEvent.getKeyText(keyCode);
  456. } else {
  457. acceleratorText += accelerator.getKeyChar();
  458. }
  459. }
  460. // layout the text and icon
  461. String text = layoutMenuItem(
  462. fm, b.getText(), fmAccel, acceleratorText, b.getIcon(),
  463. checkIcon, arrowIcon,
  464. b.getVerticalAlignment(), b.getHorizontalAlignment(),
  465. b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
  466. viewRect, iconRect, textRect, acceleratorRect,
  467. checkIconRect, arrowIconRect,
  468. b.getText() == null ? 0 : defaultTextIconGap,
  469. defaultTextIconGap
  470. );
  471. // Paint background
  472. paintBackground(g, b, background);
  473. Color holdc = g.getColor();
  474. // Paint the Check
  475. if (checkIcon != null) {
  476. if(model.isArmed() || (c instanceof JMenu && model.isSelected())) {
  477. g.setColor(foreground);
  478. } else {
  479. g.setColor(holdc);
  480. }
  481. if( useCheckAndArrow() )
  482. checkIcon.paintIcon(c, g, checkIconRect.x, checkIconRect.y);
  483. g.setColor(holdc);
  484. }
  485. // Paint the Icon
  486. if(b.getIcon() != null) {
  487. Icon icon;
  488. if(!model.isEnabled()) {
  489. icon = (Icon) b.getDisabledIcon();
  490. } else if(model.isPressed() && model.isArmed()) {
  491. icon = (Icon) b.getPressedIcon();
  492. if(icon == null) {
  493. // Use default icon
  494. icon = (Icon) b.getIcon();
  495. }
  496. } else {
  497. icon = (Icon) b.getIcon();
  498. }
  499. if (icon!=null)
  500. icon.paintIcon(c, g, iconRect.x, iconRect.y);
  501. }
  502. // Draw the Text
  503. if(text != null) {
  504. View v = (View) c.getClientProperty(BasicHTML.propertyKey);
  505. if (v != null) {
  506. v.paint(g, textRect);
  507. } else {
  508. paintText(g, b, textRect, text);
  509. }
  510. }
  511. // Draw the Accelerator Text
  512. if(acceleratorText != null && !acceleratorText.equals("")) {
  513. //Get the maxAccWidth from the parent to calculate the offset.
  514. int accOffset = 0;
  515. Container parent = menuItem.getParent();
  516. if (parent != null && parent instanceof JComponent) {
  517. JComponent p = (JComponent) parent;
  518. Integer maxValueInt = (Integer) p.getClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH);
  519. int maxValue = maxValueInt != null ?
  520. maxValueInt.intValue() : acceleratorRect.width;
  521. //Calculate the offset, with which the accelerator texts will be drawn with.
  522. accOffset = maxValue - acceleratorRect.width;
  523. }
  524. g.setFont( acceleratorFont );
  525. if(!model.isEnabled()) {
  526. // *** paint the acceleratorText disabled
  527. if ( disabledForeground != null )
  528. {
  529. g.setColor( disabledForeground );
  530. BasicGraphicsUtils.drawString(g,acceleratorText,0,
  531. acceleratorRect.x - accOffset,
  532. acceleratorRect.y + fmAccel.getAscent());
  533. }
  534. else
  535. {
  536. g.setColor(b.getBackground().brighter());
  537. BasicGraphicsUtils.drawString(g,acceleratorText,0,
  538. acceleratorRect.x - accOffset,
  539. acceleratorRect.y + fmAccel.getAscent());
  540. g.setColor(b.getBackground().darker());
  541. BasicGraphicsUtils.drawString(g,acceleratorText,0,
  542. acceleratorRect.x - accOffset - 1,
  543. acceleratorRect.y + fmAccel.getAscent() - 1);
  544. }
  545. } else {
  546. // *** paint the acceleratorText normally
  547. if (model.isArmed()|| (c instanceof JMenu && model.isSelected())) {
  548. g.setColor( acceleratorSelectionForeground );
  549. } else {
  550. g.setColor( acceleratorForeground );
  551. }
  552. BasicGraphicsUtils.drawString(g,acceleratorText, 0,
  553. acceleratorRect.x - accOffset,
  554. acceleratorRect.y + fmAccel.getAscent());
  555. }
  556. }
  557. // Paint the Arrow
  558. if (arrowIcon != null) {
  559. if(model.isArmed() || (c instanceof JMenu &&model.isSelected()))
  560. g.setColor(foreground);
  561. if(useCheckAndArrow())
  562. arrowIcon.paintIcon(c, g, arrowIconRect.x, arrowIconRect.y);
  563. }
  564. g.setColor(holdc);
  565. g.setFont(holdf);
  566. }
  567. /**
  568. * Draws the background of the menu item.
  569. *
  570. * @param g the paint graphics
  571. * @param menuItem menu item to be painted
  572. * @param bgColor selection background color
  573. * @since 1.4
  574. */
  575. protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
  576. ButtonModel model = menuItem.getModel();
  577. Color oldColor = g.getColor();
  578. int menuWidth = menuItem.getWidth();
  579. int menuHeight = menuItem.getHeight();
  580. if(menuItem.isOpaque()) {
  581. if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
  582. g.setColor(bgColor);
  583. g.fillRect(0,0, menuWidth, menuHeight);
  584. } else {
  585. g.setColor(menuItem.getBackground());
  586. g.fillRect(0,0, menuWidth, menuHeight);
  587. }
  588. g.setColor(oldColor);
  589. }
  590. }
  591. /**
  592. * Renders the text of the current menu item.
  593. * <p>
  594. * @param g graphics context
  595. * @param menuItem menu item to render
  596. * @param textRect bounding rectangle for rendering the text
  597. * @param text string to render
  598. * @since 1.4
  599. */
  600. protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
  601. ButtonModel model = menuItem.getModel();
  602. FontMetrics fm = g.getFontMetrics();
  603. int mnemIndex = menuItem.getDisplayedMnemonicIndex();
  604. if(!model.isEnabled()) {
  605. // *** paint the text disabled
  606. if ( UIManager.get("MenuItem.disabledForeground") instanceof Color ) {
  607. g.setColor( UIManager.getColor("MenuItem.disabledForeground") );
  608. BasicGraphicsUtils.drawStringUnderlineCharAt(g,text,mnemIndex,
  609. textRect.x,
  610. textRect.y + fm.getAscent());
  611. } else {
  612. g.setColor(menuItem.getBackground().brighter());
  613. BasicGraphicsUtils.drawStringUnderlineCharAt(g,text,mnemIndex,
  614. textRect.x,
  615. textRect.y + fm.getAscent());
  616. g.setColor(menuItem.getBackground().darker());
  617. BasicGraphicsUtils.drawStringUnderlineCharAt(g,text,mnemIndex,
  618. textRect.x - 1,
  619. textRect.y + fm.getAscent() - 1);
  620. }
  621. } else {
  622. // *** paint the text normally
  623. if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
  624. g.setColor(selectionForeground); // Uses protected field.
  625. }
  626. BasicGraphicsUtils.drawStringUnderlineCharAt(g,text, mnemIndex,
  627. textRect.x,
  628. textRect.y + fm.getAscent());
  629. }
  630. }
  631. /**
  632. * Compute and return the location of the icons origin, the
  633. * location of origin of the text baseline, and a possibly clipped
  634. * version of the compound labels string. Locations are computed
  635. * relative to the viewRect rectangle.
  636. */
  637. private String layoutMenuItem(
  638. FontMetrics fm,
  639. String text,
  640. FontMetrics fmAccel,
  641. String acceleratorText,
  642. Icon icon,
  643. Icon checkIcon,
  644. Icon arrowIcon,
  645. int verticalAlignment,
  646. int horizontalAlignment,
  647. int verticalTextPosition,
  648. int horizontalTextPosition,
  649. Rectangle viewRect,
  650. Rectangle iconRect,
  651. Rectangle textRect,
  652. Rectangle acceleratorRect,
  653. Rectangle checkIconRect,
  654. Rectangle arrowIconRect,
  655. int textIconGap,
  656. int menuItemGap
  657. )
  658. {
  659. SwingUtilities.layoutCompoundLabel(
  660. menuItem, fm, text, icon, verticalAlignment,
  661. horizontalAlignment, verticalTextPosition,
  662. horizontalTextPosition, viewRect, iconRect, textRect,
  663. textIconGap);
  664. /* Initialize the acceelratorText bounds rectangle textRect. If a null
  665. * or and empty String was specified we substitute "" here
  666. * and use 0,0,0,0 for acceleratorTextRect.
  667. */
  668. if( (acceleratorText == null) || acceleratorText.equals("") ) {
  669. acceleratorRect.width = acceleratorRect.height = 0;
  670. acceleratorText = "";
  671. }
  672. else {
  673. acceleratorRect.width = SwingUtilities.computeStringWidth( fmAccel, acceleratorText );
  674. acceleratorRect.height = fmAccel.getHeight();
  675. }
  676. /* Initialize the checkIcon bounds rectangle's width & height.
  677. */
  678. if( useCheckAndArrow()) {
  679. if (checkIcon != null) {
  680. checkIconRect.width = checkIcon.getIconWidth();
  681. checkIconRect.height = checkIcon.getIconHeight();
  682. }
  683. else {
  684. checkIconRect.width = checkIconRect.height = 0;
  685. }
  686. /* Initialize the arrowIcon bounds rectangle width & height.
  687. */
  688. if (arrowIcon != null) {
  689. arrowIconRect.width = arrowIcon.getIconWidth();
  690. arrowIconRect.height = arrowIcon.getIconHeight();
  691. } else {
  692. arrowIconRect.width = arrowIconRect.height = 0;
  693. }
  694. }
  695. Rectangle labelRect = iconRect.union(textRect);
  696. if( BasicGraphicsUtils.isLeftToRight(menuItem) ) {
  697. textRect.x += menuItemGap;
  698. iconRect.x += menuItemGap;
  699. // Position the Accelerator text rect
  700. acceleratorRect.x = viewRect.x + viewRect.width - arrowIconRect.width
  701. - menuItemGap - acceleratorRect.width;
  702. // Position the Check and Arrow Icons
  703. if (useCheckAndArrow()) {
  704. checkIconRect.x = viewRect.x + menuItemGap;
  705. textRect.x += menuItemGap + checkIconRect.width;
  706. iconRect.x += menuItemGap + checkIconRect.width;
  707. arrowIconRect.x = viewRect.x + viewRect.width - menuItemGap
  708. - arrowIconRect.width;
  709. }
  710. } else {
  711. textRect.x -= menuItemGap;
  712. iconRect.x -= menuItemGap;
  713. // Position the Accelerator text rect
  714. acceleratorRect.x = viewRect.x + arrowIconRect.width + menuItemGap;
  715. // Position the Check and Arrow Icons
  716. if (useCheckAndArrow()) {
  717. checkIconRect.x = viewRect.x + viewRect.width - menuItemGap
  718. - checkIconRect.width;
  719. textRect.x -= menuItemGap + checkIconRect.width;
  720. iconRect.x -= menuItemGap + checkIconRect.width;
  721. arrowIconRect.x = viewRect.x + menuItemGap;
  722. }
  723. }
  724. // Align the accelertor text and the check and arrow icons vertically
  725. // with the center of the label rect.
  726. acceleratorRect.y = labelRect.y + (labelRect.height2) - (acceleratorRect.height2);
  727. if( useCheckAndArrow() ) {
  728. arrowIconRect.y = labelRect.y + (labelRect.height2) - (arrowIconRect.height2);
  729. checkIconRect.y = labelRect.y + (labelRect.height2) - (checkIconRect.height2);
  730. }
  731. /*
  732. System.out.println("Layout: text="+menuItem.getText()+"\n\tv="
  733. +viewRect+"\n\tc="+checkIconRect+"\n\ti="
  734. +iconRect+"\n\tt="+textRect+"\n\tacc="
  735. +acceleratorRect+"\n\ta="+arrowIconRect+"\n");
  736. */
  737. return text;
  738. }
  739. /*
  740. * Returns false if the component is a JMenu and it is a top
  741. * level menu (on the menubar).
  742. */
  743. private boolean useCheckAndArrow(){
  744. boolean b = true;
  745. if((menuItem instanceof JMenu) &&
  746. (((JMenu)menuItem).isTopLevelMenu())) {
  747. b = false;
  748. }
  749. return b;
  750. }
  751. public MenuElement[] getPath() {
  752. MenuSelectionManager m = MenuSelectionManager.defaultManager();
  753. MenuElement oldPath[] = m.getSelectedPath();
  754. MenuElement newPath[];
  755. int i = oldPath.length;
  756. if (i == 0)
  757. return new MenuElement[0];
  758. Component parent = menuItem.getParent();
  759. if (oldPath[i-1].getComponent() == parent) {
  760. // The parent popup menu is the last so far
  761. newPath = new MenuElement[i+1];
  762. System.arraycopy(oldPath, 0, newPath, 0, i);
  763. newPath[i] = menuItem;
  764. } else {
  765. // A sibling menuitem is the current selection
  766. //
  767. // This probably needs to handle 'exit submenu into
  768. // a menu item. Search backwards along the current
  769. // selection until you find the parent popup menu,
  770. // then copy up to that and add yourself...
  771. int j;
  772. for (j = oldPath.length-1; j >= 0; j--) {
  773. if (oldPath[j].getComponent() == parent)
  774. break;
  775. }
  776. newPath = new MenuElement[j+2];
  777. System.arraycopy(oldPath, 0, newPath, 0, j+1);
  778. newPath[j+1] = menuItem;
  779. /*
  780. System.out.println("Sibling condition -- ");
  781. System.out.println("Old array : ");
  782. printMenuElementArray(oldPath, false);
  783. System.out.println("New array : ");
  784. printMenuElementArray(newPath, false);
  785. */
  786. }
  787. return newPath;
  788. }
  789. void printMenuElementArray(MenuElement path[], boolean dumpStack) {
  790. System.out.println("Path is(");
  791. int i, j;
  792. for(i=0,j=path.length; i<j ;i++){
  793. for (int k=0; k<=i; k++)
  794. System.out.print(" ");
  795. MenuElement me = (MenuElement) path[i];
  796. if(me instanceof JMenuItem)
  797. System.out.println(((JMenuItem)me).getText() + ", ");
  798. else if (me == null)
  799. System.out.println("NULL , ");
  800. else
  801. System.out.println("" + me + ", ");
  802. }
  803. System.out.println(")");
  804. if (dumpStack == true)
  805. Thread.dumpStack();
  806. }
  807. protected class MouseInputHandler implements MouseInputListener {
  808. public void mouseClicked(MouseEvent e) {}
  809. public void mousePressed(MouseEvent e) {
  810. }
  811. public void mouseReleased(MouseEvent e) {
  812. MenuSelectionManager manager =
  813. MenuSelectionManager.defaultManager();
  814. Point p = e.getPoint();
  815. if(p.x >= 0 && p.x < menuItem.getWidth() &&
  816. p.y >= 0 && p.y < menuItem.getHeight()) {
  817. doClick(manager);
  818. } else {
  819. manager.processMouseEvent(e);
  820. }
  821. }
  822. public void mouseEntered(MouseEvent e) {
  823. MenuSelectionManager manager = MenuSelectionManager.defaultManager();
  824. int modifiers = e.getModifiers();
  825. // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
  826. if ((modifiers & (InputEvent.BUTTON1_MASK |
  827. InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
  828. MenuSelectionManager.defaultManager().processMouseEvent(e);
  829. } else {
  830. manager.setSelectedPath(getPath());
  831. }
  832. }
  833. public void mouseExited(MouseEvent e) {
  834. MenuSelectionManager manager = MenuSelectionManager.defaultManager();
  835. int modifiers = e.getModifiers();
  836. // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
  837. if ((modifiers & (InputEvent.BUTTON1_MASK |
  838. InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
  839. MenuSelectionManager.defaultManager().processMouseEvent(e);
  840. } else {
  841. MenuElement path[] = manager.getSelectedPath();
  842. if (path.length > 1) {
  843. MenuElement newPath[] = new MenuElement[path.length-1];
  844. int i,c;
  845. for(i=0,c=path.length-1;i<c;i++)
  846. newPath[i] = path[i];
  847. manager.setSelectedPath(newPath);
  848. }
  849. }
  850. }
  851. public void mouseDragged(MouseEvent e) {
  852. MenuSelectionManager.defaultManager().processMouseEvent(e);
  853. }
  854. public void mouseMoved(MouseEvent e) {
  855. }
  856. }
  857. private class MenuDragMouseHandler implements MenuDragMouseListener {
  858. public void menuDragMouseEntered(MenuDragMouseEvent e) {
  859. MenuSelectionManager manager = e.getMenuSelectionManager();
  860. MenuElement path[] = e.getPath();
  861. manager.setSelectedPath(path);
  862. }
  863. public void menuDragMouseDragged(MenuDragMouseEvent e) {
  864. MenuSelectionManager manager = e.getMenuSelectionManager();
  865. MenuElement path[] = e.getPath();
  866. manager.setSelectedPath(path);
  867. }
  868. public void menuDragMouseExited(MenuDragMouseEvent e) {}
  869. public void menuDragMouseReleased(MenuDragMouseEvent e) {
  870. MenuSelectionManager manager = e.getMenuSelectionManager();
  871. MenuElement path[] = e.getPath();
  872. Point p = e.getPoint();
  873. if(p.x >= 0 && p.x < menuItem.getWidth() &&
  874. p.y >= 0 && p.y < menuItem.getHeight()) {
  875. doClick(manager);
  876. } else {
  877. manager.clearSelectedPath();
  878. }
  879. }
  880. }
  881. private class MenuKeyHandler implements MenuKeyListener {
  882. /**
  883. * Handles the mnemonic key typed in the MenuItem if this menuItem is in
  884. * a standalone popup menu. This invocation normally
  885. * handled in BasicMenuUI.MenuKeyHandler.menuKeyPressed. Ideally, the
  886. * MenuKeyHandlers for both BasicMenuItemUI and BasicMenuUI can be consolidated
  887. * into BasicPopupMenuUI but that would require an semantic change. This
  888. * would result in a performance win since we can shortcut a lot of the needless
  889. * processing from MenuSelectionManager.processKeyEvent(). See 4670831.
  890. */
  891. public void menuKeyTyped(MenuKeyEvent e) {
  892. if (DEBUG) {
  893. System.out.println("in BasicMenuItemUI.menuKeyTyped for " + menuItem.getText());
  894. }
  895. int key = menuItem.getMnemonic();
  896. if(key == 0 || e.getPath().length != 2) // Hack! Only proceed if in a JPopupMenu
  897. return;
  898. if(lower((char)key) == lower(e.getKeyChar())) {
  899. MenuSelectionManager manager =
  900. e.getMenuSelectionManager();
  901. doClick(manager);
  902. e.consume();
  903. }
  904. }
  905. public void menuKeyPressed(MenuKeyEvent e) {
  906. if (DEBUG) {
  907. System.out.println("in BasicMenuItemUI.menuKeyPressed for " + menuItem.getText());
  908. }
  909. }
  910. public void menuKeyReleased(MenuKeyEvent e) {}
  911. private char lower(char keyChar) {
  912. return Character.toLowerCase(keyChar);
  913. }
  914. }
  915. private class PropertyChangeHandler implements PropertyChangeListener {
  916. public void propertyChange(PropertyChangeEvent e) {
  917. String name = e.getPropertyName();
  918. if (name.equals("labelFor") || name.equals("displayedMnemonic") ||
  919. name.equals("accelerator")) {
  920. updateAcceleratorBinding();
  921. } else if (name.equals("text") || "font".equals(name) ||
  922. "foreground".equals(name)) {
  923. // remove the old html view client property if one
  924. // existed, and install a new one if the text installed
  925. // into the JLabel is html source.
  926. JMenuItem lbl = ((JMenuItem) e.getSource());
  927. String text = lbl.getText();
  928. BasicHTML.updateRenderer(lbl, text);
  929. }
  930. }
  931. }
  932. private static class ClickAction extends AbstractAction {
  933. public void actionPerformed(ActionEvent e) {
  934. JMenuItem mi = (JMenuItem)e.getSource();
  935. MenuSelectionManager.defaultManager().clearSelectedPath();
  936. mi.doClick();
  937. }
  938. }
  939. /**
  940. * Call this method when a menu item is to be activated.
  941. * This method handles some of the details of menu item activation
  942. * such as clearing the selected path and messaging the
  943. * JMenuItem's doClick() method.
  944. *
  945. * @param msm A MenuSelectionManager. The visual feedback and
  946. * internal bookkeeping tasks are delegated to
  947. * this MenuSelectionManager. If <code>null</code> is
  948. * passed as this argument, the
  949. * <code>MenuSelectionManager.defaultManager</code> is
  950. * used.
  951. * @see MenuSelectionManager
  952. * @see JMenuItem#doClick(int)
  953. * @since 1.4
  954. */
  955. protected void doClick(MenuSelectionManager msm) {
  956. // Auditory cue
  957. if (! isInternalFrameSystemMenu()) {
  958. ActionMap map = menuItem.getActionMap();
  959. if (map != null) {
  960. Action audioAction = map.get(getPropertyPrefix() +
  961. ".commandSound");
  962. if (audioAction != null) {
  963. // pass off firing the Action to a utility method
  964. BasicLookAndFeel lf = (BasicLookAndFeel)
  965. UIManager.getLookAndFeel();
  966. lf.playSound(audioAction);
  967. }
  968. }
  969. }
  970. // Visual feedback
  971. if (msm == null) {
  972. msm = MenuSelectionManager.defaultManager();
  973. }
  974. msm.clearSelectedPath();
  975. menuItem.doClick(0);
  976. }
  977. /**
  978. * This is to see if the menu item in question is part of the
  979. * system menu on an internal frame.
  980. * The Strings that are being checked can be found in
  981. * MetalInternalFrameTitlePaneUI.java,
  982. * WindowsInternalFrameTitlePaneUI.java, and
  983. * MotifInternalFrameTitlePaneUI.java.
  984. *
  985. * @since 1.4
  986. */
  987. private boolean isInternalFrameSystemMenu() {
  988. String actionCommand = menuItem.getActionCommand();
  989. if ((actionCommand == "Close") ||
  990. (actionCommand == "Minimize") ||
  991. (actionCommand == "Restore") ||
  992. (actionCommand == "Maximize")) {
  993. return true;
  994. } else {
  995. return false;
  996. }
  997. }
  998. }