# HG changeset patch # User mlapshin # Date 1218214166 -14400 # Node ID 3cf2264a5743d2476c6b9f751b3681918f8d1bc3 # Parent 16088ec1b9e765907042f2271acfe483c34b8498 6584657: GTK Look and Feel: Bugs in menu item layout Reviewed-by: peterz, alexp diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/SwingUtilities.java --- a/jdk/src/share/classes/javax/swing/SwingUtilities.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/SwingUtilities.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1997-2008 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 @@ -974,6 +974,7 @@ boolean textIsEmpty = (text == null) || text.equals(""); int lsb = 0; + int rsb = 0; /* Unless both text and icon are non-null, we effectively ignore * the value of textIconGap. */ @@ -1015,7 +1016,7 @@ if (lsb < 0) { textR.width -= lsb; } - int rsb = SwingUtilities2.getRightSideBearing(c, fm, text); + rsb = SwingUtilities2.getRightSideBearing(c, fm, text); if (rsb > 0) { textR.width += rsb; } @@ -1118,6 +1119,11 @@ // lsb is negative. Shift the x location so that the text is // visually drawn at the right location. textR.x -= lsb; + + textR.width += lsb; + } + if (rsb > 0) { + textR.width -= rsb; } return text; diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java --- a/jdk/src/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1997-2008 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 @@ -25,9 +25,6 @@ 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; @@ -39,8 +36,7 @@ import javax.swing.plaf.*; import javax.swing.text.View; -import sun.swing.UIAction; -import sun.swing.StringUIClientPropertyKey; +import sun.swing.*; /** * BasicMenuItem implementation @@ -91,24 +87,6 @@ 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)); @@ -199,13 +177,14 @@ //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( + boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout( BasicGraphicsUtils.isLeftToRight(menuItem), menuItem); if (isColumnLayout) { MenuItemCheckIconFactory iconFactory = (MenuItemCheckIconFactory) UIManager.get(prefix + ".checkIconFactory"); - if (iconFactory != null && useCheckAndArrow() + if (iconFactory != null + && MenuItemLayoutHelper.useCheckAndArrow(menuItem) && iconFactory.isCompatible(checkIcon, prefix)) { checkIcon = iconFactory.getIcon(menuItem); } @@ -256,20 +235,7 @@ 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 ); - } - + MenuItemLayoutHelper.clearUsedParentClientProperties(menuItem); menuItem = null; } @@ -405,19 +371,6 @@ 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, @@ -447,32 +400,36 @@ // 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()); + MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, + arrowIcon, MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, + acceleratorDelimiter, BasicGraphicsUtils.isLeftToRight(mi), + mi.getFont(), acceleratorFont, + MenuItemLayoutHelper.useCheckAndArrow(menuItem), + getPropertyPrefix()); Dimension result = new Dimension(); // Calculate the result width - result.width = li.leadingGap; - addWidth(li.maxCheckWidth, li.afterCheckIconGap, result); + result.width = lh.getLeadingGap(); + MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), + lh.getAfterCheckIconGap(), result); // Take into account mimimal text offset. - if ((!li.isTopLevelMenu) - && (li.minTextOffset > 0) - && (result.width < li.minTextOffset)) { - result.width = li.minTextOffset; + if ((!lh.isTopLevelMenu()) + && (lh.getMinTextOffset() > 0) + && (result.width < lh.getMinTextOffset())) { + result.width = lh.getMinTextOffset(); } - addWidth(li.maxLabelWidth, li.gap, result); - addWidth(li.maxAccWidth, li.gap, result); - addWidth(li.maxArrowWidth, li.gap, result); + MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), lh.getGap(), result); + MenuItemLayoutHelper.addMaxWidth(lh.getAccSize(), lh.getGap(), result); + MenuItemLayoutHelper.addMaxWidth(lh.getArrowSize(), lh.getGap(), result); // Calculate the result height - result.height = max(li.checkRect.height, li.labelRect.height, - li.accRect.height, li.arrowRect.height); + result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(), + lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(), + lh.getArrowSize().getHeight()); // Take into account menu item insets - Insets insets = li.mi.getInsets(); + Insets insets = lh.getMenuItem().getInsets(); if(insets != null) { result.width += insets.left + insets.right; result.height += insets.top + insets.bottom; @@ -492,500 +449,9 @@ 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 @@ -1016,122 +482,132 @@ 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); + MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, + arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, + BasicGraphicsUtils.isLeftToRight(mi), mi.getFont(), + acceleratorFont, MenuItemLayoutHelper.useCheckAndArrow(menuItem), + getPropertyPrefix()); + MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem(); paintBackground(g, mi, background); - paintCheckIcon(g, li, holdc, foreground); - paintIcon(g, li, holdc); - paintText(g, li); - paintAccText(g, li); - paintArrowIcon(g, li, foreground); + paintCheckIcon(g, lh, lr, holdc, foreground); + paintIcon(g, lh, lr, holdc); + paintText(g, lh, lr); + paintAccText(g, lh, lr); + paintArrowIcon(g, lh, lr, 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) { + private void paintIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, Color holdc) { + if (lh.getIcon() != null) { Icon icon; - ButtonModel model = li.mi.getModel(); + ButtonModel model = lh.getMenuItem().getModel(); if (!model.isEnabled()) { - icon = li.mi.getDisabledIcon(); + icon = lh.getMenuItem().getDisabledIcon(); } else if (model.isPressed() && model.isArmed()) { - icon = li.mi.getPressedIcon(); + icon = lh.getMenuItem().getPressedIcon(); if (icon == null) { // Use default icon - icon = li.mi.getIcon(); + icon = lh.getMenuItem().getIcon(); } } else { - icon = li.mi.getIcon(); + icon = lh.getMenuItem().getIcon(); } if (icon != null) { - icon.paintIcon(li.mi, g, li.iconRect.x, li.iconRect.y); + icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x, + lr.getIconRect().y); g.setColor(holdc); } } } - private void paintCheckIcon(Graphics g, LayoutInfo li, + private void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, Color holdc, Color foreground) { - if (li.checkIcon != null) { - ButtonModel model = li.mi.getModel(); - if (model.isArmed() - || (li.mi instanceof JMenu && model.isSelected())) { + if (lh.getCheckIcon() != null) { + ButtonModel model = lh.getMenuItem().getModel(); + if (model.isArmed() || (lh.getMenuItem() 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); + if (lh.useCheckAndArrow()) { + lh.getCheckIcon().paintIcon(lh.getMenuItem(), g, + lr.getCheckRect().x, lr.getCheckRect().y); } g.setColor(holdc); } } - private void paintAccText(Graphics g, LayoutInfo li) { - if (!li.accText.equals("")) { - ButtonModel model = li.mi.getModel(); - g.setFont(acceleratorFont); + private void paintAccText(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (!lh.getAccText().equals("")) { + ButtonModel model = lh.getMenuItem().getModel(); + g.setFont(lh.getAccFontMetrics().getFont()); 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()); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x, + lr.getAccRect().y + lh.getAccFontMetrics().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); + g.setColor(lh.getMenuItem().getBackground().brighter()); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x, + lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); + g.setColor(lh.getMenuItem().getBackground().darker()); + SwingUtilities2.drawString(lh.getMenuItem(), g, + lh.getAccText(), lr.getAccRect().x - 1, + lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1); } } else { // *** paint the accText normally - if (model.isArmed() || - (li.mi instanceof JMenu && model.isSelected())) { + if (model.isArmed() + || (lh.getMenuItem() 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()); + SwingUtilities2.drawString(lh.getMenuItem(), g, lh.getAccText(), + lr.getAccRect().x, lr.getAccRect().y + + lh.getAccFontMetrics().getAscent()); } } } - private void paintText(Graphics g, LayoutInfo li) { - if (!li.text.equals("")) { - if (li.htmlView != null) { + private void paintText(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (!lh.getText().equals("")) { + if (lh.getHtmlView() != null) { // Text is HTML - li.htmlView.paint(g, li.textRect); + lh.getHtmlView().paint(g, lr.getTextRect()); } else { // Text isn't HTML - paintText(g, li.mi, li.textRect, li.text); + paintText(g, lh.getMenuItem(), lr.getTextRect(), lh.getText()); } } } - 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())) { + private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr, + Color foreground) { + if (lh.getArrowIcon() != null) { + ButtonModel model = lh.getMenuItem().getModel(); + if (model.isArmed() || (lh.getMenuItem() instanceof JMenu + && model.isSelected())) { g.setColor(foreground); } - if (li.useCheckAndArrow) { - li.arrowIcon.paintIcon(li.mi, g, li.arrowRect.x, li.arrowRect.y); + if (lh.useCheckAndArrow()) { + lh.getArrowIcon().paintIcon(lh.getMenuItem(), g, + lr.getArrowRect().x, lr.getArrowRect().y); } } } @@ -1216,346 +692,6 @@ } } - - /** - * 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(); diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/basic/DefaultMenuLayout.java --- a/jdk/src/share/classes/javax/swing/plaf/basic/DefaultMenuLayout.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/basic/DefaultMenuLayout.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 1998-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 1998-2008 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 @@ -30,7 +30,6 @@ import java.awt.Container; import java.awt.Dimension; -import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET; /** * The default layout manager for Popup menus and menubars. This @@ -49,18 +48,7 @@ public Dimension preferredLayoutSize(Container target) { if (target instanceof JPopupMenu) { JPopupMenu popupMenu = (JPopupMenu) target; - - // Before the calculation of menu preferred size - // clear the previously calculated maximal widths and offsets - // in menu's Client Properties - popupMenu.putClientProperty(BasicMenuItemUI.MAX_ACC_WIDTH, null); - popupMenu.putClientProperty(BasicMenuItemUI.MAX_ARROW_WIDTH, null); - popupMenu.putClientProperty(BasicMenuItemUI.MAX_CHECK_WIDTH, null); - popupMenu.putClientProperty(BasicMenuItemUI.MAX_ICON_WIDTH, null); - popupMenu.putClientProperty(BasicMenuItemUI.MAX_LABEL_WIDTH, null); - popupMenu.putClientProperty(BasicMenuItemUI.MAX_TEXT_WIDTH, null); - popupMenu.putClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET, null); - + sun.swing.MenuItemLayoutHelper.clearUsedClientProperties(popupMenu); if (popupMenu.getComponentCount() == 0) { return new Dimension(0, 0); } diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/DefaultMenuLayout.java --- a/jdk/src/share/classes/javax/swing/plaf/synth/DefaultMenuLayout.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/DefaultMenuLayout.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2008 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 @@ -47,19 +47,22 @@ super(target, axis); } - public void invalidateLayout(Container target) { + public Dimension preferredLayoutSize(Container target) { if (target instanceof JPopupMenu) { - SynthPopupMenuUI popupUI = (SynthPopupMenuUI)((JPopupMenu)target). - getUI(); - popupUI.resetAlignmentHints(); + JPopupMenu popupMenu = (JPopupMenu) target; + + popupMenu.putClientProperty( + SynthMenuItemLayoutHelper.MAX_ACC_OR_ARROW_WIDTH, null); + sun.swing.MenuItemLayoutHelper.clearUsedClientProperties(popupMenu); + + if (popupMenu.getComponentCount() == 0) { + return new Dimension(0, 0); + } } + + // Make BoxLayout recalculate cached preferred sizes super.invalidateLayout(target); - } - public Dimension preferredLayoutSize(Container target) { - if (target instanceof JPopupMenu && target.getComponentCount() == 0) { - return new Dimension(0, 0); - } return super.preferredLayoutSize(target); } } diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java --- a/jdk/src/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/SynthGraphicsUtils.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2008 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 @@ -25,6 +25,8 @@ package javax.swing.plaf.synth; import sun.swing.SwingUtilities2; +import sun.swing.MenuItemLayoutHelper; + import java.awt.*; import javax.swing.*; import javax.swing.plaf.basic.BasicHTML; @@ -411,6 +413,198 @@ } + /** + * A quick note about how preferred sizes are calculated... Generally + * speaking, SynthPopupMenuUI will run through the list of its children + * (from top to bottom) and ask each for its preferred size. Each menu + * item will add up the max width of each element (icons, text, + * accelerator spacing, accelerator text or arrow icon) encountered thus + * far, so by the time all menu items have been calculated, we will + * know the maximum (preferred) menu item size for that popup menu. + * Later when it comes time to paint each menu item, we can use those + * same accumulated max element sizes in order to layout the item. + */ + static Dimension getPreferredMenuItemSize(SynthContext context, + SynthContext accContext, JComponent c, + Icon checkIcon, Icon arrowIcon, int defaultTextIconGap, + String acceleratorDelimiter, boolean useCheckAndArrow, + String propertyPrefix) { + + JMenuItem mi = (JMenuItem) c; + SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper( + context, accContext, mi, checkIcon, arrowIcon, + MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, + acceleratorDelimiter, SynthLookAndFeel.isLeftToRight(mi), + useCheckAndArrow, propertyPrefix); + + Dimension result = new Dimension(); + + // Calculate the result width + int gap = lh.getGap(); + result.width = 0; + MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), gap, result); + MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), gap, result); + MenuItemLayoutHelper.addWidth(lh.getMaxAccOrArrowWidth(), 5 * gap, result); + // The last gap is unnecessary + result.width -= gap; + + // Calculate the result height + result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(), + lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(), + lh.getArrowSize().getHeight()); + + // Take into account menu item insets + Insets insets = lh.getMenuItem().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 lhne 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) { + result.height++; + } + + return result; + } + + static 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); + } + } + + static void paint(SynthContext context, SynthContext accContext, Graphics g, + Icon checkIcon, Icon arrowIcon, String acceleratorDelimiter, + int defaultTextIconGap, String propertyPrefix) { + JMenuItem mi = (JMenuItem) context.getComponent(); + SynthStyle style = context.getStyle(); + g.setFont(style.getFont(context)); + + Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); + applyInsets(viewRect, mi.getInsets()); + + SynthMenuItemLayoutHelper lh = new SynthMenuItemLayoutHelper( + context, accContext, mi, checkIcon, + arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, + SynthLookAndFeel.isLeftToRight(mi), + MenuItemLayoutHelper.useCheckAndArrow(mi), propertyPrefix); + MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem(); + + paintMenuItem(g, lh, lr); + } + + static void paintMenuItem(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + // Save original graphics font and color + Font holdf = g.getFont(); + Color holdc = g.getColor(); + + paintBackground(g, lh); + paintCheckIcon(g, lh, lr); + paintIcon(g, lh, lr); + paintText(g, lh, lr); + paintAccText(g, lh, lr); + paintArrowIcon(g, lh, lr); + + // Restore original graphics font and color + g.setColor(holdc); + g.setFont(holdf); + } + + static void paintBackground(Graphics g, SynthMenuItemLayoutHelper lh) { + paintBackground(lh.getContext(), g, lh.getMenuItem()); + } + + static void paintBackground(SynthContext context, Graphics g, JComponent c) { + context.getPainter().paintMenuItemBackground(context, g, 0, 0, + c.getWidth(), c.getHeight()); + } + + static void paintIcon(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (lh.getIcon() != null) { + Icon icon; + JMenuItem mi = lh.getMenuItem(); + ButtonModel model = mi.getModel(); + if (!model.isEnabled()) { + icon = mi.getDisabledIcon(); + } else if (model.isPressed() && model.isArmed()) { + icon = mi.getPressedIcon(); + if (icon == null) { + // Use default icon + icon = mi.getIcon(); + } + } else { + icon = mi.getIcon(); + } + + if (icon != null) { + Rectangle iconRect = lr.getIconRect(); + SynthIcon.paintIcon(icon, lh.getContext(), g, iconRect.x, + iconRect.y, iconRect.width, iconRect.height); + } + } + } + + static void paintCheckIcon(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (lh.getCheckIcon() != null) { + Rectangle checkRect = lr.getCheckRect(); + SynthIcon.paintIcon(lh.getCheckIcon(), lh.getContext(), g, + checkRect.x, checkRect.y, checkRect.width, checkRect.height); + } + } + + static void paintAccText(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + String accText = lh.getAccText(); + if (accText != null && !accText.equals("")) { + g.setColor(lh.getAccStyle().getColor(lh.getAccContext(), + ColorType.TEXT_FOREGROUND)); + g.setFont(lh.getAccStyle().getFont(lh.getAccContext())); + lh.getAccGraphicsUtils().paintText(lh.getAccContext(), g, accText, + lr.getAccRect().x, lr.getAccRect().y, -1); + } + } + + static void paintText(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (!lh.getText().equals("")) { + if (lh.getHtmlView() != null) { + // Text is HTML + lh.getHtmlView().paint(g, lr.getTextRect()); + } else { + // Text isn't HTML + g.setColor(lh.getStyle().getColor( + lh.getContext(), ColorType.TEXT_FOREGROUND)); + g.setFont(lh.getStyle().getFont(lh.getContext())); + lh.getGraphicsUtils().paintText(lh.getContext(), g, lh.getText(), + lr.getTextRect().x, lr.getTextRect().y, + lh.getMenuItem().getDisplayedMnemonicIndex()); + } + } + } + + static void paintArrowIcon(Graphics g, SynthMenuItemLayoutHelper lh, + MenuItemLayoutHelper.LayoutResult lr) { + if (lh.getArrowIcon() != null) { + Rectangle arrowRect = lr.getArrowRect(); + SynthIcon.paintIcon(lh.getArrowIcon(), lh.getContext(), g, + arrowRect.x, arrowRect.y, arrowRect.width, arrowRect.height); + } + } + /** * Wraps a SynthIcon around the Icon interface, forwarding calls to * the SynthIcon with a given SynthContext. diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuItemLayoutHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuItemLayoutHelper.java Fri Aug 08 20:49:26 2008 +0400 @@ -0,0 +1,307 @@ +/* + * Copyright 2002-2008 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.synth; + +import sun.swing.StringUIClientPropertyKey; +import sun.swing.MenuItemLayoutHelper; +import sun.swing.plaf.synth.SynthIcon; + +import javax.swing.*; +import javax.swing.text.View; +import java.awt.*; + +/** + * Calculates preferred size and layouts synth menu items. + * + * All JMenuItems (and JMenus) include enough space for the insets + * plus one or more elements. When we say "label" below, we mean + * "icon and/or text." + * + * Cases to consider for SynthMenuItemUI (visualized here in a + * LTR orientation; the RTL case would be reversed): + * label + * check icon + label + * check icon + label + accelerator + * label + accelerator + * + * Cases to consider for SynthMenuUI (again visualized here in a + * LTR orientation): + * label + arrow + * + * Note that in the above scenarios, accelerator and arrow icon are + * mutually exclusive. This means that if a popup menu contains a mix + * of JMenus and JMenuItems, we only need to allow enough space for + * max(maxAccelerator, maxArrow), and both accelerators and arrow icons + * can occupy the same "column" of space in the menu. + */ +class SynthMenuItemLayoutHelper extends MenuItemLayoutHelper { + + public static final StringUIClientPropertyKey MAX_ACC_OR_ARROW_WIDTH = + new StringUIClientPropertyKey("maxAccOrArrowWidth"); + + public static final ColumnAlignment LTR_ALIGNMENT_1 = + new ColumnAlignment( + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.RIGHT, + SwingConstants.RIGHT + ); + public static final ColumnAlignment LTR_ALIGNMENT_2 = + new ColumnAlignment( + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.RIGHT + ); + public static final ColumnAlignment RTL_ALIGNMENT_1 = + new ColumnAlignment( + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.LEFT, + SwingConstants.LEFT + ); + public static final ColumnAlignment RTL_ALIGNMENT_2 = + new ColumnAlignment( + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.LEFT + ); + + private SynthContext context; + private SynthContext accContext; + private SynthStyle style; + private SynthStyle accStyle; + private SynthGraphicsUtils gu; + private SynthGraphicsUtils accGu; + private boolean alignAcceleratorText; + private int maxAccOrArrowWidth; + + public SynthMenuItemLayoutHelper(SynthContext context, SynthContext accContext, + JMenuItem mi, Icon checkIcon, Icon arrowIcon, + Rectangle viewRect, int gap, String accDelimiter, + boolean isLeftToRight, boolean useCheckAndArrow, + String propertyPrefix) { + this.context = context; + this.accContext = accContext; + this.style = context.getStyle(); + this.accStyle = accContext.getStyle(); + this.gu = style.getGraphicsUtils(context); + this.accGu = accStyle.getGraphicsUtils(accContext); + this.alignAcceleratorText = getAlignAcceleratorText(propertyPrefix); + reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter, + isLeftToRight, style.getFont(context), accStyle.getFont(accContext), + useCheckAndArrow, propertyPrefix); + setLeadingGap(0); + } + + private boolean getAlignAcceleratorText(String propertyPrefix) { + return style.getBoolean(context, + propertyPrefix + ".alignAcceleratorText", true); + } + + protected void calcWidthsAndHeights() { + // iconRect + if (getIcon() != null) { + getIconSize().setWidth(SynthIcon.getIconWidth(getIcon(), context)); + getIconSize().setHeight(SynthIcon.getIconHeight(getIcon(), context)); + } + + // accRect + if (!getAccText().equals("")) { + getAccSize().setWidth(accGu.computeStringWidth(getAccContext(), + getAccFontMetrics().getFont(), getAccFontMetrics(), + getAccText())); + getAccSize().setHeight(getAccFontMetrics().getHeight()); + } + + // textRect + if (getText() == null) { + setText(""); + } else if (!getText().equals("")) { + if (getHtmlView() != null) { + // Text is HTML + getTextSize().setWidth( + (int) getHtmlView().getPreferredSpan(View.X_AXIS)); + getTextSize().setHeight( + (int) getHtmlView().getPreferredSpan(View.Y_AXIS)); + } else { + // Text isn't HTML + getTextSize().setWidth(gu.computeStringWidth(context, + getFontMetrics().getFont(), getFontMetrics(), + getText())); + getTextSize().setHeight(getFontMetrics().getHeight()); + } + } + + if (useCheckAndArrow()) { + // checkIcon + if (getCheckIcon() != null) { + getCheckSize().setWidth( + SynthIcon.getIconWidth(getCheckIcon(), context)); + getCheckSize().setHeight( + SynthIcon.getIconHeight(getCheckIcon(), context)); + } + // arrowRect + if (getArrowIcon() != null) { + getArrowSize().setWidth( + SynthIcon.getIconWidth(getArrowIcon(), context)); + getArrowSize().setHeight( + SynthIcon.getIconHeight(getArrowIcon(), context)); + } + } + + // labelRect + if (isColumnLayout()) { + getLabelSize().setWidth(getIconSize().getWidth() + + getTextSize().getWidth() + getGap()); + getLabelSize().setHeight(MenuItemLayoutHelper.max( + getCheckSize().getHeight(), + getIconSize().getHeight(), + getTextSize().getHeight(), + getAccSize().getHeight(), + getArrowSize().getHeight())); + } else { + Rectangle textRect = new Rectangle(); + Rectangle iconRect = new Rectangle(); + gu.layoutText(context, getFontMetrics(), getText(), getIcon(), + getHorizontalAlignment(), getVerticalAlignment(), + getHorizontalTextPosition(), getVerticalTextPosition(), + getViewRect(), iconRect, textRect, getGap()); + Rectangle labelRect = iconRect.union(textRect); + getLabelSize().setHeight(labelRect.height); + getLabelSize().setWidth(labelRect.width); + } + } + + protected void calcMaxWidths() { + calcMaxWidth(getCheckSize(), MAX_CHECK_WIDTH); + maxAccOrArrowWidth = + calcMaxValue(MAX_ACC_OR_ARROW_WIDTH, getArrowSize().getWidth()); + maxAccOrArrowWidth = + calcMaxValue(MAX_ACC_OR_ARROW_WIDTH, getAccSize().getWidth()); + + if (isColumnLayout()) { + calcMaxWidth(getIconSize(), MAX_ICON_WIDTH); + calcMaxWidth(getTextSize(), MAX_TEXT_WIDTH); + int curGap = getGap(); + if ((getIconSize().getMaxWidth() == 0) + || (getTextSize().getMaxWidth() == 0)) { + curGap = 0; + } + getLabelSize().setMaxWidth( + calcMaxValue(MAX_LABEL_WIDTH, getIconSize().getMaxWidth() + + getTextSize().getMaxWidth() + curGap)); + } else { + // We shouldn't use current icon and text widths + // in maximal widths calculation for complex layout. + getIconSize().setMaxWidth(getParentIntProperty( + MAX_ICON_WIDTH)); + calcMaxWidth(getLabelSize(), MAX_LABEL_WIDTH); + // If maxLabelWidth is wider + // than the widest icon + the widest text + gap, + // we should update the maximal text witdh + int candidateTextWidth = getLabelSize().getMaxWidth() - + getIconSize().getMaxWidth(); + if (getIconSize().getMaxWidth() > 0) { + candidateTextWidth -= getGap(); + } + getTextSize().setMaxWidth(calcMaxValue( + MAX_TEXT_WIDTH, candidateTextWidth)); + } + } + + public SynthContext getContext() { + return context; + } + + public SynthContext getAccContext() { + return accContext; + } + + public SynthStyle getStyle() { + return style; + } + + public SynthStyle getAccStyle() { + return accStyle; + } + + public SynthGraphicsUtils getGraphicsUtils() { + return gu; + } + + public SynthGraphicsUtils getAccGraphicsUtils() { + return accGu; + } + + public boolean alignAcceleratorText() { + return alignAcceleratorText; + } + + public int getMaxAccOrArrowWidth() { + return maxAccOrArrowWidth; + } + + protected void prepareForLayout(LayoutResult lr) { + lr.getCheckRect().width = getCheckSize().getMaxWidth(); + // An item can have an arrow or a check icon at once + if (useCheckAndArrow() && (!"".equals(getAccText()))) { + lr.getAccRect().width = maxAccOrArrowWidth; + } else { + lr.getArrowRect().width = maxAccOrArrowWidth; + } + } + + public ColumnAlignment getLTRColumnAlignment() { + if (alignAcceleratorText()) { + return LTR_ALIGNMENT_2; + } else { + return LTR_ALIGNMENT_1; + } + } + + public ColumnAlignment getRTLColumnAlignment() { + if (alignAcceleratorText()) { + return RTL_ALIGNMENT_2; + } else { + return RTL_ALIGNMENT_1; + } + } + + protected void layoutIconAndTextInLabelRect(LayoutResult lr) { + lr.setTextRect(new Rectangle()); + lr.setIconRect(new Rectangle()); + gu.layoutText(context, getFontMetrics(), getText(), getIcon(), + getHorizontalAlignment(), getVerticalAlignment(), + getHorizontalTextPosition(), getVerticalTextPosition(), + lr.getLabelRect(), lr.getIconRect(), lr.getTextRect(), getGap()); + } +} diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuItemUI.java --- a/jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuItemUI.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuItemUI.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2008 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 @@ -37,7 +37,7 @@ import javax.swing.plaf.basic.*; import javax.swing.text.View; import sun.swing.plaf.synth.*; -import sun.swing.SwingUtilities2; +import sun.swing.MenuItemLayoutHelper; /** @@ -59,542 +59,16 @@ return new SynthMenuItemUI(); } - // - // The next handful of static methods are used by both SynthMenuUI - // and SynthMenuItemUI. This is necessitated by SynthMenuUI not - // extending SynthMenuItemUI. - // - - /* - * All JMenuItems (and JMenus) include enough space for the insets - * plus one or more elements. When we say "icon(s)" below, we mean - * "check/radio indicator and/or user icon." If both are defined for - * a given menu item, then in a LTR orientation the check/radio indicator - * is on the left side followed by the user icon to the right; it is - * just the opposite in a RTL orientation. - * - * Cases to consider for SynthMenuItemUI (visualized here in a - * LTR orientation; the RTL case would be reversed): - * text - * icon(s) + text - * icon(s) + text + accelerator - * text + accelerator - * - * Cases to consider for SynthMenuUI (again visualized here in a - * LTR orientation): - * text + arrow - * (user)icon + text + arrow - * - * Note that in the above scenarios, accelerator and arrow icon are - * mutually exclusive. This means that if a popup menu contains a mix - * of JMenus and JMenuItems, we only need to allow enough space for - * max(maxAccelerator, maxArrow), and both accelerators and arrow icons - * can occupy the same "column" of space in the menu. - * - * A quick note about how preferred sizes are calculated... Generally - * speaking, SynthPopupMenuUI will run through the list of its children - * (from top to bottom) and ask each for its preferred size. Each menu - * item will add up the max width of each element (icons, text, - * accelerator spacing, accelerator text or arrow icon) encountered thus - * far, so by the time all menu items have been calculated, we will - * know the maximum (preferred) menu item size for that popup menu. - * Later when it comes time to paint each menu item, we can use those - * same accumulated max element sizes in order to layout the item. - */ - static Dimension getPreferredMenuItemSize(SynthContext context, - SynthContext accContext, JComponent c, - Icon checkIcon, Icon arrowIcon, int defaultTextIconGap, - String acceleratorDelimiter) { - JMenuItem b = (JMenuItem) c; - Icon icon = b.getIcon(); - String text = b.getText(); - KeyStroke accelerator = b.getAccelerator(); - String acceleratorText = ""; - - if (accelerator != null) { - int modifiers = accelerator.getModifiers(); - if (modifiers > 0) { - acceleratorText = KeyEvent.getKeyModifiersText(modifiers); - acceleratorText += acceleratorDelimiter; - } - int keyCode = accelerator.getKeyCode(); - if (keyCode != 0) { - acceleratorText += KeyEvent.getKeyText(keyCode); - } else { - acceleratorText += accelerator.getKeyChar(); - } - } - - Font font = context.getStyle().getFont(context); - FontMetrics fm = b.getFontMetrics(font); - FontMetrics fmAccel = b.getFontMetrics(accContext.getStyle(). - getFont(accContext)); - - resetRects(); - - layoutMenuItem( - context, fm, accContext, text, fmAccel, acceleratorText, - icon, checkIcon, arrowIcon, b.getVerticalAlignment(), - b.getHorizontalAlignment(), b.getVerticalTextPosition(), - b.getHorizontalTextPosition(), viewRect, iconRect, textRect, - acceleratorRect, checkIconRect, arrowIconRect, - text == null ? 0 : defaultTextIconGap, defaultTextIconGap); - - r.setBounds(textRect); - - int totalIconWidth = 0; - int maxIconHeight = 0; - if (icon != null) { - // Add in the user icon - totalIconWidth += iconRect.width; - if (textRect.width > 0) { - // Allow for some room between the user icon and the text - totalIconWidth += defaultTextIconGap; - } - maxIconHeight = Math.max(iconRect.height, maxIconHeight); - } - if (checkIcon != null) { - // Add in the checkIcon - totalIconWidth += checkIconRect.width; - if (textRect.width > 0 || icon != null) { - // Allow for some room between the check/radio indicator - // and the text (or user icon, if both are specified) - totalIconWidth += defaultTextIconGap; - } - maxIconHeight = Math.max(checkIconRect.height, maxIconHeight); - } - - int arrowWidth = 0; - if (arrowIcon != null) { - // Add in the arrowIcon - arrowWidth += defaultTextIconGap; - arrowWidth += arrowIconRect.width; - maxIconHeight = Math.max(arrowIconRect.height, maxIconHeight); - } - - int accelSpacing = 0; - if (acceleratorRect.width > 0) { - // Allow for some room between the text and the accelerator - accelSpacing += 4*defaultTextIconGap; - } - - // Take text and all icons into account when determining height - r.height = Math.max(r.height, maxIconHeight); - - // To make the accelerator texts appear in a column, - // find the widest MenuItem text and the widest accelerator text. - - // Get the parent, which stores the information. - Container parent = b.getParent(); - - if (parent instanceof JPopupMenu) { - SynthPopupMenuUI popupUI = (SynthPopupMenuUI)SynthLookAndFeel. - getUIOfType(((JPopupMenu)parent).getUI(), - SynthPopupMenuUI.class); - - if (popupUI != null) { - // This gives us the widest MenuItem text encountered thus - // far in the parent JPopupMenu - r.width = popupUI.adjustTextWidth(r.width); - - // Add in the widest icon (includes both user and - // check/radio icons) encountered thus far - r.width += popupUI.adjustIconWidth(totalIconWidth); - - // Add in the widest text/accelerator spacing - // encountered thus far - r.width += popupUI.adjustAccelSpacingWidth(accelSpacing); - - // Add in the widest accelerator text (or arrow) - // encountered thus far (at least one of these values - // will always be zero, so we combine them here to - // avoid double counting) - int totalAccelOrArrow = acceleratorRect.width + arrowWidth; - r.width += popupUI.adjustAcceleratorWidth(totalAccelOrArrow); - } - } - else if (parent != null && !(b instanceof JMenu && - ((JMenu)b).isTopLevelMenu())) { - r.width += - totalIconWidth + accelSpacing + - acceleratorRect.width + arrowWidth; - } - - Insets insets = b.getInsets(); - if(insets != null) { - r.width += insets.left + insets.right; - r.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(r.width%2 == 0) { - r.width++; - } - - // if the height is even, bump it up one. This is critical - // for the text to center properly - if(r.height%2 == 0) { - r.height++; - } - return r.getSize(); - } - - static void paint(SynthContext context, SynthContext accContext, - Graphics g, Icon checkIcon, Icon arrowIcon, - String acceleratorDelimiter, - int defaultTextIconGap) { - JComponent c = context.getComponent(); - JMenuItem b = (JMenuItem)c; - ButtonModel model = b.getModel(); - Insets i = b.getInsets(); - - resetRects(); - - viewRect.setBounds(0, 0, b.getWidth(), b.getHeight()); - - viewRect.x += i.left; - viewRect.y += i.top; - viewRect.width -= (i.right + viewRect.x); - viewRect.height -= (i.bottom + viewRect.y); - - SynthStyle style = context.getStyle(); - Font f = style.getFont(context); - g.setFont(f); - FontMetrics fm = SwingUtilities2.getFontMetrics(c, g, f); - FontMetrics accFM = SwingUtilities2.getFontMetrics(c, g, - accContext.getStyle(). - getFont(accContext)); - - // get Accelerator text - KeyStroke accelerator = b.getAccelerator(); - String acceleratorText = ""; - if (accelerator != null) { - int modifiers = accelerator.getModifiers(); - if (modifiers > 0) { - acceleratorText = KeyEvent.getKeyModifiersText(modifiers); - acceleratorText += acceleratorDelimiter; - } - - int keyCode = accelerator.getKeyCode(); - if (keyCode != 0) { - acceleratorText += KeyEvent.getKeyText(keyCode); - } else { - acceleratorText += accelerator.getKeyChar(); - } - } - - // Layout the text and icon - String text = layoutMenuItem(context, fm, accContext, - b.getText(), accFM, acceleratorText, b.getIcon(), - checkIcon, arrowIcon, - b.getVerticalAlignment(), b.getHorizontalAlignment(), - b.getVerticalTextPosition(), b.getHorizontalTextPosition(), - viewRect, iconRect, textRect, acceleratorRect, - checkIconRect, arrowIconRect, - b.getText() == null ? 0 : defaultTextIconGap, - defaultTextIconGap - ); - - // Paint the Check - if (checkIcon != null) { - SynthIcon.paintIcon(checkIcon, context, g, checkIconRect.x, - checkIconRect.y, checkIconRect.width, checkIconRect.height); - } - - // Paint the Icon - if(b.getIcon() != null) { - Icon icon; - if(!model.isEnabled()) { - icon = b.getDisabledIcon(); - } else if(model.isPressed() && model.isArmed()) { - icon = b.getPressedIcon(); - if(icon == null) { - // Use default icon - icon = b.getIcon(); - } - } else { - icon = b.getIcon(); - } - - if (icon!=null) { - SynthIcon.paintIcon(icon, context, g, iconRect.x, - iconRect.y, iconRect.width, iconRect.height); - } - } - - // Draw the Text - if(text != null) { - View v = (View) c.getClientProperty(BasicHTML.propertyKey); - if (v != null) { - v.paint(g, textRect); - } else { - g.setColor(style.getColor(context, ColorType.TEXT_FOREGROUND)); - g.setFont(style.getFont(context)); - style.getGraphicsUtils(context).paintText(context, g, text, - textRect.x, textRect.y, b.getDisplayedMnemonicIndex()); - } - } - - // Draw the Accelerator Text - if(acceleratorText != null && !acceleratorText.equals("")) { - // Get the maxAccWidth from the parent to calculate the offset. - int accOffset = 0; - Container parent = b.getParent(); - if (parent != null && parent instanceof JPopupMenu) { - SynthPopupMenuUI popupUI = (SynthPopupMenuUI) - ((JPopupMenu)parent).getUI(); - - // Note that we can only get here for SynthMenuItemUI - // (not SynthMenuUI) since acceleratorText is defined, - // so this cast should be safe - SynthMenuItemUI miUI = (SynthMenuItemUI) - SynthLookAndFeel.getUIOfType(b.getUI(), - SynthMenuItemUI.class); - - if (popupUI != null && miUI != null) { - String prop = - miUI.getPropertyPrefix() + ".alignAcceleratorText"; - boolean align = style.getBoolean(context, prop, true); - - // Calculate the offset, with which the accelerator texts - // will be drawn. - if (align) { - // When align==true and we're in the LTR case, - // we add an offset here so that all accelerators - // will be left-justified in their own column. - int max = popupUI.getMaxAcceleratorWidth(); - if (max > 0) { - accOffset = max - acceleratorRect.width; - if (!SynthLookAndFeel.isLeftToRight(c)) { - // In the RTL, flip the sign so that all - // accelerators will be right-justified. - accOffset = -accOffset; - } - } - } //else { - // Don't need to do anything special here; in the - // LTR case, the accelerator is already justified - // against the right edge of the menu (and against - // the left edge in the RTL case). - //} - } - } - - SynthStyle accStyle = accContext.getStyle(); - - g.setColor(accStyle.getColor(accContext, - ColorType.TEXT_FOREGROUND)); - g.setFont(accStyle.getFont(accContext)); - accStyle.getGraphicsUtils(accContext).paintText( - accContext, g, acceleratorText, acceleratorRect.x - - accOffset, acceleratorRect.y, -1); - } - - // Paint the Arrow - if (arrowIcon != null) { - SynthIcon.paintIcon(arrowIcon, context, g, arrowIconRect.x, - arrowIconRect.y, arrowIconRect.width, arrowIconRect.height); + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + // Remove values from the parent's Client Properties. + JComponent p = MenuItemLayoutHelper.getMenuItemParent((JMenuItem) c); + if (p != null) { + p.putClientProperty( + SynthMenuItemLayoutHelper.MAX_ACC_OR_ARROW_WIDTH, null); } } - /** - * Compute and return the location of the icons origin, the - * location of origin of the text baseline, and a possibly clipped - * version of the compound labels string. Locations are computed - * relative to the viewRect rectangle. - */ - - private static String layoutMenuItem( - SynthContext context, - FontMetrics fm, - SynthContext accContext, - String text, - FontMetrics fmAccel, - String acceleratorText, - Icon icon, - Icon checkIcon, - Icon arrowIcon, - int verticalAlignment, - int horizontalAlignment, - int verticalTextPosition, - int horizontalTextPosition, - Rectangle viewRect, - Rectangle iconRect, - Rectangle textRect, - Rectangle acceleratorRect, - Rectangle checkIconRect, - Rectangle arrowIconRect, - int textIconGap, - int menuItemGap - ) - { - // If parent is JPopupMenu, get and store it's UI - SynthPopupMenuUI popupUI = null; - JComponent b = context.getComponent(); - Container parent = b.getParent(); - if(parent instanceof JPopupMenu) { - popupUI = (SynthPopupMenuUI)SynthLookAndFeel. - getUIOfType(((JPopupMenu)parent).getUI(), - SynthPopupMenuUI.class); - } - - context.getStyle().getGraphicsUtils(context).layoutText( - context, fm, text, icon,horizontalAlignment, verticalAlignment, - horizontalTextPosition, verticalTextPosition, viewRect, - iconRect, textRect, textIconGap); - - /* Initialize the acceleratorText bounds rectangle textRect. If a null - * or and empty String was specified we substitute "" here - * and use 0,0,0,0 for acceleratorTextRect. - */ - if( (acceleratorText == null) || acceleratorText.equals("") ) { - acceleratorRect.width = acceleratorRect.height = 0; - acceleratorText = ""; - } - else { - SynthStyle style = accContext.getStyle(); - acceleratorRect.width = style.getGraphicsUtils(accContext). - computeStringWidth(accContext, fmAccel.getFont(), fmAccel, - acceleratorText); - acceleratorRect.height = fmAccel.getHeight(); - } - - // Initialize the checkIcon bounds rectangle width & height. - if (checkIcon != null) { - checkIconRect.width = SynthIcon.getIconWidth(checkIcon, - context); - checkIconRect.height = SynthIcon.getIconHeight(checkIcon, - context); - } - else { - checkIconRect.width = checkIconRect.height = 0; - } - - // Initialize the arrowIcon bounds rectangle width & height. - if (arrowIcon != null) { - arrowIconRect.width = SynthIcon.getIconWidth(arrowIcon, - context); - arrowIconRect.height = SynthIcon.getIconHeight(arrowIcon, - context); - } else { - arrowIconRect.width = arrowIconRect.height = 0; - } - - // Note: layoutText() has already left room for - // the user icon, so no need to adjust textRect below - // to account for the user icon. However, we do have to - // reposition textRect when the check icon is visible. - - Rectangle labelRect = iconRect.union(textRect); - if( SynthLookAndFeel.isLeftToRight(context.getComponent()) ) { - // Position the check and user icons - iconRect.x = viewRect.x; - if (checkIcon != null) { - checkIconRect.x = viewRect.x; - iconRect.x += menuItemGap + checkIconRect.width; - textRect.x += menuItemGap + checkIconRect.width; - } - - // Position the arrow icon - arrowIconRect.x = - viewRect.x + viewRect.width - arrowIconRect.width; - - // Position the accelerator text rect - acceleratorRect.x = - viewRect.x + viewRect.width - acceleratorRect.width; - - /* Align icons and text horizontally */ - if(popupUI != null) { - int thisTextOffset = popupUI.adjustTextOffset(textRect.x - - viewRect.x); - textRect.x = thisTextOffset + viewRect.x; - - if(icon != null) { - // REMIND: The following code currently assumes the - // default (TRAILING) horizontalTextPosition, which means - // it will always place the icon to the left of the text. - // Other values of horizontalTextPosition aren't very - // useful for menu items, so we ignore them for now, but - // someday we might want to fix this situation. - int thisIconOffset = - popupUI.adjustIconOffset(iconRect.x - viewRect.x); - iconRect.x = thisIconOffset + viewRect.x; - } - } - } else { - // Position the accelerator text rect - acceleratorRect.x = viewRect.x; - - // Position the arrow icon - arrowIconRect.x = viewRect.x; - - // Position the check and user icons - iconRect.x = - viewRect.x + viewRect.width - iconRect.width; - if (checkIcon != null) { - checkIconRect.x = - viewRect.x + viewRect.width - checkIconRect.width; - textRect.x -= menuItemGap + checkIconRect.width; - iconRect.x -= menuItemGap + checkIconRect.width; - } - - /* Align icons and text horizontally */ - if(popupUI != null) { - int thisTextOffset = viewRect.x + viewRect.width - - textRect.x - textRect.width; - thisTextOffset = popupUI.adjustTextOffset(thisTextOffset); - textRect.x = viewRect.x + viewRect.width - - thisTextOffset - textRect.width; - if(icon != null) { - // REMIND: The following code currently assumes the - // default (TRAILING) horizontalTextPosition, which means - // it will always place the icon to the right of the text. - // Other values of horizontalTextPosition aren't very - // useful for menu items, so we ignore them for now, but - // someday we might want to fix this situation. - int thisIconOffset = viewRect.x + viewRect.width - - iconRect.x - iconRect.width; - thisIconOffset = - popupUI.adjustIconOffset(thisIconOffset); - iconRect.x = viewRect.x + viewRect.width - - thisIconOffset - iconRect.width; - } - } - } - - // Align the accelerator text and all icons vertically - // with the center of the label rect. - int midY = labelRect.y + (labelRect.height/2); - iconRect.y = midY - (iconRect.height/2); - acceleratorRect.y = midY - (acceleratorRect.height/2); - arrowIconRect.y = midY - (arrowIconRect.height/2); - checkIconRect.y = midY - (checkIconRect.height/2); - - return text; - } - - // these rects are used for painting and preferredsize calculations. - // they used to be regenerated constantly. Now they are reused. - static Rectangle iconRect = new Rectangle(); - static Rectangle textRect = new Rectangle(); - static Rectangle acceleratorRect = new Rectangle(); - static Rectangle checkIconRect = new Rectangle(); - static Rectangle arrowIconRect = new Rectangle(); - static Rectangle viewRect = new Rectangle(Short.MAX_VALUE,Short.MAX_VALUE); - static Rectangle r = new Rectangle(); - - private static void resetRects() { - iconRect.setBounds(0, 0, 0, 0); - textRect.setBounds(0, 0, 0, 0); - acceleratorRect.setBounds(0, 0, 0, 0); - checkIconRect.setBounds(0, 0, 0, 0); - arrowIconRect.setBounds(0, 0, 0, 0); - viewRect.setBounds(0,0,Short.MAX_VALUE, Short.MAX_VALUE); - r.setBounds(0, 0, 0, 0); - } - - protected void installDefaults() { updateStyle(menuItem); } @@ -718,9 +192,11 @@ int defaultTextIconGap) { SynthContext context = getContext(c); SynthContext accContext = getContext(c, Region.MENU_ITEM_ACCELERATOR); - Dimension value = getPreferredMenuItemSize(context, accContext, - c, checkIcon, arrowIcon, defaultTextIconGap, - acceleratorDelimiter); + Dimension value = SynthGraphicsUtils.getPreferredMenuItemSize( + context, accContext, c, checkIcon, arrowIcon, + defaultTextIconGap, acceleratorDelimiter, + MenuItemLayoutHelper.useCheckAndArrow(menuItem), + getPropertyPrefix()); context.dispose(); accContext.dispose(); return value; @@ -751,14 +227,13 @@ String prefix = getPropertyPrefix(); Icon checkIcon = style.getIcon(context, prefix + ".checkIcon"); Icon arrowIcon = style.getIcon(context, prefix + ".arrowIcon"); - paint(context, accContext, g, checkIcon, arrowIcon, - acceleratorDelimiter, defaultTextIconGap); + SynthGraphicsUtils.paint(context, accContext, g, checkIcon, arrowIcon, + acceleratorDelimiter, defaultTextIconGap, getPropertyPrefix()); accContext.dispose(); } void paintBackground(SynthContext context, Graphics g, JComponent c) { - context.getPainter().paintMenuItemBackground(context, g, 0, 0, - c.getWidth(), c.getHeight()); + SynthGraphicsUtils.paintBackground(context, g, c); } public void paintBorder(SynthContext context, Graphics g, int x, diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuUI.java --- a/jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuUI.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/SynthMenuUI.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2008 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 @@ -35,7 +35,7 @@ import java.util.Arrays; import java.util.ArrayList; import sun.swing.plaf.synth.SynthUI; - +import sun.swing.MenuItemLayoutHelper; /** * Synth's MenuUI. @@ -86,7 +86,7 @@ acceleratorDelimiter = style.getString(context, prefix + ".acceleratorDelimiter", "+"); - if (useCheckAndArrow()) { + if (MenuItemLayoutHelper.useCheckAndArrow(menuItem)) { checkIcon = style.getIcon(context, prefix + ".checkIcon"); arrowIcon = style.getIcon(context, prefix + ".arrowIcon"); } else { @@ -111,6 +111,16 @@ accContext.dispose(); } + public void uninstallUI(JComponent c) { + super.uninstallUI(c); + // Remove values from the parent's Client Properties. + JComponent p = MenuItemLayoutHelper.getMenuItemParent((JMenuItem) c); + if (p != null) { + p.putClientProperty( + SynthMenuItemLayoutHelper.MAX_ACC_OR_ARROW_WIDTH, null); + } + } + protected void uninstallDefaults() { SynthContext context = getContext(menuItem, ENABLED); style.uninstallDefaults(context); @@ -182,9 +192,11 @@ int defaultTextIconGap) { SynthContext context = getContext(c); SynthContext accContext = getContext(c, Region.MENU_ITEM_ACCELERATOR); - Dimension value = SynthMenuItemUI.getPreferredMenuItemSize( - context, accContext, c, checkIcon, arrowIcon, - defaultTextIconGap, acceleratorDelimiter); + Dimension value = SynthGraphicsUtils.getPreferredMenuItemSize( + context, accContext, c, checkIcon, arrowIcon, + defaultTextIconGap, acceleratorDelimiter, + MenuItemLayoutHelper.useCheckAndArrow(menuItem), + getPropertyPrefix()); context.dispose(); accContext.dispose(); return value; @@ -211,21 +223,12 @@ protected void paint(SynthContext context, Graphics g) { SynthContext accContext = getContext(menuItem, Region.MENU_ITEM_ACCELERATOR); - SynthStyle style = context.getStyle(); - Icon checkIcon; - Icon arrowIcon; - if (useCheckAndArrow()) { - // Refetch the appropriate icons for the current state - String prefix = getPropertyPrefix(); - checkIcon = style.getIcon(context, prefix + ".checkIcon"); - arrowIcon = style.getIcon(context, prefix + ".arrowIcon"); - } else { - // Not needed in this case - checkIcon = null; - arrowIcon = null; - } - SynthMenuItemUI.paint(context, accContext, g, checkIcon, arrowIcon, - acceleratorDelimiter, defaultTextIconGap); + // Refetch the appropriate check indicator for the current state + String prefix = getPropertyPrefix(); + Icon checkIcon = style.getIcon(context, prefix + ".checkIcon"); + Icon arrowIcon = style.getIcon(context, prefix + ".arrowIcon"); + SynthGraphicsUtils.paint(context, accContext, g, checkIcon, arrowIcon, + acceleratorDelimiter, defaultTextIconGap, getPropertyPrefix()); accContext.dispose(); } @@ -239,8 +242,4 @@ updateStyle((JMenu)e.getSource()); } } - - private boolean useCheckAndArrow() { - return !((JMenu)menuItem).isTopLevelMenu(); - } } diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/javax/swing/plaf/synth/SynthPopupMenuUI.java --- a/jdk/src/share/classes/javax/swing/plaf/synth/SynthPopupMenuUI.java Fri Jul 25 14:26:27 2008 -0400 +++ b/jdk/src/share/classes/javax/swing/plaf/synth/SynthPopupMenuUI.java Fri Aug 08 20:49:26 2008 +0400 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2002-2008 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 @@ -58,34 +58,6 @@ */ class SynthPopupMenuUI extends BasicPopupMenuUI implements PropertyChangeListener, SynthUI { - /** - * Maximum size of the text portion of the children menu items. - */ - private int maxTextWidth; - - /** - * Maximum size of the icon portion of the children menu items. - */ - private int maxIconWidth; - - /** - * Maximum size of the spacing between the text and accelerator - * portions of the children menu items. - */ - private int maxAccelSpacingWidth; - - /** - * Maximum size of the text for the accelerator portion of the children - * menu items. - */ - private int maxAcceleratorWidth; - - /* - * Maximum icon and text offsets of the children menu items. - */ - private int maxTextOffset; - private int maxIconOffset; - private SynthStyle style; public static ComponentUI createUI(JComponent x) { @@ -153,90 +125,6 @@ return SynthLookAndFeel.getComponentState(c); } - /** - * Resets the max text and accerator widths, - * text and icon offsets. - */ - void resetAlignmentHints() { - maxTextWidth = maxIconWidth - = maxAccelSpacingWidth = maxAcceleratorWidth - = maxTextOffset = maxIconOffset = 0; - } - - /** - * Adjusts the width needed to display the maximum menu item string. - * - * @param width Text width. - * @return max width - */ - int adjustTextWidth(int width) { - maxTextWidth = Math.max(maxTextWidth, width); - return maxTextWidth; - } - - /** - * Adjusts the width needed to display the maximum menu item icon. - * - * @param width Icon width. - * @return max width - */ - int adjustIconWidth(int width) { - maxIconWidth = Math.max(maxIconWidth, width); - return maxIconWidth; - } - - /** - * Adjusts the width needed to pad between the maximum menu item - * text and accelerator. - * - * @param width Spacing width. - * @return max width - */ - int adjustAccelSpacingWidth(int width) { - maxAccelSpacingWidth = Math.max(maxAccelSpacingWidth, width); - return maxAccelSpacingWidth; - } - - /** - * Adjusts the width needed to display the maximum accelerator. - * - * @param width Text width. - * @return max width - */ - int adjustAcceleratorWidth(int width) { - maxAcceleratorWidth = Math.max(maxAcceleratorWidth, width); - return maxAcceleratorWidth; - } - - /** - * Maximum size needed to display accelerators of children menu items. - */ - int getMaxAcceleratorWidth() { - return maxAcceleratorWidth; - } - - /** - * Adjusts the text offset needed to align text horizontally. - * - * @param offset Text offset - * @return max offset - */ - int adjustTextOffset(int offset) { - maxTextOffset = Math.max(maxTextOffset, offset); - return maxTextOffset; - } - - /** - * Adjusts the icon offset needed to align icons horizontally - * - * @param offset Icon offset - * @return max offset - */ - int adjustIconOffset(int offset) { - maxIconOffset = Math.max(maxIconOffset, offset); - return maxIconOffset; - } - public void update(Graphics g, JComponent c) { SynthContext context = getContext(c); diff -r 16088ec1b9e7 -r 3cf2264a5743 jdk/src/share/classes/sun/swing/MenuItemLayoutHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/sun/swing/MenuItemLayoutHelper.java Fri Aug 08 20:49:26 2008 +0400 @@ -0,0 +1,1339 @@ +/* + * Copyright 2002-2008 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 sun.swing; + +import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET; + +import javax.swing.*; +import javax.swing.plaf.basic.BasicHTML; +import javax.swing.text.View; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.Map; +import java.util.HashMap; + +/** + * Calculates preferred size and layouts menu items. + */ +public class MenuItemLayoutHelper { + + /* Client Property keys for calculation of maximal widths */ + public static final StringUIClientPropertyKey MAX_ARROW_WIDTH = + new StringUIClientPropertyKey("maxArrowWidth"); + public static final StringUIClientPropertyKey MAX_CHECK_WIDTH = + new StringUIClientPropertyKey("maxCheckWidth"); + public static final StringUIClientPropertyKey MAX_ICON_WIDTH = + new StringUIClientPropertyKey("maxIconWidth"); + public static final StringUIClientPropertyKey MAX_TEXT_WIDTH = + new StringUIClientPropertyKey("maxTextWidth"); + public static final StringUIClientPropertyKey MAX_ACC_WIDTH = + new StringUIClientPropertyKey("maxAccWidth"); + public static final StringUIClientPropertyKey MAX_LABEL_WIDTH = + new StringUIClientPropertyKey("maxLabelWidth"); + + private JMenuItem mi; + private JComponent miParent; + + private Font font; + private Font accFont; + private FontMetrics fm; + private FontMetrics accFm; + + private Icon icon; + private Icon checkIcon; + private Icon arrowIcon; + private String text; + private String accText; + + private boolean isColumnLayout; + private boolean useCheckAndArrow; + private boolean isLeftToRight; + private boolean isTopLevelMenu; + private View htmlView; + + private int verticalAlignment; + private int horizontalAlignment; + private int verticalTextPosition; + private int horizontalTextPosition; + private int gap; + private int leadingGap; + private int afterCheckIconGap; + private int minTextOffset; + + private Rectangle viewRect; + + private RectSize iconSize; + private RectSize textSize; + private RectSize accSize; + private RectSize checkSize; + private RectSize arrowSize; + private RectSize labelSize; + + /** + * The empty protected constructor is necessary for derived classes. + */ + protected MenuItemLayoutHelper() { + } + + public MenuItemLayoutHelper(JMenuItem mi, Icon checkIcon, Icon arrowIcon, + Rectangle viewRect, int gap, String accDelimiter, + boolean isLeftToRight, Font font, Font accFont, + boolean useCheckAndArrow, String propertyPrefix) { + reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter, + isLeftToRight, font, accFont, useCheckAndArrow, propertyPrefix); + } + + protected void reset(JMenuItem mi, Icon checkIcon, Icon arrowIcon, + Rectangle viewRect, int gap, String accDelimiter, + boolean isLeftToRight, Font font, Font accFont, + 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.font = font; + this.accFont = accFont; + this.fm = mi.getFontMetrics(font); + this.accFm = mi.getFontMetrics(accFont); + this.isLeftToRight = isLeftToRight; + this.isColumnLayout = isColumnLayout(isLeftToRight, + horizontalAlignment, horizontalTextPosition, + verticalTextPosition); + 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.iconSize = new RectSize(); + this.textSize = new RectSize(); + this.accSize = new RectSize(); + this.checkSize = new RectSize(); + this.arrowSize = new RectSize(); + this.labelSize = new RectSize(); + calcWidthsAndHeights(); + setOriginalWidths(); + calcMaxWidths(); + + this.leadingGap = getLeadingGap(propertyPrefix); + calcMaxTextOffset(viewRect); + } + + private void setOriginalWidths() { + iconSize.origWidth = iconSize.width; + textSize.origWidth = textSize.width; + accSize.origWidth = accSize.width; + checkSize.origWidth = checkSize.width; + arrowSize.origWidth = arrowSize.width; + } + + 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; + } + + private Icon getIcon(String propertyPrefix) { + // 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. + 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 (checkSize.getMaxWidth() > 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; + } + + protected void calcWidthsAndHeights() { + // iconRect + if (icon != null) { + iconSize.width = icon.getIconWidth(); + iconSize.height = icon.getIconHeight(); + } + + // accRect + if (!accText.equals("")) { + accSize.width = SwingUtilities2.stringWidth(mi, accFm, accText); + accSize.height = accFm.getHeight(); + } + + // textRect + if (text == null) { + text = ""; + } else if (!text.equals("")) { + if (htmlView != null) { + // Text is HTML + textSize.width = + (int) htmlView.getPreferredSpan(View.X_AXIS); + textSize.height = + (int) htmlView.getPreferredSpan(View.Y_AXIS); + } else { + // Text isn't HTML + textSize.width = SwingUtilities2.stringWidth(mi, fm, text); + textSize.height = fm.getHeight(); + } + } + + if (useCheckAndArrow) { + // checkIcon + if (checkIcon != null) { + checkSize.width = checkIcon.getIconWidth(); + checkSize.height = checkIcon.getIconHeight(); + } + // arrowRect + if (arrowIcon != null) { + arrowSize.width = arrowIcon.getIconWidth(); + arrowSize.height = arrowIcon.getIconHeight(); + } + } + + // labelRect + if (isColumnLayout) { + labelSize.width = iconSize.width + textSize.width + gap; + labelSize.height = max(checkSize.height, iconSize.height, + textSize.height, accSize.height, arrowSize.height); + } else { + Rectangle textRect = new Rectangle(); + Rectangle iconRect = new Rectangle(); + SwingUtilities.layoutCompoundLabel(mi, fm, text, icon, + verticalAlignment, horizontalAlignment, + verticalTextPosition, horizontalTextPosition, + viewRect, iconRect, textRect, gap); + Rectangle labelRect = iconRect.union(textRect); + labelSize.height = labelRect.height; + labelSize.width = labelRect.width; + } + } + + protected void calcMaxWidths() { + calcMaxWidth(checkSize, MAX_CHECK_WIDTH); + calcMaxWidth(arrowSize, MAX_ARROW_WIDTH); + calcMaxWidth(accSize, MAX_ACC_WIDTH); + + if (isColumnLayout) { + calcMaxWidth(iconSize, MAX_ICON_WIDTH); + calcMaxWidth(textSize, MAX_TEXT_WIDTH); + int curGap = gap; + if ((iconSize.getMaxWidth() == 0) + || (textSize.getMaxWidth() == 0)) { + curGap = 0; + } + labelSize.maxWidth = + calcMaxValue(MAX_LABEL_WIDTH, iconSize.maxWidth + + textSize.maxWidth + curGap); + } else { + // We shouldn't use current icon and text widths + // in maximal widths calculation for complex layout. + iconSize.maxWidth = getParentIntProperty(MAX_ICON_WIDTH); + calcMaxWidth(labelSize, MAX_LABEL_WIDTH); + // If maxLabelWidth is wider + // than the widest icon + the widest text + gap, + // we should update the maximal text witdh + int candidateTextWidth = labelSize.maxWidth - iconSize.maxWidth; + if (iconSize.maxWidth > 0) { + candidateTextWidth -= gap; + } + textSize.maxWidth = calcMaxValue(MAX_TEXT_WIDTH, candidateTextWidth); + } + } + + protected void calcMaxWidth(RectSize rs, Object key) { + rs.maxWidth = calcMaxValue(key, rs.width); + } + + /** + * Calculates and returns maximal value through specified parent component + * client property. + * + * @param propertyName name of the property, which stores the maximal value. + * @param value a value which pretends to be maximal + * @return maximal value among the parent property and the value. + */ + protected 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. + * @param propertyName name of the parent property. + * @return value of the property as int. + */ + protected 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; + } + + 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. + * + * @param viewRect the rectangle, the maximal text offset + * will be calculated for. + */ + private void calcMaxTextOffset(Rectangle viewRect) { + if (!isColumnLayout || !isLeftToRight) { + return; + } + + // Calculate the current text offset + int offset = viewRect.x + leadingGap + checkSize.maxWidth + + afterCheckIconGap + iconSize.maxWidth + gap; + if (checkSize.maxWidth == 0) { + offset -= afterCheckIconGap; + } + if (iconSize.maxWidth == 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(SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET, offset); + } + + /** + * 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 union of icon and text. + * + * The order of columns can be reversed. + * It depends on the menu item orientation. + */ + public LayoutResult layoutMenuItem() { + LayoutResult lr = createLayoutResult(); + prepareForLayout(lr); + + if (isColumnLayout()) { + if (isLeftToRight()) { + doLTRColumnLayout(lr, getLTRColumnAlignment()); + } else { + doRTLColumnLayout(lr, getRTLColumnAlignment()); + } + } else { + if (isLeftToRight()) { + doLTRComplexLayout(lr, getLTRColumnAlignment()); + } else { + doRTLComplexLayout(lr, getRTLColumnAlignment()); + } + } + + alignAccCheckAndArrowVertically(lr); + return lr; + } + + private LayoutResult createLayoutResult() { + return new LayoutResult( + new Rectangle(iconSize.width, iconSize.height), + new Rectangle(textSize.width, textSize.height), + new Rectangle(accSize.width, accSize.height), + new Rectangle(checkSize.width, checkSize.height), + new Rectangle(arrowSize.width, arrowSize.height), + new Rectangle(labelSize.width, labelSize.height) + ); + } + + public ColumnAlignment getLTRColumnAlignment() { + return ColumnAlignment.LEFT_ALIGNMENT; + } + + public ColumnAlignment getRTLColumnAlignment() { + return ColumnAlignment.RIGHT_ALIGNMENT; + } + + protected void prepareForLayout(LayoutResult lr) { + lr.checkRect.width = checkSize.maxWidth; + lr.accRect.width = accSize.maxWidth; + lr.arrowRect.width = arrowSize.maxWidth; + } + + /** + * Aligns the accelertor text and the check and arrow icons vertically + * with the center of the label rect. + */ + private void alignAccCheckAndArrowVertically(LayoutResult lr) { + lr.accRect.y = (int)(lr.labelRect.y + + (float)lr.labelRect.height/2 + - (float)lr.accRect.height/2); + fixVerticalAlignment(lr, lr.accRect); + if (useCheckAndArrow) { + lr.arrowRect.y = (int)(lr.labelRect.y + + (float)lr.labelRect.height/2 + - (float)lr.arrowRect.height/2); + lr.checkRect.y = (int)(lr.labelRect.y + + (float)lr.labelRect.height/2 + - (float)lr.checkRect.height/2); + fixVerticalAlignment(lr, lr.arrowRect); + fixVerticalAlignment(lr, lr.checkRect); + } + } + + /** + * Fixes vertical alignment of all menu item elements if rect.y + * or (rect.y + rect.height) is out of viewRect bounds + */ + private void fixVerticalAlignment(LayoutResult lr, Rectangle r) { + int delta = 0; + if (r.y < viewRect.y) { + delta = viewRect.y - r.y; + } else if (r.y + r.height > viewRect.y + viewRect.height) { + delta = viewRect.y + viewRect.height - r.y - r.height; + } + if (delta != 0) { + lr.checkRect.y += delta; + lr.iconRect.y += delta; + lr.textRect.y += delta; + lr.accRect.y += delta; + lr.arrowRect.y += delta; + lr.labelRect.y += delta; + } + } + + private void doLTRColumnLayout(LayoutResult lr, ColumnAlignment alignment) { + // Set maximal width for all the five basic rects + // (three other ones are already maximal) + lr.iconRect.width = iconSize.maxWidth; + lr.textRect.width = textSize.maxWidth; + + // Set X coordinates + // All rects will be aligned at the left side + calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.checkRect, + lr.iconRect, lr.textRect); + + // Tune afterCheckIconGap + if (lr.checkRect.width > 0) { // there is the afterCheckIconGap + lr.iconRect.x += afterCheckIconGap - gap; + lr.textRect.x += afterCheckIconGap - gap; + } + + calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap, + lr.arrowRect, lr.accRect); + + // Take into account minimal text offset + int textOffset = lr.textRect.x - viewRect.x; + if (!isTopLevelMenu && (textOffset < minTextOffset)) { + lr.textRect.x += minTextOffset - textOffset; + } + + alignRects(lr, alignment); + + // Take into account the left side bearings for text and accelerator text. + fixTextRects(lr); + + // Set Y coordinate for text and icon. + // Y coordinates for other rects + // will be calculated later in layoutMenuItem. + calcTextAndIconYPositions(lr); + + // Calculate valid X and Y coordinates for labelRect + lr.setLabelRect(lr.textRect.union(lr.iconRect)); + } + + private void doLTRComplexLayout(LayoutResult lr, ColumnAlignment alignment) { + lr.labelRect.width = labelSize.maxWidth; + + // Set X coordinates + calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.checkRect, + lr.labelRect); + + // Tune afterCheckIconGap + if (lr.checkRect.width > 0) { // there is the afterCheckIconGap + lr.labelRect.x += afterCheckIconGap - gap; + } + + calcXPositionsRTL(viewRect.x + viewRect.width, + leadingGap, gap, lr.arrowRect, lr.accRect); + + // Take into account minimal text offset + int labelOffset = lr.labelRect.x - viewRect.x; + if (!isTopLevelMenu && (labelOffset < minTextOffset)) { + lr.labelRect.x += minTextOffset - labelOffset; + } + + alignRects(lr, alignment); + + // Take into account the left side bearing for accelerator text. + // The LSB for text is taken into account in layoutCompoundLabel() below. + fixAccTextRect(lr); + + // Center labelRect vertically + calcLabelYPosition(lr); + + layoutIconAndTextInLabelRect(lr); + } + + private void doRTLColumnLayout(LayoutResult lr, ColumnAlignment alignment) { + // Set maximal width for all the five basic rects + // (three other ones are already maximal) + lr.iconRect.width = iconSize.maxWidth; + lr.textRect.width = textSize.maxWidth; + + // Set X coordinates + calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap, + lr.checkRect, lr.iconRect, lr.textRect); + + // Tune the gap after check icon + if (lr.checkRect.width > 0) { // there is the gap after check icon + lr.iconRect.x -= afterCheckIconGap - gap; + lr.textRect.x -= afterCheckIconGap - gap; + } + + calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.arrowRect, + lr.accRect); + + // Take into account minimal text offset + int textOffset = (viewRect.x + viewRect.width) + - (lr.textRect.x + lr.textRect.width); + if (!isTopLevelMenu && (textOffset < minTextOffset)) { + lr.textRect.x -= minTextOffset - textOffset; + } + + alignRects(lr, alignment); + + // Take into account the left side bearings for text and accelerator text. + fixTextRects(lr); + + // Set Y coordinates for text and icon. + // Y coordinates for other rects + // will be calculated later in layoutMenuItem. + calcTextAndIconYPositions(lr); + + // Calculate valid X and Y coordinate for labelRect + lr.setLabelRect(lr.textRect.union(lr.iconRect)); + } + + private void doRTLComplexLayout(LayoutResult lr, ColumnAlignment alignment) { + lr.labelRect.width = labelSize.maxWidth; + + // Set X coordinates + calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap, + lr.checkRect, lr.labelRect); + + // Tune the gap after check icon + if (lr.checkRect.width > 0) { // there is the gap after check icon + lr.labelRect.x -= afterCheckIconGap - gap; + } + + calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.arrowRect, lr.accRect); + + // Take into account minimal text offset + int labelOffset = (viewRect.x + viewRect.width) + - (lr.labelRect.x + lr.labelRect.width); + if (!isTopLevelMenu && (labelOffset < minTextOffset)) { + lr.labelRect.x -= minTextOffset - labelOffset; + } + + alignRects(lr, alignment); + + // Take into account the left side bearing for accelerator text. + // The LSB for text is taken into account in layoutCompoundLabel() below. + fixAccTextRect(lr); + + // Center labelRect vertically + calcLabelYPosition(lr); + + layoutIconAndTextInLabelRect(lr); + } + + private void alignRects(LayoutResult lr, ColumnAlignment alignment) { + alignRect(lr.checkRect, alignment.getCheckAlignment(), + checkSize.getOrigWidth()); + alignRect(lr.iconRect, alignment.getIconAlignment(), + iconSize.getOrigWidth()); + alignRect(lr.textRect, alignment.getTextAlignment(), + textSize.getOrigWidth()); + alignRect(lr.accRect, alignment.getAccAlignment(), + accSize.getOrigWidth()); + alignRect(lr.arrowRect, alignment.getArrowAlignment(), + arrowSize.getOrigWidth()); + } + + private void alignRect(Rectangle rect, int alignment, int origWidth) { + if (alignment != SwingUtilities.LEFT) { + rect.x = rect.x + rect.width - origWidth; + rect.width = origWidth; + } + } + + protected void layoutIconAndTextInLabelRect(LayoutResult lr) { + lr.setTextRect(new Rectangle()); + lr.setIconRect(new Rectangle()); + SwingUtilities.layoutCompoundLabel( + mi, fm, text,icon, verticalAlignment, horizontalAlignment, + verticalTextPosition, horizontalTextPosition, lr.labelRect, + lr.iconRect, lr.textRect, gap); + } + + private void calcXPositionsLTR(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 calcXPositionsRTL(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; + } + } + } + + /** + * Takes into account the left side bearings for text and accelerator text + */ + private void fixTextRects(LayoutResult lr) { + if (htmlView == null) { // The text isn't a HTML + int lsb = SwingUtilities2.getLeftSideBearing(mi, fm, text); + if (lsb < 0) { + lr.textRect.x -= lsb; + } + } + fixAccTextRect(lr); + } + + /** + * Takes into account the left side bearing for accelerator text + */ + private void fixAccTextRect(LayoutResult lr) { + int lsb = SwingUtilities2.getLeftSideBearing(mi, accFm, accText); + if (lsb < 0) { + lr.accRect.x -= lsb; + } + } + + /** + * Sets Y coordinates of text and icon + * taking into account the vertical alignment + */ + private void calcTextAndIconYPositions(LayoutResult lr) { + if (verticalAlignment == SwingUtilities.TOP) { + lr.textRect.y = (int)(viewRect.y + + (float)lr.labelRect.height/2 + - (float)lr.textRect.height/2); + lr.iconRect.y = (int)(viewRect.y + + (float)lr.labelRect.height/2 + - (float)lr.iconRect.height/2); + } else if (verticalAlignment == SwingUtilities.CENTER) { + lr.textRect.y = (int)(viewRect.y + + (float)viewRect.height/2 + - (float)lr.textRect.height/2); + lr.iconRect.y = (int)(viewRect.y + + (float)viewRect.height/2 + - (float)lr.iconRect.height/2); + } + else if (verticalAlignment == SwingUtilities.BOTTOM) { + lr.textRect.y = (int)(viewRect.y + + viewRect.height + - (float)lr.labelRect.height/2 + - (float)lr.textRect.height/2); + lr.iconRect.y = (int)(viewRect.y + + viewRect.height + - (float)lr.labelRect.height/2 + - (float)lr.iconRect.height/2); + } + } + + /** + * Sets labelRect Y coordinate + * taking into account the vertical alignment + */ + private void calcLabelYPosition(LayoutResult lr) { + if (verticalAlignment == SwingUtilities.TOP) { + lr.labelRect.y = viewRect.y; + } else if (verticalAlignment == SwingUtilities.CENTER) { + lr.labelRect.y = (int)(viewRect.y + + (float)viewRect.height/2 + - (float)lr.labelRect.height/2); + } else if (verticalAlignment == SwingUtilities.BOTTOM) { + lr.labelRect.y = viewRect.y + viewRect.height + - lr.labelRect.height; + } + } + + /** + * Returns parent of this component if it is not a top-level menu + * Otherwise returns null. + * @param menuItem the menu item whose parent will be returned. + * @return parent of this component if it is not a top-level menu + * Otherwise returns null. + */ + public static JComponent getMenuItemParent(JMenuItem menuItem) { + Container parent = menuItem.getParent(); + if ((parent instanceof JComponent) && + (!(menuItem instanceof JMenu) || + !((JMenu)menuItem).isTopLevelMenu())) { + return (JComponent) parent; + } else { + return null; + } + } + + public static void clearUsedParentClientProperties(JMenuItem menuItem) { + clearUsedClientProperties(getMenuItemParent(menuItem)); + } + + public static void clearUsedClientProperties(JComponent c) { + if (c != null) { + c.putClientProperty(MAX_ARROW_WIDTH, null); + c.putClientProperty(MAX_CHECK_WIDTH, null); + c.putClientProperty(MAX_ACC_WIDTH, null); + c.putClientProperty(MAX_TEXT_WIDTH, null); + c.putClientProperty(MAX_ICON_WIDTH, null); + c.putClientProperty(MAX_LABEL_WIDTH, null); + c.putClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET, null); + } + } + + /** + * Finds and returns maximal integer value in the given array. + * @param values array where the search will be performed. + * @return maximal vaule. + */ + public static int max(int... values) { + int maxValue = Integer.MIN_VALUE; + for (int i : values) { + if (i > maxValue) { + maxValue = i; + } + } + return maxValue; + } + + public static Rectangle createMaxRect() { + return new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + public static void addMaxWidth(RectSize size, int gap, Dimension result) { + if (size.maxWidth > 0) { + result.width += size.maxWidth + gap; + } + } + + public static void addWidth(int width, int gap, Dimension result) { + if (width > 0) { + result.width += width + gap; + } + } + + public JMenuItem getMenuItem() { + return mi; + } + + public JComponent getMenuItemParent() { + return miParent; + } + + public Font getFont() { + return font; + } + + public Font getAccFont() { + return accFont; + } + + public FontMetrics getFontMetrics() { + return fm; + } + + public FontMetrics getAccFontMetrics() { + return accFm; + } + + public Icon getIcon() { + return icon; + } + + public Icon getCheckIcon() { + return checkIcon; + } + + public Icon getArrowIcon() { + return arrowIcon; + } + + public String getText() { + return text; + } + + public String getAccText() { + return accText; + } + + public boolean isColumnLayout() { + return isColumnLayout; + } + + public boolean useCheckAndArrow() { + return useCheckAndArrow; + } + + public boolean isLeftToRight() { + return isLeftToRight; + } + + public boolean isTopLevelMenu() { + return isTopLevelMenu; + } + + public View getHtmlView() { + return htmlView; + } + + public int getVerticalAlignment() { + return verticalAlignment; + } + + public int getHorizontalAlignment() { + return horizontalAlignment; + } + + public int getVerticalTextPosition() { + return verticalTextPosition; + } + + public int getHorizontalTextPosition() { + return horizontalTextPosition; + } + + public int getGap() { + return gap; + } + + public int getLeadingGap() { + return leadingGap; + } + + public int getAfterCheckIconGap() { + return afterCheckIconGap; + } + + public int getMinTextOffset() { + return minTextOffset; + } + + public Rectangle getViewRect() { + return viewRect; + } + + public RectSize getIconSize() { + return iconSize; + } + + public RectSize getTextSize() { + return textSize; + } + + public RectSize getAccSize() { + return accSize; + } + + public RectSize getCheckSize() { + return checkSize; + } + + public RectSize getArrowSize() { + return arrowSize; + } + + public RectSize getLabelSize() { + return labelSize; + } + + protected void setMenuItem(JMenuItem mi) { + this.mi = mi; + } + + protected void setMenuItemParent(JComponent miParent) { + this.miParent = miParent; + } + + protected void setFont(Font font) { + this.font = font; + } + + protected void setAccFont(Font accFont) { + this.accFont = accFont; + } + + protected void setFontMetrics(FontMetrics fm) { + this.fm = fm; + } + + protected void setAccFontMetrics(FontMetrics accFm) { + this.accFm = accFm; + } + + protected void setIcon(Icon icon) { + this.icon = icon; + } + + protected void setCheckIcon(Icon checkIcon) { + this.checkIcon = checkIcon; + } + + protected void setArrowIcon(Icon arrowIcon) { + this.arrowIcon = arrowIcon; + } + + protected void setText(String text) { + this.text = text; + } + + protected void setAccText(String accText) { + this.accText = accText; + } + + protected void setColumnLayout(boolean columnLayout) { + isColumnLayout = columnLayout; + } + + protected void setUseCheckAndArrow(boolean useCheckAndArrow) { + this.useCheckAndArrow = useCheckAndArrow; + } + + protected void setLeftToRight(boolean leftToRight) { + isLeftToRight = leftToRight; + } + + protected void setTopLevelMenu(boolean topLevelMenu) { + isTopLevelMenu = topLevelMenu; + } + + protected void setHtmlView(View htmlView) { + this.htmlView = htmlView; + } + + protected void setVerticalAlignment(int verticalAlignment) { + this.verticalAlignment = verticalAlignment; + } + + protected void setHorizontalAlignment(int horizontalAlignment) { + this.horizontalAlignment = horizontalAlignment; + } + + protected void setVerticalTextPosition(int verticalTextPosition) { + this.verticalTextPosition = verticalTextPosition; + } + + protected void setHorizontalTextPosition(int horizontalTextPosition) { + this.horizontalTextPosition = horizontalTextPosition; + } + + protected void setGap(int gap) { + this.gap = gap; + } + + protected void setLeadingGap(int leadingGap) { + this.leadingGap = leadingGap; + } + + protected void setAfterCheckIconGap(int afterCheckIconGap) { + this.afterCheckIconGap = afterCheckIconGap; + } + + protected void setMinTextOffset(int minTextOffset) { + this.minTextOffset = minTextOffset; + } + + protected void setViewRect(Rectangle viewRect) { + this.viewRect = viewRect; + } + + protected void setIconSize(RectSize iconSize) { + this.iconSize = iconSize; + } + + protected void setTextSize(RectSize textSize) { + this.textSize = textSize; + } + + protected void setAccSize(RectSize accSize) { + this.accSize = accSize; + } + + protected void setCheckSize(RectSize checkSize) { + this.checkSize = checkSize; + } + + protected void setArrowSize(RectSize arrowSize) { + this.arrowSize = arrowSize; + } + + protected void setLabelSize(RectSize labelSize) { + this.labelSize = labelSize; + } + + /** + * Returns false if the component is a JMenu and it is a top + * level menu (on the menubar). + */ + public static boolean useCheckAndArrow(JMenuItem menuItem) { + boolean b = true; + if ((menuItem instanceof JMenu) && + (((JMenu) menuItem).isTopLevelMenu())) { + b = false; + } + return b; + } + + public static class LayoutResult { + private Rectangle iconRect; + private Rectangle textRect; + private Rectangle accRect; + private Rectangle checkRect; + private Rectangle arrowRect; + private Rectangle labelRect; + + public LayoutResult() { + iconRect = new Rectangle(); + textRect = new Rectangle(); + accRect = new Rectangle(); + checkRect = new Rectangle(); + arrowRect = new Rectangle(); + labelRect = new Rectangle(); + } + + public LayoutResult(Rectangle iconRect, Rectangle textRect, + Rectangle accRect, Rectangle checkRect, + Rectangle arrowRect, Rectangle labelRect) { + this.iconRect = iconRect; + this.textRect = textRect; + this.accRect = accRect; + this.checkRect = checkRect; + this.arrowRect = arrowRect; + this.labelRect = labelRect; + } + + public Rectangle getIconRect() { + return iconRect; + } + + public void setIconRect(Rectangle iconRect) { + this.iconRect = iconRect; + } + + public Rectangle getTextRect() { + return textRect; + } + + public void setTextRect(Rectangle textRect) { + this.textRect = textRect; + } + + public Rectangle getAccRect() { + return accRect; + } + + public void setAccRect(Rectangle accRect) { + this.accRect = accRect; + } + + public Rectangle getCheckRect() { + return checkRect; + } + + public void setCheckRect(Rectangle checkRect) { + this.checkRect = checkRect; + } + + public Rectangle getArrowRect() { + return arrowRect; + } + + public void setArrowRect(Rectangle arrowRect) { + this.arrowRect = arrowRect; + } + + public Rectangle getLabelRect() { + return labelRect; + } + + public void setLabelRect(Rectangle labelRect) { + this.labelRect = labelRect; + } + + public Map getAllRects() { + Map result = new HashMap(); + result.put("checkRect", checkRect); + result.put("iconRect", iconRect); + result.put("textRect", textRect); + result.put("accRect", accRect); + result.put("arrowRect", arrowRect); + result.put("labelRect", labelRect); + return result; + } + } + + public static class ColumnAlignment { + private int checkAlignment; + private int iconAlignment; + private int textAlignment; + private int accAlignment; + private int arrowAlignment; + + public static final ColumnAlignment LEFT_ALIGNMENT = + new ColumnAlignment( + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT, + SwingConstants.LEFT + ); + + public static final ColumnAlignment RIGHT_ALIGNMENT = + new ColumnAlignment( + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT, + SwingConstants.RIGHT + ); + + public ColumnAlignment(int checkAlignment, int iconAlignment, + int textAlignment, int accAlignment, + int arrowAlignment) { + this.checkAlignment = checkAlignment; + this.iconAlignment = iconAlignment; + this.textAlignment = textAlignment; + this.accAlignment = accAlignment; + this.arrowAlignment = arrowAlignment; + } + + public int getCheckAlignment() { + return checkAlignment; + } + + public int getIconAlignment() { + return iconAlignment; + } + + public int getTextAlignment() { + return textAlignment; + } + + public int getAccAlignment() { + return accAlignment; + } + + public int getArrowAlignment() { + return arrowAlignment; + } + } + + public static class RectSize { + private int width; + private int height; + private int origWidth; + private int maxWidth; + + public RectSize() { + } + + public RectSize(int width, int height, int origWidth, int maxWidth) { + this.width = width; + this.height = height; + this.origWidth = origWidth; + this.maxWidth = maxWidth; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getOrigWidth() { + return origWidth; + } + + public int getMaxWidth() { + return maxWidth; + } + + public void setWidth(int width) { + this.width = width; + } + + public void setHeight(int height) { + this.height = height; + } + + public void setOrigWidth(int origWidth) { + this.origWidth = origWidth; + } + + public void setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + } + + public String toString() { + return "[w=" + width + ",h=" + height + ",ow=" + + origWidth + ",mw=" + maxWidth + "]"; + } + } +}