/*
 * Copyright (c) 2002, 2021, 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.isEmpty()) {
            accSize.width = SwingUtilities2.stringWidth(mi, accFm, accText);
            accSize.height = accFm.getHeight();
        }

        // textRect
        if (text == null) {
            text = "";
        } else if (!text.isEmpty()) {
            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 width
            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 instanceof Integer intValue) {
            return intValue;
        }
        return 0;
    }

    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 value.
     */
    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 + "]";
        }
    }
}
