1. /*
  2. * @(#)TitledBorder.java 1.32 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.border;
  11. import java.awt.Graphics;
  12. import java.awt.Insets;
  13. import java.awt.Rectangle;
  14. import java.awt.Color;
  15. import java.awt.Font;
  16. import java.awt.FontMetrics;
  17. import java.awt.Point;
  18. import java.awt.Toolkit;
  19. import java.awt.Component;
  20. import java.awt.Dimension;
  21. import javax.swing.UIManager;
  22. /**
  23. * A class which implements an arbitrary border
  24. * with the addition of a String title in a
  25. * specified position and justification.
  26. * <p>
  27. * If the border, font, or color property values are not
  28. * specified in the constuctor or by invoking the appropriate
  29. * set methods, the property values will be defined by the current
  30. * look and feel, using the following property names in the
  31. * Defaults Table:
  32. * <ul>
  33. * <li>"TitledBorder.border"
  34. * <li>"TitledBorder.font"
  35. * <li>"TitledBorder.titleColor"
  36. * </ul>
  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.32 02/02/00
  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. /** 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 we'll
  289. // need to paint the border in sections to leave
  290. // space for the component's background to show
  291. // through the title.
  292. //
  293. if (border != null) {
  294. if (titlePos == TOP || titlePos == BOTTOM ||
  295. titlePos == DEFAULT_POSITION) {
  296. Rectangle clipRect = new Rectangle();
  297. // save original clip
  298. Rectangle saveClip = g.getClipBounds();
  299. // paint strip left of text
  300. clipRect.setBounds(saveClip);
  301. if (computeIntersection(clipRect, x, y, textLoc.x, height)) {
  302. g.setClip(clipRect);
  303. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  304. grooveRect.width, grooveRect.height);
  305. }
  306. // paint strip right of text
  307. clipRect.setBounds(saveClip);
  308. if (computeIntersection(clipRect, textLoc.x+stringWidth, 0,
  309. width-stringWidth-textLoc.x, height)) {
  310. g.setClip(clipRect);
  311. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  312. grooveRect.width, grooveRect.height);
  313. }
  314. // paint strip below or above text
  315. clipRect.setBounds(saveClip);
  316. if (titlePos == TOP || titlePos == DEFAULT_POSITION) {
  317. if (computeIntersection(clipRect, textLoc.x, grooveRect.y+insets.top,
  318. stringWidth, height-grooveRect.y-insets.top)) {
  319. g.setClip(clipRect);
  320. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  321. grooveRect.width, grooveRect.height);
  322. }
  323. } else { // titlePos == BOTTOM
  324. if (computeIntersection(clipRect, textLoc.x, y,
  325. stringWidth, height-insets.bottom-
  326. (height-grooveRect.height-grooveRect.y))) {
  327. g.setClip(clipRect);
  328. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  329. grooveRect.width, grooveRect.height);
  330. }
  331. }
  332. // restore clip
  333. g.setClip(saveClip);
  334. } else {
  335. border.paintBorder(c, g, grooveRect.x, grooveRect.y,
  336. grooveRect.width, grooveRect.height);
  337. }
  338. }
  339. g.setColor(getTitleColor());
  340. g.drawString(getTitle(), textLoc.x, textLoc.y);
  341. g.setFont(font);
  342. g.setColor(color);
  343. }
  344. /**
  345. * Returns the insets of the border.
  346. * @param c the component for which this border insets value applies
  347. */
  348. public Insets getBorderInsets(Component c) {
  349. return getBorderInsets(c, new Insets(0, 0, 0, 0));
  350. }
  351. /**
  352. * Reinitialize the insets parameter with this Border's current Insets.
  353. * @param c the component for which this border insets value applies
  354. * @param insets the object to be reinitialized
  355. */
  356. public Insets getBorderInsets(Component c, Insets insets) {
  357. FontMetrics fm;
  358. int descent = 0;
  359. int ascent = 16;
  360. Border border = getBorder();
  361. if (border != null) {
  362. if (border instanceof AbstractBorder) {
  363. ((AbstractBorder)border).getBorderInsets(c, insets);
  364. } else {
  365. // Can't reuse border insets because the Border interface
  366. // can't be enhanced.
  367. Insets i = border.getBorderInsets(c);
  368. insets.top = i.top;
  369. insets.right = i.right;
  370. insets.bottom = i.bottom;
  371. insets.left = i.left;
  372. }
  373. } else {
  374. insets.left = insets.top = insets.right = insets.bottom = 0;
  375. }
  376. insets.left += EDGE_SPACING + TEXT_SPACING;
  377. insets.right += EDGE_SPACING + TEXT_SPACING;
  378. insets.top += EDGE_SPACING + TEXT_SPACING;
  379. insets.bottom += EDGE_SPACING + TEXT_SPACING;
  380. if(c == null || getTitle() == null || getTitle().equals("")) {
  381. return insets;
  382. }
  383. Font font = getFont(c);
  384. fm = c.getFontMetrics(font);
  385. if(fm != null) {
  386. descent = fm.getDescent();
  387. ascent = fm.getAscent();
  388. }
  389. switch (getTitlePosition()) {
  390. case ABOVE_TOP:
  391. insets.top += ascent + descent
  392. + (Math.max(EDGE_SPACING, TEXT_SPACING*2)
  393. - EDGE_SPACING);
  394. break;
  395. case TOP:
  396. case DEFAULT_POSITION:
  397. insets.top += ascent + descent;
  398. break;
  399. case BELOW_TOP:
  400. insets.top += ascent + descent + TEXT_SPACING;
  401. break;
  402. case ABOVE_BOTTOM:
  403. insets.bottom += ascent + descent + TEXT_SPACING;
  404. break;
  405. case BOTTOM:
  406. insets.bottom += ascent + descent;
  407. break;
  408. case BELOW_BOTTOM:
  409. insets.bottom += ascent + TEXT_SPACING;
  410. break;
  411. }
  412. return insets;
  413. }
  414. /**
  415. * Returns whether or not the border is opaque.
  416. */
  417. public boolean isBorderOpaque() { return false; }
  418. /**
  419. * Returns the title of the titled border.
  420. */
  421. public String getTitle() { return title; }
  422. /**
  423. * Returns the border of the titled border.
  424. */
  425. public Border getBorder() {
  426. Border b = border;
  427. if (b == null)
  428. b = UIManager.getBorder("TitledBorder.border");
  429. return b;
  430. }
  431. /**
  432. * Returns the title-position of the titled border.
  433. */
  434. public int getTitlePosition() { return titlePosition; }
  435. /**
  436. * Returns the title-justification of the titled border.
  437. */
  438. public int getTitleJustification() { return titleJustification; }
  439. /**
  440. * Returns the title-font of the titled border.
  441. */
  442. public Font getTitleFont() {
  443. Font f = titleFont;
  444. if (f == null)
  445. f = UIManager.getFont("TitledBorder.font");
  446. return f;
  447. }
  448. /**
  449. * Returns the title-color of the titled border.
  450. */
  451. public Color getTitleColor() {
  452. Color c = titleColor;
  453. if (c == null)
  454. c = UIManager.getColor("TitledBorder.titleColor");
  455. return c;
  456. }
  457. // REMIND(aim): remove all or some of these set methods?
  458. /**
  459. * Sets the title of the titled border.
  460. * param title the title for the border
  461. */
  462. public void setTitle(String title) { this.title = title; }
  463. /**
  464. * Sets the border of the titled border.
  465. * @param border the border
  466. */
  467. public void setBorder(Border border) { this.border = border; }
  468. /**
  469. * Sets the title-position of the titled border.
  470. * @param titlePosition the position for the border
  471. */
  472. public void setTitlePosition(int titlePosition) {
  473. switch (titlePosition) {
  474. case ABOVE_TOP:
  475. case TOP:
  476. case BELOW_TOP:
  477. case ABOVE_BOTTOM:
  478. case BOTTOM:
  479. case BELOW_BOTTOM:
  480. case DEFAULT_POSITION:
  481. this.titlePosition = titlePosition;
  482. break;
  483. default:
  484. throw new IllegalArgumentException(titlePosition +
  485. " is not a valid title position.");
  486. }
  487. }
  488. /**
  489. * Sets the title-justification of the titled border.
  490. * @param titleJustification the justification for the border
  491. */
  492. public void setTitleJustification(int titleJustification) {
  493. switch (titleJustification) {
  494. case DEFAULT_JUSTIFICATION:
  495. case LEFT:
  496. case CENTER:
  497. case RIGHT:
  498. case LEADING:
  499. case TRAILING:
  500. this.titleJustification = titleJustification;
  501. break;
  502. default:
  503. throw new IllegalArgumentException(titleJustification +
  504. " is not a valid title justification.");
  505. }
  506. }
  507. /**
  508. * Sets the title-font of the titled border.
  509. * @param titleFont the font for the border title
  510. */
  511. public void setTitleFont(Font titleFont) {
  512. this.titleFont = titleFont;
  513. }
  514. /**
  515. * Sets the title-color of the titled border.
  516. * @param titleColor the color for the border title
  517. */
  518. public void setTitleColor(Color titleColor) {
  519. this.titleColor = titleColor;
  520. }
  521. /**
  522. * Returns the minimum dimensions this border requires
  523. * in order to fully display the border and title.
  524. * @param c the component where this border will be drawn
  525. */
  526. public Dimension getMinimumSize(Component c) {
  527. Insets insets = getBorderInsets(c);
  528. Dimension minSize = new Dimension(insets.right+insets.left,
  529. insets.top+insets.bottom);
  530. Font font = getFont(c);
  531. FontMetrics fm = c.getFontMetrics(font);
  532. switch (titlePosition) {
  533. case ABOVE_TOP:
  534. case BELOW_BOTTOM:
  535. minSize.width = Math.max(fm.stringWidth(getTitle()), minSize.width);
  536. break;
  537. case BELOW_TOP:
  538. case ABOVE_BOTTOM:
  539. case TOP:
  540. case BOTTOM:
  541. case DEFAULT_POSITION:
  542. default:
  543. minSize.width += fm.stringWidth(getTitle());
  544. }
  545. return minSize;
  546. }
  547. protected Font getFont(Component c) {
  548. Font font;
  549. if ((font = getTitleFont()) != null) {
  550. return font;
  551. } else if (c != null && (font = c.getFont()) != null) {
  552. return font;
  553. }
  554. return new Font("Dialog", Font.PLAIN, 12);
  555. }
  556. private static boolean computeIntersection(Rectangle dest,
  557. int rx, int ry, int rw, int rh) {
  558. int x1 = Math.max(rx, dest.x);
  559. int x2 = Math.min(rx + rw, dest.x + dest.width);
  560. int y1 = Math.max(ry, dest.y);
  561. int y2 = Math.min(ry + rh, dest.y + dest.height);
  562. dest.x = x1;
  563. dest.y = y1;
  564. dest.width = x2 - x1;
  565. dest.height = y2 - y1;
  566. if (dest.width <= 0 || dest.height <= 0) {
  567. return false;
  568. }
  569. return true;
  570. }
  571. }