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