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