001: /*
002: * @(#)JideTitledBorder.java
003: *
004: * Copyright 2002 - 2003 JIDE Software. All rights reserved.
005: *
006: * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
007: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
008: */
009: package com.jidesoft.swing;
010:
011: import com.jidesoft.plaf.UIDefaultsLookup;
012:
013: import javax.swing.*;
014: import javax.swing.border.AbstractBorder;
015: import javax.swing.border.Border;
016: import java.awt.*;
017:
018: /**
019: * The source code is the same as TitledBorder in JDK 1.4.2 except
020: * field TEXT_INSET_H is 0 instead of 5.
021: */
022: public class JideTitledBorder extends AbstractBorder {
023:
024: protected String title;
025: protected Border border;
026: protected int titlePosition;
027: protected int titleJustification;
028: protected Font titleFont;
029: protected Color titleColor;
030:
031: private Point textLoc = new Point();
032:
033: /**
034: * Use the default vertical orientation for the title text.
035: */
036: static public final int DEFAULT_POSITION = 0;
037: /**
038: * Position the title above the border's top line.
039: */
040: static public final int ABOVE_TOP = 1;
041: /**
042: * Position the title in the middle of the border's top line.
043: */
044: static public final int TOP = 2;
045: /**
046: * Position the title below the border's top line.
047: */
048: static public final int BELOW_TOP = 3;
049: /**
050: * Position the title above the border's bottom line.
051: */
052: static public final int ABOVE_BOTTOM = 4;
053: /**
054: * Position the title in the middle of the border's bottom line.
055: */
056: static public final int BOTTOM = 5;
057: /**
058: * Position the title below the border's bottom line.
059: */
060: static public final int BELOW_BOTTOM = 6;
061:
062: /**
063: * Use the default justification for the title text.
064: */
065: static public final int DEFAULT_JUSTIFICATION = 0;
066: /**
067: * Position title text at the left side of the border line.
068: */
069: static public final int LEFT = 1;
070: /**
071: * Position title text in the center of the border line.
072: */
073: static public final int CENTER = 2;
074: /**
075: * Position title text at the right side of the border line.
076: */
077: static public final int RIGHT = 3;
078: /**
079: * Position title text at the left side of the border line
080: * for left to right orientation, at the right side of the
081: * border line for right to left orientation.
082: */
083: static public final int LEADING = 4;
084: /**
085: * Position title text at the right side of the border line
086: * for left to right orientation, at the left side of the
087: * border line for right to left orientation.
088: */
089: static public final int TRAILING = 5;
090:
091: // Space between the border and the component's edge
092: static protected final int EDGE_SPACING = 2;
093:
094: static protected final int TITLE_MARGIN = 5;
095:
096: // Space between the border and text
097: static protected final int TEXT_SPACING = 2;
098:
099: // Horizontal inset of text that is left or right justified
100: static protected final int TEXT_INSET_H = 0;
101:
102: /**
103: * Creates a JideTitledBorder instance.
104: *
105: * @param title the title the border should display
106: */
107: public JideTitledBorder(String title) {
108: this (null, title, LEADING, TOP, null, null);
109: }
110:
111: /**
112: * Creates a JideTitledBorder instance with the specified border
113: * and an empty title.
114: *
115: * @param border the border
116: */
117: public JideTitledBorder(Border border) {
118: this (border, "", LEADING, TOP, null, null);
119: }
120:
121: /**
122: * Creates a JideTitledBorder instance with the specified border
123: * and title.
124: *
125: * @param border the border
126: * @param title the title the border should display
127: */
128: public JideTitledBorder(Border border, String title) {
129: this (border, title, LEADING, TOP, null, null);
130: }
131:
132: /**
133: * Creates a JideTitledBorder instance with the specified border,
134: * title, title-justification, and title-position.
135: *
136: * @param border the border
137: * @param title the title the border should display
138: * @param titleJustification the justification for the title
139: * @param titlePosition the position for the title
140: */
141: public JideTitledBorder(Border border, String title,
142: int titleJustification, int titlePosition) {
143: this (border, title, titleJustification, titlePosition, null,
144: null);
145: }
146:
147: /**
148: * Creates a JideTitledBorder instance with the specified border,
149: * title, title-justification, title-position, and title-font.
150: *
151: * @param border the border
152: * @param title the title the border should display
153: * @param titleJustification the justification for the title
154: * @param titlePosition the position for the title
155: * @param titleFont the font for rendering the title
156: */
157: public JideTitledBorder(Border border, String title,
158: int titleJustification, int titlePosition, Font titleFont) {
159: this (border, title, titleJustification, titlePosition,
160: titleFont, null);
161: }
162:
163: /**
164: * Creates a JideTitledBorder instance with the specified border,
165: * title, title-justification, title-position, title-font, and
166: * title-color.
167: *
168: * @param border the border
169: * @param title the title the border should display
170: * @param titleJustification the justification for the title
171: * @param titlePosition the position for the title
172: * @param titleFont the font of the title
173: * @param titleColor the color of the title
174: */
175: public JideTitledBorder(Border border, String title,
176: int titleJustification, int titlePosition, Font titleFont,
177: Color titleColor) {
178: this .title = title;
179: this .border = border;
180: this .titleFont = titleFont;
181: this .titleColor = titleColor;
182:
183: setTitleJustification(titleJustification);
184: setTitlePosition(titlePosition);
185: }
186:
187: /**
188: * Paints the border for the specified component with the
189: * specified position and size.
190: *
191: * @param c the component for which this border is being painted
192: * @param g the paint graphics
193: * @param x the x position of the painted border
194: * @param y the y position of the painted border
195: * @param width the width of the painted border
196: * @param height the height of the painted border
197: */
198: @Override
199: public void paintBorder(Component c, Graphics g, int x, int y,
200: int width, int height) {
201:
202: Border border = getBorder();
203:
204: if (getTitle() == null || getTitle().equals("")) {
205: if (border != null) {
206: border.paintBorder(c, g, x, y, width, height);
207: }
208: return;
209: }
210:
211: Rectangle grooveRect = new Rectangle(x + EDGE_SPACING, y
212: + EDGE_SPACING, width - (EDGE_SPACING << 1), height
213: - (EDGE_SPACING << 1));
214: Font font = g.getFont();
215: Color color = g.getColor();
216:
217: g.setFont(getFont(c));
218:
219: FontMetrics fm = g.getFontMetrics();
220: int fontHeight = fm.getHeight();
221: int descent = fm.getDescent();
222: int ascent = fm.getAscent();
223: int diff;
224: int stringWidth = fm.stringWidth(getTitle());
225: Insets insets;
226:
227: if (border != null) {
228: insets = border.getBorderInsets(c);
229: } else {
230: insets = new Insets(0, 0, 0, 0);
231: }
232:
233: int titlePos = getTitlePosition();
234: switch (titlePos) {
235: case ABOVE_TOP:
236: diff = ascent
237: + descent
238: + (Math.max(EDGE_SPACING, TEXT_SPACING << 1) - EDGE_SPACING);
239: grooveRect.y += diff;
240: grooveRect.height -= diff;
241: textLoc.y = grooveRect.y - (descent + TEXT_SPACING);
242: break;
243: case TOP:
244: case DEFAULT_POSITION:
245: diff = Math.max(0, ((ascent >> 1) + TEXT_SPACING)
246: - EDGE_SPACING);
247: grooveRect.y += diff;
248: grooveRect.height -= diff;
249: textLoc.y = (grooveRect.y - descent)
250: + ((insets.top + ascent + descent) >> 1);
251: break;
252: case BELOW_TOP:
253: textLoc.y = grooveRect.y + insets.top + ascent
254: + TEXT_SPACING;
255: break;
256: case ABOVE_BOTTOM:
257: textLoc.y = (grooveRect.y + grooveRect.height)
258: - (insets.bottom + descent + TEXT_SPACING);
259: break;
260: case BOTTOM:
261: grooveRect.height -= fontHeight >> 1;
262: textLoc.y = ((grooveRect.y + grooveRect.height) - descent)
263: + (((ascent + descent) - insets.bottom) >> 1);
264: break;
265: case BELOW_BOTTOM:
266: grooveRect.height -= fontHeight;
267: textLoc.y = grooveRect.y + grooveRect.height + ascent
268: + TEXT_SPACING;
269: break;
270: }
271:
272: int justification = getTitleJustification();
273: if (c.getComponentOrientation().isLeftToRight()) {
274: if (justification == LEADING
275: || justification == DEFAULT_JUSTIFICATION) {
276: justification = LEFT;
277: } else if (justification == TRAILING) {
278: justification = RIGHT;
279: }
280: } else {
281: if (justification == LEADING
282: || justification == DEFAULT_JUSTIFICATION) {
283: justification = RIGHT;
284: } else if (justification == TRAILING) {
285: justification = LEFT;
286: }
287: }
288:
289: switch (justification) {
290: case LEFT:
291: textLoc.x = grooveRect.x + TEXT_INSET_H + insets.left;
292: break;
293: case RIGHT:
294: textLoc.x = (grooveRect.x + grooveRect.width)
295: - (stringWidth + TEXT_INSET_H + insets.right);
296: break;
297: case CENTER:
298: textLoc.x = grooveRect.x
299: + ((grooveRect.width - stringWidth) >> 1);
300: break;
301: }
302:
303: // If title is positioned in middle of border AND its fontsize
304: // is greater than the border's thickness, we'll need to paint
305: // the border in sections to leave space for the component's background
306: // to show through the title.
307: //
308: if (border != null) {
309: if (((titlePos == TOP || titlePos == DEFAULT_POSITION) && (grooveRect.y > textLoc.y
310: - ascent))
311: || (titlePos == BOTTOM && (grooveRect.y
312: + grooveRect.height < textLoc.y + descent))) {
313:
314: Rectangle clipRect = new Rectangle();
315:
316: // save original clip
317: Rectangle saveClip = g.getClipBounds();
318:
319: // paint strip left of text
320: clipRect.setBounds(saveClip);
321: if (computeIntersection(clipRect, x, y, textLoc.x
322: - TITLE_MARGIN - x, height)) {
323: g.setClip(clipRect);
324: border.paintBorder(c, g, grooveRect.x,
325: grooveRect.y, grooveRect.width,
326: grooveRect.height);
327: }
328:
329: // paint strip right of text
330: clipRect.setBounds(saveClip);
331: if (computeIntersection(clipRect, textLoc.x
332: + stringWidth + TITLE_MARGIN, y, x + width
333: - (textLoc.x + stringWidth + TITLE_MARGIN),
334: height)) {
335: g.setClip(clipRect);
336: border.paintBorder(c, g, grooveRect.x,
337: grooveRect.y, grooveRect.width,
338: grooveRect.height);
339: }
340:
341: if (titlePos == TOP || titlePos == DEFAULT_POSITION) {
342: // paint strip below text
343: clipRect.setBounds(saveClip);
344: if (computeIntersection(clipRect, textLoc.x
345: - TITLE_MARGIN, textLoc.y + descent,
346: stringWidth + 2 * TITLE_MARGIN, y + height
347: - textLoc.y - descent)) {
348: g.setClip(clipRect);
349: border.paintBorder(c, g, grooveRect.x,
350: grooveRect.y, grooveRect.width,
351: grooveRect.height);
352: }
353:
354: } else { // titlePos == BOTTOM
355: // paint strip above text
356: clipRect.setBounds(saveClip);
357: if (computeIntersection(clipRect, textLoc.x
358: - TITLE_MARGIN, y, stringWidth + 2
359: * TITLE_MARGIN, textLoc.y - ascent - y)) {
360: g.setClip(clipRect);
361: border.paintBorder(c, g, grooveRect.x,
362: grooveRect.y, grooveRect.width,
363: grooveRect.height);
364: }
365: }
366:
367: // restore clip
368: g.setClip(saveClip);
369:
370: } else {
371: border.paintBorder(c, g, grooveRect.x, grooveRect.y,
372: grooveRect.width, grooveRect.height);
373: }
374: }
375:
376: g.setColor(getTitleColor());
377: JideSwingUtilities.drawString((JComponent) c, g, getTitle(),
378: textLoc.x, textLoc.y);
379:
380: g.setFont(font);
381: g.setColor(color);
382: }
383:
384: /**
385: * Returns the insets of the border.
386: *
387: * @param c the component for which this border insets value applies
388: */
389: @Override
390: public Insets getBorderInsets(Component c) {
391: return getBorderInsets(c, new Insets(0, 0, 0, 0));
392: }
393:
394: /**
395: * Reinitialize the insets parameter with this Border's current Insets.
396: *
397: * @param c the component for which this border insets value applies
398: * @param insets the object to be reinitialized
399: */
400: @Override
401: public Insets getBorderInsets(Component c, Insets insets) {
402: FontMetrics fm;
403: int descent = 0;
404: int ascent = 16;
405: int height = 16;
406:
407: Border border = getBorder();
408: if (border != null) {
409: if (border instanceof AbstractBorder) {
410: ((AbstractBorder) border).getBorderInsets(c, insets);
411: } else {
412: // Can't reuse border insets because the Border interface
413: // can't be enhanced.
414: Insets i = border.getBorderInsets(c);
415: insets.top = i.top;
416: insets.right = i.right;
417: insets.bottom = i.bottom;
418: insets.left = i.left;
419: }
420: } else {
421: insets.left = insets.top = insets.right = insets.bottom = 0;
422: }
423:
424: insets.left += EDGE_SPACING + TEXT_SPACING;
425: insets.right += EDGE_SPACING + TEXT_SPACING;
426: insets.top += EDGE_SPACING + TEXT_SPACING;
427: insets.bottom += EDGE_SPACING + TEXT_SPACING;
428:
429: if (c == null || getTitle() == null || getTitle().equals("")) {
430: return insets;
431: }
432:
433: Font font = getFont(c);
434:
435: fm = c.getFontMetrics(font);
436:
437: if (fm != null) {
438: descent = fm.getDescent();
439: ascent = fm.getAscent();
440: height = fm.getHeight();
441: }
442:
443: switch (getTitlePosition()) {
444: case ABOVE_TOP:
445: insets.top += ascent
446: + descent
447: + (Math.max(EDGE_SPACING, TEXT_SPACING << 1) - EDGE_SPACING);
448: break;
449: case TOP:
450: case DEFAULT_POSITION:
451: insets.top += ascent + descent;
452: break;
453: case BELOW_TOP:
454: insets.top += ascent + descent + TEXT_SPACING;
455: break;
456: case ABOVE_BOTTOM:
457: insets.bottom += ascent + descent + TEXT_SPACING;
458: break;
459: case BOTTOM:
460: insets.bottom += ascent + descent;
461: break;
462: case BELOW_BOTTOM:
463: insets.bottom += height;
464: break;
465: }
466: return insets;
467: }
468:
469: /**
470: * Returns whether or not the border is opaque.
471: */
472: @Override
473: public boolean isBorderOpaque() {
474: return false;
475: }
476:
477: /**
478: * Returns the title of the titled border.
479: */
480: public String getTitle() {
481: return title;
482: }
483:
484: /**
485: * Returns the border of the titled border.
486: */
487: public Border getBorder() {
488: Border b = border;
489: if (b == null)
490: b = UIDefaultsLookup.getBorder("TitledBorder.border");
491: return b;
492: }
493:
494: /**
495: * Returns the title-position of the titled border.
496: */
497: public int getTitlePosition() {
498: return titlePosition;
499: }
500:
501: /**
502: * Returns the title-justification of the titled border.
503: */
504: public int getTitleJustification() {
505: return titleJustification;
506: }
507:
508: /**
509: * Returns the title-font of the titled border.
510: */
511: public Font getTitleFont() {
512: Font f = titleFont;
513: if (f == null)
514: f = UIDefaultsLookup.getFont("TitledBorder.font");
515: return f;
516: }
517:
518: /**
519: * Returns the title-color of the titled border.
520: */
521: public Color getTitleColor() {
522: Color c = titleColor;
523: if (c == null)
524: c = UIDefaultsLookup.getColor("TitledBorder.titleColor");
525: return c;
526: }
527:
528: // REMIND(aim): remove all or some of these set methods?
529:
530: /**
531: * Sets the title of the titled border.
532: * param title the title for the border
533: */
534: public void setTitle(String title) {
535: this .title = title;
536: }
537:
538: /**
539: * Sets the border of the titled border.
540: *
541: * @param border the border
542: */
543: public void setBorder(Border border) {
544: this .border = border;
545: }
546:
547: /**
548: * Sets the title-position of the titled border.
549: *
550: * @param titlePosition the position for the border
551: */
552: public void setTitlePosition(int titlePosition) {
553: switch (titlePosition) {
554: case ABOVE_TOP:
555: case TOP:
556: case BELOW_TOP:
557: case ABOVE_BOTTOM:
558: case BOTTOM:
559: case BELOW_BOTTOM:
560: case DEFAULT_POSITION:
561: this .titlePosition = titlePosition;
562: break;
563: default:
564: throw new IllegalArgumentException(titlePosition
565: + " is not a valid title position.");
566: }
567: }
568:
569: /**
570: * Sets the title-justification of the titled border.
571: *
572: * @param titleJustification the justification for the border
573: */
574: public void setTitleJustification(int titleJustification) {
575: switch (titleJustification) {
576: case DEFAULT_JUSTIFICATION:
577: case LEFT:
578: case CENTER:
579: case RIGHT:
580: case LEADING:
581: case TRAILING:
582: this .titleJustification = titleJustification;
583: break;
584: default:
585: throw new IllegalArgumentException(titleJustification
586: + " is not a valid title justification.");
587: }
588: }
589:
590: /**
591: * Sets the title-font of the titled border.
592: *
593: * @param titleFont the font for the border title
594: */
595: public void setTitleFont(Font titleFont) {
596: this .titleFont = titleFont;
597: }
598:
599: /**
600: * Sets the title-color of the titled border.
601: *
602: * @param titleColor the color for the border title
603: */
604: public void setTitleColor(Color titleColor) {
605: this .titleColor = titleColor;
606: }
607:
608: /**
609: * Returns the minimum dimensions this border requires
610: * in order to fully display the border and title.
611: *
612: * @param c the component where this border will be drawn
613: */
614: public Dimension getMinimumSize(Component c) {
615: Insets insets = getBorderInsets(c);
616: Dimension minSize = new Dimension(insets.right + insets.left,
617: insets.top + insets.bottom);
618: Font font = getFont(c);
619: FontMetrics fm = c.getFontMetrics(font);
620: switch (titlePosition) {
621: case ABOVE_TOP:
622: case BELOW_BOTTOM:
623: minSize.width = Math.max(fm.stringWidth(getTitle()),
624: minSize.width);
625: break;
626: case BELOW_TOP:
627: case ABOVE_BOTTOM:
628: case TOP:
629: case BOTTOM:
630: case DEFAULT_POSITION:
631: default:
632: minSize.width += fm.stringWidth(getTitle());
633: }
634: return minSize;
635: }
636:
637: protected Font getFont(Component c) {
638: Font font;
639: if ((font = getTitleFont()) != null) {
640: return font;
641: } else if (c != null && (font = c.getFont()) != null) {
642: return font;
643: }
644: return new Font("Dialog", Font.PLAIN, 12);
645: }
646:
647: private static boolean computeIntersection(Rectangle dest, int rx,
648: int ry, int rw, int rh) {
649: int x1 = Math.max(rx, dest.x);
650: int x2 = Math.min(rx + rw, dest.x + dest.width);
651: int y1 = Math.max(ry, dest.y);
652: int y2 = Math.min(ry + rh, dest.y + dest.height);
653: dest.x = x1;
654: dest.y = y1;
655: dest.width = x2 - x1;
656: dest.height = y2 - y1;
657:
658: if (dest.width <= 0 || dest.height <= 0) {
659: return false;
660: }
661: return true;
662: }
663: }
|