1 /*
   2  * Copyright (c) 2002, 2019, 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 javax.swing.plaf.synth;
  27 
  28 import sun.swing.StringUIClientPropertyKey;
  29 import sun.swing.MenuItemLayoutHelper;
  30 
  31 import javax.swing.*;
  32 import javax.swing.text.View;
  33 import java.awt.*;
  34 
  35 /**
  36  * Calculates preferred size and layouts synth menu items.
  37  *
  38  * All JMenuItems (and JMenus) include enough space for the insets
  39  * plus one or more elements.  When we say "label" below, we mean
  40  * "icon and/or text."
  41  *
  42  * Cases to consider for SynthMenuItemUI (visualized here in a
  43  * LTR orientation; the RTL case would be reversed):
  44  *                   label
  45  *      check icon + label
  46  *      check icon + label + accelerator
  47  *                   label + accelerator
  48  *
  49  * Cases to consider for SynthMenuUI (again visualized here in a
  50  * LTR orientation):
  51  *                   label + arrow
  52  *
  53  * Note that in the above scenarios, accelerator and arrow icon are
  54  * mutually exclusive.  This means that if a popup menu contains a mix
  55  * of JMenus and JMenuItems, we only need to allow enough space for
  56  * max(maxAccelerator, maxArrow), and both accelerators and arrow icons
  57  * can occupy the same "column" of space in the menu.
  58  */
  59 class SynthMenuItemLayoutHelper extends MenuItemLayoutHelper {
  60 
  61     public static final StringUIClientPropertyKey MAX_ACC_OR_ARROW_WIDTH =
  62             new StringUIClientPropertyKey("maxAccOrArrowWidth");
  63 
  64     public static final ColumnAlignment LTR_ALIGNMENT_1 =
  65             new ColumnAlignment(
  66                     SwingConstants.LEFT,
  67                     SwingConstants.LEFT,
  68                     SwingConstants.LEFT,
  69                     SwingConstants.RIGHT,
  70                     SwingConstants.RIGHT
  71             );
  72     public static final ColumnAlignment LTR_ALIGNMENT_2 =
  73             new ColumnAlignment(
  74                     SwingConstants.LEFT,
  75                     SwingConstants.LEFT,
  76                     SwingConstants.LEFT,
  77                     SwingConstants.LEFT,
  78                     SwingConstants.RIGHT
  79             );
  80     public static final ColumnAlignment RTL_ALIGNMENT_1 =
  81             new ColumnAlignment(
  82                     SwingConstants.RIGHT,
  83                     SwingConstants.RIGHT,
  84                     SwingConstants.RIGHT,
  85                     SwingConstants.LEFT,
  86                     SwingConstants.LEFT
  87             );
  88     public static final ColumnAlignment RTL_ALIGNMENT_2 =
  89             new ColumnAlignment(
  90                     SwingConstants.RIGHT,
  91                     SwingConstants.RIGHT,
  92                     SwingConstants.RIGHT,
  93                     SwingConstants.RIGHT,
  94                     SwingConstants.LEFT
  95             );
  96 
  97     private SynthContext context;
  98     private SynthContext accContext;
  99     private SynthStyle style;
 100     private SynthStyle accStyle;
 101     private SynthGraphicsUtils gu;
 102     private SynthGraphicsUtils accGu;
 103     private boolean alignAcceleratorText;
 104     private int maxAccOrArrowWidth;
 105 
 106     public SynthMenuItemLayoutHelper(SynthContext context, SynthContext accContext,
 107                                      JMenuItem mi, Icon checkIcon, Icon arrowIcon,
 108                                      Rectangle viewRect, int gap, String accDelimiter,
 109                                      boolean isLeftToRight, boolean useCheckAndArrow,
 110                                      String propertyPrefix) {
 111         this.context = context;
 112         this.accContext = accContext;
 113         this.style = context.getStyle();
 114         this.accStyle = accContext.getStyle();
 115         this.gu = style.getGraphicsUtils(context);
 116         this.accGu = accStyle.getGraphicsUtils(accContext);
 117         this.alignAcceleratorText = getAlignAcceleratorText(propertyPrefix);
 118         reset(mi, checkIcon, arrowIcon, viewRect, gap, accDelimiter,
 119               isLeftToRight, style.getFont(context), accStyle.getFont(accContext),
 120               useCheckAndArrow, propertyPrefix);
 121         setLeadingGap(0);
 122     }
 123 
 124     private boolean getAlignAcceleratorText(String propertyPrefix) {
 125         return style.getBoolean(context,
 126                 propertyPrefix + ".alignAcceleratorText", true);
 127     }
 128 
 129     protected void calcWidthsAndHeights() {
 130         // iconRect
 131         if (getIcon() != null) {
 132             getIconSize().setWidth(SynthGraphicsUtils.getIconWidth(getIcon(), context));
 133             getIconSize().setHeight(SynthGraphicsUtils.getIconHeight(getIcon(), context));
 134         }
 135 
 136         // accRect
 137         if (!getAccText().isEmpty()) {
 138              getAccSize().setWidth(accGu.computeStringWidth(getAccContext(),
 139                     getAccFontMetrics().getFont(), getAccFontMetrics(),
 140                     getAccText()));
 141             getAccSize().setHeight(getAccFontMetrics().getHeight());
 142         }
 143 
 144         // textRect
 145         if (getText() == null) {
 146             setText("");
 147         } else if (!getText().isEmpty()) {
 148             if (getHtmlView() != null) {
 149                 // Text is HTML
 150                 getTextSize().setWidth(
 151                         (int) getHtmlView().getPreferredSpan(View.X_AXIS));
 152                 getTextSize().setHeight(
 153                         (int) getHtmlView().getPreferredSpan(View.Y_AXIS));
 154             } else {
 155                 // Text isn't HTML
 156                 getTextSize().setWidth(gu.computeStringWidth(context,
 157                         getFontMetrics().getFont(), getFontMetrics(),
 158                         getText()));
 159                 getTextSize().setHeight(getFontMetrics().getHeight());
 160             }
 161         }
 162 
 163         if (useCheckAndArrow()) {
 164             // checkIcon
 165             if (getCheckIcon() != null) {
 166                 getCheckSize().setWidth(
 167                         SynthGraphicsUtils.getIconWidth(getCheckIcon(), context));
 168                 getCheckSize().setHeight(
 169                         SynthGraphicsUtils.getIconHeight(getCheckIcon(), context));
 170             }
 171             // arrowRect
 172             if (getArrowIcon() != null) {
 173                 getArrowSize().setWidth(
 174                         SynthGraphicsUtils.getIconWidth(getArrowIcon(), context));
 175                 getArrowSize().setHeight(
 176                         SynthGraphicsUtils.getIconHeight(getArrowIcon(), context));
 177             }
 178         }
 179 
 180         // labelRect
 181         if (isColumnLayout()) {
 182             getLabelSize().setWidth(getIconSize().getWidth()
 183                     + getTextSize().getWidth() + getGap());
 184             getLabelSize().setHeight(MenuItemLayoutHelper.max(
 185                     getCheckSize().getHeight(),
 186                     getIconSize().getHeight(),
 187                     getTextSize().getHeight(),
 188                     getAccSize().getHeight(),
 189                     getArrowSize().getHeight()));
 190         } else {
 191             Rectangle textRect = new Rectangle();
 192             Rectangle iconRect = new Rectangle();
 193             gu.layoutText(context, getFontMetrics(), getText(), getIcon(),
 194                     getHorizontalAlignment(), getVerticalAlignment(),
 195                     getHorizontalTextPosition(), getVerticalTextPosition(),
 196                     getViewRect(), iconRect, textRect, getGap());
 197             textRect.width += getLeftTextExtraWidth();
 198             Rectangle labelRect = iconRect.union(textRect);
 199             getLabelSize().setHeight(labelRect.height);
 200             getLabelSize().setWidth(labelRect.width);
 201         }
 202     }
 203 
 204     protected void calcMaxWidths() {
 205         calcMaxWidth(getCheckSize(), MAX_CHECK_WIDTH);
 206         maxAccOrArrowWidth =
 207                 calcMaxValue(MAX_ACC_OR_ARROW_WIDTH, getArrowSize().getWidth());
 208         maxAccOrArrowWidth =
 209                 calcMaxValue(MAX_ACC_OR_ARROW_WIDTH, getAccSize().getWidth());
 210 
 211         if (isColumnLayout()) {
 212             calcMaxWidth(getIconSize(), MAX_ICON_WIDTH);
 213             calcMaxWidth(getTextSize(), MAX_TEXT_WIDTH);
 214             int curGap = getGap();
 215             if ((getIconSize().getMaxWidth() == 0)
 216                     || (getTextSize().getMaxWidth() == 0)) {
 217                 curGap = 0;
 218             }
 219             getLabelSize().setMaxWidth(
 220                     calcMaxValue(MAX_LABEL_WIDTH, getIconSize().getMaxWidth()
 221                             + getTextSize().getMaxWidth() + curGap));
 222         } else {
 223             // We shouldn't use current icon and text widths
 224             // in maximal widths calculation for complex layout.
 225             getIconSize().setMaxWidth(getParentIntProperty(
 226                     MAX_ICON_WIDTH));
 227             calcMaxWidth(getLabelSize(), MAX_LABEL_WIDTH);
 228             // If maxLabelWidth is wider
 229             // than the widest icon + the widest text + gap,
 230             // we should update the maximal text witdh
 231             int candidateTextWidth = getLabelSize().getMaxWidth() -
 232                     getIconSize().getMaxWidth();
 233             if (getIconSize().getMaxWidth() > 0) {
 234                 candidateTextWidth -= getGap();
 235             }
 236             getTextSize().setMaxWidth(calcMaxValue(
 237                     MAX_TEXT_WIDTH, candidateTextWidth));
 238         }
 239     }
 240 
 241     public SynthContext getContext() {
 242         return context;
 243     }
 244 
 245     public SynthContext getAccContext() {
 246         return accContext;
 247     }
 248 
 249     public SynthStyle getStyle() {
 250         return style;
 251     }
 252 
 253     public SynthStyle getAccStyle() {
 254         return accStyle;
 255     }
 256 
 257     public SynthGraphicsUtils getGraphicsUtils() {
 258         return gu;
 259     }
 260 
 261     public SynthGraphicsUtils getAccGraphicsUtils() {
 262         return accGu;
 263     }
 264 
 265     public boolean alignAcceleratorText() {
 266         return alignAcceleratorText;
 267     }
 268 
 269     public int getMaxAccOrArrowWidth() {
 270         return maxAccOrArrowWidth;
 271     }
 272 
 273     protected void prepareForLayout(LayoutResult lr) {
 274         lr.getCheckRect().width = getCheckSize().getMaxWidth();
 275         // An item can have an arrow or a check icon at once
 276         if (useCheckAndArrow() && (!"".equals(getAccText()))) {
 277             lr.getAccRect().width = maxAccOrArrowWidth;
 278         } else {
 279             lr.getArrowRect().width = maxAccOrArrowWidth;
 280         }
 281     }
 282 
 283     public ColumnAlignment getLTRColumnAlignment() {
 284         if (alignAcceleratorText()) {
 285             return LTR_ALIGNMENT_2;
 286         } else {
 287             return LTR_ALIGNMENT_1;
 288         }
 289     }
 290 
 291     public ColumnAlignment getRTLColumnAlignment() {
 292         if (alignAcceleratorText()) {
 293             return RTL_ALIGNMENT_2;
 294         } else {
 295             return RTL_ALIGNMENT_1;
 296         }
 297     }
 298 
 299     protected void layoutIconAndTextInLabelRect(LayoutResult lr) {
 300         lr.setTextRect(new Rectangle());
 301         lr.setIconRect(new Rectangle());
 302         gu.layoutText(context, getFontMetrics(), getText(), getIcon(),
 303                 getHorizontalAlignment(), getVerticalAlignment(),
 304                 getHorizontalTextPosition(), getVerticalTextPosition(),
 305                 lr.getLabelRect(), lr.getIconRect(), lr.getTextRect(), getGap());
 306     }
 307 }