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
26package sun.swing;
27
28import static sun.swing.SwingUtilities2.BASICMENUITEMUI_MAX_TEXT_OFFSET;
29
30import javax.swing.*;
31import javax.swing.plaf.basic.BasicHTML;
32import javax.swing.text.View;
33import java.awt.*;
34import java.awt.event.KeyEvent;
35import java.util.Map;
36import java.util.HashMap;
37
38/**
39 * Calculates preferred size and layouts menu items.
40 */
41public 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}
1325