blob: afe007aeb78d00633cfed3c2b5d077cacac3150e [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.MenuItemCheckIconFactory;
29import sun.swing.SwingUtilities2;
30import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET;
31import java.awt.*;
32import java.awt.event.*;
33import java.beans.PropertyChangeEvent;
34import java.beans.PropertyChangeListener;
35
36import javax.swing.*;
37import javax.swing.event.*;
38import javax.swing.border.*;
39import javax.swing.plaf.*;
40import javax.swing.text.View;
41
42import sun.swing.UIAction;
43import sun.swing.StringUIClientPropertyKey;
44
45/**
46 * BasicMenuItem implementation
47 *
48 * @author Georges Saab
49 * @author David Karlton
50 * @author Arnaud Weber
51 * @author Fredrik Lagerblad
52 */
53public class BasicMenuItemUI extends MenuItemUI
54{
55 protected JMenuItem menuItem = null;
56 protected Color selectionBackground;
57 protected Color selectionForeground;
58 protected Color disabledForeground;
59 protected Color acceleratorForeground;
60 protected Color acceleratorSelectionForeground;
61 private String acceleratorDelimiter;
62
63 protected int defaultTextIconGap;
64 protected Font acceleratorFont;
65
66 protected MouseInputListener mouseInputListener;
67 protected MenuDragMouseListener menuDragMouseListener;
68 protected MenuKeyListener menuKeyListener;
69 /**
70 * <code>PropertyChangeListener</code> returned from
71 * <code>createPropertyChangeListener</code>. You should not
72 * need to access this field, rather if you want to customize the
73 * <code>PropertyChangeListener</code> override
74 * <code>createPropertyChangeListener</code>.
75 *
76 * @since 1.6
77 * @see #createPropertyChangeListener
78 */
79 protected PropertyChangeListener propertyChangeListener;
80 // BasicMenuUI also uses this.
81 Handler handler;
82
83 protected Icon arrowIcon = null;
84 protected Icon checkIcon = null;
85
86 protected boolean oldBorderPainted;
87
88 /* diagnostic aids -- should be false for production builds. */
89 private static final boolean TRACE = false; // trace creates and disposes
90
91 private static final boolean VERBOSE = false; // show reuse hits/misses
92 private static final boolean DEBUG = false; // show bad params, misc.
93
94 // Allows to reuse layoutInfo object.
95 // Shouldn't be used directly. Use getLayoutInfo() instead.
96 private final transient LayoutInfo layoutInfo = new LayoutInfo();
97
98 /* Client Property keys for calculation of maximal widths */
99 static final StringUIClientPropertyKey MAX_ARROW_WIDTH =
100 new StringUIClientPropertyKey("maxArrowWidth");
101 static final StringUIClientPropertyKey MAX_CHECK_WIDTH =
102 new StringUIClientPropertyKey("maxCheckWidth");
103 static final StringUIClientPropertyKey MAX_ICON_WIDTH =
104 new StringUIClientPropertyKey("maxIconWidth");
105 static final StringUIClientPropertyKey MAX_TEXT_WIDTH =
106 new StringUIClientPropertyKey("maxTextWidth");
107 static final StringUIClientPropertyKey MAX_ACC_WIDTH =
108 new StringUIClientPropertyKey("maxAccWidth");
109 static final StringUIClientPropertyKey MAX_LABEL_WIDTH =
110 new StringUIClientPropertyKey("maxLabelWidth");
111
112 static void loadActionMap(LazyActionMap map) {
113 // NOTE: BasicMenuUI also calls into this method.
114 map.put(new Actions(Actions.CLICK));
115 BasicLookAndFeel.installAudioActionMap(map);
116 }
117
118 public static ComponentUI createUI(JComponent c) {
119 return new BasicMenuItemUI();
120 }
121
122 public void installUI(JComponent c) {
123 menuItem = (JMenuItem) c;
124
125 installDefaults();
126 installComponents(menuItem);
127 installListeners();
128 installKeyboardActions();
129 }
130
131
132 protected void installDefaults() {
133 String prefix = getPropertyPrefix();
134
135 acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont");
136
137 Object opaque = UIManager.get(getPropertyPrefix() + ".opaque");
138 if (opaque != null) {
139 LookAndFeel.installProperty(menuItem, "opaque", opaque);
140 }
141 else {
142 LookAndFeel.installProperty(menuItem, "opaque", Boolean.TRUE);
143 }
144 if(menuItem.getMargin() == null ||
145 (menuItem.getMargin() instanceof UIResource)) {
146 menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
147 }
148
149 LookAndFeel.installProperty(menuItem, "iconTextGap", new Integer(4));
150 defaultTextIconGap = menuItem.getIconTextGap();
151
152 LookAndFeel.installBorder(menuItem, prefix + ".border");
153 oldBorderPainted = menuItem.isBorderPainted();
154 LookAndFeel.installProperty(menuItem, "borderPainted",
155 UIManager.get(prefix + ".borderPainted"));
156 LookAndFeel.installColorsAndFont(menuItem,
157 prefix + ".background",
158 prefix + ".foreground",
159 prefix + ".font");
160
161 // MenuItem specific defaults
162 if (selectionBackground == null ||
163 selectionBackground instanceof UIResource) {
164 selectionBackground =
165 UIManager.getColor(prefix + ".selectionBackground");
166 }
167 if (selectionForeground == null ||
168 selectionForeground instanceof UIResource) {
169 selectionForeground =
170 UIManager.getColor(prefix + ".selectionForeground");
171 }
172 if (disabledForeground == null ||
173 disabledForeground instanceof UIResource) {
174 disabledForeground =
175 UIManager.getColor(prefix + ".disabledForeground");
176 }
177 if (acceleratorForeground == null ||
178 acceleratorForeground instanceof UIResource) {
179 acceleratorForeground =
180 UIManager.getColor(prefix + ".acceleratorForeground");
181 }
182 if (acceleratorSelectionForeground == null ||
183 acceleratorSelectionForeground instanceof UIResource) {
184 acceleratorSelectionForeground =
185 UIManager.getColor(prefix + ".acceleratorSelectionForeground");
186 }
187 // Get accelerator delimiter
188 acceleratorDelimiter =
189 UIManager.getString("MenuItem.acceleratorDelimiter");
190 if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; }
191 // Icons
192 if (arrowIcon == null ||
193 arrowIcon instanceof UIResource) {
194 arrowIcon = UIManager.getIcon(prefix + ".arrowIcon");
195 }
196 if (checkIcon == null ||
197 checkIcon instanceof UIResource) {
198 checkIcon = UIManager.getIcon(prefix + ".checkIcon");
199 //In case of column layout, .checkIconFactory is defined for this UI,
200 //the icon is compatible with it and useCheckAndArrow() is true,
201 //then the icon is handled by the checkIcon.
202 boolean isColumnLayout = LayoutInfo.isColumnLayout(
203 BasicGraphicsUtils.isLeftToRight(menuItem), menuItem);
204 if (isColumnLayout) {
205 MenuItemCheckIconFactory iconFactory =
206 (MenuItemCheckIconFactory) UIManager.get(prefix
207 + ".checkIconFactory");
208 if (iconFactory != null && useCheckAndArrow()
209 && iconFactory.isCompatible(checkIcon, prefix)) {
210 checkIcon = iconFactory.getIcon(menuItem);
211 }
212 }
213 }
214 }
215
216 /**
217 * @since 1.3
218 */
219 protected void installComponents(JMenuItem menuItem){
220 BasicHTML.updateRenderer(menuItem, menuItem.getText());
221 }
222
223 protected String getPropertyPrefix() {
224 return "MenuItem";
225 }
226
227 protected void installListeners() {
228 if ((mouseInputListener = createMouseInputListener(menuItem)) != null) {
229 menuItem.addMouseListener(mouseInputListener);
230 menuItem.addMouseMotionListener(mouseInputListener);
231 }
232 if ((menuDragMouseListener = createMenuDragMouseListener(menuItem)) != null) {
233 menuItem.addMenuDragMouseListener(menuDragMouseListener);
234 }
235 if ((menuKeyListener = createMenuKeyListener(menuItem)) != null) {
236 menuItem.addMenuKeyListener(menuKeyListener);
237 }
238 if ((propertyChangeListener = createPropertyChangeListener(menuItem)) != null) {
239 menuItem.addPropertyChangeListener(propertyChangeListener);
240 }
241 }
242
243 protected void installKeyboardActions() {
244 installLazyActionMap();
245 updateAcceleratorBinding();
246 }
247
248 void installLazyActionMap() {
249 LazyActionMap.installLazyActionMap(menuItem, BasicMenuItemUI.class,
250 getPropertyPrefix() + ".actionMap");
251 }
252
253 public void uninstallUI(JComponent c) {
254 menuItem = (JMenuItem)c;
255 uninstallDefaults();
256 uninstallComponents(menuItem);
257 uninstallListeners();
258 uninstallKeyboardActions();
259
260
261 // Remove values from the parent's Client Properties.
262 JComponent p = getMenuItemParent(menuItem);
263 if(p != null) {
264 p.putClientProperty(BasicMenuItemUI.MAX_ARROW_WIDTH, null );
265 p.putClientProperty(BasicMenuItemUI.MAX_CHECK_WIDTH, null );
266 p.putClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH, null );
267 p.putClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH, null );
268 p.putClientProperty(BasicMenuItemUI.MAX_ICON_WIDTH, null );
269 p.putClientProperty(BasicMenuItemUI.MAX_LABEL_WIDTH, null );
270 p.putClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET, null );
271 }
272
273 menuItem = null;
274 }
275
276
277 protected void uninstallDefaults() {
278 LookAndFeel.uninstallBorder(menuItem);
279 LookAndFeel.installProperty(menuItem, "borderPainted", oldBorderPainted);
280 if (menuItem.getMargin() instanceof UIResource)
281 menuItem.setMargin(null);
282 if (arrowIcon instanceof UIResource)
283 arrowIcon = null;
284 if (checkIcon instanceof UIResource)
285 checkIcon = null;
286 }
287
288 /**
289 * @since 1.3
290 */
291 protected void uninstallComponents(JMenuItem menuItem){
292 BasicHTML.updateRenderer(menuItem, "");
293 }
294
295 protected void uninstallListeners() {
296 if (mouseInputListener != null) {
297 menuItem.removeMouseListener(mouseInputListener);
298 menuItem.removeMouseMotionListener(mouseInputListener);
299 }
300 if (menuDragMouseListener != null) {
301 menuItem.removeMenuDragMouseListener(menuDragMouseListener);
302 }
303 if (menuKeyListener != null) {
304 menuItem.removeMenuKeyListener(menuKeyListener);
305 }
306 if (propertyChangeListener != null) {
307 menuItem.removePropertyChangeListener(propertyChangeListener);
308 }
309
310 mouseInputListener = null;
311 menuDragMouseListener = null;
312 menuKeyListener = null;
313 propertyChangeListener = null;
314 handler = null;
315 }
316
317 protected void uninstallKeyboardActions() {
318 SwingUtilities.replaceUIActionMap(menuItem, null);
319 SwingUtilities.replaceUIInputMap(menuItem, JComponent.
320 WHEN_IN_FOCUSED_WINDOW, null);
321 }
322
323 protected MouseInputListener createMouseInputListener(JComponent c) {
324 return getHandler();
325 }
326
327 protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
328 return getHandler();
329 }
330
331 protected MenuKeyListener createMenuKeyListener(JComponent c) {
332 return null;
333 }
334
335 /**
336 * Creates a <code>PropertyChangeListener</code> which will be added to
337 * the menu item.
338 * If this method returns null then it will not be added to the menu item.
339 *
340 * @return an instance of a <code>PropertyChangeListener</code> or null
341 * @since 1.6
342 */
343 protected PropertyChangeListener
344 createPropertyChangeListener(JComponent c) {
345 return getHandler();
346 }
347
348 Handler getHandler() {
349 if (handler == null) {
350 handler = new Handler();
351 }
352 return handler;
353 }
354
355 InputMap createInputMap(int condition) {
356 if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
357 return new ComponentInputMapUIResource(menuItem);
358 }
359 return null;
360 }
361
362 void updateAcceleratorBinding() {
363 KeyStroke accelerator = menuItem.getAccelerator();
364 InputMap windowInputMap = SwingUtilities.getUIInputMap(
365 menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
366
367 if (windowInputMap != null) {
368 windowInputMap.clear();
369 }
370 if (accelerator != null) {
371 if (windowInputMap == null) {
372 windowInputMap = createInputMap(JComponent.
373 WHEN_IN_FOCUSED_WINDOW);
374 SwingUtilities.replaceUIInputMap(menuItem,
375 JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap);
376 }
377 windowInputMap.put(accelerator, "doClick");
378 }
379 }
380
381 public Dimension getMinimumSize(JComponent c) {
382 Dimension d = null;
383 View v = (View) c.getClientProperty(BasicHTML.propertyKey);
384 if (v != null) {
385 d = getPreferredSize(c);
386 d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
387 }
388 return d;
389 }
390
391 public Dimension getPreferredSize(JComponent c) {
392 return getPreferredMenuItemSize(c,
393 checkIcon,
394 arrowIcon,
395 defaultTextIconGap);
396 }
397
398 public Dimension getMaximumSize(JComponent c) {
399 Dimension d = null;
400 View v = (View) c.getClientProperty(BasicHTML.propertyKey);
401 if (v != null) {
402 d = getPreferredSize(c);
403 d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
404 }
405 return d;
406 }
407
408 // Returns parent of this component if it is not a top-level menu
409 // Otherwise returns null
410 private static JComponent getMenuItemParent(JMenuItem mi) {
411 Container parent = mi.getParent();
412 if ((parent instanceof JComponent) &&
413 (!(mi instanceof JMenu) ||
414 !((JMenu)mi).isTopLevelMenu())) {
415 return (JComponent) parent;
416 } else {
417 return null;
418 }
419 }
420
421 protected Dimension getPreferredMenuItemSize(JComponent c,
422 Icon checkIcon,
423 Icon arrowIcon,
424 int defaultTextIconGap) {
425
426 // The method also determines the preferred width of the
427 // parent popup menu (through DefaultMenuLayout class).
428 // The menu width equals to the maximal width
429 // among child menu items.
430
431 // Menu item width will be a sum of the widest check icon, label,
432 // arrow icon and accelerator text among neighbor menu items.
433 // For the latest menu item we will know the maximal widths exactly.
434 // It will be the widest menu item and it will determine
435 // the width of the parent popup menu.
436
437 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
438 // There is a conceptual problem: if user sets preferred size manually
439 // for a menu item, this method won't be called for it
440 // (see JComponent.getPreferredSize()),
441 // maximal widths won't be calculated, other menu items won't be able
442 // to take them into account and will be layouted in such a way,
443 // as there is no the item with manual preferred size.
444 // But after the first paint() method call, all maximal widths
445 // will be correctly calculated and layout of some menu items
446 // can be changed. For example, it can cause a shift of
447 // the icon and text when user points a menu item by mouse.
448
449 JMenuItem mi = (JMenuItem) c;
450 LayoutInfo li = getLayoutInfo(mi, checkIcon, arrowIcon,
451 createMaxViewRect(), defaultTextIconGap, acceleratorDelimiter,
452 BasicGraphicsUtils.isLeftToRight(mi), acceleratorFont,
453 useCheckAndArrow(), getPropertyPrefix());
454
455 Dimension result = new Dimension();
456
457 // Calculate the result width
458 result.width = li.leadingGap;
459 addWidth(li.maxCheckWidth, li.afterCheckIconGap, result);
460 // Take into account mimimal text offset.
461 if ((!li.isTopLevelMenu)
462 && (li.minTextOffset > 0)
463 && (result.width < li.minTextOffset)) {
464 result.width = li.minTextOffset;
465 }
466 addWidth(li.maxLabelWidth, li.gap, result);
467 addWidth(li.maxAccWidth, li.gap, result);
468 addWidth(li.maxArrowWidth, li.gap, result);
469
470 // Calculate the result height
471 result.height = max(li.checkRect.height, li.labelRect.height,
472 li.accRect.height, li.arrowRect.height);
473
474 // Take into account menu item insets
475 Insets insets = li.mi.getInsets();
476 if(insets != null) {
477 result.width += insets.left + insets.right;
478 result.height += insets.top + insets.bottom;
479 }
480
481 // if the width is even, bump it up one. This is critical
482 // for the focus dash line to draw properly
483 if(result.width%2 == 0) {
484 result.width++;
485 }
486
487 // if the height is even, bump it up one. This is critical
488 // for the text to center properly
489 if(result.height%2 == 0
490 && Boolean.TRUE !=
491 UIManager.get(getPropertyPrefix() + ".evenHeight")) {
492 result.height++;
493 }
494
495 li.clear();
496 return result;
497 }
498
499 private Rectangle createMaxViewRect() {
500 return new Rectangle(0,0,Short.MAX_VALUE, Short.MAX_VALUE);
501 }
502
503 private void addWidth(int width, int gap, Dimension result) {
504 if (width > 0) {
505 result.width += width + gap;
506 }
507 }
508
509 private static int max(int... values) {
510 int maxValue = Integer.MIN_VALUE;
511 for (int i : values) {
512 if (i > maxValue) {
513 maxValue = i;
514 }
515 }
516 return maxValue;
517 }
518
519 // LayoutInfo helps to calculate preferred size and to paint a menu item
520 private static class LayoutInfo {
521 JMenuItem mi;
522 JComponent miParent;
523
524 FontMetrics fm;
525 FontMetrics accFm;
526
527 Icon icon;
528 Icon checkIcon;
529 Icon arrowIcon;
530 String text;
531 String accText;
532
533 boolean isColumnLayout;
534 boolean useCheckAndArrow;
535 boolean isLeftToRight;
536 boolean isTopLevelMenu;
537 View htmlView;
538
539 int verticalAlignment;
540 int horizontalAlignment;
541 int verticalTextPosition;
542 int horizontalTextPosition;
543 int gap;
544 int leadingGap;
545 int afterCheckIconGap;
546 int minTextOffset;
547
548 Rectangle viewRect;
549 Rectangle iconRect;
550 Rectangle textRect;
551 Rectangle accRect;
552 Rectangle checkRect;
553 Rectangle arrowRect;
554 Rectangle labelRect;
555
556 int origIconWidth;
557 int origTextWidth;
558 int origAccWidth;
559 int origCheckWidth;
560 int origArrowWidth;
561
562 int maxIconWidth;
563 int maxTextWidth;
564 int maxAccWidth;
565 int maxCheckWidth;
566 int maxArrowWidth;
567 int maxLabelWidth;
568
569 // Empty constructor helps to create "final" LayoutInfo object
570 public LayoutInfo() {
571 }
572
573 public LayoutInfo(JMenuItem mi, Icon checkIcon, Icon arrowIcon,
574 Rectangle viewRect, int gap, String accDelimiter,
575 boolean isLeftToRight, Font acceleratorFont,
576 boolean useCheckAndArrow, String propertyPrefix) {
577 reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter,
578 isLeftToRight, acceleratorFont, useCheckAndArrow,
579 propertyPrefix);
580 }
581
582 // Allows to reuse a LayoutInfo object
583 public void reset(JMenuItem mi, Icon checkIcon, Icon arrowIcon,
584 Rectangle viewRect, int gap, String accDelimiter,
585 boolean isLeftToRight, Font acceleratorFont,
586 boolean useCheckAndArrow, String propertyPrefix) {
587 this.mi = mi;
588 this.miParent = getMenuItemParent(mi);
589 this.accText = getAccText(accDelimiter);
590 this.verticalAlignment = mi.getVerticalAlignment();
591 this.horizontalAlignment = mi.getHorizontalAlignment();
592 this.verticalTextPosition = mi.getVerticalTextPosition();
593 this.horizontalTextPosition = mi.getHorizontalTextPosition();
594 this.useCheckAndArrow = useCheckAndArrow;
595 this.fm = mi.getFontMetrics(mi.getFont());
596 this.accFm = mi.getFontMetrics(acceleratorFont);
597 this.isLeftToRight = isLeftToRight;
598 this.isColumnLayout = isColumnLayout();
599 this.isTopLevelMenu = (this.miParent == null)? true : false;
600 this.checkIcon = checkIcon;
601 this.icon = getIcon(propertyPrefix);
602 this.arrowIcon = arrowIcon;
603 this.text = mi.getText();
604 this.gap = gap;
605 this.afterCheckIconGap = getAfterCheckIconGap(propertyPrefix);
606 this.minTextOffset = getMinTextOffset(propertyPrefix);
607 this.htmlView = (View) mi.getClientProperty(BasicHTML.propertyKey);
608
609 this.viewRect = viewRect;
610 this.iconRect = new Rectangle();
611 this.textRect = new Rectangle();
612 this.accRect = new Rectangle();
613 this.checkRect = new Rectangle();
614 this.arrowRect = new Rectangle();
615 this.labelRect = new Rectangle();
616
617 calcWidthsAndHeights();
618 this.origIconWidth = iconRect.width;
619 this.origTextWidth = textRect.width;
620 this.origAccWidth = accRect.width;
621 this.origCheckWidth = checkRect.width;
622 this.origArrowWidth = arrowRect.width;
623
624 calcMaxWidths();
625 this.leadingGap = getLeadingGap(propertyPrefix);
626 calcMaxTextOffset();
627 }
628
629 // Clears fields to remove all links to other objects
630 // to prevent memory leaks
631 public void clear() {
632 mi = null;
633 miParent = null;
634 fm = null;
635 accFm = null;
636 icon = null;
637 checkIcon = null;
638 arrowIcon = null;
639 text = null;
640 accText = null;
641 htmlView = null;
642 viewRect = null;
643 iconRect = null;
644 textRect = null;
645 accRect = null;
646 checkRect = null;
647 arrowRect = null;
648 labelRect = null;
649 }
650
651 private String getAccText(String acceleratorDelimiter) {
652 String accText = "";
653 KeyStroke accelerator = mi.getAccelerator();
654 if (accelerator != null) {
655 int modifiers = accelerator.getModifiers();
656 if (modifiers > 0) {
657 accText = KeyEvent.getKeyModifiersText(modifiers);
658 accText += acceleratorDelimiter;
659 }
660 int keyCode = accelerator.getKeyCode();
661 if (keyCode != 0) {
662 accText += KeyEvent.getKeyText(keyCode);
663 } else {
664 accText += accelerator.getKeyChar();
665 }
666 }
667 return accText;
668 }
669
670 // In case of column layout, .checkIconFactory is defined for this UI,
671 // the icon is compatible with it and useCheckAndArrow() is true,
672 // then the icon is handled by the checkIcon.
673 private Icon getIcon(String propertyPrefix) {
674 Icon icon = null;
675 MenuItemCheckIconFactory iconFactory =
676 (MenuItemCheckIconFactory) UIManager.get(propertyPrefix
677 + ".checkIconFactory");
678 if (!isColumnLayout || !useCheckAndArrow || iconFactory == null
679 || !iconFactory.isCompatible(checkIcon, propertyPrefix)) {
680 icon = mi.getIcon();
681 }
682 return icon;
683 }
684
685 private int getMinTextOffset(String propertyPrefix) {
686 int minimumTextOffset = 0;
687 Object minimumTextOffsetObject =
688 UIManager.get(propertyPrefix + ".minimumTextOffset");
689 if (minimumTextOffsetObject instanceof Integer) {
690 minimumTextOffset = (Integer) minimumTextOffsetObject;
691 }
692 return minimumTextOffset;
693 }
694
695 private int getAfterCheckIconGap(String propertyPrefix) {
696 int afterCheckIconGap = gap;
697 Object afterCheckIconGapObject =
698 UIManager.get(propertyPrefix + ".afterCheckIconGap");
699 if (afterCheckIconGapObject instanceof Integer) {
700 afterCheckIconGap = (Integer) afterCheckIconGapObject;
701 }
702 return afterCheckIconGap;
703 }
704
705 private int getLeadingGap(String propertyPrefix) {
706 if (maxCheckWidth > 0) {
707 return getCheckOffset(propertyPrefix);
708 } else {
709 return gap; // There is no any check icon
710 }
711 }
712
713 private int getCheckOffset(String propertyPrefix) {
714 int checkIconOffset = gap;
715 Object checkIconOffsetObject =
716 UIManager.get(propertyPrefix + ".checkIconOffset");
717 if (checkIconOffsetObject instanceof Integer) {
718 checkIconOffset = (Integer) checkIconOffsetObject;
719 }
720 return checkIconOffset;
721 }
722
723 private void calcWidthsAndHeights()
724 {
725 // iconRect
726 if (icon != null) {
727 iconRect.width = icon.getIconWidth();
728 iconRect.height = icon.getIconHeight();
729 }
730
731 // accRect
732 if (!accText.equals("")) {
733 accRect.width = SwingUtilities2.stringWidth(
734 mi, accFm, accText);
735 accRect.height = accFm.getHeight();
736 }
737
738 // textRect
739 if (text == null) {
740 text = "";
741 } else if (!text.equals("")) {
742 if (htmlView != null) {
743 // Text is HTML
744 textRect.width =
745 (int) htmlView.getPreferredSpan(View.X_AXIS);
746 textRect.height =
747 (int) htmlView.getPreferredSpan(View.Y_AXIS);
748 } else {
749 // Text isn't HTML
750 textRect.width =
751 SwingUtilities2.stringWidth(mi, fm, text);
752 textRect.height = fm.getHeight();
753 }
754 }
755
756 if (useCheckAndArrow) {
757 // checkIcon
758 if (checkIcon != null) {
759 checkRect.width = checkIcon.getIconWidth();
760 checkRect.height = checkIcon.getIconHeight();
761 }
762 // arrowRect
763 if (arrowIcon != null) {
764 arrowRect.width = arrowIcon.getIconWidth();
765 arrowRect.height = arrowIcon.getIconHeight();
766 }
767 }
768
769 // labelRect
770 if (isColumnLayout) {
771 labelRect.width = iconRect.width + textRect.width + gap;
772 labelRect.height = max(checkRect.height, iconRect.height,
773 textRect.height, accRect.height, arrowRect.height);
774 } else {
775 textRect = new Rectangle();
776 iconRect = new Rectangle();
777 SwingUtilities.layoutCompoundLabel(mi, fm, text, icon,
778 verticalAlignment, horizontalAlignment,
779 verticalTextPosition, horizontalTextPosition,
780 viewRect, iconRect, textRect, gap);
781 labelRect = iconRect.union(textRect);
782 }
783 }
784
785 private void calcMaxWidths() {
786 maxCheckWidth = calcMaxValue(BasicMenuItemUI.MAX_CHECK_WIDTH,
787 checkRect.width);
788 maxArrowWidth = calcMaxValue(BasicMenuItemUI.MAX_ARROW_WIDTH,
789 arrowRect.width);
790 maxAccWidth = calcMaxValue(BasicMenuItemUI.MAX_ACC_WIDTH,
791 accRect.width);
792
793 if (isColumnLayout) {
794 maxIconWidth = calcMaxValue(BasicMenuItemUI.MAX_ICON_WIDTH,
795 iconRect.width);
796 maxTextWidth = calcMaxValue(BasicMenuItemUI.MAX_TEXT_WIDTH,
797 textRect.width);
798 int curGap = gap;
799 if ((maxIconWidth == 0) || (maxTextWidth == 0)) {
800 curGap = 0;
801 }
802 maxLabelWidth =
803 calcMaxValue(BasicMenuItemUI.MAX_LABEL_WIDTH,
804 maxIconWidth + maxTextWidth + curGap);
805 } else {
806 // We shouldn't use current icon and text widths
807 // in maximal widths calculation for complex layout.
808 maxIconWidth = getParentIntProperty(BasicMenuItemUI.MAX_ICON_WIDTH);
809 maxLabelWidth = calcMaxValue(BasicMenuItemUI.MAX_LABEL_WIDTH,
810 labelRect.width);
811 // If maxLabelWidth is wider
812 // than the widest icon + the widest text + gap,
813 // we should update the maximal text witdh
814 int candidateTextWidth = maxLabelWidth - maxIconWidth;
815 if (maxIconWidth > 0) {
816 candidateTextWidth -= gap;
817 }
818 maxTextWidth = calcMaxValue(BasicMenuItemUI.MAX_TEXT_WIDTH,
819 candidateTextWidth);
820 }
821 }
822
823 // Calculates and returns maximal value
824 // through specified parent component client property.
825 private int calcMaxValue(Object propertyName, int value) {
826 // Get maximal value from parent client property
827 int maxValue = getParentIntProperty(propertyName);
828 // Store new maximal width in parent client property
829 if (value > maxValue) {
830 if (miParent != null) {
831 miParent.putClientProperty(propertyName, value);
832 }
833 return value;
834 } else {
835 return maxValue;
836 }
837 }
838
839 // Returns parent client property as int
840 private int getParentIntProperty(Object propertyName) {
841 Object value = null;
842 if (miParent != null) {
843 value = miParent.getClientProperty(propertyName);
844 }
845 if ((value == null) || !(value instanceof Integer)){
846 value = 0;
847 }
848 return (Integer)value;
849 }
850
851 private boolean isColumnLayout() {
852 return isColumnLayout(isLeftToRight, horizontalAlignment,
853 horizontalTextPosition, verticalTextPosition);
854 }
855
856 public static boolean isColumnLayout(boolean isLeftToRight,
857 JMenuItem mi) {
858 assert(mi != null);
859 return isColumnLayout(isLeftToRight, mi.getHorizontalAlignment(),
860 mi.getHorizontalTextPosition(), mi.getVerticalTextPosition());
861 }
862
863 // Answers should we do column layout for a menu item or not.
864 // We do it when a user doesn't set any alignments
865 // and text positions manually, except the vertical alignment.
866 public static boolean isColumnLayout( boolean isLeftToRight,
867 int horizontalAlignment, int horizontalTextPosition,
868 int verticalTextPosition) {
869 if (verticalTextPosition != SwingConstants.CENTER) {
870 return false;
871 }
872 if (isLeftToRight) {
873 if (horizontalAlignment != SwingConstants.LEADING
874 && horizontalAlignment != SwingConstants.LEFT) {
875 return false;
876 }
877 if (horizontalTextPosition != SwingConstants.TRAILING
878 && horizontalTextPosition != SwingConstants.RIGHT) {
879 return false;
880 }
881 } else {
882 if (horizontalAlignment != SwingConstants.LEADING
883 && horizontalAlignment != SwingConstants.RIGHT) {
884 return false;
885 }
886 if (horizontalTextPosition != SwingConstants.TRAILING
887 && horizontalTextPosition != SwingConstants.LEFT) {
888 return false;
889 }
890 }
891 return true;
892 }
893
894 // Calculates maximal text offset.
895 // It is required for some L&Fs (ex: Vista L&F).
896 // The offset is meaningful only for L2R column layout.
897 private void calcMaxTextOffset() {
898 if (!isColumnLayout || !isLeftToRight) {
899 return;
900 }
901
902 // Calculate the current text offset
903 int offset = viewRect.x + leadingGap + maxCheckWidth
904 + afterCheckIconGap + maxIconWidth + gap;
905 if (maxCheckWidth == 0) {
906 offset -= afterCheckIconGap;
907 }
908 if (maxIconWidth == 0) {
909 offset -= gap;
910 }
911
912 // maximal text offset shouldn't be less than minimal text offset;
913 if (offset < minTextOffset) {
914 offset = minTextOffset;
915 }
916
917 // Calculate and store the maximal text offset
918 calcMaxValue(BASICMENUITEMUI_MAX_TEXT_OFFSET, offset);
919 }
920
921 public String toString() {
922 StringBuilder result = new StringBuilder();
923 result.append(super.toString()).append("\n");
924 result.append("accFm = ").append(accFm).append("\n");
925 result.append("accRect = ").append(accRect).append("\n");
926 result.append("accText = ").append(accText).append("\n");
927 result.append("afterCheckIconGap = ").append(afterCheckIconGap)
928 .append("\n");
929 result.append("arrowIcon = ").append(arrowIcon).append("\n");
930 result.append("arrowRect = ").append(arrowRect).append("\n");
931 result.append("checkIcon = ").append(checkIcon).append("\n");
932 result.append("checkRect = ").append(checkRect).append("\n");
933 result.append("fm = ").append(fm).append("\n");
934 result.append("gap = ").append(gap).append("\n");
935 result.append("horizontalAlignment = ").append(horizontalAlignment)
936 .append("\n");
937 result.append("horizontalTextPosition = ")
938 .append(horizontalTextPosition).append("\n");
939 result.append("htmlView = ").append(htmlView).append("\n");
940 result.append("icon = ").append(icon).append("\n");
941 result.append("iconRect = ").append(iconRect).append("\n");
942 result.append("isColumnLayout = ").append(isColumnLayout).append("\n");
943 result.append("isLeftToRight = ").append(isLeftToRight).append("\n");
944 result.append("isTopLevelMenu = ").append(isTopLevelMenu).append("\n");
945 result.append("labelRect = ").append(labelRect).append("\n");
946 result.append("leadingGap = ").append(leadingGap).append("\n");
947 result.append("maxAccWidth = ").append(maxAccWidth).append("\n");
948 result.append("maxArrowWidth = ").append(maxArrowWidth).append("\n");
949 result.append("maxCheckWidth = ").append(maxCheckWidth).append("\n");
950 result.append("maxIconWidth = ").append(maxIconWidth).append("\n");
951 result.append("maxLabelWidth = ").append(maxLabelWidth).append("\n");
952 result.append("maxTextWidth = ").append(maxTextWidth).append("\n");
953 result.append("maxTextOffset = ")
954 .append(getParentIntProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET))
955 .append("\n");
956 result.append("mi = ").append(mi).append("\n");
957 result.append("minTextOffset = ").append(minTextOffset).append("\n");
958 result.append("miParent = ").append(miParent).append("\n");
959 result.append("origAccWidth = ").append(origAccWidth).append("\n");
960 result.append("origArrowWidth = ").append(origArrowWidth).append("\n");
961 result.append("origCheckWidth = ").append(origCheckWidth).append("\n");
962 result.append("origIconWidth = ").append(origIconWidth).append("\n");
963 result.append("origTextWidth = ").append(origTextWidth).append("\n");
964 result.append("text = ").append(text).append("\n");
965 result.append("textRect = ").append(textRect).append("\n");
966 result.append("useCheckAndArrow = ").append(useCheckAndArrow)
967 .append("\n");
968 result.append("verticalAlignment = ").append(verticalAlignment)
969 .append("\n");
970 result.append("verticalTextPosition = ")
971 .append(verticalTextPosition).append("\n");
972 result.append("viewRect = ").append(viewRect).append("\n");
973 return result.toString();
974 }
975 } // End of LayoutInfo
976
977 // Reuses layoutInfo object to reduce the amount of produced garbage
978 private LayoutInfo getLayoutInfo(JMenuItem mi, Icon checkIcon, Icon arrowIcon,
979 Rectangle viewRect, int gap, String accDelimiter,
980 boolean isLeftToRight, Font acceleratorFont,
981 boolean useCheckAndArrow, String propertyPrefix) {
982 // layoutInfo is final and always not null
983 layoutInfo.reset(mi, checkIcon, arrowIcon, viewRect,
984 gap, accDelimiter, isLeftToRight, acceleratorFont,
985 useCheckAndArrow, propertyPrefix);
986 return layoutInfo;
987 }
988
989 /**
990 * We draw the background in paintMenuItem()
991 * so override update (which fills the background of opaque
992 * components by default) to just call paint().
993 *
994 */
995 public void update(Graphics g, JComponent c) {
996 paint(g, c);
997 }
998
999 public void paint(Graphics g, JComponent c) {
1000 paintMenuItem(g, c, checkIcon, arrowIcon,
1001 selectionBackground, selectionForeground,
1002 defaultTextIconGap);
1003 }
1004
1005 protected void paintMenuItem(Graphics g, JComponent c,
1006 Icon checkIcon, Icon arrowIcon,
1007 Color background, Color foreground,
1008 int defaultTextIconGap) {
1009 // Save original graphics font and color
1010 Font holdf = g.getFont();
1011 Color holdc = g.getColor();
1012
1013 JMenuItem mi = (JMenuItem) c;
1014 g.setFont(mi.getFont());
1015
1016 Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight());
1017 applyInsets(viewRect, mi.getInsets());
1018
1019 LayoutInfo li = getLayoutInfo(mi, checkIcon, arrowIcon,
1020 viewRect, defaultTextIconGap, acceleratorDelimiter,
1021 BasicGraphicsUtils.isLeftToRight(mi), acceleratorFont,
1022 useCheckAndArrow(), getPropertyPrefix());
1023 layoutMenuItem(li);
1024
1025 paintBackground(g, mi, background);
1026 paintCheckIcon(g, li, holdc, foreground);
1027 paintIcon(g, li, holdc);
1028 paintText(g, li);
1029 paintAccText(g, li);
1030 paintArrowIcon(g, li, foreground);
1031
1032 // Restore original graphics font and color
1033 g.setColor(holdc);
1034 g.setFont(holdf);
1035
1036 li.clear();
1037 }
1038
1039 private void paintIcon(Graphics g, LayoutInfo li, Color holdc) {
1040 if (li.icon != null) {
1041 Icon icon;
1042 ButtonModel model = li.mi.getModel();
1043 if (!model.isEnabled()) {
1044 icon = (Icon) li.mi.getDisabledIcon();
1045 } else if (model.isPressed() && model.isArmed()) {
1046 icon = (Icon) li.mi.getPressedIcon();
1047 if (icon == null) {
1048 // Use default icon
1049 icon = (Icon) li.mi.getIcon();
1050 }
1051 } else {
1052 icon = (Icon) li.mi.getIcon();
1053 }
1054
1055 if (icon != null) {
1056 icon.paintIcon(li.mi, g, li.iconRect.x, li.iconRect.y);
1057 g.setColor(holdc);
1058 }
1059 }
1060 }
1061
1062 private void paintCheckIcon(Graphics g, LayoutInfo li,
1063 Color holdc, Color foreground) {
1064 if (li.checkIcon != null) {
1065 ButtonModel model = li.mi.getModel();
1066 if (model.isArmed()
1067 || (li.mi instanceof JMenu && model.isSelected())) {
1068 g.setColor(foreground);
1069 } else {
1070 g.setColor(holdc);
1071 }
1072 if (li.useCheckAndArrow) {
1073 li.checkIcon.paintIcon(li.mi, g, li.checkRect.x,
1074 li.checkRect.y);
1075 }
1076 g.setColor(holdc);
1077 }
1078 }
1079
1080 private void paintAccText(Graphics g, LayoutInfo li) {
1081 if (!li.accText.equals("")) {
1082 ButtonModel model = li.mi.getModel();
1083 g.setFont(acceleratorFont);
1084 if (!model.isEnabled()) {
1085 // *** paint the accText disabled
1086 if (disabledForeground != null) {
1087 g.setColor(disabledForeground);
1088 SwingUtilities2.drawString(li.mi, g, li.accText,
1089 li.accRect.x,
1090 li.accRect.y + li.accFm.getAscent());
1091 } else {
1092 g.setColor(li.mi.getBackground().brighter());
1093 SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x,
1094 li.accRect.y + li.accFm.getAscent());
1095 g.setColor(li.mi.getBackground().darker());
1096 SwingUtilities2.drawString(li.mi, g, li.accText,
1097 li.accRect.x - 1,
1098 li.accRect.y + li.accFm.getAscent() - 1);
1099 }
1100 } else {
1101 // *** paint the accText normally
1102 if (model.isArmed() ||
1103 (li.mi instanceof JMenu && model.isSelected())) {
1104 g.setColor(acceleratorSelectionForeground);
1105 } else {
1106 g.setColor(acceleratorForeground);
1107 }
1108 SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x,
1109 li.accRect.y + li.accFm.getAscent());
1110 }
1111 }
1112 }
1113
1114 private void paintText(Graphics g, LayoutInfo li) {
1115 if (!li.text.equals("")) {
1116 if (li.htmlView != null) {
1117 // Text is HTML
1118 li.htmlView.paint(g, li.textRect);
1119 } else {
1120 // Text isn't HTML
1121 paintText(g, li.mi, li.textRect, li.text);
1122 }
1123 }
1124 }
1125
1126 private void paintArrowIcon(Graphics g, LayoutInfo li, Color foreground) {
1127 if (li.arrowIcon != null) {
1128 ButtonModel model = li.mi.getModel();
1129 if (model.isArmed()
1130 || (li.mi instanceof JMenu && model.isSelected())) {
1131 g.setColor(foreground);
1132 }
1133 if (li.useCheckAndArrow) {
1134 li.arrowIcon.paintIcon(li.mi, g, li.arrowRect.x, li.arrowRect.y);
1135 }
1136 }
1137 }
1138
1139 private void applyInsets(Rectangle rect, Insets insets) {
1140 if(insets != null) {
1141 rect.x += insets.left;
1142 rect.y += insets.top;
1143 rect.width -= (insets.right + rect.x);
1144 rect.height -= (insets.bottom + rect.y);
1145 }
1146 }
1147
1148 /**
1149 * Draws the background of the menu item.
1150 *
1151 * @param g the paint graphics
1152 * @param menuItem menu item to be painted
1153 * @param bgColor selection background color
1154 * @since 1.4
1155 */
1156 protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
1157 ButtonModel model = menuItem.getModel();
1158 Color oldColor = g.getColor();
1159 int menuWidth = menuItem.getWidth();
1160 int menuHeight = menuItem.getHeight();
1161
1162 if(menuItem.isOpaque()) {
1163 if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
1164 g.setColor(bgColor);
1165 g.fillRect(0,0, menuWidth, menuHeight);
1166 } else {
1167 g.setColor(menuItem.getBackground());
1168 g.fillRect(0,0, menuWidth, menuHeight);
1169 }
1170 g.setColor(oldColor);
1171 }
1172 else if (model.isArmed() || (menuItem instanceof JMenu &&
1173 model.isSelected())) {
1174 g.setColor(bgColor);
1175 g.fillRect(0,0, menuWidth, menuHeight);
1176 g.setColor(oldColor);
1177 }
1178 }
1179
1180 /**
1181 * Renders the text of the current menu item.
1182 * <p>
1183 * @param g graphics context
1184 * @param menuItem menu item to render
1185 * @param textRect bounding rectangle for rendering the text
1186 * @param text string to render
1187 * @since 1.4
1188 */
1189 protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
1190 ButtonModel model = menuItem.getModel();
1191 FontMetrics fm = SwingUtilities2.getFontMetrics(menuItem, g);
1192 int mnemIndex = menuItem.getDisplayedMnemonicIndex();
1193
1194 if(!model.isEnabled()) {
1195 // *** paint the text disabled
1196 if ( UIManager.get("MenuItem.disabledForeground") instanceof Color ) {
1197 g.setColor( UIManager.getColor("MenuItem.disabledForeground") );
1198 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
1199 mnemIndex, textRect.x, textRect.y + fm.getAscent());
1200 } else {
1201 g.setColor(menuItem.getBackground().brighter());
1202 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g, text,
1203 mnemIndex, textRect.x, textRect.y + fm.getAscent());
1204 g.setColor(menuItem.getBackground().darker());
1205 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
1206 mnemIndex, textRect.x - 1, textRect.y +
1207 fm.getAscent() - 1);
1208 }
1209 } else {
1210 // *** paint the text normally
1211 if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) {
1212 g.setColor(selectionForeground); // Uses protected field.
1213 }
1214 SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text,
1215 mnemIndex, textRect.x, textRect.y + fm.getAscent());
1216 }
1217 }
1218
1219
1220 /**
1221 * Layout icon, text, check icon, accelerator text and arrow icon
1222 * in the viewRect and return their positions.
1223 *
1224 * If horizontalAlignment, verticalTextPosition and horizontalTextPosition
1225 * are default (user doesn't set any manually) the layouting algorithm is:
1226 * Elements are layouted in the five columns:
1227 * check icon + icon + text + accelerator text + arrow icon
1228 *
1229 * In the other case elements are layouted in the four columns:
1230 * check icon + label + accelerator text + arrow icon
1231 * Label is icon and text rectangles union.
1232 *
1233 * The order of columns can be reversed.
1234 * It depends on the menu item orientation.
1235 */
1236 private void layoutMenuItem(LayoutInfo li)
1237 {
1238 li.checkRect.width = li.maxCheckWidth;
1239 li.accRect.width = li.maxAccWidth;
1240 li.arrowRect.width = li.maxArrowWidth;
1241
1242 if (li.isColumnLayout) {
1243 if (li.isLeftToRight) {
1244 doLTRColumnLayout(li);
1245 } else {
1246 doRTLColumnLayout(li);
1247 }
1248 } else {
1249 if (li.isLeftToRight) {
1250 doLTRComplexLayout(li);
1251 } else {
1252 doRTLComplexLayout(li);
1253 }
1254 }
1255
1256 alignAccCheckAndArrowVertically(li);
1257 }
1258
1259 // Aligns the accelertor text and the check and arrow icons vertically
1260 // with the center of the label rect.
1261 private void alignAccCheckAndArrowVertically(LayoutInfo li) {
1262 li.accRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2
1263 - (float)li.accRect.height/2);
1264 fixVerticalAlignment(li, li.accRect);
1265 if (li.useCheckAndArrow) {
1266 li.arrowRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2
1267 - (float)li.arrowRect.height/2);
1268 li.checkRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2
1269 - (float)li.checkRect.height/2);
1270 fixVerticalAlignment(li, li.arrowRect);
1271 fixVerticalAlignment(li, li.checkRect);
1272 }
1273 }
1274
1275 // Fixes vertical alignment of all menu item elements if a rect.y
1276 // or (rect.y + rect.height) is out of viewRect bounds
1277 private void fixVerticalAlignment(LayoutInfo li, Rectangle r) {
1278 int delta = 0;
1279 if (r.y < li.viewRect.y) {
1280 delta = li.viewRect.y - r.y;
1281 } else if (r.y + r.height > li.viewRect.y + li.viewRect.height) {
1282 delta = li.viewRect.y + li.viewRect.height - r.y - r.height;
1283 }
1284 if (delta != 0) {
1285 li.checkRect.y += delta;
1286 li.iconRect.y += delta;
1287 li.textRect.y += delta;
1288 li.accRect.y += delta;
1289 li.arrowRect.y += delta;
1290 }
1291 }
1292
1293 private void doLTRColumnLayout(LayoutInfo li) {
1294 // Set maximal width for all the five basic rects
1295 // (three other ones are already maximal)
1296 li.iconRect.width = li.maxIconWidth;
1297 li.textRect.width = li.maxTextWidth;
1298
1299 // Set X coordinates
1300 // All rects will be aligned at the left side
1301 calcXPositionsL2R(li.viewRect.x, li.leadingGap, li.gap, li.checkRect,
1302 li.iconRect, li.textRect);
1303
1304 // Tune afterCheckIconGap
1305 if (li.checkRect.width > 0) { // there is the afterCheckIconGap
1306 li.iconRect.x += li.afterCheckIconGap - li.gap;
1307 li.textRect.x += li.afterCheckIconGap - li.gap;
1308 }
1309
1310 calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.gap,
1311 li.arrowRect, li.accRect);
1312
1313 // Take into account minimal text offset
1314 int textOffset = li.textRect.x - li.viewRect.x;
1315 if (!li.isTopLevelMenu && (textOffset < li.minTextOffset)) {
1316 li.textRect.x += li.minTextOffset - textOffset;
1317 }
1318
1319 // Take into account the left side bearings for text and accelerator text.
1320 fixTextRects(li);
1321
1322 // Set Y coordinate for text and icon.
1323 // Y coordinates for other rects
1324 // will be calculated later in layoutMenuItem.
1325 calcTextAndIconYPositions(li);
1326
1327 // Calculate valid X and Y coordinates for labelRect
1328 li.labelRect = li.textRect.union(li.iconRect);
1329 }
1330
1331 private void doLTRComplexLayout(LayoutInfo li) {
1332 li.labelRect.width = li.maxLabelWidth;
1333
1334 // Set X coordinates
1335 calcXPositionsL2R(li.viewRect.x, li.leadingGap, li.gap, li.checkRect,
1336 li.labelRect);
1337
1338 // Tune afterCheckIconGap
1339 if (li.checkRect.width > 0) { // there is the afterCheckIconGap
1340 li.labelRect.x += li.afterCheckIconGap - li.gap;
1341 }
1342
1343 calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.gap,
1344 li.arrowRect, li.accRect);
1345
1346 // Take into account minimal text offset
1347 int labelOffset = li.labelRect.x - li.viewRect.x;
1348 if (!li.isTopLevelMenu && (labelOffset < li.minTextOffset)) {
1349 li.labelRect.x += li.minTextOffset - labelOffset;
1350 }
1351
1352 // Take into account the left side bearing for accelerator text.
1353 // The LSB for text is taken into account in layoutCompoundLabel() below.
1354 fixAccTextRect(li);
1355
1356 // Layout icon and text with SwingUtilities.layoutCompoundLabel()
1357 // within the labelRect
1358 li.textRect = new Rectangle();
1359 li.iconRect = new Rectangle();
1360 SwingUtilities.layoutCompoundLabel(
1361 li.mi, li.fm, li.text, li.icon, li.verticalAlignment,
1362 li.horizontalAlignment, li.verticalTextPosition,
1363 li.horizontalTextPosition, li.labelRect,
1364 li.iconRect, li.textRect, li.gap);
1365 }
1366
1367 private void doRTLColumnLayout(LayoutInfo li) {
1368 // Set maximal width for all the five basic rects
1369 // (three other ones are already maximal)
1370 li.iconRect.width = li.maxIconWidth;
1371 li.textRect.width = li.maxTextWidth;
1372
1373 // Set X coordinates
1374 calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.leadingGap,
1375 li.gap, li.checkRect, li.iconRect, li.textRect);
1376
1377 // Tune the gap after check icon
1378 if (li.checkRect.width > 0) { // there is the gap after check icon
1379 li.iconRect.x -= li.afterCheckIconGap - li.gap;
1380 li.textRect.x -= li.afterCheckIconGap - li.gap;
1381 }
1382
1383 calcXPositionsL2R(li.viewRect.x, li.gap, li.arrowRect,
1384 li.accRect);
1385
1386 // Take into account minimal text offset
1387 int textOffset = (li.viewRect.x + li.viewRect.width)
1388 - (li.textRect.x + li.textRect.width);
1389 if (!li.isTopLevelMenu && (textOffset < li.minTextOffset)) {
1390 li.textRect.x -= li.minTextOffset - textOffset;
1391 }
1392
1393 // Align icon, text, accelerator text, check icon and arrow icon
1394 // at the right side
1395 rightAlignAllRects(li);
1396
1397 // Take into account the left side bearings for text and accelerator text.
1398 fixTextRects(li);
1399
1400 // Set Y coordinates for text and icon.
1401 // Y coordinates for other rects
1402 // will be calculated later in layoutMenuItem.
1403 calcTextAndIconYPositions(li);
1404
1405 // Calculate valid X and Y coordinate for labelRect
1406 li.labelRect = li.textRect.union(li.iconRect);
1407 }
1408
1409 private void doRTLComplexLayout(LayoutInfo li) {
1410 li.labelRect.width = li.maxLabelWidth;
1411
1412 // Set X coordinates
1413 calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.leadingGap,
1414 li.gap, li.checkRect, li.labelRect);
1415
1416 // Tune the gap after check icon
1417 if (li.checkRect.width > 0) { // there is the gap after check icon
1418 li.labelRect.x -= li.afterCheckIconGap - li.gap;
1419 }
1420
1421 calcXPositionsL2R(li.viewRect.x, li.gap, li.arrowRect,
1422 li.accRect);
1423
1424 // Take into account minimal text offset
1425 int labelOffset = (li.viewRect.x + li.viewRect.width)
1426 - (li.labelRect.x + li.labelRect.width);
1427 if (!li.isTopLevelMenu && (labelOffset < li.minTextOffset)) {
1428 li.labelRect.x -= li.minTextOffset - labelOffset;
1429 }
1430
1431 // Align icon, text, accelerator text, check icon and arrow icon
1432 // at the right side
1433 rightAlignAllRects(li);
1434
1435 // Take into account the left side bearing for accelerator text.
1436 // The LSB for text is taken into account in layoutCompoundLabel() below.
1437 fixAccTextRect(li);
1438
1439 // Layout icon and text with SwingUtilities.layoutCompoundLabel()
1440 // within the labelRect
1441 li.textRect = new Rectangle();
1442 li.iconRect = new Rectangle();
1443 SwingUtilities.layoutCompoundLabel(
1444 menuItem, li.fm, li.text, li.icon, li.verticalAlignment,
1445 li.horizontalAlignment, li.verticalTextPosition,
1446 li.horizontalTextPosition, li.labelRect,
1447 li.iconRect, li.textRect, li.gap);
1448 }
1449
1450 private void calcXPositionsL2R(int startXPos, int leadingGap,
1451 int gap, Rectangle... rects) {
1452 int curXPos = startXPos + leadingGap;
1453 for (Rectangle rect : rects) {
1454 rect.x = curXPos;
1455 if (rect.width > 0) {
1456 curXPos += rect.width + gap;
1457 }
1458 }
1459 }
1460
1461 private void calcXPositionsL2R(int startXPos, int gap, Rectangle... rects) {
1462 calcXPositionsL2R(startXPos, gap, gap, rects);
1463 }
1464
1465 private void calcXPositionsR2L(int startXPos, int leadingGap,
1466 int gap, Rectangle... rects) {
1467 int curXPos = startXPos - leadingGap;
1468 for (Rectangle rect : rects) {
1469 rect.x = curXPos - rect.width;
1470 if (rect.width > 0) {
1471 curXPos -= rect.width + gap;
1472 }
1473 }
1474 }
1475
1476 private void calcXPositionsR2L(int startXPos, int gap, Rectangle... rects) {
1477 calcXPositionsR2L(startXPos, gap, gap, rects);
1478 }
1479
1480 // Takes into account the left side bearings for text and accelerator text
1481 private void fixTextRects(LayoutInfo li) {
1482 if (li.htmlView == null) { // The text isn't a HTML
1483 int lsb = SwingUtilities2.getLeftSideBearing(li.mi, li.fm, li.text);
1484 if (lsb < 0) {
1485 li.textRect.x -= lsb;
1486 }
1487 }
1488 fixAccTextRect(li);
1489 }
1490
1491 // Takes into account the left side bearing for accelerator text
1492 private void fixAccTextRect(LayoutInfo li) {
1493 int lsb = SwingUtilities2
1494 .getLeftSideBearing(li.mi, li.accFm, li.accText);
1495 if (lsb < 0) {
1496 li.accRect.x -= lsb;
1497 }
1498 }
1499
1500 // Sets Y coordinates of text and icon
1501 // taking into account the vertical alignment
1502 private void calcTextAndIconYPositions(LayoutInfo li) {
1503 if (li.verticalAlignment == SwingUtilities.TOP) {
1504 li.textRect.y = (int)(li.viewRect.y
1505 + (float)li.labelRect.height/2
1506 - (float)li.textRect.height/2);
1507 li.iconRect.y = (int)(li.viewRect.y
1508 + (float)li.labelRect.height/2
1509 - (float)li.iconRect.height/2);
1510 } else if (li.verticalAlignment == SwingUtilities.CENTER) {
1511 li.textRect.y = (int)(li.viewRect.y
1512 + (float)li.viewRect.height/2
1513 - (float)li.textRect.height/2);
1514 li.iconRect.y = (int)(li.viewRect.y
1515 + (float)li.viewRect.height/2
1516 - (float)li.iconRect.height/2);
1517 }
1518 else if (li.verticalAlignment == SwingUtilities.BOTTOM) {
1519 li.textRect.y = (int)(li.viewRect.y + li.viewRect.height
1520 - (float)li.labelRect.height/2
1521 - (float)li.textRect.height/2);
1522 li.iconRect.y = (int)(li.viewRect.y + li.viewRect.height
1523 - (float)li.labelRect.height/2
1524 - (float)li.iconRect.height/2);
1525 }
1526 }
1527
1528 // Aligns icon, text, accelerator text, check icon and arrow icon
1529 // at the right side
1530 private void rightAlignAllRects(LayoutInfo li) {
1531 li.iconRect.x = li.iconRect.x + li.iconRect.width - li.origIconWidth;
1532 li.iconRect.width = li.origIconWidth;
1533 li.textRect.x = li.textRect.x + li.textRect.width - li.origTextWidth;
1534 li.textRect.width = li.origTextWidth;
1535 li.accRect.x = li.accRect.x + li.accRect.width
1536 - li.origAccWidth;
1537 li.accRect.width = li.origAccWidth;
1538 li.checkRect.x = li.checkRect.x + li.checkRect.width
1539 - li.origCheckWidth;
1540 li.checkRect.width = li.origCheckWidth;
1541 li.arrowRect.x = li.arrowRect.x + li.arrowRect.width -
1542 li.origArrowWidth;
1543 li.arrowRect.width = li.origArrowWidth;
1544 }
1545
1546 /*
1547 * Returns false if the component is a JMenu and it is a top
1548 * level menu (on the menubar).
1549 */
1550 private boolean useCheckAndArrow(){
1551 boolean b = true;
1552 if((menuItem instanceof JMenu) &&
1553 (((JMenu)menuItem).isTopLevelMenu())) {
1554 b = false;
1555 }
1556 return b;
1557 }
1558
1559 public MenuElement[] getPath() {
1560 MenuSelectionManager m = MenuSelectionManager.defaultManager();
1561 MenuElement oldPath[] = m.getSelectedPath();
1562 MenuElement newPath[];
1563 int i = oldPath.length;
1564 if (i == 0)
1565 return new MenuElement[0];
1566 Component parent = menuItem.getParent();
1567 if (oldPath[i-1].getComponent() == parent) {
1568 // The parent popup menu is the last so far
1569 newPath = new MenuElement[i+1];
1570 System.arraycopy(oldPath, 0, newPath, 0, i);
1571 newPath[i] = menuItem;
1572 } else {
1573 // A sibling menuitem is the current selection
1574 //
1575 // This probably needs to handle 'exit submenu into
1576 // a menu item. Search backwards along the current
1577 // selection until you find the parent popup menu,
1578 // then copy up to that and add yourself...
1579 int j;
1580 for (j = oldPath.length-1; j >= 0; j--) {
1581 if (oldPath[j].getComponent() == parent)
1582 break;
1583 }
1584 newPath = new MenuElement[j+2];
1585 System.arraycopy(oldPath, 0, newPath, 0, j+1);
1586 newPath[j+1] = menuItem;
1587 /*
1588 System.out.println("Sibling condition -- ");
1589 System.out.println("Old array : ");
1590 printMenuElementArray(oldPath, false);
1591 System.out.println("New array : ");
1592 printMenuElementArray(newPath, false);
1593 */
1594 }
1595 return newPath;
1596 }
1597
1598 void printMenuElementArray(MenuElement path[], boolean dumpStack) {
1599 System.out.println("Path is(");
1600 int i, j;
1601 for(i=0,j=path.length; i<j ;i++){
1602 for (int k=0; k<=i; k++)
1603 System.out.print(" ");
1604 MenuElement me = (MenuElement) path[i];
1605 if(me instanceof JMenuItem)
1606 System.out.println(((JMenuItem)me).getText() + ", ");
1607 else if (me == null)
1608 System.out.println("NULL , ");
1609 else
1610 System.out.println("" + me + ", ");
1611 }
1612 System.out.println(")");
1613
1614 if (dumpStack == true)
1615 Thread.dumpStack();
1616 }
1617 protected class MouseInputHandler implements MouseInputListener {
1618 // NOTE: This class exists only for backward compatability. All
1619 // its functionality has been moved into Handler. If you need to add
1620 // new functionality add it to the Handler, but make sure this
1621 // class calls into the Handler.
1622
1623 public void mouseClicked(MouseEvent e) {
1624 getHandler().mouseClicked(e);
1625 }
1626 public void mousePressed(MouseEvent e) {
1627 getHandler().mousePressed(e);
1628 }
1629 public void mouseReleased(MouseEvent e) {
1630 getHandler().mouseReleased(e);
1631 }
1632 public void mouseEntered(MouseEvent e) {
1633 getHandler().mouseEntered(e);
1634 }
1635 public void mouseExited(MouseEvent e) {
1636 getHandler().mouseExited(e);
1637 }
1638 public void mouseDragged(MouseEvent e) {
1639 getHandler().mouseDragged(e);
1640 }
1641 public void mouseMoved(MouseEvent e) {
1642 getHandler().mouseMoved(e);
1643 }
1644 }
1645
1646
1647 private static class Actions extends UIAction {
1648 private static final String CLICK = "doClick";
1649
1650 Actions(String key) {
1651 super(key);
1652 }
1653
1654 public void actionPerformed(ActionEvent e) {
1655 JMenuItem mi = (JMenuItem)e.getSource();
1656 MenuSelectionManager.defaultManager().clearSelectedPath();
1657 mi.doClick();
1658 }
1659 }
1660
1661 /**
1662 * Call this method when a menu item is to be activated.
1663 * This method handles some of the details of menu item activation
1664 * such as clearing the selected path and messaging the
1665 * JMenuItem's doClick() method.
1666 *
1667 * @param msm A MenuSelectionManager. The visual feedback and
1668 * internal bookkeeping tasks are delegated to
1669 * this MenuSelectionManager. If <code>null</code> is
1670 * passed as this argument, the
1671 * <code>MenuSelectionManager.defaultManager</code> is
1672 * used.
1673 * @see MenuSelectionManager
1674 * @see JMenuItem#doClick(int)
1675 * @since 1.4
1676 */
1677 protected void doClick(MenuSelectionManager msm) {
1678 // Auditory cue
1679 if (! isInternalFrameSystemMenu()) {
1680 BasicLookAndFeel.playSound(menuItem, getPropertyPrefix() +
1681 ".commandSound");
1682 }
1683 // Visual feedback
1684 if (msm == null) {
1685 msm = MenuSelectionManager.defaultManager();
1686 }
1687 msm.clearSelectedPath();
1688 menuItem.doClick(0);
1689 }
1690
1691 /**
1692 * This is to see if the menu item in question is part of the
1693 * system menu on an internal frame.
1694 * The Strings that are being checked can be found in
1695 * MetalInternalFrameTitlePaneUI.java,
1696 * WindowsInternalFrameTitlePaneUI.java, and
1697 * MotifInternalFrameTitlePaneUI.java.
1698 *
1699 * @since 1.4
1700 */
1701 private boolean isInternalFrameSystemMenu() {
1702 String actionCommand = menuItem.getActionCommand();
1703 if ((actionCommand == "Close") ||
1704 (actionCommand == "Minimize") ||
1705 (actionCommand == "Restore") ||
1706 (actionCommand == "Maximize")) {
1707 return true;
1708 } else {
1709 return false;
1710 }
1711 }
1712
1713
1714 // BasicMenuUI subclasses this.
1715 class Handler implements MenuDragMouseListener,
1716 MouseInputListener, PropertyChangeListener {
1717 //
1718 // MouseInputListener
1719 //
1720 public void mouseClicked(MouseEvent e) {}
1721 public void mousePressed(MouseEvent e) {
1722 }
1723 public void mouseReleased(MouseEvent e) {
1724 if (!menuItem.isEnabled()) {
1725 return;
1726 }
1727 MenuSelectionManager manager =
1728 MenuSelectionManager.defaultManager();
1729 Point p = e.getPoint();
1730 if(p.x >= 0 && p.x < menuItem.getWidth() &&
1731 p.y >= 0 && p.y < menuItem.getHeight()) {
1732 doClick(manager);
1733 } else {
1734 manager.processMouseEvent(e);
1735 }
1736 }
1737 public void mouseEntered(MouseEvent e) {
1738 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1739 int modifiers = e.getModifiers();
1740 // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
1741 if ((modifiers & (InputEvent.BUTTON1_MASK |
1742 InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
1743 MenuSelectionManager.defaultManager().processMouseEvent(e);
1744 } else {
1745 manager.setSelectedPath(getPath());
1746 }
1747 }
1748 public void mouseExited(MouseEvent e) {
1749 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1750
1751 int modifiers = e.getModifiers();
1752 // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
1753 if ((modifiers & (InputEvent.BUTTON1_MASK |
1754 InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) {
1755 MenuSelectionManager.defaultManager().processMouseEvent(e);
1756 } else {
1757
1758 MenuElement path[] = manager.getSelectedPath();
1759 if (path.length > 1 && path[path.length-1] == menuItem) {
1760 MenuElement newPath[] = new MenuElement[path.length-1];
1761 int i,c;
1762 for(i=0,c=path.length-1;i<c;i++)
1763 newPath[i] = path[i];
1764 manager.setSelectedPath(newPath);
1765 }
1766 }
1767 }
1768
1769 public void mouseDragged(MouseEvent e) {
1770 MenuSelectionManager.defaultManager().processMouseEvent(e);
1771 }
1772 public void mouseMoved(MouseEvent e) {
1773 }
1774
1775 //
1776 // MenuDragListener
1777 //
1778 public void menuDragMouseEntered(MenuDragMouseEvent e) {
1779 MenuSelectionManager manager = e.getMenuSelectionManager();
1780 MenuElement path[] = e.getPath();
1781 manager.setSelectedPath(path);
1782 }
1783 public void menuDragMouseDragged(MenuDragMouseEvent e) {
1784 MenuSelectionManager manager = e.getMenuSelectionManager();
1785 MenuElement path[] = e.getPath();
1786 manager.setSelectedPath(path);
1787 }
1788 public void menuDragMouseExited(MenuDragMouseEvent e) {}
1789 public void menuDragMouseReleased(MenuDragMouseEvent e) {
1790 if (!menuItem.isEnabled()) {
1791 return;
1792 }
1793 MenuSelectionManager manager = e.getMenuSelectionManager();
1794 MenuElement path[] = e.getPath();
1795 Point p = e.getPoint();
1796 if (p.x >= 0 && p.x < menuItem.getWidth() &&
1797 p.y >= 0 && p.y < menuItem.getHeight()) {
1798 doClick(manager);
1799 } else {
1800 manager.clearSelectedPath();
1801 }
1802 }
1803
1804
1805 //
1806 // PropertyChangeListener
1807 //
1808 public void propertyChange(PropertyChangeEvent e) {
1809 String name = e.getPropertyName();
1810
1811 if (name == "labelFor" || name == "displayedMnemonic" ||
1812 name == "accelerator") {
1813 updateAcceleratorBinding();
1814 } else if (name == "text" || "font" == name ||
1815 "foreground" == name) {
1816 // remove the old html view client property if one
1817 // existed, and install a new one if the text installed
1818 // into the JLabel is html source.
1819 JMenuItem lbl = ((JMenuItem) e.getSource());
1820 String text = lbl.getText();
1821 BasicHTML.updateRenderer(lbl, text);
1822 } else if (name == "iconTextGap") {
1823 defaultTextIconGap = ((Number)e.getNewValue()).intValue();
1824 }
1825 }
1826 }
1827}