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