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