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