| /* |
| * Copyright (c) 2002, 2008, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.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 int leftTextExtraWidth; |
| |
| 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(); |
| calcExtraWidths(); |
| calcWidthsAndHeights(); |
| setOriginalWidths(); |
| calcMaxWidths(); |
| |
| this.leadingGap = getLeadingGap(propertyPrefix); |
| calcMaxTextOffset(viewRect); |
| } |
| |
| private void calcExtraWidths() { |
| leftTextExtraWidth = getLeftExtraWidth(text); |
| } |
| |
| private int getLeftExtraWidth(String str) { |
| int lsb = SwingUtilities2.getLeftSideBearing(mi, fm, str); |
| if (lsb < 0) { |
| return -lsb; |
| } else { |
| return 0; |
| } |
| } |
| |
| private void setOriginalWidths() { |
| iconSize.origWidth = iconSize.width; |
| textSize.origWidth = textSize.width; |
| accSize.origWidth = accSize.width; |
| checkSize.origWidth = checkSize.width; |
| arrowSize.origWidth = arrowSize.width; |
| } |
| |
| @SuppressWarnings("deprecation") |
| 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); |
| textRect.width += leftTextExtraWidth; |
| 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); |
| |
| // 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); |
| |
| // 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); |
| |
| // 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); |
| |
| // 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 == SwingConstants.RIGHT) { |
| 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; |
| } |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| public int getLeftTextExtraWidth() { |
| return leftTextExtraWidth; |
| } |
| |
| /** |
| * 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<String, Rectangle> getAllRects() { |
| Map<String, Rectangle> result = new HashMap<String, Rectangle>(); |
| 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 + "]"; |
| } |
| } |
| } |