1. /*
  2. * @(#)TitledBorder.java 1.40 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.border;
  8. import com.sun.java.swing.SwingUtilities2;
  9. import java.awt.Graphics;
  10. import java.awt.Insets;
  11. import java.awt.Rectangle;
  12. import java.awt.Color;
  13. import java.awt.Font;
  14. import java.awt.FontMetrics;
  15. import java.awt.Point;
  16. import java.awt.Toolkit;
  17. import java.awt.Component;
  18. import java.awt.Dimension;
  19. import javax.swing.JComponent;
  20. import javax.swing.UIManager;
  21. /**
  22. * A class which implements an arbitrary border
  23. * with the addition of a String title in a
  24. * specified position and justification.
  25. * <p>
  26. * If the border, font, or color property values are not
  27. * specified in the constuctor or by invoking the appropriate
  28. * set methods, the property values will be defined by the current
  29. * look and feel, using the following property names in the
  30. * Defaults Table:
  31. * <ul>
  32. * <li>"TitledBorder.border"
  33. * <li>"TitledBorder.font"
  34. * <li>"TitledBorder.titleColor"
  35. * </ul>
  36. * <p>
  37. * <strong>Warning:</strong>
  38. * Serialized objects of this class will not be compatible with
  39. * future Swing releases. The current serialization support is
  40. * appropriate for short term storage or RMI between applications running
  41. * the same version of Swing. As of 1.4, support for long term storage
  42. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  43. * has been added to the <code>java.beans</code> package.
  44. * Please see {@link java.beans.XMLEncoder}.
  45. *
  46. * @version 1.40 12/19/03
  47. * @author David Kloba
  48. * @author Amy Fowler
  49. */
  50. public class TitledBorder extends AbstractBorder
  51. {
  52. protected String title;
  53. protected Border border;
  54. protected int titlePosition;
  55. protected int titleJustification;
  56. protected Font titleFont;
  57. protected Color titleColor;
  58. private Point textLoc = new Point();
  59. /**
  60. * Use the default vertical orientation for the title text.
  61. */
  62. static public final int DEFAULT_POSITION = 0;
  63. /** Position the title above the border's top line. */
  64. static public final int ABOVE_TOP = 1;
  65. /** Position the title in the middle of the border's top line. */
  66. static public final int TOP = 2;
  67. /** Position the title below the border's top line. */
  68. static public final int BELOW_TOP = 3;
  69. /** Position the title above the border's bottom line. */
  70. static public final int ABOVE_BOTTOM = 4;
  71. /** Position the title in the middle of the border's bottom line. */
  72. static public final int BOTTOM = 5;
  73. /** Position the title below the border's bottom line. */
  74. static public final int BELOW_BOTTOM = 6;
  75. /**
  76. * Use the default justification for the title text.
  77. */
  78. static public final int DEFAULT_JUSTIFICATION = 0;
  79. /** Position title text at the left side of the border line. */
  80. static public final int LEFT = 1;
  81. /** Position title text in the center of the border line. */
  82. static public final int CENTER = 2;
  83. /** Position title text at the right side of the border line. */
  84. static public final int RIGHT = 3;
  85. /** Position title text at the left side of the border line
  86. * for left to right orientation, at the right side of the
  87. * border line for right to left orientation.
  88. */
  89. static public final int LEADING = 4;
  90. /** Position title text at the right side of the border line
  91. * for left to right orientation, at the left side of the
  92. * border line for right to left orientation.
  93. */
  94. static public final int TRAILING = 5;
  95. // Space between the border and the component's edge
  96. static protected final int EDGE_SPACING = 2;
  97. // Space between the border and text
  98. static protected final int TEXT_SPACING = 2;
  99. // Horizontal inset of text that is left or right justified
  100. static protected final int TEXT_INSET_H = 5;
  101. /**
  102. * Creates a TitledBorder instance.
  103. *
  104. * @param title the title the border should display
  105. */
  106. public TitledBorder(String title) {
  107. this(null, title, LEADING, TOP, null, null);
  108. }
  109. /**
  110. * Creates a TitledBorder instance with the specified border
  111. * and an empty title.
  112. *
  113. * @param border the border
  114. */
  115. public TitledBorder(Border border) {
  116. this(border, "", LEADING, TOP, null, null);
  117. }
  118. /**
  119. * Creates a TitledBorder instance with the specified border
  120. * and title.
  121. *
  122. * @param border the border
  123. * @param title the title the border should display
  124. */
  125. public TitledBorder(Border border, String title) {
  126. this(border, title, LEADING, TOP, null, null);
  127. }
  128. /**
  129. * Creates a TitledBorder instance with the specified border,
  130. * title, title-justification, and title-position.
  131. *
  132. * @param border the border
  133. * @param title the title the border should display
  134. * @param titleJustification the justification for the title
  135. * @param titlePosition the position for the title
  136. */
  137. public TitledBorder(Border border,
  138. String title,
  139. int titleJustification,
  140. int titlePosition) {
  141. this(border, title, titleJustification,
  142. titlePosition, null, null);
  143. }
  144. /**
  145. * Creates a TitledBorder instance with the specified border,
  146. * title, title-justification, title-position, and title-font.
  147. *
  148. * @param border the border
  149. * @param title the title the border should display
  150. * @param titleJustification the justification for the title
  151. * @param titlePosition the position for the title
  152. * @param titleFont the font for rendering the title
  153. */
  154. public TitledBorder(Border border,
  155. String title,
  156. int titleJustification,
  157. int titlePosition,
  158. Font titleFont) {
  159. this(border, title, titleJustification,
  160. titlePosition, titleFont, null);
  161. }
  162. /**
  163. * Creates a TitledBorder instance with the specified border,
  164. * title, title-justification, title-position, title-font, and
  165. * title-color.
  166. *
  167. * @param border the border
  168. * @param title the title the border should display
  169. * @param titleJustification the justification for the title
  170. * @param titlePosition the position for the title
  171. * @param titleFont the font of the title
  172. * @param titleColor the color of the title
  173. */
  174. public TitledBorder(Border border,
  175. String title,
  176. int titleJustification,
  177. int titlePosition,
  178. Font titleFont,
  179. Color titleColor) {
  180. this.title = title;
  181. this.border = border;
  182. this.titleFont = titleFont;
  183. this.titleColor = titleColor;
  184. setTitleJustification(titleJustification);
  185. setTitlePosition(titlePosition);
  186. }
  187. /**
  188. * Paints the border for the specified component with the
  189. * specified position and size.
  190. * @param c the component for which this border is being painted
  191. * @param g the paint graphics
  192. * @param x the x position of the painted border
  193. * @param y the y position of the painted border
  194. * @param width the width of the painted border
  195. * @param height the height of the painted border
  196. */
  197. public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
  198. Border border = getBorder();
  199. if (getTitle() == null || getTitle().equals("")) {
  200. if (border != null) {
  201. border.paintBorder(c, g, x, y, width, height);
  202. }
  203. return;
  204. }
  205. Rectangle grooveRect = new Rectangle(x + EDGE_SPACING, y + EDGE_SPACING,
  206. width - (EDGE_SPACING * 2),
  207. height - (EDGE_SPACING * 2));
  208. Font font = g.getFont();
  209. Color color = g.getColor();
  210. g.setFont(getFont(c));
  211. JComponent jc = (c instanceof JComponent) ? (JComponent)c : null;
  212. FontMetrics fm = SwingUtilities2.getFontMetrics(jc, g);
  213. int fontHeight = fm.getHeight();
  214. int descent = fm.getDescent();
  215. int ascent = fm.getAscent();
  216. int diff;
  217. int stringWidth = SwingUtilities2.stringWidth(jc, fm,
  218. getTitle());
  219. Insets insets;
  220. if (border != null) {
  221. insets = border.getBorderInsets(c);
  222. } else {
  223. insets = new Insets(0, 0, 0, 0);
  224. }
  225. int titlePos = getTitlePosition();
  226. switch (titlePos) {
  227. case ABOVE_TOP:
  228. diff = ascent + descent + (Math.max(EDGE_SPACING,
  229. TEXT_SPACING*2) - EDGE_SPACING);
  230. grooveRect.y += diff;
  231. grooveRect.height -= diff;
  232. textLoc.y = grooveRect.y - (descent + TEXT_SPACING);
  233. break;
  234. case TOP:
  235. case DEFAULT_POSITION:
  236. diff = Math.max(0, ((ascent2) + TEXT_SPACING) - EDGE_SPACING);
  237. grooveRect.y += diff;
  238. grooveRect.height -= diff;
  239. textLoc.y = (grooveRect.y - descent) +
  240. (insets.top + ascent + descent)/2;
  241. break;
  242. case BELOW_TOP:
  243. textLoc.y = grooveRect.y + insets.top + ascent + TEXT_SPACING;
  244. break;
  245. case ABOVE_BOTTOM:
  246. textLoc.y = (grooveRect.y + grooveRect.height) -
  247. (insets.bottom + descent + TEXT_SPACING);
  248. break;
  249. case BOTTOM:
  250. grooveRect.height -= fontHeight2;
  251. textLoc.y = ((grooveRect.y + grooveRect.height) - descent) +
  252. ((ascent + descent) - insets.bottom)/2;
  253. break;
  254. case BELOW_BOTTOM:
  255. grooveRect.height -= fontHeight;
  256. textLoc.y = grooveRect.y + grooveRect.height + ascent +
  257. TEXT_SPACING;
  258. break;
  259. }
  260. int justification = getTitleJustification();
  261. if(isLeftToRight(c)) {
  262. if(justification==LEADING ||
  263. justification==DEFAULT_JUSTIFICATION) {
  264. justification = LEFT;
  265. }
  266. else if(justification==TRAILING) {
  267. justification = RIGHT;
  268. }
  269. }
  270. else {
  271. if(justification==LEADING ||
  272. justification==DEFAULT_JUSTIFICATION) {
  273. justification = RIGHT;
  274. }
  275. else if(justification==TRAILING) {
  276. justification = LEFT;
  277. }
  278. }
  279. switch (justification) {
  280. case LEFT:
  281. textLoc.x = grooveRect.x + TEXT_INSET_H + insets.left;
  282. break;
  283. case RIGHT:
  284. textLoc.x = (grooveRect.x + grooveRect.width) -
  285. (stringWidth + TEXT_INSET_H + insets.right);
  286. break;
  287. case CENTER:
  288. textLoc.x = grooveRect.x +
  289. ((grooveRect.width - stringWidth) / 2);
  290. break;
  291. }
  292. // If title is positioned in middle of border AND its fontsize
  293. // is greater than the border's thickness, we'll need to paint
  294. // the border in sections to leave space for the component's background
  295. // to show through the title.
  296. //
  297. if (border != null) {
  298. if (((titlePos == TOP || titlePos == DEFAULT_POSITION) &&
  299. (grooveRect.y > textLoc.y - ascent)) ||
  300. (titlePos == BOTTOM &&
  301. (grooveRect.y + grooveRect.height < textLoc.y + descent))) {
  302. Rectangle clipRect = new Rectangle();
  303. // save original clip
  304. Rectangle saveClip = g.getClipBounds();
  305. // paint strip left of text
  306. clipRect.setBounds(saveClip);
  307. if (computeIntersection(clipRect, x, y, textLoc.x-1-x, height)) {
  308. g.setClip(clipRect);
  309. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  310. grooveRect.width, grooveRect.height);
  311. }
  312. // paint strip right of text
  313. clipRect.setBounds(saveClip);
  314. if (computeIntersection(clipRect, textLoc.x+stringWidth+1, y,
  315. x+width-(textLoc.x+stringWidth+1), height)) {
  316. g.setClip(clipRect);
  317. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  318. grooveRect.width, grooveRect.height);
  319. }
  320. if (titlePos == TOP || titlePos == DEFAULT_POSITION) {
  321. // paint strip below text
  322. clipRect.setBounds(saveClip);
  323. if (computeIntersection(clipRect, textLoc.x-1, textLoc.y+descent,
  324. stringWidth+2, y+height-textLoc.y-descent)) {
  325. g.setClip(clipRect);
  326. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  327. grooveRect.width, grooveRect.height);
  328. }
  329. } else { // titlePos == BOTTOM
  330. // paint strip above text
  331. clipRect.setBounds(saveClip);
  332. if (computeIntersection(clipRect, textLoc.x-1, y,
  333. stringWidth+2, textLoc.y - ascent - y)) {
  334. g.setClip(clipRect);
  335. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  336. grooveRect.width, grooveRect.height);
  337. }
  338. }
  339. // restore clip
  340. g.setClip(saveClip);
  341. } else {
  342. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  343. grooveRect.width, grooveRect.height);
  344. }
  345. }
  346. g.setColor(getTitleColor());
  347. SwingUtilities2.drawString(jc, g, getTitle(), textLoc.x, textLoc.y);
  348. g.setFont(font);
  349. g.setColor(color);
  350. }
  351. /**
  352. * Returns the insets of the border.
  353. * @param c the component for which this border insets value applies
  354. */
  355. public Insets getBorderInsets(Component c) {
  356. return getBorderInsets(c, new Insets(0, 0, 0, 0));
  357. }
  358. /**
  359. * Reinitialize the insets parameter with this Border's current Insets.
  360. * @param c the component for which this border insets value applies
  361. * @param insets the object to be reinitialized
  362. */
  363. public Insets getBorderInsets(Component c, Insets insets) {
  364. FontMetrics fm;
  365. int descent = 0;
  366. int ascent = 16;
  367. int height = 16;
  368. Border border = getBorder();
  369. if (border != null) {
  370. if (border instanceof AbstractBorder) {
  371. ((AbstractBorder)border).getBorderInsets(c, insets);
  372. } else {
  373. // Can't reuse border insets because the Border interface
  374. // can't be enhanced.
  375. Insets i = border.getBorderInsets(c);
  376. insets.top = i.top;
  377. insets.right = i.right;
  378. insets.bottom = i.bottom;
  379. insets.left = i.left;
  380. }
  381. } else {
  382. insets.left = insets.top = insets.right = insets.bottom = 0;
  383. }
  384. insets.left += EDGE_SPACING + TEXT_SPACING;
  385. insets.right += EDGE_SPACING + TEXT_SPACING;
  386. insets.top += EDGE_SPACING + TEXT_SPACING;
  387. insets.bottom += EDGE_SPACING + TEXT_SPACING;
  388. if(c == null || getTitle() == null || getTitle().equals("")) {
  389. return insets;
  390. }
  391. Font font = getFont(c);
  392. fm = c.getFontMetrics(font);
  393. if(fm != null) {
  394. descent = fm.getDescent();
  395. ascent = fm.getAscent();
  396. height = fm.getHeight();
  397. }
  398. switch (getTitlePosition()) {
  399. case ABOVE_TOP:
  400. insets.top += ascent + descent
  401. + (Math.max(EDGE_SPACING, TEXT_SPACING*2)
  402. - EDGE_SPACING);
  403. break;
  404. case TOP:
  405. case DEFAULT_POSITION:
  406. insets.top += ascent + descent;
  407. break;
  408. case BELOW_TOP:
  409. insets.top += ascent + descent + TEXT_SPACING;
  410. break;
  411. case ABOVE_BOTTOM:
  412. insets.bottom += ascent + descent + TEXT_SPACING;
  413. break;
  414. case BOTTOM:
  415. insets.bottom += ascent + descent;
  416. break;
  417. case BELOW_BOTTOM:
  418. insets.bottom += height;
  419. break;
  420. }
  421. return insets;
  422. }
  423. /**
  424. * Returns whether or not the border is opaque.
  425. */
  426. public boolean isBorderOpaque() { return false; }
  427. /**
  428. * Returns the title of the titled border.
  429. */
  430. public String getTitle() { return title; }
  431. /**
  432. * Returns the border of the titled border.
  433. */
  434. public Border getBorder() {
  435. Border b = border;
  436. if (b == null)
  437. b = UIManager.getBorder("TitledBorder.border");
  438. return b;
  439. }
  440. /**
  441. * Returns the title-position of the titled border.
  442. */
  443. public int getTitlePosition() { return titlePosition; }
  444. /**
  445. * Returns the title-justification of the titled border.
  446. */
  447. public int getTitleJustification() { return titleJustification; }
  448. /**
  449. * Returns the title-font of the titled border.
  450. */
  451. public Font getTitleFont() {
  452. Font f = titleFont;
  453. if (f == null)
  454. f = UIManager.getFont("TitledBorder.font");
  455. return f;
  456. }
  457. /**
  458. * Returns the title-color of the titled border.
  459. */
  460. public Color getTitleColor() {
  461. Color c = titleColor;
  462. if (c == null)
  463. c = UIManager.getColor("TitledBorder.titleColor");
  464. return c;
  465. }
  466. // REMIND(aim): remove all or some of these set methods?
  467. /**
  468. * Sets the title of the titled border.
  469. * param title the title for the border
  470. */
  471. public void setTitle(String title) { this.title = title; }
  472. /**
  473. * Sets the border of the titled border.
  474. * @param border the border
  475. */
  476. public void setBorder(Border border) { this.border = border; }
  477. /**
  478. * Sets the title-position of the titled border.
  479. * @param titlePosition the position for the border
  480. */
  481. public void setTitlePosition(int titlePosition) {
  482. switch (titlePosition) {
  483. case ABOVE_TOP:
  484. case TOP:
  485. case BELOW_TOP:
  486. case ABOVE_BOTTOM:
  487. case BOTTOM:
  488. case BELOW_BOTTOM:
  489. case DEFAULT_POSITION:
  490. this.titlePosition = titlePosition;
  491. break;
  492. default:
  493. throw new IllegalArgumentException(titlePosition +
  494. " is not a valid title position.");
  495. }
  496. }
  497. /**
  498. * Sets the title-justification of the titled border.
  499. * @param titleJustification the justification for the border
  500. */
  501. public void setTitleJustification(int titleJustification) {
  502. switch (titleJustification) {
  503. case DEFAULT_JUSTIFICATION:
  504. case LEFT:
  505. case CENTER:
  506. case RIGHT:
  507. case LEADING:
  508. case TRAILING:
  509. this.titleJustification = titleJustification;
  510. break;
  511. default:
  512. throw new IllegalArgumentException(titleJustification +
  513. " is not a valid title justification.");
  514. }
  515. }
  516. /**
  517. * Sets the title-font of the titled border.
  518. * @param titleFont the font for the border title
  519. */
  520. public void setTitleFont(Font titleFont) {
  521. this.titleFont = titleFont;
  522. }
  523. /**
  524. * Sets the title-color of the titled border.
  525. * @param titleColor the color for the border title
  526. */
  527. public void setTitleColor(Color titleColor) {
  528. this.titleColor = titleColor;
  529. }
  530. /**
  531. * Returns the minimum dimensions this border requires
  532. * in order to fully display the border and title.
  533. * @param c the component where this border will be drawn
  534. */
  535. public Dimension getMinimumSize(Component c) {
  536. Insets insets = getBorderInsets(c);
  537. Dimension minSize = new Dimension(insets.right+insets.left,
  538. insets.top+insets.bottom);
  539. Font font = getFont(c);
  540. FontMetrics fm = c.getFontMetrics(font);
  541. JComponent jc = (c instanceof JComponent) ? (JComponent)c : null;
  542. switch (titlePosition) {
  543. case ABOVE_TOP:
  544. case BELOW_BOTTOM:
  545. minSize.width = Math.max(SwingUtilities2.stringWidth(jc, fm,
  546. getTitle()), minSize.width);
  547. break;
  548. case BELOW_TOP:
  549. case ABOVE_BOTTOM:
  550. case TOP:
  551. case BOTTOM:
  552. case DEFAULT_POSITION:
  553. default:
  554. minSize.width += SwingUtilities2.stringWidth(jc, fm, getTitle());
  555. }
  556. return minSize;
  557. }
  558. protected Font getFont(Component c) {
  559. Font font;
  560. if ((font = getTitleFont()) != null) {
  561. return font;
  562. } else if (c != null && (font = c.getFont()) != null) {
  563. return font;
  564. }
  565. return new Font("Dialog", Font.PLAIN, 12);
  566. }
  567. private static boolean computeIntersection(Rectangle dest,
  568. int rx, int ry, int rw, int rh) {
  569. int x1 = Math.max(rx, dest.x);
  570. int x2 = Math.min(rx + rw, dest.x + dest.width);
  571. int y1 = Math.max(ry, dest.y);
  572. int y2 = Math.min(ry + rh, dest.y + dest.height);
  573. dest.x = x1;
  574. dest.y = y1;
  575. dest.width = x2 - x1;
  576. dest.height = y2 - y1;
  577. if (dest.width <= 0 || dest.height <= 0) {
  578. return false;
  579. }
  580. return true;
  581. }
  582. }