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