/* * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package javax.swing.plaf.basic; import sun.swing.MenuItemCheckIconFactory; import sun.swing.SwingUtilities2; import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET; import java.awt.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import javax.swing.plaf.*; import javax.swing.text.View; import sun.swing.UIAction; import sun.swing.StringUIClientPropertyKey; /** * BasicMenuItem implementation * * @author Georges Saab * @author David Karlton * @author Arnaud Weber * @author Fredrik Lagerblad */ public class BasicMenuItemUI extends MenuItemUI { protected JMenuItem menuItem = null; protected Color selectionBackground; protected Color selectionForeground; protected Color disabledForeground; protected Color acceleratorForeground; protected Color acceleratorSelectionForeground; private String acceleratorDelimiter; protected int defaultTextIconGap; protected Font acceleratorFont; protected MouseInputListener mouseInputListener; protected MenuDragMouseListener menuDragMouseListener; protected MenuKeyListener menuKeyListener; /** * <code>PropertyChangeListener</code> returned from * <code>createPropertyChangeListener</code>. You should not * need to access this field, rather if you want to customize the * <code>PropertyChangeListener</code> override * <code>createPropertyChangeListener</code>. * * @since 1.6 * @see #createPropertyChangeListener */ protected PropertyChangeListener propertyChangeListener; // BasicMenuUI also uses this. Handler handler; protected Icon arrowIcon = null; protected Icon checkIcon = null; protected boolean oldBorderPainted; /* diagnostic aids -- should be false for production builds. */ private static final boolean TRACE = false; // trace creates and disposes private static final boolean VERBOSE = false; // show reuse hits/misses private static final boolean DEBUG = false; // show bad params, misc. // Allows to reuse layoutInfo object. // Shouldn't be used directly. Use getLayoutInfo() instead. private final transient LayoutInfo layoutInfo = new LayoutInfo(); /* Client Property keys for calculation of maximal widths */ static final StringUIClientPropertyKey MAX_ARROW_WIDTH = new StringUIClientPropertyKey("maxArrowWidth"); static final StringUIClientPropertyKey MAX_CHECK_WIDTH = new StringUIClientPropertyKey("maxCheckWidth"); static final StringUIClientPropertyKey MAX_ICON_WIDTH = new StringUIClientPropertyKey("maxIconWidth"); static final StringUIClientPropertyKey MAX_TEXT_WIDTH = new StringUIClientPropertyKey("maxTextWidth"); static final StringUIClientPropertyKey MAX_ACC_WIDTH = new StringUIClientPropertyKey("maxAccWidth"); static final StringUIClientPropertyKey MAX_LABEL_WIDTH = new StringUIClientPropertyKey("maxLabelWidth"); static void loadActionMap(LazyActionMap map) { // NOTE: BasicMenuUI also calls into this method. map.put(new Actions(Actions.CLICK)); BasicLookAndFeel.installAudioActionMap(map); } public static ComponentUI createUI(JComponent c) { return new BasicMenuItemUI(); } public void installUI(JComponent c) { menuItem = (JMenuItem) c; installDefaults(); installComponents(menuItem); installListeners(); installKeyboardActions(); } protected void installDefaults() { String prefix = getPropertyPrefix(); acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont"); Object opaque = UIManager.get(getPropertyPrefix() + ".opaque"); if (opaque != null) { LookAndFeel.installProperty(menuItem, "opaque", opaque); } else { LookAndFeel.installProperty(menuItem, "opaque", Boolean.TRUE); } if(menuItem.getMargin() == null || (menuItem.getMargin() instanceof UIResource)) { menuItem.setMargin(UIManager.getInsets(prefix + ".margin")); } LookAndFeel.installProperty(menuItem, "iconTextGap", new Integer(4)); defaultTextIconGap = menuItem.getIconTextGap(); LookAndFeel.installBorder(menuItem, prefix + ".border"); oldBorderPainted = menuItem.isBorderPainted(); LookAndFeel.installProperty(menuItem, "borderPainted", UIManager.get(prefix + ".borderPainted")); LookAndFeel.installColorsAndFont(menuItem, prefix + ".background", prefix + ".foreground", prefix + ".font"); // MenuItem specific defaults if (selectionBackground == null || selectionBackground instanceof UIResource) { selectionBackground = UIManager.getColor(prefix + ".selectionBackground"); } if (selectionForeground == null || selectionForeground instanceof UIResource) { selectionForeground = UIManager.getColor(prefix + ".selectionForeground"); } if (disabledForeground == null || disabledForeground instanceof UIResource) { disabledForeground = UIManager.getColor(prefix + ".disabledForeground"); } if (acceleratorForeground == null || acceleratorForeground instanceof UIResource) { acceleratorForeground = UIManager.getColor(prefix + ".acceleratorForeground"); } if (acceleratorSelectionForeground == null || acceleratorSelectionForeground instanceof UIResource) { acceleratorSelectionForeground = UIManager.getColor(prefix + ".acceleratorSelectionForeground"); } // Get accelerator delimiter acceleratorDelimiter = UIManager.getString("MenuItem.acceleratorDelimiter"); if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; } // Icons if (arrowIcon == null || arrowIcon instanceof UIResource) { arrowIcon = UIManager.getIcon(prefix + ".arrowIcon"); } if (checkIcon == null || checkIcon instanceof UIResource) { checkIcon = UIManager.getIcon(prefix + ".checkIcon"); //In case of column layout, .checkIconFactory is defined for this UI, //the icon is compatible with it and useCheckAndArrow() is true, //then the icon is handled by the checkIcon. boolean isColumnLayout = LayoutInfo.isColumnLayout( BasicGraphicsUtils.isLeftToRight(menuItem), menuItem); if (isColumnLayout) { MenuItemCheckIconFactory iconFactory = (MenuItemCheckIconFactory) UIManager.get(prefix + ".checkIconFactory"); if (iconFactory != null && useCheckAndArrow() && iconFactory.isCompatible(checkIcon, prefix)) { checkIcon = iconFactory.getIcon(menuItem); } } } } /** * @since 1.3 */ protected void installComponents(JMenuItem menuItem){ BasicHTML.updateRenderer(menuItem, menuItem.getText()); } protected String getPropertyPrefix() { return "MenuItem"; } protected void installListeners() { if ((mouseInputListener = createMouseInputListener(menuItem)) != null) { menuItem.addMouseListener(mouseInputListener); menuItem.addMouseMotionListener(mouseInputListener); } if ((menuDragMouseListener = createMenuDragMouseListener(menuItem)) != null) { menuItem.addMenuDragMouseListener(menuDragMouseListener); } if ((menuKeyListener = createMenuKeyListener(menuItem)) != null) { menuItem.addMenuKeyListener(menuKeyListener); } if ((propertyChangeListener = createPropertyChangeListener(menuItem)) != null) { menuItem.addPropertyChangeListener(propertyChangeListener); } } protected void installKeyboardActions() { installLazyActionMap(); updateAcceleratorBinding(); } void installLazyActionMap() { LazyActionMap.installLazyActionMap(menuItem, BasicMenuItemUI.class, getPropertyPrefix() + ".actionMap"); } public void uninstallUI(JComponent c) { menuItem = (JMenuItem)c; uninstallDefaults(); uninstallComponents(menuItem); uninstallListeners(); uninstallKeyboardActions(); // Remove values from the parent's Client Properties. JComponent p = getMenuItemParent(menuItem); if(p != null) { p.putClientProperty(BasicMenuItemUI.MAX_ARROW_WIDTH, null ); p.putClientProperty(BasicMenuItemUI.MAX_CHECK_WIDTH, null ); p.putClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH, null ); p.putClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH, null ); p.putClientProperty(BasicMenuItemUI.MAX_ICON_WIDTH, null ); p.putClientProperty(BasicMenuItemUI.MAX_LABEL_WIDTH, null ); p.putClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET, null ); } menuItem = null; } protected void uninstallDefaults() { LookAndFeel.uninstallBorder(menuItem); LookAndFeel.installProperty(menuItem, "borderPainted", oldBorderPainted); if (menuItem.getMargin() instanceof UIResource) menuItem.setMargin(null); if (arrowIcon instanceof UIResource) arrowIcon = null; if (checkIcon instanceof UIResource) checkIcon = null; } /** * @since 1.3 */ protected void uninstallComponents(JMenuItem menuItem){ BasicHTML.updateRenderer(menuItem, ""); } protected void uninstallListeners() { if (mouseInputListener != null) { menuItem.removeMouseListener(mouseInputListener); menuItem.removeMouseMotionListener(mouseInputListener); } if (menuDragMouseListener != null) { menuItem.removeMenuDragMouseListener(menuDragMouseListener); } if (menuKeyListener != null) { menuItem.removeMenuKeyListener(menuKeyListener); } if (propertyChangeListener != null) { menuItem.removePropertyChangeListener(propertyChangeListener); } mouseInputListener = null; menuDragMouseListener = null; menuKeyListener = null; propertyChangeListener = null; handler = null; } protected void uninstallKeyboardActions() { SwingUtilities.replaceUIActionMap(menuItem, null); SwingUtilities.replaceUIInputMap(menuItem, JComponent. WHEN_IN_FOCUSED_WINDOW, null); } protected MouseInputListener createMouseInputListener(JComponent c) { return getHandler(); } protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) { return getHandler(); } protected MenuKeyListener createMenuKeyListener(JComponent c) { return null; } /** * Creates a <code>PropertyChangeListener</code> which will be added to * the menu item. * If this method returns null then it will not be added to the menu item. * * @return an instance of a <code>PropertyChangeListener</code> or null * @since 1.6 */ protected PropertyChangeListener createPropertyChangeListener(JComponent c) { return getHandler(); } Handler getHandler() { if (handler == null) { handler = new Handler(); } return handler; } InputMap createInputMap(int condition) { if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) { return new ComponentInputMapUIResource(menuItem); } return null; } void updateAcceleratorBinding() { KeyStroke accelerator = menuItem.getAccelerator(); InputMap windowInputMap = SwingUtilities.getUIInputMap( menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW); if (windowInputMap != null) { windowInputMap.clear(); } if (accelerator != null) { if (windowInputMap == null) { windowInputMap = createInputMap(JComponent. WHEN_IN_FOCUSED_WINDOW); SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap); } windowInputMap.put(accelerator, "doClick"); } } public Dimension getMinimumSize(JComponent c) { Dimension d = null; View v = (View) c.getClientProperty(BasicHTML.propertyKey); if (v != null) { d = getPreferredSize(c); d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS); } return d; } public Dimension getPreferredSize(JComponent c) { return getPreferredMenuItemSize(c, checkIcon, arrowIcon, defaultTextIconGap); } public Dimension getMaximumSize(JComponent c) { Dimension d = null; View v = (View) c.getClientProperty(BasicHTML.propertyKey); if (v != null) { d = getPreferredSize(c); d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS); } return d; } // Returns parent of this component if it is not a top-level menu // Otherwise returns null private static JComponent getMenuItemParent(JMenuItem mi) { Container parent = mi.getParent(); if ((parent instanceof JComponent) && (!(mi instanceof JMenu) || !((JMenu)mi).isTopLevelMenu())) { return (JComponent) parent; } else { return null; } } protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon, Icon arrowIcon, int defaultTextIconGap) { // The method also determines the preferred width of the // parent popup menu (through DefaultMenuLayout class). // The menu width equals to the maximal width // among child menu items. // Menu item width will be a sum of the widest check icon, label, // arrow icon and accelerator text among neighbor menu items. // For the latest menu item we will know the maximal widths exactly. // It will be the widest menu item and it will determine // the width of the parent popup menu. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // There is a conceptual problem: if user sets preferred size manually // for a menu item, this method won't be called for it // (see JComponent.getPreferredSize()), // maximal widths won't be calculated, other menu items won't be able // to take them into account and will be layouted in such a way, // as there is no the item with manual preferred size. // But after the first paint() method call, all maximal widths // will be correctly calculated and layout of some menu items // can be changed. For example, it can cause a shift of // the icon and text when user points a menu item by mouse. JMenuItem mi = (JMenuItem) c; LayoutInfo li = getLayoutInfo(mi, checkIcon, arrowIcon, createMaxViewRect(), defaultTextIconGap, acceleratorDelimiter, BasicGraphicsUtils.isLeftToRight(mi), acceleratorFont, useCheckAndArrow(), getPropertyPrefix()); Dimension result = new Dimension(); // Calculate the result width result.width = li.leadingGap; addWidth(li.maxCheckWidth, li.afterCheckIconGap, result); // Take into account mimimal text offset. if ((!li.isTopLevelMenu) && (li.minTextOffset > 0) && (result.width < li.minTextOffset)) { result.width = li.minTextOffset; } addWidth(li.maxLabelWidth, li.gap, result); addWidth(li.maxAccWidth, li.gap, result); addWidth(li.maxArrowWidth, li.gap, result); // Calculate the result height result.height = max(li.checkRect.height, li.labelRect.height, li.accRect.height, li.arrowRect.height); // Take into account menu item insets Insets insets = li.mi.getInsets(); if(insets != null) { result.width += insets.left + insets.right; result.height += insets.top + insets.bottom; } // if the width is even, bump it up one. This is critical // for the focus dash line to draw properly if(result.width%2 == 0) { result.width++; } // if the height is even, bump it up one. This is critical // for the text to center properly if(result.height%2 == 0 && Boolean.TRUE != UIManager.get(getPropertyPrefix() + ".evenHeight")) { result.height++; } li.clear(); return result; } private Rectangle createMaxViewRect() { return new Rectangle(0,0,Short.MAX_VALUE, Short.MAX_VALUE); } private void addWidth(int width, int gap, Dimension result) { if (width > 0) { result.width += width + gap; } } private static int max(int... values) { int maxValue = Integer.MIN_VALUE; for (int i : values) { if (i > maxValue) { maxValue = i; } } return maxValue; } // LayoutInfo helps to calculate preferred size and to paint a menu item private static class LayoutInfo { JMenuItem mi; JComponent miParent; FontMetrics fm; FontMetrics accFm; Icon icon; Icon checkIcon; Icon arrowIcon; String text; String accText; boolean isColumnLayout; boolean useCheckAndArrow; boolean isLeftToRight; boolean isTopLevelMenu; View htmlView; int verticalAlignment; int horizontalAlignment; int verticalTextPosition; int horizontalTextPosition; int gap; int leadingGap; int afterCheckIconGap; int minTextOffset; Rectangle viewRect; Rectangle iconRect; Rectangle textRect; Rectangle accRect; Rectangle checkRect; Rectangle arrowRect; Rectangle labelRect; int origIconWidth; int origTextWidth; int origAccWidth; int origCheckWidth; int origArrowWidth; int maxIconWidth; int maxTextWidth; int maxAccWidth; int maxCheckWidth; int maxArrowWidth; int maxLabelWidth; // Empty constructor helps to create "final" LayoutInfo object public LayoutInfo() { } public LayoutInfo(JMenuItem mi, Icon checkIcon, Icon arrowIcon, Rectangle viewRect, int gap, String accDelimiter, boolean isLeftToRight, Font acceleratorFont, boolean useCheckAndArrow, String propertyPrefix) { reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter, isLeftToRight, acceleratorFont, useCheckAndArrow, propertyPrefix); } // Allows to reuse a LayoutInfo object public void reset(JMenuItem mi, Icon checkIcon, Icon arrowIcon, Rectangle viewRect, int gap, String accDelimiter, boolean isLeftToRight, Font acceleratorFont, boolean useCheckAndArrow, String propertyPrefix) { this.mi = mi; this.miParent = getMenuItemParent(mi); this.accText = getAccText(accDelimiter); this.verticalAlignment = mi.getVerticalAlignment(); this.horizontalAlignment = mi.getHorizontalAlignment(); this.verticalTextPosition = mi.getVerticalTextPosition(); this.horizontalTextPosition = mi.getHorizontalTextPosition(); this.useCheckAndArrow = useCheckAndArrow; this.fm = mi.getFontMetrics(mi.getFont()); this.accFm = mi.getFontMetrics(acceleratorFont); this.isLeftToRight = isLeftToRight; this.isColumnLayout = isColumnLayout(); this.isTopLevelMenu = (this.miParent == null)? true : false; this.checkIcon = checkIcon; this.icon = getIcon(propertyPrefix); this.arrowIcon = arrowIcon; this.text = mi.getText(); this.gap = gap; this.afterCheckIconGap = getAfterCheckIconGap(propertyPrefix); this.minTextOffset = getMinTextOffset(propertyPrefix); this.htmlView = (View) mi.getClientProperty(BasicHTML.propertyKey); this.viewRect = viewRect; this.iconRect = new Rectangle(); this.textRect = new Rectangle(); this.accRect = new Rectangle(); this.checkRect = new Rectangle(); this.arrowRect = new Rectangle(); this.labelRect = new Rectangle(); calcWidthsAndHeights(); this.origIconWidth = iconRect.width; this.origTextWidth = textRect.width; this.origAccWidth = accRect.width; this.origCheckWidth = checkRect.width; this.origArrowWidth = arrowRect.width; calcMaxWidths(); this.leadingGap = getLeadingGap(propertyPrefix); calcMaxTextOffset(); } // Clears fields to remove all links to other objects // to prevent memory leaks public void clear() { mi = null; miParent = null; fm = null; accFm = null; icon = null; checkIcon = null; arrowIcon = null; text = null; accText = null; htmlView = null; viewRect = null; iconRect = null; textRect = null; accRect = null; checkRect = null; arrowRect = null; labelRect = null; } private String getAccText(String acceleratorDelimiter) { String accText = ""; KeyStroke accelerator = mi.getAccelerator(); if (accelerator != null) { int modifiers = accelerator.getModifiers(); if (modifiers > 0) { accText = KeyEvent.getKeyModifiersText(modifiers); accText += acceleratorDelimiter; } int keyCode = accelerator.getKeyCode(); if (keyCode != 0) { accText += KeyEvent.getKeyText(keyCode); } else { accText += accelerator.getKeyChar(); } } return accText; } // In case of column layout, .checkIconFactory is defined for this UI, // the icon is compatible with it and useCheckAndArrow() is true, // then the icon is handled by the checkIcon. private Icon getIcon(String propertyPrefix) { Icon icon = null; MenuItemCheckIconFactory iconFactory = (MenuItemCheckIconFactory) UIManager.get(propertyPrefix + ".checkIconFactory"); if (!isColumnLayout || !useCheckAndArrow || iconFactory == null || !iconFactory.isCompatible(checkIcon, propertyPrefix)) { icon = mi.getIcon(); } return icon; } private int getMinTextOffset(String propertyPrefix) { int minimumTextOffset = 0; Object minimumTextOffsetObject = UIManager.get(propertyPrefix + ".minimumTextOffset"); if (minimumTextOffsetObject instanceof Integer) { minimumTextOffset = (Integer) minimumTextOffsetObject; } return minimumTextOffset; } private int getAfterCheckIconGap(String propertyPrefix) { int afterCheckIconGap = gap; Object afterCheckIconGapObject = UIManager.get(propertyPrefix + ".afterCheckIconGap"); if (afterCheckIconGapObject instanceof Integer) { afterCheckIconGap = (Integer) afterCheckIconGapObject; } return afterCheckIconGap; } private int getLeadingGap(String propertyPrefix) { if (maxCheckWidth > 0) { return getCheckOffset(propertyPrefix); } else { return gap; // There is no any check icon } } private int getCheckOffset(String propertyPrefix) { int checkIconOffset = gap; Object checkIconOffsetObject = UIManager.get(propertyPrefix + ".checkIconOffset"); if (checkIconOffsetObject instanceof Integer) { checkIconOffset = (Integer) checkIconOffsetObject; } return checkIconOffset; } private void calcWidthsAndHeights() { // iconRect if (icon != null) { iconRect.width = icon.getIconWidth(); iconRect.height = icon.getIconHeight(); } // accRect if (!accText.equals("")) { accRect.width = SwingUtilities2.stringWidth( mi, accFm, accText); accRect.height = accFm.getHeight(); } // textRect if (text == null) { text = ""; } else if (!text.equals("")) { if (htmlView != null) { // Text is HTML textRect.width = (int) htmlView.getPreferredSpan(View.X_AXIS); textRect.height = (int) htmlView.getPreferredSpan(View.Y_AXIS); } else { // Text isn't HTML textRect.width = SwingUtilities2.stringWidth(mi, fm, text); textRect.height = fm.getHeight(); } } if (useCheckAndArrow) { // checkIcon if (checkIcon != null) { checkRect.width = checkIcon.getIconWidth(); checkRect.height = checkIcon.getIconHeight(); } // arrowRect if (arrowIcon != null) { arrowRect.width = arrowIcon.getIconWidth(); arrowRect.height = arrowIcon.getIconHeight(); } } // labelRect if (isColumnLayout) { labelRect.width = iconRect.width + textRect.width + gap; labelRect.height = max(checkRect.height, iconRect.height, textRect.height, accRect.height, arrowRect.height); } else { textRect = new Rectangle(); iconRect = new Rectangle(); SwingUtilities.layoutCompoundLabel(mi, fm, text, icon, verticalAlignment, horizontalAlignment, verticalTextPosition, horizontalTextPosition, viewRect, iconRect, textRect, gap); labelRect = iconRect.union(textRect); } } private void calcMaxWidths() { maxCheckWidth = calcMaxValue(BasicMenuItemUI.MAX_CHECK_WIDTH, checkRect.width); maxArrowWidth = calcMaxValue(BasicMenuItemUI.MAX_ARROW_WIDTH, arrowRect.width); maxAccWidth = calcMaxValue(BasicMenuItemUI.MAX_ACC_WIDTH, accRect.width); if (isColumnLayout) { maxIconWidth = calcMaxValue(BasicMenuItemUI.MAX_ICON_WIDTH, iconRect.width); maxTextWidth = calcMaxValue(BasicMenuItemUI.MAX_TEXT_WIDTH, textRect.width); int curGap = gap; if ((maxIconWidth == 0) || (maxTextWidth == 0)) { curGap = 0; } maxLabelWidth = calcMaxValue(BasicMenuItemUI.MAX_LABEL_WIDTH, maxIconWidth + maxTextWidth + curGap); } else { // We shouldn't use current icon and text widths // in maximal widths calculation for complex layout. maxIconWidth = getParentIntProperty(BasicMenuItemUI.MAX_ICON_WIDTH); maxLabelWidth = calcMaxValue(BasicMenuItemUI.MAX_LABEL_WIDTH, labelRect.width); // If maxLabelWidth is wider // than the widest icon + the widest text + gap, // we should update the maximal text witdh int candidateTextWidth = maxLabelWidth - maxIconWidth; if (maxIconWidth > 0) { candidateTextWidth -= gap; } maxTextWidth = calcMaxValue(BasicMenuItemUI.MAX_TEXT_WIDTH, candidateTextWidth); } } // Calculates and returns maximal value // through specified parent component client property. private int calcMaxValue(Object propertyName, int value) { // Get maximal value from parent client property int maxValue = getParentIntProperty(propertyName); // Store new maximal width in parent client property if (value > maxValue) { if (miParent != null) { miParent.putClientProperty(propertyName, value); } return value; } else { return maxValue; } } // Returns parent client property as int private int getParentIntProperty(Object propertyName) { Object value = null; if (miParent != null) { value = miParent.getClientProperty(propertyName); } if ((value == null) || !(value instanceof Integer)){ value = 0; } return (Integer)value; } private boolean isColumnLayout() { return isColumnLayout(isLeftToRight, horizontalAlignment, horizontalTextPosition, verticalTextPosition); } public static boolean isColumnLayout(boolean isLeftToRight, JMenuItem mi) { assert(mi != null); return isColumnLayout(isLeftToRight, mi.getHorizontalAlignment(), mi.getHorizontalTextPosition(), mi.getVerticalTextPosition()); } // Answers should we do column layout for a menu item or not. // We do it when a user doesn't set any alignments // and text positions manually, except the vertical alignment. public static boolean isColumnLayout( boolean isLeftToRight, int horizontalAlignment, int horizontalTextPosition, int verticalTextPosition) { if (verticalTextPosition != SwingConstants.CENTER) { return false; } if (isLeftToRight) { if (horizontalAlignment != SwingConstants.LEADING && horizontalAlignment != SwingConstants.LEFT) { return false; } if (horizontalTextPosition != SwingConstants.TRAILING && horizontalTextPosition != SwingConstants.RIGHT) { return false; } } else { if (horizontalAlignment != SwingConstants.LEADING && horizontalAlignment != SwingConstants.RIGHT) { return false; } if (horizontalTextPosition != SwingConstants.TRAILING && horizontalTextPosition != SwingConstants.LEFT) { return false; } } return true; } // Calculates maximal text offset. // It is required for some L&Fs (ex: Vista L&F). // The offset is meaningful only for L2R column layout. private void calcMaxTextOffset() { if (!isColumnLayout || !isLeftToRight) { return; } // Calculate the current text offset int offset = viewRect.x + leadingGap + maxCheckWidth + afterCheckIconGap + maxIconWidth + gap; if (maxCheckWidth == 0) { offset -= afterCheckIconGap; } if (maxIconWidth == 0) { offset -= gap; } // maximal text offset shouldn't be less than minimal text offset; if (offset < minTextOffset) { offset = minTextOffset; } // Calculate and store the maximal text offset calcMaxValue(BASICMENUITEMUI_MAX_TEXT_OFFSET, offset); } public String toString() { StringBuilder result = new StringBuilder(); result.append(super.toString()).append("\n"); result.append("accFm = ").append(accFm).append("\n"); result.append("accRect = ").append(accRect).append("\n"); result.append("accText = ").append(accText).append("\n"); result.append("afterCheckIconGap = ").append(afterCheckIconGap) .append("\n"); result.append("arrowIcon = ").append(arrowIcon).append("\n"); result.append("arrowRect = ").append(arrowRect).append("\n"); result.append("checkIcon = ").append(checkIcon).append("\n"); result.append("checkRect = ").append(checkRect).append("\n"); result.append("fm = ").append(fm).append("\n"); result.append("gap = ").append(gap).append("\n"); result.append("horizontalAlignment = ").append(horizontalAlignment) .append("\n"); result.append("horizontalTextPosition = ") .append(horizontalTextPosition).append("\n"); result.append("htmlView = ").append(htmlView).append("\n"); result.append("icon = ").append(icon).append("\n"); result.append("iconRect = ").append(iconRect).append("\n"); result.append("isColumnLayout = ").append(isColumnLayout).append("\n"); result.append("isLeftToRight = ").append(isLeftToRight).append("\n"); result.append("isTopLevelMenu = ").append(isTopLevelMenu).append("\n"); result.append("labelRect = ").append(labelRect).append("\n"); result.append("leadingGap = ").append(leadingGap).append("\n"); result.append("maxAccWidth = ").append(maxAccWidth).append("\n"); result.append("maxArrowWidth = ").append(maxArrowWidth).append("\n"); result.append("maxCheckWidth = ").append(maxCheckWidth).append("\n"); result.append("maxIconWidth = ").append(maxIconWidth).append("\n"); result.append("maxLabelWidth = ").append(maxLabelWidth).append("\n"); result.append("maxTextWidth = ").append(maxTextWidth).append("\n"); result.append("maxTextOffset = ") .append(getParentIntProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET)) .append("\n"); result.append("mi = ").append(mi).append("\n"); result.append("minTextOffset = ").append(minTextOffset).append("\n"); result.append("miParent = ").append(miParent).append("\n"); result.append("origAccWidth = ").append(origAccWidth).append("\n"); result.append("origArrowWidth = ").append(origArrowWidth).append("\n"); result.append("origCheckWidth = ").append(origCheckWidth).append("\n"); result.append("origIconWidth = ").append(origIconWidth).append("\n"); result.append("origTextWidth = ").append(origTextWidth).append("\n"); result.append("text = ").append(text).append("\n"); result.append("textRect = ").append(textRect).append("\n"); result.append("useCheckAndArrow = ").append(useCheckAndArrow) .append("\n"); result.append("verticalAlignment = ").append(verticalAlignment) .append("\n"); result.append("verticalTextPosition = ") .append(verticalTextPosition).append("\n"); result.append("viewRect = ").append(viewRect).append("\n"); return result.toString(); } } // End of LayoutInfo // Reuses layoutInfo object to reduce the amount of produced garbage private LayoutInfo getLayoutInfo(JMenuItem mi, Icon checkIcon, Icon arrowIcon, Rectangle viewRect, int gap, String accDelimiter, boolean isLeftToRight, Font acceleratorFont, boolean useCheckAndArrow, String propertyPrefix) { // layoutInfo is final and always not null layoutInfo.reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter, isLeftToRight, acceleratorFont, useCheckAndArrow, propertyPrefix); return layoutInfo; } /** * We draw the background in paintMenuItem() * so override update (which fills the background of opaque * components by default) to just call paint(). * */ public void update(Graphics g, JComponent c) { paint(g, c); } public void paint(Graphics g, JComponent c) { paintMenuItem(g, c, checkIcon, arrowIcon, selectionBackground, selectionForeground, defaultTextIconGap); } protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon, Icon arrowIcon, Color background, Color foreground, int defaultTextIconGap) { // Save original graphics font and color Font holdf = g.getFont(); Color holdc = g.getColor(); JMenuItem mi = (JMenuItem) c; g.setFont(mi.getFont()); Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); applyInsets(viewRect, mi.getInsets()); LayoutInfo li = getLayoutInfo(mi, checkIcon, arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, BasicGraphicsUtils.isLeftToRight(mi), acceleratorFont, useCheckAndArrow(), getPropertyPrefix()); layoutMenuItem(li); paintBackground(g, mi, background); paintCheckIcon(g, li, holdc, foreground); paintIcon(g, li, holdc); paintText(g, li); paintAccText(g, li); paintArrowIcon(g, li, foreground); // Restore original graphics font and color g.setColor(holdc); g.setFont(holdf); li.clear(); } private void paintIcon(Graphics g, LayoutInfo li, Color holdc) { if (li.icon != null) { Icon icon; ButtonModel model = li.mi.getModel(); if (!model.isEnabled()) { icon = (Icon) li.mi.getDisabledIcon(); } else if (model.isPressed() && model.isArmed()) { icon = (Icon) li.mi.getPressedIcon(); if (icon == null) { // Use default icon icon = (Icon) li.mi.getIcon(); } } else { icon = (Icon) li.mi.getIcon(); } if (icon != null) { icon.paintIcon(li.mi, g, li.iconRect.x, li.iconRect.y); g.setColor(holdc); } } } private void paintCheckIcon(Graphics g, LayoutInfo li, Color holdc, Color foreground) { if (li.checkIcon != null) { ButtonModel model = li.mi.getModel(); if (model.isArmed() || (li.mi instanceof JMenu && model.isSelected())) { g.setColor(foreground); } else { g.setColor(holdc); } if (li.useCheckAndArrow) { li.checkIcon.paintIcon(li.mi, g, li.checkRect.x, li.checkRect.y); } g.setColor(holdc); } } private void paintAccText(Graphics g, LayoutInfo li) { if (!li.accText.equals("")) { ButtonModel model = li.mi.getModel(); g.setFont(acceleratorFont); if (!model.isEnabled()) { // *** paint the accText disabled if (disabledForeground != null) { g.setColor(disabledForeground); SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x, li.accRect.y + li.accFm.getAscent()); } else { g.setColor(li.mi.getBackground().brighter()); SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x, li.accRect.y + li.accFm.getAscent()); g.setColor(li.mi.getBackground().darker()); SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x - 1, li.accRect.y + li.accFm.getAscent() - 1); } } else { // *** paint the accText normally if (model.isArmed() || (li.mi instanceof JMenu && model.isSelected())) { g.setColor(acceleratorSelectionForeground); } else { g.setColor(acceleratorForeground); } SwingUtilities2.drawString(li.mi, g, li.accText, li.accRect.x, li.accRect.y + li.accFm.getAscent()); } } } private void paintText(Graphics g, LayoutInfo li) { if (!li.text.equals("")) { if (li.htmlView != null) { // Text is HTML li.htmlView.paint(g, li.textRect); } else { // Text isn't HTML paintText(g, li.mi, li.textRect, li.text); } } } private void paintArrowIcon(Graphics g, LayoutInfo li, Color foreground) { if (li.arrowIcon != null) { ButtonModel model = li.mi.getModel(); if (model.isArmed() || (li.mi instanceof JMenu && model.isSelected())) { g.setColor(foreground); } if (li.useCheckAndArrow) { li.arrowIcon.paintIcon(li.mi, g, li.arrowRect.x, li.arrowRect.y); } } } private void applyInsets(Rectangle rect, Insets insets) { if(insets != null) { rect.x += insets.left; rect.y += insets.top; rect.width -= (insets.right + rect.x); rect.height -= (insets.bottom + rect.y); } } /** * Draws the background of the menu item. * * @param g the paint graphics * @param menuItem menu item to be painted * @param bgColor selection background color * @since 1.4 */ protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) { ButtonModel model = menuItem.getModel(); Color oldColor = g.getColor(); int menuWidth = menuItem.getWidth(); int menuHeight = menuItem.getHeight(); if(menuItem.isOpaque()) { if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) { g.setColor(bgColor); g.fillRect(0,0, menuWidth, menuHeight); } else { g.setColor(menuItem.getBackground()); g.fillRect(0,0, menuWidth, menuHeight); } g.setColor(oldColor); } else if (model.isArmed() || (menuItem instanceof JMenu && model.isSelected())) { g.setColor(bgColor); g.fillRect(0,0, menuWidth, menuHeight); g.setColor(oldColor); } } /** * Renders the text of the current menu item. * <p> * @param g graphics context * @param menuItem menu item to render * @param textRect bounding rectangle for rendering the text * @param text string to render * @since 1.4 */ protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) { ButtonModel model = menuItem.getModel(); FontMetrics fm = SwingUtilities2.getFontMetrics(menuItem, g); int mnemIndex = menuItem.getDisplayedMnemonicIndex(); if(!model.isEnabled()) { // *** paint the text disabled if ( UIManager.get("MenuItem.disabledForeground") instanceof Color ) { g.setColor( UIManager.getColor("MenuItem.disabledForeground") ); SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text, mnemIndex, textRect.x, textRect.y + fm.getAscent()); } else { g.setColor(menuItem.getBackground().brighter()); SwingUtilities2.drawStringUnderlineCharAt(menuItem, g, text, mnemIndex, textRect.x, textRect.y + fm.getAscent()); g.setColor(menuItem.getBackground().darker()); SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text, mnemIndex, textRect.x - 1, textRect.y + fm.getAscent() - 1); } } else { // *** paint the text normally if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) { g.setColor(selectionForeground); // Uses protected field. } SwingUtilities2.drawStringUnderlineCharAt(menuItem, g,text, mnemIndex, textRect.x, textRect.y + fm.getAscent()); } } /** * Layout icon, text, check icon, accelerator text and arrow icon * in the viewRect and return their positions. * * If horizontalAlignment, verticalTextPosition and horizontalTextPosition * are default (user doesn't set any manually) the layouting algorithm is: * Elements are layouted in the five columns: * check icon + icon + text + accelerator text + arrow icon * * In the other case elements are layouted in the four columns: * check icon + label + accelerator text + arrow icon * Label is icon and text rectangles union. * * The order of columns can be reversed. * It depends on the menu item orientation. */ private void layoutMenuItem(LayoutInfo li) { li.checkRect.width = li.maxCheckWidth; li.accRect.width = li.maxAccWidth; li.arrowRect.width = li.maxArrowWidth; if (li.isColumnLayout) { if (li.isLeftToRight) { doLTRColumnLayout(li); } else { doRTLColumnLayout(li); } } else { if (li.isLeftToRight) { doLTRComplexLayout(li); } else { doRTLComplexLayout(li); } } alignAccCheckAndArrowVertically(li); } // Aligns the accelertor text and the check and arrow icons vertically // with the center of the label rect. private void alignAccCheckAndArrowVertically(LayoutInfo li) { li.accRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2 - (float)li.accRect.height/2); fixVerticalAlignment(li, li.accRect); if (li.useCheckAndArrow) { li.arrowRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2 - (float)li.arrowRect.height/2); li.checkRect.y = (int)(li.labelRect.y + (float)li.labelRect.height/2 - (float)li.checkRect.height/2); fixVerticalAlignment(li, li.arrowRect); fixVerticalAlignment(li, li.checkRect); } } // Fixes vertical alignment of all menu item elements if a rect.y // or (rect.y + rect.height) is out of viewRect bounds private void fixVerticalAlignment(LayoutInfo li, Rectangle r) { int delta = 0; if (r.y < li.viewRect.y) { delta = li.viewRect.y - r.y; } else if (r.y + r.height > li.viewRect.y + li.viewRect.height) { delta = li.viewRect.y + li.viewRect.height - r.y - r.height; } if (delta != 0) { li.checkRect.y += delta; li.iconRect.y += delta; li.textRect.y += delta; li.accRect.y += delta; li.arrowRect.y += delta; } } private void doLTRColumnLayout(LayoutInfo li) { // Set maximal width for all the five basic rects // (three other ones are already maximal) li.iconRect.width = li.maxIconWidth; li.textRect.width = li.maxTextWidth; // Set X coordinates // All rects will be aligned at the left side calcXPositionsL2R(li.viewRect.x, li.leadingGap, li.gap, li.checkRect, li.iconRect, li.textRect); // Tune afterCheckIconGap if (li.checkRect.width > 0) { // there is the afterCheckIconGap li.iconRect.x += li.afterCheckIconGap - li.gap; li.textRect.x += li.afterCheckIconGap - li.gap; } calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.gap, li.arrowRect, li.accRect); // Take into account minimal text offset int textOffset = li.textRect.x - li.viewRect.x; if (!li.isTopLevelMenu && (textOffset < li.minTextOffset)) { li.textRect.x += li.minTextOffset - textOffset; } // Take into account the left side bearings for text and accelerator text. fixTextRects(li); // Set Y coordinate for text and icon. // Y coordinates for other rects // will be calculated later in layoutMenuItem. calcTextAndIconYPositions(li); // Calculate valid X and Y coordinates for labelRect li.labelRect = li.textRect.union(li.iconRect); } private void doLTRComplexLayout(LayoutInfo li) { li.labelRect.width = li.maxLabelWidth; // Set X coordinates calcXPositionsL2R(li.viewRect.x, li.leadingGap, li.gap, li.checkRect, li.labelRect); // Tune afterCheckIconGap if (li.checkRect.width > 0) { // there is the afterCheckIconGap li.labelRect.x += li.afterCheckIconGap - li.gap; } calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.gap, li.arrowRect, li.accRect); // Take into account minimal text offset int labelOffset = li.labelRect.x - li.viewRect.x; if (!li.isTopLevelMenu && (labelOffset < li.minTextOffset)) { li.labelRect.x += li.minTextOffset - labelOffset; } // Take into account the left side bearing for accelerator text. // The LSB for text is taken into account in layoutCompoundLabel() below. fixAccTextRect(li); // Layout icon and text with SwingUtilities.layoutCompoundLabel() // within the labelRect li.textRect = new Rectangle(); li.iconRect = new Rectangle(); SwingUtilities.layoutCompoundLabel( li.mi, li.fm, li.text, li.icon, li.verticalAlignment, li.horizontalAlignment, li.verticalTextPosition, li.horizontalTextPosition, li.labelRect, li.iconRect, li.textRect, li.gap); } private void doRTLColumnLayout(LayoutInfo li) { // Set maximal width for all the five basic rects // (three other ones are already maximal) li.iconRect.width = li.maxIconWidth; li.textRect.width = li.maxTextWidth; // Set X coordinates calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.leadingGap, li.gap, li.checkRect, li.iconRect, li.textRect); // Tune the gap after check icon if (li.checkRect.width > 0) { // there is the gap after check icon li.iconRect.x -= li.afterCheckIconGap - li.gap; li.textRect.x -= li.afterCheckIconGap - li.gap; } calcXPositionsL2R(li.viewRect.x, li.gap, li.arrowRect, li.accRect); // Take into account minimal text offset int textOffset = (li.viewRect.x + li.viewRect.width) - (li.textRect.x + li.textRect.width); if (!li.isTopLevelMenu && (textOffset < li.minTextOffset)) { li.textRect.x -= li.minTextOffset - textOffset; } // Align icon, text, accelerator text, check icon and arrow icon // at the right side rightAlignAllRects(li); // Take into account the left side bearings for text and accelerator text. fixTextRects(li); // Set Y coordinates for text and icon. // Y coordinates for other rects // will be calculated later in layoutMenuItem. calcTextAndIconYPositions(li); // Calculate valid X and Y coordinate for labelRect li.labelRect = li.textRect.union(li.iconRect); } private void doRTLComplexLayout(LayoutInfo li) { li.labelRect.width = li.maxLabelWidth; // Set X coordinates calcXPositionsR2L(li.viewRect.x + li.viewRect.width, li.leadingGap, li.gap, li.checkRect, li.labelRect); // Tune the gap after check icon if (li.checkRect.width > 0) { // there is the gap after check icon li.labelRect.x -= li.afterCheckIconGap - li.gap; } calcXPositionsL2R(li.viewRect.x, li.gap, li.arrowRect, li.accRect); // Take into account minimal text offset int labelOffset = (li.viewRect.x + li.viewRect.width) - (li.labelRect.x + li.labelRect.width); if (!li.isTopLevelMenu && (labelOffset < li.minTextOffset)) { li.labelRect.x -= li.minTextOffset - labelOffset; } // Align icon, text, accelerator text, check icon and arrow icon // at the right side rightAlignAllRects(li); // Take into account the left side bearing for accelerator text. // The LSB for text is taken into account in layoutCompoundLabel() below. fixAccTextRect(li); // Layout icon and text with SwingUtilities.layoutCompoundLabel() // within the labelRect li.textRect = new Rectangle(); li.iconRect = new Rectangle(); SwingUtilities.layoutCompoundLabel( menuItem, li.fm, li.text, li.icon, li.verticalAlignment, li.horizontalAlignment, li.verticalTextPosition, li.horizontalTextPosition, li.labelRect, li.iconRect, li.textRect, li.gap); } private void calcXPositionsL2R(int startXPos, int leadingGap, int gap, Rectangle... rects) { int curXPos = startXPos + leadingGap; for (Rectangle rect : rects) { rect.x = curXPos; if (rect.width > 0) { curXPos += rect.width + gap; } } } private void calcXPositionsL2R(int startXPos, int gap, Rectangle... rects) { calcXPositionsL2R(startXPos, gap, gap, rects); } private void calcXPositionsR2L(int startXPos, int leadingGap, int gap, Rectangle... rects) { int curXPos = startXPos - leadingGap; for (Rectangle rect : rects) { rect.x = curXPos - rect.width; if (rect.width > 0) { curXPos -= rect.width + gap; } } } private void calcXPositionsR2L(int startXPos, int gap, Rectangle... rects) { calcXPositionsR2L(startXPos, gap, gap, rects); } // Takes into account the left side bearings for text and accelerator text private void fixTextRects(LayoutInfo li) { if (li.htmlView == null) { // The text isn't a HTML int lsb = SwingUtilities2.getLeftSideBearing(li.mi, li.fm, li.text); if (lsb < 0) { li.textRect.x -= lsb; } } fixAccTextRect(li); } // Takes into account the left side bearing for accelerator text private void fixAccTextRect(LayoutInfo li) { int lsb = SwingUtilities2 .getLeftSideBearing(li.mi, li.accFm, li.accText); if (lsb < 0) { li.accRect.x -= lsb; } } // Sets Y coordinates of text and icon // taking into account the vertical alignment private void calcTextAndIconYPositions(LayoutInfo li) { if (li.verticalAlignment == SwingUtilities.TOP) { li.textRect.y = (int)(li.viewRect.y + (float)li.labelRect.height/2 - (float)li.textRect.height/2); li.iconRect.y = (int)(li.viewRect.y + (float)li.labelRect.height/2 - (float)li.iconRect.height/2); } else if (li.verticalAlignment == SwingUtilities.CENTER) { li.textRect.y = (int)(li.viewRect.y + (float)li.viewRect.height/2 - (float)li.textRect.height/2); li.iconRect.y = (int)(li.viewRect.y + (float)li.viewRect.height/2 - (float)li.iconRect.height/2); } else if (li.verticalAlignment == SwingUtilities.BOTTOM) { li.textRect.y = (int)(li.viewRect.y + li.viewRect.height - (float)li.labelRect.height/2 - (float)li.textRect.height/2); li.iconRect.y = (int)(li.viewRect.y + li.viewRect.height - (float)li.labelRect.height/2 - (float)li.iconRect.height/2); } } // Aligns icon, text, accelerator text, check icon and arrow icon // at the right side private void rightAlignAllRects(LayoutInfo li) { li.iconRect.x = li.iconRect.x + li.iconRect.width - li.origIconWidth; li.iconRect.width = li.origIconWidth; li.textRect.x = li.textRect.x + li.textRect.width - li.origTextWidth; li.textRect.width = li.origTextWidth; li.accRect.x = li.accRect.x + li.accRect.width - li.origAccWidth; li.accRect.width = li.origAccWidth; li.checkRect.x = li.checkRect.x + li.checkRect.width - li.origCheckWidth; li.checkRect.width = li.origCheckWidth; li.arrowRect.x = li.arrowRect.x + li.arrowRect.width - li.origArrowWidth; li.arrowRect.width = li.origArrowWidth; } /* * Returns false if the component is a JMenu and it is a top * level menu (on the menubar). */ private boolean useCheckAndArrow(){ boolean b = true; if((menuItem instanceof JMenu) && (((JMenu)menuItem).isTopLevelMenu())) { b = false; } return b; } public MenuElement[] getPath() { MenuSelectionManager m = MenuSelectionManager.defaultManager(); MenuElement oldPath[] = m.getSelectedPath(); MenuElement newPath[]; int i = oldPath.length; if (i == 0) return new MenuElement[0]; Component parent = menuItem.getParent(); if (oldPath[i-1].getComponent() == parent) { // The parent popup menu is the last so far newPath = new MenuElement[i+1]; System.arraycopy(oldPath, 0, newPath, 0, i); newPath[i] = menuItem; } else { // A sibling menuitem is the current selection // // This probably needs to handle 'exit submenu into // a menu item. Search backwards along the current // selection until you find the parent popup menu, // then copy up to that and add yourself... int j; for (j = oldPath.length-1; j >= 0; j--) { if (oldPath[j].getComponent() == parent) break; } newPath = new MenuElement[j+2]; System.arraycopy(oldPath, 0, newPath, 0, j+1); newPath[j+1] = menuItem; /* System.out.println("Sibling condition -- "); System.out.println("Old array : "); printMenuElementArray(oldPath, false); System.out.println("New array : "); printMenuElementArray(newPath, false); */ } return newPath; } void printMenuElementArray(MenuElement path[], boolean dumpStack) { System.out.println("Path is("); int i, j; for(i=0,j=path.length; i<j ;i++){ for (int k=0; k<=i; k++) System.out.print(" "); MenuElement me = (MenuElement) path[i]; if(me instanceof JMenuItem) System.out.println(((JMenuItem)me).getText() + ", "); else if (me == null) System.out.println("NULL , "); else System.out.println("" + me + ", "); } System.out.println(")"); if (dumpStack == true) Thread.dumpStack(); } protected class MouseInputHandler implements MouseInputListener { // NOTE: This class exists only for backward compatability. All // its functionality has been moved into Handler. If you need to add // new functionality add it to the Handler, but make sure this // class calls into the Handler. public void mouseClicked(MouseEvent e) { getHandler().mouseClicked(e); } public void mousePressed(MouseEvent e) { getHandler().mousePressed(e); } public void mouseReleased(MouseEvent e) { getHandler().mouseReleased(e); } public void mouseEntered(MouseEvent e) { getHandler().mouseEntered(e); } public void mouseExited(MouseEvent e) { getHandler().mouseExited(e); } public void mouseDragged(MouseEvent e) { getHandler().mouseDragged(e); } public void mouseMoved(MouseEvent e) { getHandler().mouseMoved(e); } } private static class Actions extends UIAction { private static final String CLICK = "doClick"; Actions(String key) { super(key); } public void actionPerformed(ActionEvent e) { JMenuItem mi = (JMenuItem)e.getSource(); MenuSelectionManager.defaultManager().clearSelectedPath(); mi.doClick(); } } /** * Call this method when a menu item is to be activated. * This method handles some of the details of menu item activation * such as clearing the selected path and messaging the * JMenuItem's doClick() method. * * @param msm A MenuSelectionManager. The visual feedback and * internal bookkeeping tasks are delegated to * this MenuSelectionManager. If <code>null</code> is * passed as this argument, the * <code>MenuSelectionManager.defaultManager</code> is * used. * @see MenuSelectionManager * @see JMenuItem#doClick(int) * @since 1.4 */ protected void doClick(MenuSelectionManager msm) { // Auditory cue if (! isInternalFrameSystemMenu()) { BasicLookAndFeel.playSound(menuItem, getPropertyPrefix() + ".commandSound"); } // Visual feedback if (msm == null) { msm = MenuSelectionManager.defaultManager(); } msm.clearSelectedPath(); menuItem.doClick(0); } /** * This is to see if the menu item in question is part of the * system menu on an internal frame. * The Strings that are being checked can be found in * MetalInternalFrameTitlePaneUI.java, * WindowsInternalFrameTitlePaneUI.java, and * MotifInternalFrameTitlePaneUI.java. * * @since 1.4 */ private boolean isInternalFrameSystemMenu() { String actionCommand = menuItem.getActionCommand(); if ((actionCommand == "Close") || (actionCommand == "Minimize") || (actionCommand == "Restore") || (actionCommand == "Maximize")) { return true; } else { return false; } } // BasicMenuUI subclasses this. class Handler implements MenuDragMouseListener, MouseInputListener, PropertyChangeListener { // // MouseInputListener // public void mouseClicked(MouseEvent e) {} public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { if (!menuItem.isEnabled()) { return; } MenuSelectionManager manager = MenuSelectionManager.defaultManager(); Point p = e.getPoint(); if(p.x >= 0 && p.x < menuItem.getWidth() && p.y >= 0 && p.y < menuItem.getHeight()) { doClick(manager); } else { manager.processMouseEvent(e); } } public void mouseEntered(MouseEvent e) { MenuSelectionManager manager = MenuSelectionManager.defaultManager(); int modifiers = e.getModifiers(); // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2 if ((modifiers & (InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) { MenuSelectionManager.defaultManager().processMouseEvent(e); } else { manager.setSelectedPath(getPath()); } } public void mouseExited(MouseEvent e) { MenuSelectionManager manager = MenuSelectionManager.defaultManager(); int modifiers = e.getModifiers(); // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2 if ((modifiers & (InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) { MenuSelectionManager.defaultManager().processMouseEvent(e); } else { MenuElement path[] = manager.getSelectedPath(); if (path.length > 1 && path[path.length-1] == menuItem) { MenuElement newPath[] = new MenuElement[path.length-1]; int i,c; for(i=0,c=path.length-1;i<c;i++) newPath[i] = path[i]; manager.setSelectedPath(newPath); } } } public void mouseDragged(MouseEvent e) { MenuSelectionManager.defaultManager().processMouseEvent(e); } public void mouseMoved(MouseEvent e) { } // // MenuDragListener // public void menuDragMouseEntered(MenuDragMouseEvent e) { MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); manager.setSelectedPath(path); } public void menuDragMouseDragged(MenuDragMouseEvent e) { MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); manager.setSelectedPath(path); } public void menuDragMouseExited(MenuDragMouseEvent e) {} public void menuDragMouseReleased(MenuDragMouseEvent e) { if (!menuItem.isEnabled()) { return; } MenuSelectionManager manager = e.getMenuSelectionManager(); MenuElement path[] = e.getPath(); Point p = e.getPoint(); if (p.x >= 0 && p.x < menuItem.getWidth() && p.y >= 0 && p.y < menuItem.getHeight()) { doClick(manager); } else { manager.clearSelectedPath(); } } // // PropertyChangeListener // public void propertyChange(PropertyChangeEvent e) { String name = e.getPropertyName(); if (name == "labelFor" || name == "displayedMnemonic" || name == "accelerator") { updateAcceleratorBinding(); } else if (name == "text" || "font" == name || "foreground" == name) { // remove the old html view client property if one // existed, and install a new one if the text installed // into the JLabel is html source. JMenuItem lbl = ((JMenuItem) e.getSource()); String text = lbl.getText(); BasicHTML.updateRenderer(lbl, text); } else if (name == "iconTextGap") { defaultTextIconGap = ((Number)e.getNewValue()).intValue(); } } } }