1 /*
   2  * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.swing;
  27 
  28 import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET;
  29 
  30 import javax.swing.*;
  31 import javax.swing.plaf.basic.BasicHTML;
  32 import javax.swing.text.View;
  33 import java.awt.*;
  34 import java.awt.event.KeyEvent;
  35 import java.util.Map;
  36 import java.util.HashMap;
  37 
  38 /**
  39  * Calculates preferred size and layouts menu items.
  40  */
  41 public class MenuItemLayoutHelper {
  42 
  43     /* Client Property keys for calculation of maximal widths */
  44     public static final StringUIClientPropertyKey MAX_ARROW_WIDTH =
  45                         new StringUIClientPropertyKey("maxArrowWidth");
  46     public static final StringUIClientPropertyKey MAX_CHECK_WIDTH =
  47                         new StringUIClientPropertyKey("maxCheckWidth");
  48     public static final StringUIClientPropertyKey MAX_ICON_WIDTH =
  49                         new StringUIClientPropertyKey("maxIconWidth");
  50     public static final StringUIClientPropertyKey MAX_TEXT_WIDTH =
  51                         new StringUIClientPropertyKey("maxTextWidth");
  52     public static final StringUIClientPropertyKey MAX_ACC_WIDTH =
  53                         new StringUIClientPropertyKey("maxAccWidth");
  54     public static final StringUIClientPropertyKey MAX_LABEL_WIDTH =
  55                         new StringUIClientPropertyKey("maxLabelWidth");
  56 
  57     private JMenuItem mi;
  58     private JComponent miParent;
  59 
  60     private Font font;
  61     private Font accFont;
  62     private FontMetrics fm;
  63     private FontMetrics accFm;
  64 
  65     private Icon icon;
  66     private Icon checkIcon;
  67     private Icon arrowIcon;
  68     private String text;
  69     private String accText;
  70 
  71     private boolean isColumnLayout;
  72     private boolean useCheckAndArrow;
  73     private boolean isLeftToRight;
  74     private boolean isTopLevelMenu;
  75     private View htmlView;
  76 
  77     private int verticalAlignment;
  78     private int horizontalAlignment;
  79     private int verticalTextPosition;
  80     private int horizontalTextPosition;
  81     private int gap;
  82     private int leadingGap;
  83     private int afterCheckIconGap;
  84     private int minTextOffset;
  85 
  86     private int leftTextExtraWidth;
  87 
  88     private Rectangle viewRect;
  89 
  90     private RectSize iconSize;
  91     private RectSize textSize;
  92     private RectSize accSize;
  93     private RectSize checkSize;
  94     private RectSize arrowSize;
  95     private RectSize labelSize;
  96 
  97     /**
  98      * The empty protected constructor is necessary for derived classes.
  99      */
 100     protected MenuItemLayoutHelper() {
 101     }
 102 
 103     public MenuItemLayoutHelper(JMenuItem mi, Icon checkIcon, Icon arrowIcon,
 104                       Rectangle viewRect, int gap, String accDelimiter,
 105                       boolean isLeftToRight, Font font, Font accFont,
 106                       boolean useCheckAndArrow, String propertyPrefix) {
 107         reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter,
 108               isLeftToRight, font, accFont, useCheckAndArrow, propertyPrefix);
 109     }
 110 
 111     protected void reset(JMenuItem mi, Icon checkIcon, Icon arrowIcon,
 112                       Rectangle viewRect, int gap, String accDelimiter,
 113                       boolean isLeftToRight, Font font, Font accFont,
 114                       boolean useCheckAndArrow, String propertyPrefix) {
 115         this.mi = mi;
 116         this.miParent = getMenuItemParent(mi);
 117         this.accText = getAccText(accDelimiter);
 118         this.verticalAlignment = mi.getVerticalAlignment();
 119         this.horizontalAlignment = mi.getHorizontalAlignment();
 120         this.verticalTextPosition = mi.getVerticalTextPosition();
 121         this.horizontalTextPosition = mi.getHorizontalTextPosition();
 122         this.useCheckAndArrow = useCheckAndArrow;
 123         this.font = font;
 124         this.accFont = accFont;
 125         this.fm = mi.getFontMetrics(font);
 126         this.accFm = mi.getFontMetrics(accFont);
 127         this.isLeftToRight = isLeftToRight;
 128         this.isColumnLayout = isColumnLayout(isLeftToRight,
 129                 horizontalAlignment, horizontalTextPosition,
 130                 verticalTextPosition);
 131         this.isTopLevelMenu = (this.miParent == null) ? true : false;
 132         this.checkIcon = checkIcon;
 133         this.icon = getIcon(propertyPrefix);
 134         this.arrowIcon = arrowIcon;
 135         this.text = mi.getText();
 136         this.gap = gap;
 137         this.afterCheckIconGap = getAfterCheckIconGap(propertyPrefix);
 138         this.minTextOffset = getMinTextOffset(propertyPrefix);
 139         this.htmlView = (View) mi.getClientProperty(BasicHTML.propertyKey);
 140         this.viewRect = viewRect;
 141 
 142         this.iconSize = new RectSize();
 143         this.textSize = new RectSize();
 144         this.accSize = new RectSize();
 145         this.checkSize = new RectSize();
 146         this.arrowSize = new RectSize();
 147         this.labelSize = new RectSize();
 148         calcExtraWidths();
 149         calcWidthsAndHeights();
 150         setOriginalWidths();
 151         calcMaxWidths();
 152 
 153         this.leadingGap = getLeadingGap(propertyPrefix);
 154         calcMaxTextOffset(viewRect);
 155     }
 156 
 157     private void calcExtraWidths() {
 158         leftTextExtraWidth = getLeftExtraWidth(text);
 159     }
 160 
 161     private int getLeftExtraWidth(String str) {
 162         int lsb = SwingUtilities2.getLeftSideBearing(mi, fm, str);
 163         if (lsb < 0) {
 164             return -lsb;
 165         } else {
 166             return 0;
 167         }
 168     }
 169 
 170     private void setOriginalWidths() {
 171         iconSize.origWidth = iconSize.width;
 172         textSize.origWidth = textSize.width;
 173         accSize.origWidth = accSize.width;
 174         checkSize.origWidth = checkSize.width;
 175         arrowSize.origWidth = arrowSize.width;
 176     }
 177 
 178     @SuppressWarnings("deprecation")
 179     private String getAccText(String acceleratorDelimiter) {
 180         String accText = "";
 181         KeyStroke accelerator = mi.getAccelerator();
 182         if (accelerator != null) {
 183             int modifiers = accelerator.getModifiers();
 184             if (modifiers > 0) {
 185                 accText = KeyEvent.getKeyModifiersText(modifiers);
 186                 accText += acceleratorDelimiter;
 187             }
 188             int keyCode = accelerator.getKeyCode();
 189             if (keyCode != 0) {
 190                 accText += KeyEvent.getKeyText(keyCode);
 191             } else {
 192                 accText += accelerator.getKeyChar();
 193             }
 194         }
 195         return accText;
 196     }
 197 
 198     private Icon getIcon(String propertyPrefix) {
 199         // In case of column layout, .checkIconFactory is defined for this UI,
 200         // the icon is compatible with it and useCheckAndArrow() is true,
 201         // then the icon is handled by the checkIcon.
 202         Icon icon = null;
 203         MenuItemCheckIconFactory iconFactory =
 204                 (MenuItemCheckIconFactory) UIManager.get(propertyPrefix
 205                         + ".checkIconFactory");
 206         if (!isColumnLayout || !useCheckAndArrow || iconFactory == null
 207                 || !iconFactory.isCompatible(checkIcon, propertyPrefix)) {
 208             icon = mi.getIcon();
 209         }
 210         return icon;
 211     }
 212 
 213     private int getMinTextOffset(String propertyPrefix) {
 214         int minimumTextOffset = 0;
 215         Object minimumTextOffsetObject =
 216                 UIManager.get(propertyPrefix + ".minimumTextOffset");
 217         if (minimumTextOffsetObject instanceof Integer) {
 218             minimumTextOffset = (Integer) minimumTextOffsetObject;
 219         }
 220         return minimumTextOffset;
 221     }
 222 
 223     private int getAfterCheckIconGap(String propertyPrefix) {
 224         int afterCheckIconGap = gap;
 225         Object afterCheckIconGapObject =
 226                 UIManager.get(propertyPrefix + ".afterCheckIconGap");
 227         if (afterCheckIconGapObject instanceof Integer) {
 228             afterCheckIconGap = (Integer) afterCheckIconGapObject;
 229         }
 230         return afterCheckIconGap;
 231     }
 232 
 233     private int getLeadingGap(String propertyPrefix) {
 234         if (checkSize.getMaxWidth() > 0) {
 235             return getCheckOffset(propertyPrefix);
 236         } else {
 237             return gap; // There is no any check icon
 238         }
 239     }
 240 
 241     private int getCheckOffset(String propertyPrefix) {
 242         int checkIconOffset = gap;
 243         Object checkIconOffsetObject =
 244                 UIManager.get(propertyPrefix + ".checkIconOffset");
 245         if (checkIconOffsetObject instanceof Integer) {
 246             checkIconOffset = (Integer) checkIconOffsetObject;
 247         }
 248         return checkIconOffset;
 249     }
 250 
 251     protected void calcWidthsAndHeights() {
 252         // iconRect
 253         if (icon != null) {
 254             iconSize.width = icon.getIconWidth();
 255             iconSize.height = icon.getIconHeight();
 256         }
 257 
 258         // accRect
 259         if (!accText.equals("")) {
 260             accSize.width = SwingUtilities2.stringWidth(mi, accFm, accText);
 261             accSize.height = accFm.getHeight();
 262         }
 263 
 264         // textRect
 265         if (text == null) {
 266             text = "";
 267         } else if (!text.equals("")) {
 268             if (htmlView != null) {
 269                 // Text is HTML
 270                 textSize.width =
 271                         (int) htmlView.getPreferredSpan(View.X_AXIS);
 272                 textSize.height =
 273                         (int) htmlView.getPreferredSpan(View.Y_AXIS);
 274             } else {
 275                 // Text isn't HTML
 276                 textSize.width = SwingUtilities2.stringWidth(mi, fm, text);
 277                 textSize.height = fm.getHeight();
 278             }
 279         }
 280 
 281         if (useCheckAndArrow) {
 282             // checkIcon
 283             if (checkIcon != null) {
 284                 checkSize.width = checkIcon.getIconWidth();
 285                 checkSize.height = checkIcon.getIconHeight();
 286             }
 287             // arrowRect
 288             if (arrowIcon != null) {
 289                 arrowSize.width = arrowIcon.getIconWidth();
 290                 arrowSize.height = arrowIcon.getIconHeight();
 291             }
 292         }
 293 
 294         // labelRect
 295         if (isColumnLayout) {
 296             labelSize.width = iconSize.width + textSize.width + gap;
 297             labelSize.height = max(checkSize.height, iconSize.height,
 298                     textSize.height, accSize.height, arrowSize.height);
 299         } else {
 300             Rectangle textRect = new Rectangle();
 301             Rectangle iconRect = new Rectangle();
 302             SwingUtilities.layoutCompoundLabel(mi, fm, text, icon,
 303                     verticalAlignment, horizontalAlignment,
 304                     verticalTextPosition, horizontalTextPosition,
 305                     viewRect, iconRect, textRect, gap);
 306             textRect.width += leftTextExtraWidth;
 307             Rectangle labelRect = iconRect.union(textRect);
 308             labelSize.height = labelRect.height;
 309             labelSize.width = labelRect.width;
 310         }
 311     }
 312 
 313     protected void calcMaxWidths() {
 314         calcMaxWidth(checkSize, MAX_CHECK_WIDTH);
 315         calcMaxWidth(arrowSize, MAX_ARROW_WIDTH);
 316         calcMaxWidth(accSize, MAX_ACC_WIDTH);
 317 
 318         if (isColumnLayout) {
 319             calcMaxWidth(iconSize, MAX_ICON_WIDTH);
 320             calcMaxWidth(textSize, MAX_TEXT_WIDTH);
 321             int curGap = gap;
 322             if ((iconSize.getMaxWidth() == 0)
 323                     || (textSize.getMaxWidth() == 0)) {
 324                 curGap = 0;
 325             }
 326             labelSize.maxWidth =
 327                     calcMaxValue(MAX_LABEL_WIDTH, iconSize.maxWidth
 328                             + textSize.maxWidth + curGap);
 329         } else {
 330             // We shouldn't use current icon and text widths
 331             // in maximal widths calculation for complex layout.
 332             iconSize.maxWidth = getParentIntProperty(MAX_ICON_WIDTH);
 333             calcMaxWidth(labelSize, MAX_LABEL_WIDTH);
 334             // If maxLabelWidth is wider
 335             // than the widest icon + the widest text + gap,
 336             // we should update the maximal text witdh
 337             int candidateTextWidth = labelSize.maxWidth - iconSize.maxWidth;
 338             if (iconSize.maxWidth > 0) {
 339                 candidateTextWidth -= gap;
 340             }
 341             textSize.maxWidth = calcMaxValue(MAX_TEXT_WIDTH, candidateTextWidth);
 342         }
 343     }
 344 
 345     protected void calcMaxWidth(RectSize rs, Object key) {
 346         rs.maxWidth = calcMaxValue(key, rs.width);
 347     }
 348 
 349     /**
 350      * Calculates and returns maximal value through specified parent component
 351      * client property.
 352      *
 353      * @param propertyName name of the property, which stores the maximal value.
 354      * @param value a value which pretends to be maximal
 355      * @return maximal value among the parent property and the value.
 356      */
 357     protected int calcMaxValue(Object propertyName, int value) {
 358         // Get maximal value from parent client property
 359         int maxValue = getParentIntProperty(propertyName);
 360         // Store new maximal width in parent client property
 361         if (value > maxValue) {
 362             if (miParent != null) {
 363                 miParent.putClientProperty(propertyName, value);
 364             }
 365             return value;
 366         } else {
 367             return maxValue;
 368         }
 369     }
 370 
 371     /**
 372      * Returns parent client property as int.
 373      * @param propertyName name of the parent property.
 374      * @return value of the property as int.
 375      */
 376     protected int getParentIntProperty(Object propertyName) {
 377         Object value = null;
 378         if (miParent != null) {
 379             value = miParent.getClientProperty(propertyName);
 380         }
 381         if ((value == null) || !(value instanceof Integer)) {
 382             value = 0;
 383         }
 384         return (Integer) value;
 385     }
 386 
 387     public static boolean isColumnLayout(boolean isLeftToRight,
 388                                          JMenuItem mi) {
 389         assert(mi != null);
 390         return isColumnLayout(isLeftToRight, mi.getHorizontalAlignment(),
 391                 mi.getHorizontalTextPosition(), mi.getVerticalTextPosition());
 392     }
 393 
 394     /**
 395      * Answers should we do column layout for a menu item or not.
 396      * We do it when a user doesn't set any alignments
 397      * and text positions manually, except the vertical alignment.
 398      */
 399     public static boolean isColumnLayout(boolean isLeftToRight,
 400                                          int horizontalAlignment,
 401                                          int horizontalTextPosition,
 402                                          int verticalTextPosition) {
 403         if (verticalTextPosition != SwingConstants.CENTER) {
 404             return false;
 405         }
 406         if (isLeftToRight) {
 407             if (horizontalAlignment != SwingConstants.LEADING
 408                     && horizontalAlignment != SwingConstants.LEFT) {
 409                 return false;
 410             }
 411             if (horizontalTextPosition != SwingConstants.TRAILING
 412                     && horizontalTextPosition != SwingConstants.RIGHT) {
 413                 return false;
 414             }
 415         } else {
 416             if (horizontalAlignment != SwingConstants.LEADING
 417                     && horizontalAlignment != SwingConstants.RIGHT) {
 418                 return false;
 419             }
 420             if (horizontalTextPosition != SwingConstants.TRAILING
 421                     && horizontalTextPosition != SwingConstants.LEFT) {
 422                 return false;
 423             }
 424         }
 425         return true;
 426     }
 427 
 428     /**
 429      * Calculates maximal text offset.
 430      * It is required for some L&Fs (ex: Vista L&F).
 431      * The offset is meaningful only for L2R column layout.
 432      *
 433      * @param viewRect the rectangle, the maximal text offset
 434      * will be calculated for.
 435      */
 436     private void calcMaxTextOffset(Rectangle viewRect) {
 437         if (!isColumnLayout || !isLeftToRight) {
 438             return;
 439         }
 440 
 441         // Calculate the current text offset
 442         int offset = viewRect.x + leadingGap + checkSize.maxWidth
 443                 + afterCheckIconGap + iconSize.maxWidth + gap;
 444         if (checkSize.maxWidth == 0) {
 445             offset -= afterCheckIconGap;
 446         }
 447         if (iconSize.maxWidth == 0) {
 448             offset -= gap;
 449         }
 450 
 451         // maximal text offset shouldn't be less than minimal text offset;
 452         if (offset < minTextOffset) {
 453             offset = minTextOffset;
 454         }
 455 
 456         // Calculate and store the maximal text offset
 457         calcMaxValue(SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET, offset);
 458     }
 459 
 460     /**
 461      * Layout icon, text, check icon, accelerator text and arrow icon
 462      * in the viewRect and return their positions.
 463      *
 464      * If horizontalAlignment, verticalTextPosition and horizontalTextPosition
 465      * are default (user doesn't set any manually) the layouting algorithm is:
 466      * Elements are layouted in the five columns:
 467      * check icon + icon + text + accelerator text + arrow icon
 468      *
 469      * In the other case elements are layouted in the four columns:
 470      * check icon + label + accelerator text + arrow icon
 471      * Label is union of icon and text.
 472      *
 473      * The order of columns can be reversed.
 474      * It depends on the menu item orientation.
 475      */
 476     public LayoutResult layoutMenuItem() {
 477         LayoutResult lr = createLayoutResult();
 478         prepareForLayout(lr);
 479 
 480         if (isColumnLayout()) {
 481             if (isLeftToRight()) {
 482                 doLTRColumnLayout(lr, getLTRColumnAlignment());
 483             } else {
 484                 doRTLColumnLayout(lr, getRTLColumnAlignment());
 485             }
 486         } else {
 487             if (isLeftToRight()) {
 488                 doLTRComplexLayout(lr, getLTRColumnAlignment());
 489             } else {
 490                 doRTLComplexLayout(lr, getRTLColumnAlignment());
 491             }
 492         }
 493 
 494         alignAccCheckAndArrowVertically(lr);
 495         return lr;
 496     }
 497 
 498     private LayoutResult createLayoutResult() {
 499         return new LayoutResult(
 500                 new Rectangle(iconSize.width, iconSize.height),
 501                 new Rectangle(textSize.width, textSize.height),
 502                 new Rectangle(accSize.width,  accSize.height),
 503                 new Rectangle(checkSize.width, checkSize.height),
 504                 new Rectangle(arrowSize.width, arrowSize.height),
 505                 new Rectangle(labelSize.width, labelSize.height)
 506         );
 507     }
 508 
 509     public ColumnAlignment getLTRColumnAlignment() {
 510         return ColumnAlignment.LEFT_ALIGNMENT;
 511     }
 512 
 513     public ColumnAlignment getRTLColumnAlignment() {
 514         return ColumnAlignment.RIGHT_ALIGNMENT;
 515     }
 516 
 517     protected void prepareForLayout(LayoutResult lr) {
 518         lr.checkRect.width = checkSize.maxWidth;
 519         lr.accRect.width = accSize.maxWidth;
 520         lr.arrowRect.width = arrowSize.maxWidth;
 521     }
 522 
 523     /**
 524      * Aligns the accelertor text and the check and arrow icons vertically
 525      * with the center of the label rect.
 526      */
 527     private void alignAccCheckAndArrowVertically(LayoutResult lr) {
 528         lr.accRect.y = (int)(lr.labelRect.y
 529                 + (float)lr.labelRect.height/2
 530                 - (float)lr.accRect.height/2);
 531         fixVerticalAlignment(lr, lr.accRect);
 532         if (useCheckAndArrow) {
 533             lr.arrowRect.y = (int)(lr.labelRect.y
 534                     + (float)lr.labelRect.height/2
 535                     - (float)lr.arrowRect.height/2);
 536             lr.checkRect.y = (int)(lr.labelRect.y
 537                     + (float)lr.labelRect.height/2
 538                     - (float)lr.checkRect.height/2);
 539             fixVerticalAlignment(lr, lr.arrowRect);
 540             fixVerticalAlignment(lr, lr.checkRect);
 541         }
 542     }
 543 
 544     /**
 545      * Fixes vertical alignment of all menu item elements if rect.y
 546      * or (rect.y + rect.height) is out of viewRect bounds
 547      */
 548     private void fixVerticalAlignment(LayoutResult lr, Rectangle r) {
 549         int delta = 0;
 550         if (r.y < viewRect.y) {
 551             delta = viewRect.y - r.y;
 552         } else if (r.y + r.height > viewRect.y + viewRect.height) {
 553             delta = viewRect.y + viewRect.height - r.y - r.height;
 554         }
 555         if (delta != 0) {
 556             lr.checkRect.y += delta;
 557             lr.iconRect.y += delta;
 558             lr.textRect.y += delta;
 559             lr.accRect.y += delta;
 560             lr.arrowRect.y += delta;
 561             lr.labelRect.y += delta;
 562         }
 563     }
 564 
 565     private void doLTRColumnLayout(LayoutResult lr, ColumnAlignment alignment) {
 566         // Set maximal width for all the five basic rects
 567         // (three other ones are already maximal)
 568         lr.iconRect.width = iconSize.maxWidth;
 569         lr.textRect.width = textSize.maxWidth;
 570 
 571         // Set X coordinates
 572         // All rects will be aligned at the left side
 573         calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.checkRect,
 574                 lr.iconRect, lr.textRect);
 575 
 576         // Tune afterCheckIconGap
 577         if (lr.checkRect.width > 0) { // there is the afterCheckIconGap
 578             lr.iconRect.x += afterCheckIconGap - gap;
 579             lr.textRect.x += afterCheckIconGap - gap;
 580         }
 581 
 582         calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap,
 583                 lr.arrowRect, lr.accRect);
 584 
 585         // Take into account minimal text offset
 586         int textOffset = lr.textRect.x - viewRect.x;
 587         if (!isTopLevelMenu && (textOffset < minTextOffset)) {
 588             lr.textRect.x += minTextOffset - textOffset;
 589         }
 590 
 591         alignRects(lr, alignment);
 592 
 593         // Set Y coordinate for text and icon.
 594         // Y coordinates for other rects
 595         // will be calculated later in layoutMenuItem.
 596         calcTextAndIconYPositions(lr);
 597 
 598         // Calculate valid X and Y coordinates for labelRect
 599         lr.setLabelRect(lr.textRect.union(lr.iconRect));
 600     }
 601 
 602     private void doLTRComplexLayout(LayoutResult lr, ColumnAlignment alignment) {
 603         lr.labelRect.width = labelSize.maxWidth;
 604 
 605         // Set X coordinates
 606         calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.checkRect,
 607                 lr.labelRect);
 608 
 609         // Tune afterCheckIconGap
 610         if (lr.checkRect.width > 0) { // there is the afterCheckIconGap
 611             lr.labelRect.x += afterCheckIconGap - gap;
 612         }
 613 
 614         calcXPositionsRTL(viewRect.x + viewRect.width,
 615                 leadingGap, gap, lr.arrowRect, lr.accRect);
 616 
 617         // Take into account minimal text offset
 618         int labelOffset = lr.labelRect.x - viewRect.x;
 619         if (!isTopLevelMenu && (labelOffset < minTextOffset)) {
 620             lr.labelRect.x += minTextOffset - labelOffset;
 621         }
 622 
 623         alignRects(lr, alignment);
 624 
 625         // Center labelRect vertically
 626         calcLabelYPosition(lr);
 627 
 628         layoutIconAndTextInLabelRect(lr);
 629     }
 630 
 631     private void doRTLColumnLayout(LayoutResult lr, ColumnAlignment alignment) {
 632         // Set maximal width for all the five basic rects
 633         // (three other ones are already maximal)
 634         lr.iconRect.width = iconSize.maxWidth;
 635         lr.textRect.width = textSize.maxWidth;
 636 
 637         // Set X coordinates
 638         calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap,
 639                 lr.checkRect, lr.iconRect, lr.textRect);
 640 
 641         // Tune the gap after check icon
 642         if (lr.checkRect.width > 0) { // there is the gap after check icon
 643             lr.iconRect.x -= afterCheckIconGap - gap;
 644             lr.textRect.x -= afterCheckIconGap - gap;
 645         }
 646 
 647         calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.arrowRect,
 648                 lr.accRect);
 649 
 650         // Take into account minimal text offset
 651         int textOffset = (viewRect.x + viewRect.width)
 652                        - (lr.textRect.x + lr.textRect.width);
 653         if (!isTopLevelMenu && (textOffset < minTextOffset)) {
 654             lr.textRect.x -= minTextOffset - textOffset;
 655         }
 656 
 657         alignRects(lr, alignment);
 658 
 659         // Set Y coordinates for text and icon.
 660         // Y coordinates for other rects
 661         // will be calculated later in layoutMenuItem.
 662         calcTextAndIconYPositions(lr);
 663 
 664         // Calculate valid X and Y coordinate for labelRect
 665         lr.setLabelRect(lr.textRect.union(lr.iconRect));
 666     }
 667 
 668     private void doRTLComplexLayout(LayoutResult lr, ColumnAlignment alignment) {
 669         lr.labelRect.width = labelSize.maxWidth;
 670 
 671         // Set X coordinates
 672         calcXPositionsRTL(viewRect.x + viewRect.width, leadingGap, gap,
 673                 lr.checkRect, lr.labelRect);
 674 
 675         // Tune the gap after check icon
 676         if (lr.checkRect.width > 0) { // there is the gap after check icon
 677             lr.labelRect.x -= afterCheckIconGap - gap;
 678         }
 679 
 680         calcXPositionsLTR(viewRect.x, leadingGap, gap, lr.arrowRect, lr.accRect);
 681 
 682         // Take into account minimal text offset
 683         int labelOffset = (viewRect.x + viewRect.width)
 684                         - (lr.labelRect.x + lr.labelRect.width);
 685         if (!isTopLevelMenu && (labelOffset < minTextOffset)) {
 686             lr.labelRect.x -= minTextOffset - labelOffset;
 687         }
 688 
 689         alignRects(lr, alignment);
 690 
 691         // Center labelRect vertically
 692         calcLabelYPosition(lr);
 693 
 694         layoutIconAndTextInLabelRect(lr);
 695     }
 696 
 697     private void alignRects(LayoutResult lr, ColumnAlignment alignment) {
 698         alignRect(lr.checkRect, alignment.getCheckAlignment(),
 699                   checkSize.getOrigWidth());
 700         alignRect(lr.iconRect, alignment.getIconAlignment(),
 701                   iconSize.getOrigWidth());
 702         alignRect(lr.textRect, alignment.getTextAlignment(),
 703                   textSize.getOrigWidth());
 704         alignRect(lr.accRect, alignment.getAccAlignment(),
 705                   accSize.getOrigWidth());
 706         alignRect(lr.arrowRect, alignment.getArrowAlignment(),
 707                   arrowSize.getOrigWidth());
 708     }
 709 
 710     private void alignRect(Rectangle rect, int alignment, int origWidth) {
 711         if (alignment == SwingConstants.RIGHT) {
 712             rect.x = rect.x + rect.width - origWidth;
 713         }
 714         rect.width = origWidth;
 715     }
 716 
 717     protected void layoutIconAndTextInLabelRect(LayoutResult lr) {
 718         lr.setTextRect(new Rectangle());
 719         lr.setIconRect(new Rectangle());
 720         SwingUtilities.layoutCompoundLabel(
 721                 mi, fm, text,icon, verticalAlignment, horizontalAlignment,
 722                 verticalTextPosition, horizontalTextPosition, lr.labelRect,
 723                 lr.iconRect, lr.textRect, gap);
 724     }
 725 
 726     private void calcXPositionsLTR(int startXPos, int leadingGap,
 727                                    int gap, Rectangle... rects) {
 728         int curXPos = startXPos + leadingGap;
 729         for (Rectangle rect : rects) {
 730             rect.x = curXPos;
 731             if (rect.width > 0) {
 732                 curXPos += rect.width + gap;
 733             }
 734         }
 735     }
 736 
 737     private void calcXPositionsRTL(int startXPos, int leadingGap,
 738                                    int gap, Rectangle... rects) {
 739         int curXPos = startXPos - leadingGap;
 740         for (Rectangle rect : rects) {
 741             rect.x = curXPos - rect.width;
 742             if (rect.width > 0) {
 743                 curXPos -= rect.width + gap;
 744             }
 745         }
 746     }
 747 
 748    /**
 749      * Sets Y coordinates of text and icon
 750      * taking into account the vertical alignment
 751      */
 752     private void calcTextAndIconYPositions(LayoutResult lr) {
 753         if (verticalAlignment == SwingUtilities.TOP) {
 754             lr.textRect.y  = (int)(viewRect.y
 755                     + (float)lr.labelRect.height/2
 756                     - (float)lr.textRect.height/2);
 757             lr.iconRect.y  = (int)(viewRect.y
 758                     + (float)lr.labelRect.height/2
 759                     - (float)lr.iconRect.height/2);
 760         } else if (verticalAlignment == SwingUtilities.CENTER) {
 761             lr.textRect.y = (int)(viewRect.y
 762                     + (float)viewRect.height/2
 763                     - (float)lr.textRect.height/2);
 764             lr.iconRect.y = (int)(viewRect.y
 765                     + (float)viewRect.height/2
 766                     - (float)lr.iconRect.height/2);
 767         }
 768         else if (verticalAlignment == SwingUtilities.BOTTOM) {
 769             lr.textRect.y = (int)(viewRect.y
 770                     + viewRect.height
 771                     - (float)lr.labelRect.height/2
 772                     - (float)lr.textRect.height/2);
 773             lr.iconRect.y = (int)(viewRect.y
 774                     + viewRect.height
 775                     - (float)lr.labelRect.height/2
 776                     - (float)lr.iconRect.height/2);
 777         }
 778     }
 779 
 780     /**
 781      * Sets labelRect Y coordinate
 782      * taking into account the vertical alignment
 783      */
 784     private void calcLabelYPosition(LayoutResult lr) {
 785         if (verticalAlignment == SwingUtilities.TOP) {
 786             lr.labelRect.y  = viewRect.y;
 787         } else if (verticalAlignment == SwingUtilities.CENTER) {
 788             lr.labelRect.y = (int)(viewRect.y
 789                     + (float)viewRect.height/2
 790                     - (float)lr.labelRect.height/2);
 791         } else if (verticalAlignment == SwingUtilities.BOTTOM) {
 792             lr.labelRect.y  = viewRect.y + viewRect.height
 793                     - lr.labelRect.height;
 794         }
 795     }
 796 
 797     /**
 798      * Returns parent of this component if it is not a top-level menu
 799      * Otherwise returns null.
 800      * @param menuItem the menu item whose parent will be returned.
 801      * @return parent of this component if it is not a top-level menu
 802      * Otherwise returns null.
 803      */
 804     public static JComponent getMenuItemParent(JMenuItem menuItem) {
 805         Container parent = menuItem.getParent();
 806         if ((parent instanceof JComponent) &&
 807              (!(menuItem instanceof JMenu) ||
 808                !((JMenu)menuItem).isTopLevelMenu())) {
 809             return (JComponent) parent;
 810         } else {
 811             return null;
 812         }
 813     }
 814 
 815     public static void clearUsedParentClientProperties(JMenuItem menuItem) {
 816         clearUsedClientProperties(getMenuItemParent(menuItem));
 817     }
 818 
 819     public static void clearUsedClientProperties(JComponent c) {
 820         if (c != null) {
 821             c.putClientProperty(MAX_ARROW_WIDTH, null);
 822             c.putClientProperty(MAX_CHECK_WIDTH, null);
 823             c.putClientProperty(MAX_ACC_WIDTH, null);
 824             c.putClientProperty(MAX_TEXT_WIDTH, null);
 825             c.putClientProperty(MAX_ICON_WIDTH, null);
 826             c.putClientProperty(MAX_LABEL_WIDTH, null);
 827             c.putClientProperty(BASICMENUITEMUI_MAX_TEXT_OFFSET, null);
 828         }
 829     }
 830 
 831     /**
 832      * Finds and returns maximal integer value in the given array.
 833      * @param values array where the search will be performed.
 834      * @return maximal vaule.
 835      */
 836     public static int max(int... values) {
 837         int maxValue = Integer.MIN_VALUE;
 838         for (int i : values) {
 839             if (i > maxValue) {
 840                 maxValue = i;
 841             }
 842         }
 843         return maxValue;
 844     }
 845 
 846     public static Rectangle createMaxRect() {
 847         return new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
 848     }
 849 
 850     public static void addMaxWidth(RectSize size, int gap, Dimension result) {
 851         if (size.maxWidth > 0) {
 852             result.width += size.maxWidth + gap;
 853         }
 854     }
 855 
 856     public static void addWidth(int width, int gap, Dimension result) {
 857         if (width > 0) {
 858             result.width += width + gap;
 859         }
 860     }
 861 
 862     public JMenuItem getMenuItem() {
 863         return mi;
 864     }
 865 
 866     public JComponent getMenuItemParent() {
 867         return miParent;
 868     }
 869 
 870     public Font getFont() {
 871         return font;
 872     }
 873 
 874     public Font getAccFont() {
 875         return accFont;
 876     }
 877 
 878     public FontMetrics getFontMetrics() {
 879         return fm;
 880     }
 881 
 882     public FontMetrics getAccFontMetrics() {
 883         return accFm;
 884     }
 885 
 886     public Icon getIcon() {
 887         return icon;
 888     }
 889 
 890     public Icon getCheckIcon() {
 891         return checkIcon;
 892     }
 893 
 894     public Icon getArrowIcon() {
 895         return arrowIcon;
 896     }
 897 
 898     public String getText() {
 899         return text;
 900     }
 901 
 902     public String getAccText() {
 903         return accText;
 904     }
 905 
 906     public boolean isColumnLayout() {
 907         return isColumnLayout;
 908     }
 909 
 910     public boolean useCheckAndArrow() {
 911         return useCheckAndArrow;
 912     }
 913 
 914     public boolean isLeftToRight() {
 915         return isLeftToRight;
 916     }
 917 
 918     public boolean isTopLevelMenu() {
 919         return isTopLevelMenu;
 920     }
 921 
 922     public View getHtmlView() {
 923         return htmlView;
 924     }
 925 
 926     public int getVerticalAlignment() {
 927         return verticalAlignment;
 928     }
 929 
 930     public int getHorizontalAlignment() {
 931         return horizontalAlignment;
 932     }
 933 
 934     public int getVerticalTextPosition() {
 935         return verticalTextPosition;
 936     }
 937 
 938     public int getHorizontalTextPosition() {
 939         return horizontalTextPosition;
 940     }
 941 
 942     public int getGap() {
 943         return gap;
 944     }
 945 
 946     public int getLeadingGap() {
 947         return leadingGap;
 948     }
 949 
 950     public int getAfterCheckIconGap() {
 951         return afterCheckIconGap;
 952     }
 953 
 954     public int getMinTextOffset() {
 955         return minTextOffset;
 956     }
 957 
 958     public Rectangle getViewRect() {
 959         return viewRect;
 960     }
 961 
 962     public RectSize getIconSize() {
 963         return iconSize;
 964     }
 965 
 966     public RectSize getTextSize() {
 967         return textSize;
 968     }
 969 
 970     public RectSize getAccSize() {
 971         return accSize;
 972     }
 973 
 974     public RectSize getCheckSize() {
 975         return checkSize;
 976     }
 977 
 978     public RectSize getArrowSize() {
 979         return arrowSize;
 980     }
 981 
 982     public RectSize getLabelSize() {
 983         return labelSize;
 984     }
 985 
 986     protected void setMenuItem(JMenuItem mi) {
 987         this.mi = mi;
 988     }
 989 
 990     protected void setMenuItemParent(JComponent miParent) {
 991         this.miParent = miParent;
 992     }
 993 
 994     protected void setFont(Font font) {
 995         this.font = font;
 996     }
 997 
 998     protected void setAccFont(Font accFont) {
 999         this.accFont = accFont;
1000     }
1001 
1002     protected void setFontMetrics(FontMetrics fm) {
1003         this.fm = fm;
1004     }
1005 
1006     protected void setAccFontMetrics(FontMetrics accFm) {
1007         this.accFm = accFm;
1008     }
1009 
1010     protected void setIcon(Icon icon) {
1011         this.icon = icon;
1012     }
1013 
1014     protected void setCheckIcon(Icon checkIcon) {
1015         this.checkIcon = checkIcon;
1016     }
1017 
1018     protected void setArrowIcon(Icon arrowIcon) {
1019         this.arrowIcon = arrowIcon;
1020     }
1021 
1022     protected void setText(String text) {
1023         this.text = text;
1024     }
1025 
1026     protected void setAccText(String accText) {
1027         this.accText = accText;
1028     }
1029 
1030     protected void setColumnLayout(boolean columnLayout) {
1031         isColumnLayout = columnLayout;
1032     }
1033 
1034     protected void setUseCheckAndArrow(boolean useCheckAndArrow) {
1035         this.useCheckAndArrow = useCheckAndArrow;
1036     }
1037 
1038     protected void setLeftToRight(boolean leftToRight) {
1039         isLeftToRight = leftToRight;
1040     }
1041 
1042     protected void setTopLevelMenu(boolean topLevelMenu) {
1043         isTopLevelMenu = topLevelMenu;
1044     }
1045 
1046     protected void setHtmlView(View htmlView) {
1047         this.htmlView = htmlView;
1048     }
1049 
1050     protected void setVerticalAlignment(int verticalAlignment) {
1051         this.verticalAlignment = verticalAlignment;
1052     }
1053 
1054     protected void setHorizontalAlignment(int horizontalAlignment) {
1055         this.horizontalAlignment = horizontalAlignment;
1056     }
1057 
1058     protected void setVerticalTextPosition(int verticalTextPosition) {
1059         this.verticalTextPosition = verticalTextPosition;
1060     }
1061 
1062     protected void setHorizontalTextPosition(int horizontalTextPosition) {
1063         this.horizontalTextPosition = horizontalTextPosition;
1064     }
1065 
1066     protected void setGap(int gap) {
1067         this.gap = gap;
1068     }
1069 
1070     protected void setLeadingGap(int leadingGap) {
1071         this.leadingGap = leadingGap;
1072     }
1073 
1074     protected void setAfterCheckIconGap(int afterCheckIconGap) {
1075         this.afterCheckIconGap = afterCheckIconGap;
1076     }
1077 
1078     protected void setMinTextOffset(int minTextOffset) {
1079         this.minTextOffset = minTextOffset;
1080     }
1081 
1082     protected void setViewRect(Rectangle viewRect) {
1083         this.viewRect = viewRect;
1084     }
1085 
1086     protected void setIconSize(RectSize iconSize) {
1087         this.iconSize = iconSize;
1088     }
1089 
1090     protected void setTextSize(RectSize textSize) {
1091         this.textSize = textSize;
1092     }
1093 
1094     protected void setAccSize(RectSize accSize) {
1095         this.accSize = accSize;
1096     }
1097 
1098     protected void setCheckSize(RectSize checkSize) {
1099         this.checkSize = checkSize;
1100     }
1101 
1102     protected void setArrowSize(RectSize arrowSize) {
1103         this.arrowSize = arrowSize;
1104     }
1105 
1106     protected void setLabelSize(RectSize labelSize) {
1107         this.labelSize = labelSize;
1108     }
1109 
1110     public int getLeftTextExtraWidth() {
1111         return leftTextExtraWidth;
1112     }
1113 
1114     /**
1115      * Returns false if the component is a JMenu and it is a top
1116      * level menu (on the menubar).
1117      */
1118     public static boolean useCheckAndArrow(JMenuItem menuItem) {
1119         boolean b = true;
1120         if ((menuItem instanceof JMenu) &&
1121                 (((JMenu) menuItem).isTopLevelMenu())) {
1122             b = false;
1123         }
1124         return b;
1125     }
1126 
1127     public static class LayoutResult {
1128         private Rectangle iconRect;
1129         private Rectangle textRect;
1130         private Rectangle accRect;
1131         private Rectangle checkRect;
1132         private Rectangle arrowRect;
1133         private Rectangle labelRect;
1134 
1135         public LayoutResult() {
1136             iconRect = new Rectangle();
1137             textRect = new Rectangle();
1138             accRect = new Rectangle();
1139             checkRect = new Rectangle();
1140             arrowRect = new Rectangle();
1141             labelRect = new Rectangle();
1142         }
1143 
1144         public LayoutResult(Rectangle iconRect, Rectangle textRect,
1145                             Rectangle accRect, Rectangle checkRect,
1146                             Rectangle arrowRect, Rectangle labelRect) {
1147             this.iconRect = iconRect;
1148             this.textRect = textRect;
1149             this.accRect = accRect;
1150             this.checkRect = checkRect;
1151             this.arrowRect = arrowRect;
1152             this.labelRect = labelRect;
1153         }
1154 
1155         public Rectangle getIconRect() {
1156             return iconRect;
1157         }
1158 
1159         public void setIconRect(Rectangle iconRect) {
1160             this.iconRect = iconRect;
1161         }
1162 
1163         public Rectangle getTextRect() {
1164             return textRect;
1165         }
1166 
1167         public void setTextRect(Rectangle textRect) {
1168             this.textRect = textRect;
1169         }
1170 
1171         public Rectangle getAccRect() {
1172             return accRect;
1173         }
1174 
1175         public void setAccRect(Rectangle accRect) {
1176             this.accRect = accRect;
1177         }
1178 
1179         public Rectangle getCheckRect() {
1180             return checkRect;
1181         }
1182 
1183         public void setCheckRect(Rectangle checkRect) {
1184             this.checkRect = checkRect;
1185         }
1186 
1187         public Rectangle getArrowRect() {
1188             return arrowRect;
1189         }
1190 
1191         public void setArrowRect(Rectangle arrowRect) {
1192             this.arrowRect = arrowRect;
1193         }
1194 
1195         public Rectangle getLabelRect() {
1196             return labelRect;
1197         }
1198 
1199         public void setLabelRect(Rectangle labelRect) {
1200             this.labelRect = labelRect;
1201         }
1202 
1203         public Map<String, Rectangle> getAllRects() {
1204             Map<String, Rectangle> result = new HashMap<String, Rectangle>();
1205             result.put("checkRect", checkRect);
1206             result.put("iconRect", iconRect);
1207             result.put("textRect", textRect);
1208             result.put("accRect", accRect);
1209             result.put("arrowRect", arrowRect);
1210             result.put("labelRect", labelRect);
1211             return result;
1212         }
1213     }
1214 
1215     public static class ColumnAlignment {
1216         private int checkAlignment;
1217         private int iconAlignment;
1218         private int textAlignment;
1219         private int accAlignment;
1220         private int arrowAlignment;
1221 
1222         public static final ColumnAlignment LEFT_ALIGNMENT =
1223                 new ColumnAlignment(
1224                         SwingConstants.LEFT,
1225                         SwingConstants.LEFT,
1226                         SwingConstants.LEFT,
1227                         SwingConstants.LEFT,
1228                         SwingConstants.LEFT
1229                 );
1230 
1231         public static final ColumnAlignment RIGHT_ALIGNMENT =
1232                 new ColumnAlignment(
1233                         SwingConstants.RIGHT,
1234                         SwingConstants.RIGHT,
1235                         SwingConstants.RIGHT,
1236                         SwingConstants.RIGHT,
1237                         SwingConstants.RIGHT
1238                 );
1239 
1240         public ColumnAlignment(int checkAlignment, int iconAlignment,
1241                                int textAlignment, int accAlignment,
1242                                int arrowAlignment) {
1243             this.checkAlignment = checkAlignment;
1244             this.iconAlignment = iconAlignment;
1245             this.textAlignment = textAlignment;
1246             this.accAlignment = accAlignment;
1247             this.arrowAlignment = arrowAlignment;
1248         }
1249 
1250         public int getCheckAlignment() {
1251             return checkAlignment;
1252         }
1253 
1254         public int getIconAlignment() {
1255             return iconAlignment;
1256         }
1257 
1258         public int getTextAlignment() {
1259             return textAlignment;
1260         }
1261 
1262         public int getAccAlignment() {
1263             return accAlignment;
1264         }
1265 
1266         public int getArrowAlignment() {
1267             return arrowAlignment;
1268         }
1269     }
1270 
1271     public static class RectSize {
1272         private int width;
1273         private int height;
1274         private int origWidth;
1275         private int maxWidth;
1276 
1277         public RectSize() {
1278         }
1279 
1280         public RectSize(int width, int height, int origWidth, int maxWidth) {
1281             this.width = width;
1282             this.height = height;
1283             this.origWidth = origWidth;
1284             this.maxWidth = maxWidth;
1285         }
1286 
1287         public int getWidth() {
1288             return width;
1289         }
1290 
1291         public int getHeight() {
1292             return height;
1293         }
1294 
1295         public int getOrigWidth() {
1296             return origWidth;
1297         }
1298 
1299         public int getMaxWidth() {
1300             return maxWidth;
1301         }
1302 
1303         public void setWidth(int width) {
1304             this.width = width;
1305         }
1306 
1307         public void setHeight(int height) {
1308             this.height = height;
1309         }
1310 
1311         public void setOrigWidth(int origWidth) {
1312             this.origWidth = origWidth;
1313         }
1314 
1315         public void setMaxWidth(int maxWidth) {
1316             this.maxWidth = maxWidth;
1317         }
1318 
1319         public String toString() {
1320             return "[w=" + width + ",h=" + height + ",ow="
1321                     + origWidth + ",mw=" + maxWidth + "]";
1322         }
1323     }
1324 }