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