1. /*
  2. * @(#)SynthMenuItemUI.java 1.21 04/06/24
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.synth;
  8. import javax.swing.plaf.basic.BasicHTML;
  9. import java.awt.*;
  10. import java.awt.event.*;
  11. import java.beans.PropertyChangeEvent;
  12. import java.beans.PropertyChangeListener;
  13. import javax.swing.*;
  14. import javax.swing.event.*;
  15. import javax.swing.border.*;
  16. import javax.swing.plaf.*;
  17. import javax.swing.plaf.basic.*;
  18. import javax.swing.text.View;
  19. import sun.swing.plaf.synth.*;
  20. import com.sun.java.swing.SwingUtilities2;
  21. /**
  22. * Synth's MenuItemUI.
  23. *
  24. * @version 1.21, 06/24/04
  25. * @author Georges Saab
  26. * @author David Karlton
  27. * @author Arnaud Weber
  28. * @author Fredrik Lagerblad
  29. */
  30. class SynthMenuItemUI extends BasicMenuItemUI implements
  31. PropertyChangeListener, SynthUI {
  32. private SynthStyle style;
  33. private SynthStyle accStyle;
  34. private String acceleratorDelimiter;
  35. public static ComponentUI createUI(JComponent c) {
  36. return new SynthMenuItemUI();
  37. }
  38. //
  39. // The next handful of static methods are used by both SynthMenuUI
  40. // and SynthMenuItemUI. This is necessitated by SynthMenuUI not
  41. // extending SynthMenuItemUI.
  42. //
  43. static Dimension getPreferredMenuItemSize(SynthContext context,
  44. SynthContext accContext, boolean useCheckAndArrow, JComponent c,
  45. Icon checkIcon, Icon arrowIcon, int defaultTextIconGap,
  46. String acceleratorDelimiter) {
  47. JMenuItem b = (JMenuItem) c;
  48. Icon icon = (Icon) b.getIcon();
  49. String text = b.getText();
  50. KeyStroke accelerator = b.getAccelerator();
  51. String acceleratorText = "";
  52. if (accelerator != null) {
  53. int modifiers = accelerator.getModifiers();
  54. if (modifiers > 0) {
  55. acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
  56. acceleratorText += acceleratorDelimiter;
  57. }
  58. int keyCode = accelerator.getKeyCode();
  59. if (keyCode != 0) {
  60. acceleratorText += KeyEvent.getKeyText(keyCode);
  61. } else {
  62. acceleratorText += accelerator.getKeyChar();
  63. }
  64. }
  65. Font font = context.getStyle().getFont(context);
  66. FontMetrics fm = b.getFontMetrics(font);
  67. FontMetrics fmAccel = b.getFontMetrics(accContext.getStyle().
  68. getFont(accContext));
  69. resetRects();
  70. layoutMenuItem(
  71. context, fm, accContext, text, fmAccel, acceleratorText,
  72. icon, checkIcon, arrowIcon, b.getVerticalAlignment(),
  73. b.getHorizontalAlignment(), b.getVerticalTextPosition(),
  74. b.getHorizontalTextPosition(), viewRect, iconRect, textRect,
  75. acceleratorRect, checkIconRect, arrowIconRect,
  76. text == null ? 0 : defaultTextIconGap, defaultTextIconGap,
  77. useCheckAndArrow);
  78. // find the union of the icon and text rects
  79. r.setBounds(textRect);
  80. r = SwingUtilities.computeUnion(iconRect.x,
  81. iconRect.y,
  82. iconRect.width,
  83. iconRect.height,
  84. r);
  85. // To make the accelerator texts appear in a column,
  86. // find the widest MenuItem text and the widest accelerator text.
  87. // Get the parent, which stores the information.
  88. Container parent = b.getParent();
  89. if (parent instanceof JPopupMenu) {
  90. SynthPopupMenuUI popupUI = (SynthPopupMenuUI)SynthLookAndFeel.
  91. getUIOfType(((JPopupMenu)parent).getUI(),
  92. SynthPopupMenuUI.class);
  93. if (popupUI != null) {
  94. r.width = popupUI.adjustTextWidth(r.width);
  95. popupUI.adjustAcceleratorWidth(acceleratorRect.width);
  96. r.width += popupUI.getMaxAcceleratorWidth();
  97. }
  98. }
  99. else if (parent != null && !(b instanceof JMenu &&
  100. ((JMenu)b).isTopLevelMenu())) {
  101. r.width += acceleratorRect.width;
  102. }
  103. if( useCheckAndArrow ) {
  104. // Add in the checkIcon
  105. r.width += checkIconRect.width;
  106. r.width += defaultTextIconGap;
  107. // Add in the arrowIcon
  108. r.width += defaultTextIconGap;
  109. r.width += arrowIconRect.width;
  110. }
  111. r.width += 2*defaultTextIconGap;
  112. Insets insets = b.getInsets();
  113. if(insets != null) {
  114. r.width += insets.left + insets.right;
  115. r.height += insets.top + insets.bottom;
  116. }
  117. // if the width is even, bump it up one. This is critical
  118. // for the focus dash line to draw properly
  119. if(r.width%2 == 0) {
  120. r.width++;
  121. }
  122. // if the height is even, bump it up one. This is critical
  123. // for the text to center properly
  124. if(r.height%2 == 0) {
  125. r.height++;
  126. }
  127. return r.getSize();
  128. }
  129. static void paint(SynthContext context, SynthContext accContext,
  130. Graphics g, Icon checkIcon, Icon arrowIcon,
  131. boolean useCheckAndArrow, String acceleratorDelimiter,
  132. int defaultTextIconGap) {
  133. JComponent c = context.getComponent();
  134. JMenuItem b = (JMenuItem)c;
  135. ButtonModel model = b.getModel();
  136. Insets i = b.getInsets();
  137. resetRects();
  138. viewRect.setBounds(0, 0, b.getWidth(), b.getHeight());
  139. viewRect.x += i.left;
  140. viewRect.y += i.top;
  141. viewRect.width -= (i.right + viewRect.x);
  142. viewRect.height -= (i.bottom + viewRect.y);
  143. SynthStyle style = context.getStyle();
  144. Font f = style.getFont(context);
  145. g.setFont(f);
  146. FontMetrics fm = SwingUtilities2.getFontMetrics(c, g, f);
  147. FontMetrics accFM = SwingUtilities2.getFontMetrics(c, g,
  148. accContext.getStyle().
  149. getFont(accContext));
  150. // get Accelerator text
  151. KeyStroke accelerator = b.getAccelerator();
  152. String acceleratorText = "";
  153. if (accelerator != null) {
  154. int modifiers = accelerator.getModifiers();
  155. if (modifiers > 0) {
  156. acceleratorText = KeyEvent.getKeyModifiersText(modifiers);
  157. acceleratorText += acceleratorDelimiter;
  158. }
  159. int keyCode = accelerator.getKeyCode();
  160. if (keyCode != 0) {
  161. acceleratorText += KeyEvent.getKeyText(keyCode);
  162. } else {
  163. acceleratorText += accelerator.getKeyChar();
  164. }
  165. }
  166. // layoutl the text and icon
  167. String text = layoutMenuItem(context, fm, accContext,
  168. b.getText(), accFM, acceleratorText, b.getIcon(),
  169. checkIcon, arrowIcon,
  170. b.getVerticalAlignment(), b.getHorizontalAlignment(),
  171. b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
  172. viewRect, iconRect, textRect, acceleratorRect,
  173. checkIconRect, arrowIconRect,
  174. b.getText() == null ? 0 : defaultTextIconGap,
  175. defaultTextIconGap, useCheckAndArrow
  176. );
  177. // Paint the Check
  178. if (checkIcon != null && useCheckAndArrow ) {
  179. SynthIcon.paintIcon(checkIcon, context, g, checkIconRect.x,
  180. checkIconRect.y, checkIconRect.width, checkIconRect.height);
  181. }
  182. // Paint the Icon
  183. if(b.getIcon() != null) {
  184. Icon icon;
  185. if(!model.isEnabled()) {
  186. icon = (Icon) b.getDisabledIcon();
  187. } else if(model.isPressed() && model.isArmed()) {
  188. icon = (Icon) b.getPressedIcon();
  189. if(icon == null) {
  190. // Use default icon
  191. icon = (Icon) b.getIcon();
  192. }
  193. } else {
  194. icon = (Icon) b.getIcon();
  195. }
  196. if (icon!=null) {
  197. SynthIcon.paintIcon(icon, context, g, iconRect.x,
  198. iconRect.y, iconRect.width, iconRect.height);
  199. }
  200. }
  201. // Draw the Text
  202. if(text != null) {
  203. View v = (View) c.getClientProperty(BasicHTML.propertyKey);
  204. if (v != null) {
  205. v.paint(g, textRect);
  206. } else {
  207. g.setColor(style.getColor(context, ColorType.TEXT_FOREGROUND));
  208. g.setFont(style.getFont(context));
  209. style.getGraphicsUtils(context).paintText(context, g, text,
  210. textRect.x, textRect.y, b.getDisplayedMnemonicIndex());
  211. }
  212. }
  213. // Draw the Accelerator Text
  214. if(acceleratorText != null && !acceleratorText.equals("")) {
  215. // Get the maxAccWidth from the parent to calculate the offset.
  216. int accOffset = 0;
  217. Container parent = b.getParent();
  218. if (parent != null && parent instanceof JPopupMenu) {
  219. SynthPopupMenuUI popupUI = (SynthPopupMenuUI)
  220. ((JPopupMenu)parent).getUI();
  221. if (popupUI != null) {
  222. // Calculate the offset, with which the accelerator texts
  223. // will be drawn with.
  224. int max = popupUI.getMaxAcceleratorWidth();
  225. if (max > 0) {
  226. accOffset = max - acceleratorRect.width;
  227. }
  228. }
  229. }
  230. SynthStyle accStyle = accContext.getStyle();
  231. g.setColor(accStyle.getColor(accContext,
  232. ColorType.TEXT_FOREGROUND));
  233. g.setFont(accStyle.getFont(accContext));
  234. accStyle.getGraphicsUtils(accContext).paintText(
  235. accContext, g, acceleratorText, acceleratorRect.x -
  236. accOffset, acceleratorRect.y, -1);
  237. }
  238. // Paint the Arrow
  239. if (arrowIcon != null && useCheckAndArrow) {
  240. SynthIcon.paintIcon(arrowIcon, context, g, arrowIconRect.x,
  241. arrowIconRect.y, arrowIconRect.width, arrowIconRect.height);
  242. }
  243. }
  244. /**
  245. * Compute and return the location of the icons origin, the
  246. * location of origin of the text baseline, and a possibly clipped
  247. * version of the compound labels string. Locations are computed
  248. * relative to the viewRect rectangle.
  249. */
  250. private static String layoutMenuItem(
  251. SynthContext context,
  252. FontMetrics fm,
  253. SynthContext accContext,
  254. String text,
  255. FontMetrics fmAccel,
  256. String acceleratorText,
  257. Icon icon,
  258. Icon checkIcon,
  259. Icon arrowIcon,
  260. int verticalAlignment,
  261. int horizontalAlignment,
  262. int verticalTextPosition,
  263. int horizontalTextPosition,
  264. Rectangle viewRect,
  265. Rectangle iconRect,
  266. Rectangle textRect,
  267. Rectangle acceleratorRect,
  268. Rectangle checkIconRect,
  269. Rectangle arrowIconRect,
  270. int textIconGap,
  271. int menuItemGap, boolean useCheckAndArrow
  272. )
  273. {
  274. context.getStyle().getGraphicsUtils(context).layoutText(
  275. context, fm, text, icon,horizontalAlignment, verticalAlignment,
  276. horizontalTextPosition, verticalTextPosition, viewRect,
  277. iconRect, textRect, textIconGap);
  278. /* Initialize the acceelratorText bounds rectangle textRect. If a null
  279. * or and empty String was specified we substitute "" here
  280. * and use 0,0,0,0 for acceleratorTextRect.
  281. */
  282. if( (acceleratorText == null) || acceleratorText.equals("") ) {
  283. acceleratorRect.width = acceleratorRect.height = 0;
  284. acceleratorText = "";
  285. }
  286. else {
  287. SynthStyle style = accContext.getStyle();
  288. acceleratorRect.width = style.getGraphicsUtils(accContext).
  289. computeStringWidth(accContext, fmAccel.getFont(), fmAccel,
  290. acceleratorText);
  291. acceleratorRect.height = fmAccel.getHeight();
  292. }
  293. /* Initialize the checkIcon bounds rectangle's width & height.
  294. */
  295. if( useCheckAndArrow) {
  296. if (checkIcon != null) {
  297. checkIconRect.width = SynthIcon.getIconWidth(checkIcon,
  298. context);
  299. checkIconRect.height = SynthIcon.getIconHeight(checkIcon,
  300. context);
  301. }
  302. else {
  303. checkIconRect.width = checkIconRect.height = 0;
  304. }
  305. /* Initialize the arrowIcon bounds rectangle width & height.
  306. */
  307. if (arrowIcon != null) {
  308. arrowIconRect.width = SynthIcon.getIconWidth(arrowIcon,
  309. context);
  310. arrowIconRect.height = SynthIcon.getIconHeight(arrowIcon,
  311. context);
  312. } else {
  313. arrowIconRect.width = arrowIconRect.height = 0;
  314. }
  315. }
  316. Rectangle labelRect = iconRect.union(textRect);
  317. if( SynthLookAndFeel.isLeftToRight(context.getComponent()) ) {
  318. textRect.x += menuItemGap;
  319. iconRect.x += menuItemGap;
  320. // Position the Accelerator text rect
  321. acceleratorRect.x = viewRect.x + viewRect.width -
  322. arrowIconRect.width - menuItemGap - acceleratorRect.width;
  323. // Position the Check and Arrow Icons
  324. if (useCheckAndArrow) {
  325. checkIconRect.x = viewRect.x + menuItemGap;
  326. textRect.x += menuItemGap + checkIconRect.width;
  327. iconRect.x += menuItemGap + checkIconRect.width;
  328. arrowIconRect.x = viewRect.x + viewRect.width - menuItemGap
  329. - arrowIconRect.width;
  330. }
  331. } else {
  332. textRect.x -= menuItemGap;
  333. iconRect.x -= menuItemGap;
  334. // Position the Accelerator text rect
  335. acceleratorRect.x = viewRect.x + arrowIconRect.width + menuItemGap;
  336. // Position the Check and Arrow Icons
  337. if (useCheckAndArrow) {
  338. checkIconRect.x = viewRect.x + viewRect.width - menuItemGap
  339. - checkIconRect.width;
  340. textRect.x -= menuItemGap + checkIconRect.width;
  341. iconRect.x -= menuItemGap + checkIconRect.width;
  342. arrowIconRect.x = viewRect.x + menuItemGap;
  343. }
  344. }
  345. // Align the accelertor text and the check and arrow icons vertically
  346. // with the center of the label rect.
  347. acceleratorRect.y = labelRect.y + (labelRect.height2) - (acceleratorRect.height2);
  348. if( useCheckAndArrow ) {
  349. arrowIconRect.y = labelRect.y + (labelRect.height2) - (arrowIconRect.height2);
  350. checkIconRect.y = labelRect.y + (labelRect.height2) - (checkIconRect.height2);
  351. }
  352. return text;
  353. }
  354. // these rects are used for painting and preferredsize calculations.
  355. // they used to be regenerated constantly. Now they are reused.
  356. static Rectangle iconRect = new Rectangle();
  357. static Rectangle textRect = new Rectangle();
  358. static Rectangle acceleratorRect = new Rectangle();
  359. static Rectangle checkIconRect = new Rectangle();
  360. static Rectangle arrowIconRect = new Rectangle();
  361. static Rectangle viewRect = new Rectangle(Short.MAX_VALUE,Short.MAX_VALUE);
  362. static Rectangle r = new Rectangle();
  363. private static void resetRects() {
  364. iconRect.setBounds(0, 0, 0, 0);
  365. textRect.setBounds(0, 0, 0, 0);
  366. acceleratorRect.setBounds(0, 0, 0, 0);
  367. checkIconRect.setBounds(0, 0, 0, 0);
  368. arrowIconRect.setBounds(0, 0, 0, 0);
  369. viewRect.setBounds(0,0,Short.MAX_VALUE, Short.MAX_VALUE);
  370. r.setBounds(0, 0, 0, 0);
  371. }
  372. protected void installDefaults() {
  373. updateStyle(menuItem);
  374. }
  375. protected void installListeners() {
  376. super.installListeners();
  377. menuItem.addPropertyChangeListener(this);
  378. }
  379. private void updateStyle(JMenuItem mi) {
  380. SynthContext context = getContext(mi, ENABLED);
  381. SynthStyle oldStyle = style;
  382. style = SynthLookAndFeel.updateStyle(context, this);
  383. if (oldStyle != style) {
  384. String prefix = getPropertyPrefix();
  385. defaultTextIconGap = style.getInt(
  386. context, prefix + ".textIconGap", 4);
  387. if (menuItem.getMargin() == null ||
  388. (menuItem.getMargin() instanceof UIResource)) {
  389. Insets insets = (Insets)style.get(context, prefix + ".margin");
  390. if (insets == null) {
  391. // Some places assume margins are non-null.
  392. insets = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
  393. }
  394. menuItem.setMargin(insets);
  395. }
  396. acceleratorDelimiter = style.getString(context, prefix +
  397. ".acceleratorDelimiter", "+");
  398. arrowIcon = style.getIcon(context, prefix + ".arrowIcon");
  399. checkIcon = style.getIcon(context, prefix + ".checkIcon");
  400. if (oldStyle != null) {
  401. uninstallKeyboardActions();
  402. installKeyboardActions();
  403. }
  404. }
  405. context.dispose();
  406. SynthContext accContext = getContext(mi, Region.MENU_ITEM_ACCELERATOR,
  407. ENABLED);
  408. accStyle = SynthLookAndFeel.updateStyle(accContext, this);
  409. accContext.dispose();
  410. }
  411. protected void uninstallDefaults() {
  412. SynthContext context = getContext(menuItem, ENABLED);
  413. style.uninstallDefaults(context);
  414. context.dispose();
  415. style = null;
  416. SynthContext accContext = getContext(menuItem,
  417. Region.MENU_ITEM_ACCELERATOR, ENABLED);
  418. accStyle.uninstallDefaults(accContext);
  419. accContext.dispose();
  420. accStyle = null;
  421. super.uninstallDefaults();
  422. }
  423. protected void uninstallListeners() {
  424. super.uninstallListeners();
  425. menuItem.removePropertyChangeListener(this);
  426. }
  427. public SynthContext getContext(JComponent c) {
  428. return getContext(c, getComponentState(c));
  429. }
  430. SynthContext getContext(JComponent c, int state) {
  431. return SynthContext.getContext(SynthContext.class, c,
  432. SynthLookAndFeel.getRegion(c), style, state);
  433. }
  434. public SynthContext getContext(JComponent c, Region region) {
  435. return getContext(c, region, getComponentState(c, region));
  436. }
  437. private SynthContext getContext(JComponent c, Region region, int state) {
  438. return SynthContext.getContext(SynthContext.class, c,
  439. region, accStyle, state);
  440. }
  441. private Region getRegion(JComponent c) {
  442. return SynthLookAndFeel.getRegion(c);
  443. }
  444. private int getComponentState(JComponent c) {
  445. int state;
  446. if (!c.isEnabled()) {
  447. return DISABLED;
  448. }
  449. if (menuItem.isArmed()) {
  450. state = MOUSE_OVER;
  451. }
  452. else {
  453. state = SynthLookAndFeel.getComponentState(c);
  454. }
  455. if (menuItem.isSelected()) {
  456. state |= SELECTED;
  457. }
  458. return state;
  459. }
  460. private int getComponentState(JComponent c, Region region) {
  461. return getComponentState(c);
  462. }
  463. protected Dimension getPreferredMenuItemSize(JComponent c,
  464. Icon checkIcon,
  465. Icon arrowIcon,
  466. int defaultTextIconGap) {
  467. SynthContext context = getContext(c);
  468. SynthContext accContext = getContext(c, Region.MENU_ITEM_ACCELERATOR);
  469. Dimension value = getPreferredMenuItemSize(context, accContext,
  470. true, c, checkIcon, arrowIcon, defaultTextIconGap,
  471. acceleratorDelimiter);
  472. context.dispose();
  473. accContext.dispose();
  474. return value;
  475. }
  476. public void update(Graphics g, JComponent c) {
  477. SynthContext context = getContext(c);
  478. SynthLookAndFeel.update(context, g);
  479. paintBackground(context, g, c);
  480. paint(context, g);
  481. context.dispose();
  482. }
  483. public void paint(Graphics g, JComponent c) {
  484. SynthContext context = getContext(c);
  485. paint(context, g);
  486. context.dispose();
  487. }
  488. protected void paint(SynthContext context, Graphics g) {
  489. SynthContext accContext = getContext(menuItem,
  490. Region.MENU_ITEM_ACCELERATOR);
  491. String prefix = getPropertyPrefix();
  492. paint(context, accContext, g,
  493. style.getIcon(getContext(context.getComponent()),
  494. prefix + ".checkIcon"),
  495. style.getIcon(getContext(context.getComponent()),
  496. prefix + ".arrowIcon"),
  497. true, acceleratorDelimiter, defaultTextIconGap);
  498. accContext.dispose();
  499. }
  500. void paintBackground(SynthContext context, Graphics g, JComponent c) {
  501. context.getPainter().paintMenuItemBackground(context, g, 0, 0,
  502. c.getWidth(), c.getHeight());
  503. }
  504. public void paintBorder(SynthContext context, Graphics g, int x,
  505. int y, int w, int h) {
  506. context.getPainter().paintMenuItemBorder(context, g, x, y, w, h);
  507. }
  508. public void propertyChange(PropertyChangeEvent e) {
  509. if (SynthLookAndFeel.shouldUpdateStyle(e)) {
  510. updateStyle((JMenuItem)e.getSource());
  511. }
  512. }
  513. }