001 /*
002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.border;
026
027 import sun.swing.SwingUtilities2;
028
029 import java.awt.Graphics;
030 import java.awt.Insets;
031 import java.awt.Rectangle;
032 import java.awt.Color;
033 import java.awt.Font;
034 import java.awt.FontMetrics;
035 import java.awt.Point;
036 import java.awt.Toolkit;
037 import java.awt.Component;
038 import java.awt.Dimension;
039 import java.beans.ConstructorProperties;
040
041 import javax.swing.JComponent;
042 import javax.swing.UIManager;
043
044 /**
045 * A class which implements an arbitrary border
046 * with the addition of a String title in a
047 * specified position and justification.
048 * <p>
049 * If the border, font, or color property values are not
050 * specified in the constuctor or by invoking the appropriate
051 * set methods, the property values will be defined by the current
052 * look and feel, using the following property names in the
053 * Defaults Table:
054 * <ul>
055 * <li>"TitledBorder.border"
056 * <li>"TitledBorder.font"
057 * <li>"TitledBorder.titleColor"
058 * </ul>
059 * <p>
060 * <strong>Warning:</strong>
061 * Serialized objects of this class will not be compatible with
062 * future Swing releases. The current serialization support is
063 * appropriate for short term storage or RMI between applications running
064 * the same version of Swing. As of 1.4, support for long term storage
065 * of all JavaBeans<sup><font size="-2">TM</font></sup>
066 * has been added to the <code>java.beans</code> package.
067 * Please see {@link java.beans.XMLEncoder}.
068 *
069 * @version 1.49 05/05/07
070 * @author David Kloba
071 * @author Amy Fowler
072 */
073 public class TitledBorder extends AbstractBorder {
074 protected String title;
075 protected Border border;
076 protected int titlePosition;
077 protected int titleJustification;
078 protected Font titleFont;
079 protected Color titleColor;
080
081 private Point textLoc = new Point();
082
083 /**
084 * Use the default vertical orientation for the title text.
085 */
086 static public final int DEFAULT_POSITION = 0;
087 /** Position the title above the border's top line. */
088 static public final int ABOVE_TOP = 1;
089 /** Position the title in the middle of the border's top line. */
090 static public final int TOP = 2;
091 /** Position the title below the border's top line. */
092 static public final int BELOW_TOP = 3;
093 /** Position the title above the border's bottom line. */
094 static public final int ABOVE_BOTTOM = 4;
095 /** Position the title in the middle of the border's bottom line. */
096 static public final int BOTTOM = 5;
097 /** Position the title below the border's bottom line. */
098 static public final int BELOW_BOTTOM = 6;
099
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, String title,
170 int titleJustification, int titlePosition) {
171 this (border, title, titleJustification, titlePosition, null,
172 null);
173 }
174
175 /**
176 * Creates a TitledBorder instance with the specified border,
177 * title, title-justification, title-position, and title-font.
178 *
179 * @param border the border
180 * @param title the title the border should display
181 * @param titleJustification the justification for the title
182 * @param titlePosition the position for the title
183 * @param titleFont the font for rendering the title
184 */
185 public TitledBorder(Border border, String title,
186 int titleJustification, int titlePosition, Font titleFont) {
187 this (border, title, titleJustification, titlePosition,
188 titleFont, null);
189 }
190
191 /**
192 * Creates a TitledBorder instance with the specified border,
193 * title, title-justification, title-position, title-font, and
194 * title-color.
195 *
196 * @param border the border
197 * @param title the title the border should display
198 * @param titleJustification the justification for the title
199 * @param titlePosition the position for the title
200 * @param titleFont the font of the title
201 * @param titleColor the color of the title
202 */
203 @ConstructorProperties({"border","title","titleJustification","titlePosition","titleFont","titleColor"})
204 public TitledBorder(Border border, String title,
205 int titleJustification, int titlePosition, Font titleFont,
206 Color titleColor) {
207 this .title = title;
208 this .border = border;
209 this .titleFont = titleFont;
210 this .titleColor = titleColor;
211
212 setTitleJustification(titleJustification);
213 setTitlePosition(titlePosition);
214 }
215
216 /**
217 * Paints the border for the specified component with the
218 * specified position and size.
219 * @param c the component for which this border is being painted
220 * @param g the paint graphics
221 * @param x the x position of the painted border
222 * @param y the y position of the painted border
223 * @param width the width of the painted border
224 * @param height the height of the painted border
225 */
226 public void paintBorder(Component c, Graphics g, int x, int y,
227 int width, int height) {
228
229 Border border = getBorder();
230
231 if (getTitle() == null || getTitle().equals("")) {
232 if (border != null) {
233 border.paintBorder(c, g, x, y, width, height);
234 }
235 return;
236 }
237
238 Rectangle grooveRect = new Rectangle(x + EDGE_SPACING, y
239 + EDGE_SPACING, width - (EDGE_SPACING * 2), height
240 - (EDGE_SPACING * 2));
241 Font font = g.getFont();
242 Color color = g.getColor();
243
244 g.setFont(getFont(c));
245
246 JComponent jc = (c instanceof JComponent) ? (JComponent) c
247 : null;
248 FontMetrics fm = SwingUtilities2.getFontMetrics(jc, g);
249 int fontHeight = fm.getHeight();
250 int descent = fm.getDescent();
251 int ascent = fm.getAscent();
252 int diff;
253 int stringWidth = SwingUtilities2.stringWidth(jc, fm,
254 getTitle());
255 Insets insets;
256
257 if (border != null) {
258 insets = border.getBorderInsets(c);
259 } else {
260 insets = new Insets(0, 0, 0, 0);
261 }
262
263 int titlePos = getTitlePosition();
264 switch (titlePos) {
265 case ABOVE_TOP:
266 diff = ascent
267 + descent
268 + (Math.max(EDGE_SPACING, TEXT_SPACING * 2) - EDGE_SPACING);
269 grooveRect.y += diff;
270 grooveRect.height -= diff;
271 textLoc.y = grooveRect.y - (descent + TEXT_SPACING);
272 break;
273 case TOP:
274 case DEFAULT_POSITION:
275 diff = Math.max(0, ((ascent / 2) + TEXT_SPACING)
276 - EDGE_SPACING);
277 grooveRect.y += diff;
278 grooveRect.height -= diff;
279 textLoc.y = (grooveRect.y - descent)
280 + (insets.top + ascent + descent) / 2;
281 break;
282 case BELOW_TOP:
283 textLoc.y = grooveRect.y + insets.top + ascent
284 + TEXT_SPACING;
285 break;
286 case ABOVE_BOTTOM:
287 textLoc.y = (grooveRect.y + grooveRect.height)
288 - (insets.bottom + descent + TEXT_SPACING);
289 break;
290 case BOTTOM:
291 grooveRect.height -= fontHeight / 2;
292 textLoc.y = ((grooveRect.y + grooveRect.height) - descent)
293 + ((ascent + descent) - insets.bottom) / 2;
294 break;
295 case BELOW_BOTTOM:
296 grooveRect.height -= fontHeight;
297 textLoc.y = grooveRect.y + grooveRect.height + ascent
298 + TEXT_SPACING;
299 break;
300 }
301
302 int justification = getTitleJustification();
303 if (isLeftToRight(c)) {
304 if (justification == LEADING
305 || justification == DEFAULT_JUSTIFICATION) {
306 justification = LEFT;
307 } else if (justification == TRAILING) {
308 justification = RIGHT;
309 }
310 } else {
311 if (justification == LEADING
312 || justification == DEFAULT_JUSTIFICATION) {
313 justification = RIGHT;
314 } else if (justification == TRAILING) {
315 justification = LEFT;
316 }
317 }
318
319 switch (justification) {
320 case LEFT:
321 textLoc.x = grooveRect.x + TEXT_INSET_H + insets.left;
322 break;
323 case RIGHT:
324 textLoc.x = (grooveRect.x + grooveRect.width)
325 - (stringWidth + TEXT_INSET_H + insets.right);
326 break;
327 case CENTER:
328 textLoc.x = grooveRect.x
329 + ((grooveRect.width - stringWidth) / 2);
330 break;
331 }
332
333 // If title is positioned in middle of border AND its fontsize
334 // is greater than the border's thickness, we'll need to paint
335 // the border in sections to leave space for the component's background
336 // to show through the title.
337 //
338 if (border != null) {
339 if (((titlePos == TOP || titlePos == DEFAULT_POSITION) && (grooveRect.y > textLoc.y
340 - ascent))
341 || (titlePos == BOTTOM && (grooveRect.y
342 + grooveRect.height < textLoc.y + descent))) {
343
344 Rectangle clipRect = new Rectangle();
345
346 // save original clip
347 Rectangle saveClip = g.getClipBounds();
348
349 // paint strip left of text
350 clipRect.setBounds(saveClip);
351 if (computeIntersection(clipRect, x, y, textLoc.x - 1
352 - x, height)) {
353 g.setClip(clipRect);
354 border.paintBorder(c, g, grooveRect.x,
355 grooveRect.y, grooveRect.width,
356 grooveRect.height);
357 }
358
359 // paint strip right of text
360 clipRect.setBounds(saveClip);
361 if (computeIntersection(clipRect, textLoc.x
362 + stringWidth + 1, y, x + width
363 - (textLoc.x + stringWidth + 1), height)) {
364 g.setClip(clipRect);
365 border.paintBorder(c, g, grooveRect.x,
366 grooveRect.y, grooveRect.width,
367 grooveRect.height);
368 }
369
370 if (titlePos == TOP || titlePos == DEFAULT_POSITION) {
371 // paint strip below text
372 clipRect.setBounds(saveClip);
373 if (computeIntersection(clipRect, textLoc.x - 1,
374 textLoc.y + descent, stringWidth + 2, y
375 + height - textLoc.y - descent)) {
376 g.setClip(clipRect);
377 border.paintBorder(c, g, grooveRect.x,
378 grooveRect.y, grooveRect.width,
379 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,
389 grooveRect.y, grooveRect.width,
390 grooveRect.height);
391 }
392 }
393
394 // restore clip
395 g.setClip(saveClip);
396
397 } else {
398 border.paintBorder(c, g, grooveRect.x, grooveRect.y,
399 grooveRect.width, grooveRect.height);
400 }
401 }
402
403 g.setColor(getTitleColor());
404 SwingUtilities2.drawString(jc, g, getTitle(), textLoc.x,
405 textLoc.y);
406
407 g.setFont(font);
408 g.setColor(color);
409 }
410
411 /**
412 * Returns the insets of the border.
413 * @param c the component for which this border insets value applies
414 */
415 public Insets getBorderInsets(Component c) {
416 return getBorderInsets(c, new Insets(0, 0, 0, 0));
417 }
418
419 /**
420 * Reinitialize the insets parameter with this Border's current Insets.
421 * @param c the component for which this border insets value applies
422 * @param insets the object to be reinitialized
423 */
424 public Insets getBorderInsets(Component c, Insets insets) {
425 FontMetrics fm;
426 int descent = 0;
427 int ascent = 16;
428 int height = 16;
429
430 Border border = getBorder();
431 if (border != null) {
432 if (border instanceof AbstractBorder) {
433 ((AbstractBorder) border).getBorderInsets(c, insets);
434 } else {
435 // Can't reuse border insets because the Border interface
436 // can't be enhanced.
437 Insets i = border.getBorderInsets(c);
438 insets.top = i.top;
439 insets.right = i.right;
440 insets.bottom = i.bottom;
441 insets.left = i.left;
442 }
443 } else {
444 insets.left = insets.top = insets.right = insets.bottom = 0;
445 }
446
447 insets.left += EDGE_SPACING + TEXT_SPACING;
448 insets.right += EDGE_SPACING + TEXT_SPACING;
449 insets.top += EDGE_SPACING + TEXT_SPACING;
450 insets.bottom += EDGE_SPACING + TEXT_SPACING;
451
452 if (c == null || getTitle() == null || getTitle().equals("")) {
453 return insets;
454 }
455
456 Font font = getFont(c);
457
458 fm = c.getFontMetrics(font);
459
460 if (fm != null) {
461 descent = fm.getDescent();
462 ascent = fm.getAscent();
463 height = fm.getHeight();
464 }
465
466 switch (getTitlePosition()) {
467 case ABOVE_TOP:
468 insets.top += ascent
469 + descent
470 + (Math.max(EDGE_SPACING, TEXT_SPACING * 2) - EDGE_SPACING);
471 break;
472 case TOP:
473 case DEFAULT_POSITION:
474 insets.top += ascent + descent;
475 break;
476 case BELOW_TOP:
477 insets.top += ascent + descent + TEXT_SPACING;
478 break;
479 case ABOVE_BOTTOM:
480 insets.bottom += ascent + descent + TEXT_SPACING;
481 break;
482 case BOTTOM:
483 insets.bottom += ascent + descent;
484 break;
485 case BELOW_BOTTOM:
486 insets.bottom += height;
487 break;
488 }
489 return insets;
490 }
491
492 /**
493 * Returns whether or not the border is opaque.
494 */
495 public boolean isBorderOpaque() {
496 return false;
497 }
498
499 /**
500 * Returns the title of the titled border.
501 */
502 public String getTitle() {
503 return title;
504 }
505
506 /**
507 * Returns the border of the titled border.
508 */
509 public Border getBorder() {
510 Border b = border;
511 if (b == null)
512 b = UIManager.getBorder("TitledBorder.border");
513 return b;
514 }
515
516 /**
517 * Returns the title-position of the titled border.
518 */
519 public int getTitlePosition() {
520 return titlePosition;
521 }
522
523 /**
524 * Returns the title-justification of the titled border.
525 */
526 public int getTitleJustification() {
527 return titleJustification;
528 }
529
530 /**
531 * Returns the title-font of the titled border.
532 */
533 public Font getTitleFont() {
534 Font f = titleFont;
535 if (f == null)
536 f = UIManager.getFont("TitledBorder.font");
537 return f;
538 }
539
540 /**
541 * Returns the title-color of the titled border.
542 */
543 public Color getTitleColor() {
544 Color c = titleColor;
545 if (c == null)
546 c = UIManager.getColor("TitledBorder.titleColor");
547 return c;
548 }
549
550 // REMIND(aim): remove all or some of these set methods?
551
552 /**
553 * Sets the title of the titled border.
554 * param title the title for the border
555 */
556 public void setTitle(String title) {
557 this .title = title;
558 }
559
560 /**
561 * Sets the border of the titled border.
562 * @param border the border
563 */
564 public void setBorder(Border border) {
565 this .border = border;
566 }
567
568 /**
569 * Sets the title-position of the titled border.
570 * @param titlePosition the position for the border
571 */
572 public void setTitlePosition(int titlePosition) {
573 switch (titlePosition) {
574 case ABOVE_TOP:
575 case TOP:
576 case BELOW_TOP:
577 case ABOVE_BOTTOM:
578 case BOTTOM:
579 case BELOW_BOTTOM:
580 case DEFAULT_POSITION:
581 this .titlePosition = titlePosition;
582 break;
583 default:
584 throw new IllegalArgumentException(titlePosition
585 + " is not a valid title position.");
586 }
587 }
588
589 /**
590 * Sets the title-justification of the titled border.
591 * @param titleJustification the justification for the border
592 */
593 public void setTitleJustification(int titleJustification) {
594 switch (titleJustification) {
595 case DEFAULT_JUSTIFICATION:
596 case LEFT:
597 case CENTER:
598 case RIGHT:
599 case LEADING:
600 case TRAILING:
601 this .titleJustification = titleJustification;
602 break;
603 default:
604 throw new IllegalArgumentException(titleJustification
605 + " is not a valid title justification.");
606 }
607 }
608
609 /**
610 * Sets the title-font of the titled border.
611 * @param titleFont the font for the border title
612 */
613 public void setTitleFont(Font titleFont) {
614 this .titleFont = titleFont;
615 }
616
617 /**
618 * Sets the title-color of the titled border.
619 * @param titleColor the color for the border title
620 */
621 public void setTitleColor(Color titleColor) {
622 this .titleColor = titleColor;
623 }
624
625 /**
626 * Returns the minimum dimensions this border requires
627 * in order to fully display the border and title.
628 * @param c the component where this border will be drawn
629 */
630 public Dimension getMinimumSize(Component c) {
631 Insets insets = getBorderInsets(c);
632 Dimension minSize = new Dimension(insets.right + insets.left,
633 insets.top + insets.bottom);
634 Font font = getFont(c);
635 FontMetrics fm = c.getFontMetrics(font);
636 JComponent jc = (c instanceof JComponent) ? (JComponent) c
637 : null;
638 switch (titlePosition) {
639 case ABOVE_TOP:
640 case BELOW_BOTTOM:
641 minSize.width = Math.max(SwingUtilities2.stringWidth(jc,
642 fm, getTitle()), minSize.width);
643 break;
644 case BELOW_TOP:
645 case ABOVE_BOTTOM:
646 case TOP:
647 case BOTTOM:
648 case DEFAULT_POSITION:
649 default:
650 minSize.width += SwingUtilities2.stringWidth(jc, fm,
651 getTitle());
652 }
653 return minSize;
654 }
655
656 /**
657 * Returns the baseline.
658 *
659 * @throws NullPointerException {@inheritDoc}
660 * @throws IllegalArgumentException {@inheritDoc}
661 * @see javax.swing.JComponent#getBaseline(int, int)
662 * @since 1.6
663 */
664 public int getBaseline(Component c, int width, int height) {
665 if (c == null) {
666 throw new NullPointerException(
667 "Must supply non-null component");
668 }
669 if (height < 0) {
670 throw new IllegalArgumentException("Height must be >= 0");
671 }
672 String title = getTitle();
673 if (title != null && !"".equals(title)) {
674 Font font = getFont(c);
675 Border border2 = getBorder();
676 Insets borderInsets;
677 if (border2 != null) {
678 borderInsets = border2.getBorderInsets(c);
679 } else {
680 borderInsets = new Insets(0, 0, 0, 0);
681 }
682 FontMetrics fm = c.getFontMetrics(font);
683 int fontHeight = fm.getHeight();
684 int descent = fm.getDescent();
685 int ascent = fm.getAscent();
686 int y = EDGE_SPACING;
687 int h = height - EDGE_SPACING * 2;
688 int diff;
689 switch (getTitlePosition()) {
690 case ABOVE_TOP:
691 diff = ascent
692 + descent
693 + (Math.max(EDGE_SPACING, TEXT_SPACING * 2) - EDGE_SPACING);
694 return y + diff - (descent + TEXT_SPACING);
695 case TOP:
696 case DEFAULT_POSITION:
697 diff = Math.max(0, ((ascent / 2) + TEXT_SPACING)
698 - EDGE_SPACING);
699 return (y + diff - descent)
700 + (borderInsets.top + ascent + descent) / 2;
701 case BELOW_TOP:
702 return y + borderInsets.top + ascent + TEXT_SPACING;
703 case ABOVE_BOTTOM:
704 return (y + h)
705 - (borderInsets.bottom + descent + TEXT_SPACING);
706 case BOTTOM:
707 h -= fontHeight / 2;
708 return ((y + h) - descent)
709 + ((ascent + descent) - borderInsets.bottom)
710 / 2;
711 case BELOW_BOTTOM:
712 h -= fontHeight;
713 return y + h + ascent + TEXT_SPACING;
714 }
715 }
716 return -1;
717 }
718
719 /**
720 * Returns an enum indicating how the baseline of the border
721 * changes as the size changes.
722 *
723 * @throws NullPointerException {@inheritDoc}
724 * @see javax.swing.JComponent#getBaseline(int, int)
725 * @since 1.6
726 */
727 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
728 Component c) {
729 super .getBaselineResizeBehavior(c);
730 switch (getTitlePosition()) {
731 case TitledBorder.ABOVE_TOP:
732 case TitledBorder.TOP:
733 case TitledBorder.DEFAULT_POSITION:
734 case TitledBorder.BELOW_TOP:
735 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
736 case TitledBorder.ABOVE_BOTTOM:
737 case TitledBorder.BOTTOM:
738 case TitledBorder.BELOW_BOTTOM:
739 return JComponent.BaselineResizeBehavior.CONSTANT_DESCENT;
740 }
741 return Component.BaselineResizeBehavior.OTHER;
742 }
743
744 protected Font getFont(Component c) {
745 Font font;
746 if ((font = getTitleFont()) != null) {
747 return font;
748 } else if (c != null && (font = c.getFont()) != null) {
749 return font;
750 }
751 return new Font(Font.DIALOG, Font.PLAIN, 12);
752 }
753
754 private static boolean computeIntersection(Rectangle dest, int rx,
755 int ry, int rw, int rh) {
756 int x1 = Math.max(rx, dest.x);
757 int x2 = Math.min(rx + rw, dest.x + dest.width);
758 int y1 = Math.max(ry, dest.y);
759 int y2 = Math.min(ry + rh, dest.y + dest.height);
760 dest.x = x1;
761 dest.y = y1;
762 dest.width = x2 - x1;
763 dest.height = y2 - y1;
764
765 if (dest.width <= 0 || dest.height <= 0) {
766 return false;
767 }
768 return true;
769 }
770 }
|