1 /* 2 * Copyright (c) 1997, 2015, 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 package javax.swing.border; 26 27 import java.awt.Color; 28 import java.awt.Component; 29 import java.awt.Dimension; 30 import java.awt.Font; 31 import java.awt.Graphics; 32 import java.awt.Graphics2D; 33 import java.awt.Insets; 34 import java.awt.Rectangle; 35 import java.awt.geom.Path2D; 36 import java.beans.ConstructorProperties; 37 import java.beans.PropertyChangeEvent; 38 import java.beans.PropertyChangeListener; 39 import java.lang.ref.WeakReference; 40 import javax.swing.JComponent; 41 import javax.swing.JLabel; 42 import javax.swing.UIManager; 43 import javax.swing.plaf.basic.BasicHTML; 44 45 import jdk.internal.ref.CleanerFactory; 46 47 /** 48 * A class which implements an arbitrary border 49 * with the addition of a String title in a 50 * specified position and justification. 51 * <p> 52 * If the border, font, or color property values are not 53 * specified in the constructor or by invoking the appropriate 54 * set methods, the property values will be defined by the current 55 * look and feel, using the following property names in the 56 * Defaults Table: 57 * <ul> 58 * <li>"TitledBorder.border" 59 * <li>"TitledBorder.font" 60 * <li>"TitledBorder.titleColor" 61 * </ul> 62 * <p> 63 * <strong>Warning:</strong> 64 * Serialized objects of this class will not be compatible with 65 * future Swing releases. The current serialization support is 66 * appropriate for short term storage or RMI between applications running 67 * the same version of Swing. As of 1.4, support for long term storage 68 * of all JavaBeans™ 69 * has been added to the <code>java.beans</code> package. 70 * Please see {@link java.beans.XMLEncoder}. 71 * 72 * @author David Kloba 73 * @author Amy Fowler 74 */ 75 @SuppressWarnings("serial") 76 public class TitledBorder extends AbstractBorder 77 { 78 /** 79 * The title the border should display. 80 */ 81 protected String title; 82 /** 83 * The border. 84 */ 85 protected Border border; 86 /** 87 * The position for the title. 88 */ 89 protected int titlePosition; 90 /** 91 * The justification for the title. 92 */ 93 protected int titleJustification; 94 /** 95 * The font for rendering the title. 96 */ 97 protected Font titleFont; 98 /** 99 * The color of the title. 100 */ 101 protected Color titleColor; 102 103 private final JLabel label; 104 105 /** 106 * Use the default vertical orientation for the title text. 107 */ 108 public static final int DEFAULT_POSITION = 0; 109 /** Position the title above the border's top line. */ 110 public static final int ABOVE_TOP = 1; 111 /** Position the title in the middle of the border's top line. */ 112 public static final int TOP = 2; 113 /** Position the title below the border's top line. */ 114 public static final int BELOW_TOP = 3; 115 /** Position the title above the border's bottom line. */ 116 public static final int ABOVE_BOTTOM = 4; 117 /** Position the title in the middle of the border's bottom line. */ 118 public static final int BOTTOM = 5; 119 /** Position the title below the border's bottom line. */ 120 public static final int BELOW_BOTTOM = 6; 121 122 /** 123 * Use the default justification for the title text. 124 */ 125 public static final int DEFAULT_JUSTIFICATION = 0; 126 /** Position title text at the left side of the border line. */ 127 public static final int LEFT = 1; 128 /** Position title text in the center of the border line. */ 129 public static final int CENTER = 2; 130 /** Position title text at the right side of the border line. */ 131 public static final int RIGHT = 3; 132 /** Position title text at the left side of the border line 133 * for left to right orientation, at the right side of the 134 * border line for right to left orientation. 135 */ 136 public static final int LEADING = 4; 137 /** Position title text at the right side of the border line 138 * for left to right orientation, at the left side of the 139 * border line for right to left orientation. 140 */ 141 public static final int TRAILING = 5; 142 143 /** 144 * Space between the border and the component's edge 145 */ 146 protected static final int EDGE_SPACING = 2; 147 148 /** 149 * Space between the border and text 150 */ 151 protected static final int TEXT_SPACING = 2; 152 153 /** 154 * Horizontal inset of text that is left or right justified 155 */ 156 protected static final int TEXT_INSET_H = 5; 157 158 /** 159 * Creates a TitledBorder instance. 160 * 161 * @param title the title the border should display 162 */ 163 public TitledBorder(String title) { 164 this(null, title, LEADING, DEFAULT_POSITION, null, null); 165 } 166 167 /** 168 * Creates a TitledBorder instance with the specified border 169 * and an empty title. 170 * 171 * @param border the border 172 */ 173 public TitledBorder(Border border) { 174 this(border, "", LEADING, DEFAULT_POSITION, null, null); 175 } 176 177 /** 178 * Creates a TitledBorder instance with the specified border 179 * and title. 180 * 181 * @param border the border 182 * @param title the title the border should display 183 */ 184 public TitledBorder(Border border, String title) { 185 this(border, title, LEADING, DEFAULT_POSITION, null, null); 186 } 187 188 /** 189 * Creates a TitledBorder instance with the specified border, 190 * title, title-justification, and title-position. 191 * 192 * @param border the border 193 * @param title the title the border should display 194 * @param titleJustification the justification for the title 195 * @param titlePosition the position for the title 196 */ 197 public TitledBorder(Border border, 198 String title, 199 int titleJustification, 200 int titlePosition) { 201 this(border, title, titleJustification, 202 titlePosition, null, null); 203 } 204 205 /** 206 * Creates a TitledBorder instance with the specified border, 207 * title, title-justification, title-position, and title-font. 208 * 209 * @param border the border 210 * @param title the title the border should display 211 * @param titleJustification the justification for the title 212 * @param titlePosition the position for the title 213 * @param titleFont the font for rendering the title 214 */ 215 public TitledBorder(Border border, 216 String title, 217 int titleJustification, 218 int titlePosition, 219 Font titleFont) { 220 this(border, title, titleJustification, 221 titlePosition, titleFont, null); 222 } 223 224 /** 225 * Creates a TitledBorder instance with the specified border, 226 * title, title-justification, title-position, title-font, and 227 * title-color. 228 * 229 * @param border the border 230 * @param title the title the border should display 231 * @param titleJustification the justification for the title 232 * @param titlePosition the position for the title 233 * @param titleFont the font of the title 234 * @param titleColor the color of the title 235 */ 236 @ConstructorProperties({"border", "title", "titleJustification", "titlePosition", "titleFont", "titleColor"}) 237 public TitledBorder(Border border, 238 String title, 239 int titleJustification, 240 int titlePosition, 241 Font titleFont, 242 Color titleColor) { 243 this.title = title; 244 this.border = border; 245 this.titleFont = titleFont; 246 this.titleColor = titleColor; 247 248 setTitleJustification(titleJustification); 249 setTitlePosition(titlePosition); 250 251 this.label = new JLabel(); 252 this.label.setOpaque(false); 253 this.label.putClientProperty(BasicHTML.propertyKey, null); 254 installPropertyChangeListeners(); 255 } 256 257 /** 258 * Paints the border for the specified component with the 259 * specified position and size. 260 * @param c the component for which this border is being painted 261 * @param g the paint graphics 262 * @param x the x position of the painted border 263 * @param y the y position of the painted border 264 * @param width the width of the painted border 265 * @param height the height of the painted border 266 */ 267 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 268 Border border = getBorder(); 269 String title = getTitle(); 270 if ((title != null) && !title.isEmpty()) { 271 int edge = (border instanceof TitledBorder) ? 0 : EDGE_SPACING; 272 JLabel label = getLabel(c); 273 Dimension size = label.getPreferredSize(); 274 Insets insets = getBorderInsets(border, c, new Insets(0, 0, 0, 0)); 275 276 int borderX = x + edge; 277 int borderY = y + edge; 278 int borderW = width - edge - edge; 279 int borderH = height - edge - edge; 280 281 int labelY = y; 282 int labelH = size.height; 283 int position = getPosition(); 284 switch (position) { 285 case ABOVE_TOP: 286 insets.left = 0; 287 insets.right = 0; 288 borderY += labelH - edge; 289 borderH -= labelH - edge; 290 break; 291 case TOP: 292 insets.top = edge + insets.top/2 - labelH/2; 293 if (insets.top < edge) { 294 borderY -= insets.top; 295 borderH += insets.top; 296 } 297 else { 298 labelY += insets.top; 299 } 300 break; 301 case BELOW_TOP: 302 labelY += insets.top + edge; 303 break; 304 case ABOVE_BOTTOM: 305 labelY += height - labelH - insets.bottom - edge; 306 break; 307 case BOTTOM: 308 labelY += height - labelH; 309 insets.bottom = edge + (insets.bottom - labelH) / 2; 310 if (insets.bottom < edge) { 311 borderH += insets.bottom; 312 } 313 else { 314 labelY -= insets.bottom; 315 } 316 break; 317 case BELOW_BOTTOM: 318 insets.left = 0; 319 insets.right = 0; 320 labelY += height - labelH; 321 borderH -= labelH - edge; 322 break; 323 } 324 insets.left += edge + TEXT_INSET_H; 325 insets.right += edge + TEXT_INSET_H; 326 327 int labelX = x; 328 int labelW = width - insets.left - insets.right; 329 if (labelW > size.width) { 330 labelW = size.width; 331 } 332 switch (getJustification(c)) { 333 case LEFT: 334 labelX += insets.left; 335 break; 336 case RIGHT: 337 labelX += width - insets.right - labelW; 338 break; 339 case CENTER: 340 labelX += (width - labelW) / 2; 341 break; 342 } 343 344 if (border != null) { 345 if ((position != TOP) && (position != BOTTOM)) { 346 border.paintBorder(c, g, borderX, borderY, borderW, borderH); 347 } 348 else { 349 Graphics g2 = g.create(); 350 if (g2 instanceof Graphics2D) { 351 Graphics2D g2d = (Graphics2D) g2; 352 Path2D path = new Path2D.Float(); 353 path.append(new Rectangle(borderX, borderY, borderW, labelY - borderY), false); 354 path.append(new Rectangle(borderX, labelY, labelX - borderX - TEXT_SPACING, labelH), false); 355 path.append(new Rectangle(labelX + labelW + TEXT_SPACING, labelY, borderX - labelX + borderW - labelW - TEXT_SPACING, labelH), false); 356 path.append(new Rectangle(borderX, labelY + labelH, borderW, borderY - labelY + borderH - labelH), false); 357 g2d.clip(path); 358 } 359 border.paintBorder(c, g2, borderX, borderY, borderW, borderH); 360 g2.dispose(); 361 } 362 } 363 g.translate(labelX, labelY); 364 label.setSize(labelW, labelH); 365 label.paint(g); 366 g.translate(-labelX, -labelY); 367 } 368 else if (border != null) { 369 border.paintBorder(c, g, x, y, width, height); 370 } 371 } 372 373 /** 374 * Reinitialize the insets parameter with this Border's current Insets. 375 * @param c the component for which this border insets value applies 376 * @param insets the object to be reinitialized 377 */ 378 public Insets getBorderInsets(Component c, Insets insets) { 379 Border border = getBorder(); 380 insets = getBorderInsets(border, c, insets); 381 382 String title = getTitle(); 383 if ((title != null) && !title.isEmpty()) { 384 int edge = (border instanceof TitledBorder) ? 0 : EDGE_SPACING; 385 JLabel label = getLabel(c); 386 Dimension size = label.getPreferredSize(); 387 388 switch (getPosition()) { 389 case ABOVE_TOP: 390 insets.top += size.height - edge; 391 break; 392 case TOP: { 393 if (insets.top < size.height) { 394 insets.top = size.height - edge; 395 } 396 break; 397 } 398 case BELOW_TOP: 399 insets.top += size.height; 400 break; 401 case ABOVE_BOTTOM: 402 insets.bottom += size.height; 403 break; 404 case BOTTOM: { 405 if (insets.bottom < size.height) { 406 insets.bottom = size.height - edge; 407 } 408 break; 409 } 410 case BELOW_BOTTOM: 411 insets.bottom += size.height - edge; 412 break; 413 } 414 insets.top += edge + TEXT_SPACING; 415 insets.left += edge + TEXT_SPACING; 416 insets.right += edge + TEXT_SPACING; 417 insets.bottom += edge + TEXT_SPACING; 418 } 419 return insets; 420 } 421 422 /** 423 * Returns whether or not the border is opaque. 424 */ 425 public boolean isBorderOpaque() { 426 return false; 427 } 428 429 /** 430 * Returns the title of the titled border. 431 * 432 * @return the title of the titled border 433 */ 434 public String getTitle() { 435 return title; 436 } 437 438 /** 439 * Returns the border of the titled border. 440 * 441 * @return the border of the titled border 442 */ 443 public Border getBorder() { 444 return border != null 445 ? border 446 : UIManager.getBorder("TitledBorder.border"); 447 } 448 449 /** 450 * Returns the title-position of the titled border. 451 * 452 * @return the title-position of the titled border 453 */ 454 public int getTitlePosition() { 455 return titlePosition; 456 } 457 458 /** 459 * Returns the title-justification of the titled border. 460 * 461 * @return the title-justification of the titled border 462 */ 463 public int getTitleJustification() { 464 return titleJustification; 465 } 466 467 /** 468 * Returns the title-font of the titled border. 469 * 470 * @return the title-font of the titled border 471 */ 472 public Font getTitleFont() { 473 return titleFont == null ? UIManager.getFont("TitledBorder.font") : titleFont; 474 } 475 476 /** 477 * Returns the title-color of the titled border. 478 * 479 * @return the title-color of the titled border 480 */ 481 public Color getTitleColor() { 482 return titleColor == null ? UIManager.getColor("TitledBorder.titleColor") : titleColor; 483 } 484 485 486 // REMIND(aim): remove all or some of these set methods? 487 488 /** 489 * Sets the title of the titled border. 490 * @param title the title for the border 491 */ 492 public void setTitle(String title) { 493 this.title = title; 494 } 495 496 /** 497 * Sets the border of the titled border. 498 * @param border the border 499 */ 500 public void setBorder(Border border) { 501 this.border = border; 502 } 503 504 /** 505 * Sets the title-position of the titled border. 506 * @param titlePosition the position for the border 507 */ 508 public void setTitlePosition(int titlePosition) { 509 switch (titlePosition) { 510 case ABOVE_TOP: 511 case TOP: 512 case BELOW_TOP: 513 case ABOVE_BOTTOM: 514 case BOTTOM: 515 case BELOW_BOTTOM: 516 case DEFAULT_POSITION: 517 this.titlePosition = titlePosition; 518 break; 519 default: 520 throw new IllegalArgumentException(titlePosition + 521 " is not a valid title position."); 522 } 523 } 524 525 /** 526 * Sets the title-justification of the titled border. 527 * @param titleJustification the justification for the border 528 */ 529 public void setTitleJustification(int titleJustification) { 530 switch (titleJustification) { 531 case DEFAULT_JUSTIFICATION: 532 case LEFT: 533 case CENTER: 534 case RIGHT: 535 case LEADING: 536 case TRAILING: 537 this.titleJustification = titleJustification; 538 break; 539 default: 540 throw new IllegalArgumentException(titleJustification + 541 " is not a valid title justification."); 542 } 543 } 544 545 /** 546 * Sets the title-font of the titled border. 547 * @param titleFont the font for the border title 548 */ 549 public void setTitleFont(Font titleFont) { 550 this.titleFont = titleFont; 551 } 552 553 /** 554 * Sets the title-color of the titled border. 555 * @param titleColor the color for the border title 556 */ 557 public void setTitleColor(Color titleColor) { 558 this.titleColor = titleColor; 559 } 560 561 /** 562 * Returns the minimum dimensions this border requires 563 * in order to fully display the border and title. 564 * @param c the component where this border will be drawn 565 * @return the {@code Dimension} object 566 */ 567 public Dimension getMinimumSize(Component c) { 568 Insets insets = getBorderInsets(c); 569 Dimension minSize = new Dimension(insets.right+insets.left, 570 insets.top+insets.bottom); 571 String title = getTitle(); 572 if ((title != null) && !title.isEmpty()) { 573 JLabel label = getLabel(c); 574 Dimension size = label.getPreferredSize(); 575 576 int position = getPosition(); 577 if ((position != ABOVE_TOP) && (position != BELOW_BOTTOM)) { 578 minSize.width += size.width; 579 } 580 else if (minSize.width < size.width) { 581 minSize.width += size.width; 582 } 583 } 584 return minSize; 585 } 586 587 /** 588 * Returns the baseline. 589 * 590 * @throws NullPointerException {@inheritDoc} 591 * @throws IllegalArgumentException {@inheritDoc} 592 * @see javax.swing.JComponent#getBaseline(int, int) 593 * @since 1.6 594 */ 595 public int getBaseline(Component c, int width, int height) { 596 if (c == null) { 597 throw new NullPointerException("Must supply non-null component"); 598 } 599 if (width < 0) { 600 throw new IllegalArgumentException("Width must be >= 0"); 601 } 602 if (height < 0) { 603 throw new IllegalArgumentException("Height must be >= 0"); 604 } 605 Border border = getBorder(); 606 String title = getTitle(); 607 if ((title != null) && !title.isEmpty()) { 608 int edge = (border instanceof TitledBorder) ? 0 : EDGE_SPACING; 609 JLabel label = getLabel(c); 610 Dimension size = label.getPreferredSize(); 611 Insets insets = getBorderInsets(border, c, new Insets(0, 0, 0, 0)); 612 613 int baseline = label.getBaseline(size.width, size.height); 614 switch (getPosition()) { 615 case ABOVE_TOP: 616 return baseline; 617 case TOP: 618 insets.top = edge + (insets.top - size.height) / 2; 619 return (insets.top < edge) 620 ? baseline 621 : baseline + insets.top; 622 case BELOW_TOP: 623 return baseline + insets.top + edge; 624 case ABOVE_BOTTOM: 625 return baseline + height - size.height - insets.bottom - edge; 626 case BOTTOM: 627 insets.bottom = edge + (insets.bottom - size.height) / 2; 628 return (insets.bottom < edge) 629 ? baseline + height - size.height 630 : baseline + height - size.height + insets.bottom; 631 case BELOW_BOTTOM: 632 return baseline + height - size.height; 633 } 634 } 635 return -1; 636 } 637 638 /** 639 * Returns an enum indicating how the baseline of the border 640 * changes as the size changes. 641 * 642 * @throws NullPointerException {@inheritDoc} 643 * @see javax.swing.JComponent#getBaseline(int, int) 644 * @since 1.6 645 */ 646 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 647 Component c) { 648 super.getBaselineResizeBehavior(c); 649 switch (getPosition()) { 650 case TitledBorder.ABOVE_TOP: 651 case TitledBorder.TOP: 652 case TitledBorder.BELOW_TOP: 653 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 654 case TitledBorder.ABOVE_BOTTOM: 655 case TitledBorder.BOTTOM: 656 case TitledBorder.BELOW_BOTTOM: 657 return JComponent.BaselineResizeBehavior.CONSTANT_DESCENT; 658 } 659 return Component.BaselineResizeBehavior.OTHER; 660 } 661 662 private int getPosition() { 663 int position = getTitlePosition(); 664 if (position != DEFAULT_POSITION) { 665 return position; 666 } 667 Object value = UIManager.get("TitledBorder.position"); 668 if (value instanceof Integer) { 669 int i = (Integer) value; 670 if ((0 < i) && (i <= 6)) { 671 return i; 672 } 673 } 674 else if (value instanceof String) { 675 String s = (String) value; 676 if (s.equalsIgnoreCase("ABOVE_TOP")) { 677 return ABOVE_TOP; 678 } 679 if (s.equalsIgnoreCase("TOP")) { 680 return TOP; 681 } 682 if (s.equalsIgnoreCase("BELOW_TOP")) { 683 return BELOW_TOP; 684 } 685 if (s.equalsIgnoreCase("ABOVE_BOTTOM")) { 686 return ABOVE_BOTTOM; 687 } 688 if (s.equalsIgnoreCase("BOTTOM")) { 689 return BOTTOM; 690 } 691 if (s.equalsIgnoreCase("BELOW_BOTTOM")) { 692 return BELOW_BOTTOM; 693 } 694 } 695 return TOP; 696 } 697 698 private int getJustification(Component c) { 699 int justification = getTitleJustification(); 700 if ((justification == LEADING) || (justification == DEFAULT_JUSTIFICATION)) { 701 return c.getComponentOrientation().isLeftToRight() ? LEFT : RIGHT; 702 } 703 if (justification == TRAILING) { 704 return c.getComponentOrientation().isLeftToRight() ? RIGHT : LEFT; 705 } 706 return justification; 707 } 708 709 /** 710 * Returns default font of the titled border. 711 * @return default font of the titled border 712 * @param c the component 713 */ 714 protected Font getFont(Component c) { 715 Font font = getTitleFont(); 716 if (font != null) { 717 return font; 718 } 719 if (c != null) { 720 font = c.getFont(); 721 if (font != null) { 722 return font; 723 } 724 } 725 return new Font(Font.DIALOG, Font.PLAIN, 12); 726 } 727 728 private Color getColor(Component c) { 729 Color color = getTitleColor(); 730 if (color != null) { 731 return color; 732 } 733 return (c != null) 734 ? c.getForeground() 735 : null; 736 } 737 738 private JLabel getLabel(Component c) { 739 this.label.setText(getTitle()); 740 this.label.setFont(getFont(c)); 741 this.label.setForeground(getColor(c)); 742 this.label.setComponentOrientation(c.getComponentOrientation()); 743 this.label.setEnabled(c.isEnabled()); 744 return this.label; 745 } 746 747 private static Insets getBorderInsets(Border border, Component c, Insets insets) { 748 if (border == null) { 749 insets.set(0, 0, 0, 0); 750 } 751 else if (border instanceof AbstractBorder) { 752 AbstractBorder ab = (AbstractBorder) border; 753 insets = ab.getBorderInsets(c, insets); 754 } 755 else { 756 Insets i = border.getBorderInsets(c); 757 insets.set(i.top, i.left, i.bottom, i.right); 758 } 759 return insets; 760 } 761 762 private void installPropertyChangeListeners() { 763 final WeakReference<TitledBorder> weakReference = new WeakReference<TitledBorder>(this); 764 final PropertyChangeListener listener = evt -> { 765 TitledBorder tb = weakReference.get(); 766 String prop = evt.getPropertyName(); 767 if (tb != null && ("lookAndFeel".equals(prop) || "LabelUI".equals(prop))) { 768 tb.label.updateUI(); 769 } 770 }; 771 772 UIManager.addPropertyChangeListener(listener); 773 UIManager.getDefaults().addPropertyChangeListener(listener); 774 CleanerFactory.cleaner().register(this, () -> { 775 UIManager.removePropertyChangeListener(listener); 776 UIManager.getDefaults().removePropertyChangeListener(listener); 777 }); 778 } 779 }