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 }