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