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