001: /*
002: * @(#)BasicStyledLabelUI.java 9/6/2005
003: *
004: * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.plaf.basic;
007:
008: import com.jidesoft.swing.JideSwingUtilities;
009: import com.jidesoft.swing.StyleRange;
010: import com.jidesoft.swing.StyledLabel;
011: import com.sun.java.swing.plaf.windows.WindowsLookAndFeel;
012:
013: import javax.swing.*;
014: import javax.swing.plaf.ComponentUI;
015: import javax.swing.plaf.basic.BasicLabelUI;
016: import javax.swing.text.View;
017: import java.awt.*;
018: import java.beans.PropertyChangeEvent;
019: import java.util.ArrayList;
020: import java.util.Arrays;
021: import java.util.Comparator;
022: import java.util.List;
023:
024: public class BasicStyledLabelUI extends BasicLabelUI implements
025: SwingConstants {
026: public static Comparator<StyleRange> _comparator;
027:
028: protected static BasicStyledLabelUI styledLabelUI = new BasicStyledLabelUI();
029:
030: public static ComponentUI createUI(JComponent c) {
031: return styledLabelUI;
032: }
033:
034: class StyledText {
035: StyleRange styleRange;
036: String text;
037:
038: public StyledText(String text) {
039: this .text = text;
040: }
041:
042: public StyledText(String text, StyleRange styleRange) {
043: this .text = text;
044: this .styleRange = styleRange;
045: }
046: }
047:
048: private List<StyledText> _styledTexts;
049:
050: @Override
051: public void propertyChange(PropertyChangeEvent e) {
052: super .propertyChange(e);
053: if (StyledLabel.PROPERTY_STYLE_RANGE
054: .equals(e.getPropertyName())) {
055: _styledTexts = null;
056: if (e.getSource() instanceof StyledLabel) {
057: ((StyledLabel) e.getSource()).revalidate();
058: ((StyledLabel) e.getSource()).repaint();
059: }
060: } else if (StyledLabel.PROPERTY_IGNORE_COLOR_SETTINGS.equals(e
061: .getPropertyName())) {
062: if (e.getSource() instanceof StyledLabel) {
063: ((StyledLabel) e.getSource()).repaint();
064: }
065: }
066: }
067:
068: @Override
069: protected void paintEnabledText(JLabel l, Graphics g, String s,
070: int textX, int textY) {
071: View v = (l != null) ? (View) l.getClientProperty("html")
072: : null;
073: if (v != null) {
074: super .paintEnabledText(l, g, s, textX, textY);
075: } else {
076: paintStyledText((StyledLabel) l, g, textX, textY);
077: }
078: }
079:
080: @Override
081: protected void paintDisabledText(JLabel l, Graphics g, String s,
082: int textX, int textY) {
083: View v = (l != null) ? (View) l.getClientProperty("html")
084: : null;
085: if (v != null) {
086: super .paintDisabledText(l, g, s, textX, textY);
087: } else {
088: paintStyledText((StyledLabel) l, g, textX, textY);
089: }
090: }
091:
092: protected void buildStyledText(StyledLabel label) {
093: if (_styledTexts == null) {
094: _styledTexts = new ArrayList();
095: } else {
096: _styledTexts.clear();
097: }
098: StyleRange[] styleRanges = label.getStyleRanges();
099: if (_comparator == null) {
100: _comparator = new Comparator<StyleRange>() {
101: public int compare(StyleRange r1, StyleRange r2) {
102: if (r1.getStart() < r2.getStart()) {
103: return -1;
104: } else if (r1.getStart() > r2.getStart()) {
105: return 1;
106: } else {
107: return 0;
108: }
109: }
110: };
111: }
112: Arrays.sort(styleRanges, _comparator);
113:
114: String s = label.getText();
115: if (s != null && s.length() > 0) { // do not do anything if the text is empty
116: int index = 0;
117: for (StyleRange styleRange : styleRanges) {
118: if (styleRange.getStart() > index) { // fill in the gap
119: _styledTexts.add(new StyledText(s.substring(index,
120: styleRange.getStart())));
121: index = styleRange.getStart();
122: }
123:
124: if (styleRange.getStart() == index) { // exactly on
125: if (styleRange.getLength() == -1) {
126: _styledTexts.add(new StyledText(s
127: .substring(index), styleRange));
128: index = s.length();
129: } else {
130: _styledTexts.add(new StyledText(s.substring(
131: index, index + styleRange.getLength()),
132: styleRange));
133: index += styleRange.getLength();
134: }
135: } else if (styleRange.getStart() < index) { // overlap
136: // ignore
137: }
138: }
139: if (index < s.length()) {
140: _styledTexts.add(new StyledText(s.substring(index, s
141: .length())));
142: }
143: }
144: }
145:
146: @Override
147: protected String layoutCL(JLabel label, FontMetrics fontMetrics,
148: String text, Icon icon, Rectangle viewR, Rectangle iconR,
149: Rectangle textR) {
150: Dimension size = getPreferredSize((StyledLabel) label);
151: textR.width = size.width;
152: textR.height = size.height;
153:
154: return layoutCompoundLabel(label, fontMetrics, text, icon,
155: label.getVerticalAlignment(), label
156: .getHorizontalAlignment(), label
157: .getVerticalTextPosition(), label
158: .getHorizontalTextPosition(), viewR, iconR,
159: textR, label.getIconTextGap());
160: }
161:
162: protected Dimension getPreferredSize(StyledLabel label) {
163: buildStyledText(label);
164:
165: int width = 0;
166: Font font = label.getFont();
167: FontMetrics fm = label.getFontMetrics(font);
168: FontMetrics fm2;
169: int lineHeight = 0;
170: for (int i = _styledTexts.size() - 1; i >= 0; i--) {
171: StyledText styledText = _styledTexts.get(i);
172: StyleRange style = styledText.styleRange;
173: float size = (style != null && (style.isSuperscript() || style
174: .isSubscript())) ? Math.round((float) font
175: .getSize()
176: / style.getFontShrinkRatio()) : (float) font
177: .getSize();
178: font = label.getFont();
179: if (style != null
180: && ((style.getFontStyle() != -1 && font.getStyle() != style
181: .getFontStyle()) || font.getSize() != size)) {
182: font = font.deriveFont(
183: style.getFontStyle() == -1 ? font.getStyle()
184: : style.getFontStyle(), size);
185: fm2 = label.getFontMetrics(font);
186: width += fm2.stringWidth((_styledTexts.get(i)).text);
187: } else {
188: fm2 = fm;
189: width += fm.stringWidth((_styledTexts.get(i)).text);
190: }
191:
192: if (style != null) {
193: if (style.isUnderlined() && lineHeight < 2) {
194: lineHeight = 2;
195: }
196: if (style.isDotted() && lineHeight < 3) {
197: lineHeight = 3;
198: }
199: if (style.isWaved() && lineHeight < 4) {
200: lineHeight = 4;
201: }
202: }
203: }
204:
205: int fontHeight = fm.getHeight();
206: return new Dimension(width, fontHeight + lineHeight);
207: }
208:
209: protected void paintStyledText(StyledLabel label, Graphics g,
210: int textX, int textY) {
211: int x = textX < label.getInsets().left ? label.getInsets().left
212: : textX;
213: int y;
214: int mnemonicIndex = label.getDisplayedMnemonicIndex();
215: if (UIManager.getLookAndFeel() instanceof WindowsLookAndFeel
216: && WindowsLookAndFeel.isMnemonicHidden()) {
217: mnemonicIndex = -1;
218: }
219:
220: buildStyledText(label);
221:
222: Color oldColor = g.getColor();
223:
224: int charDisplayed = 0;
225: boolean displayMnemonic = false;
226: int mneIndex = 0;
227: Font font = label.getFont();
228: FontMetrics fm = label.getFontMetrics(font);
229: FontMetrics fm2;
230:
231: for (StyledText styledText : _styledTexts) {
232: StyleRange style = styledText.styleRange;
233:
234: if (styledText.text.length() > mnemonicIndex
235: - charDisplayed) {
236: displayMnemonic = true;
237: mneIndex = mnemonicIndex - charDisplayed;
238: }
239: charDisplayed += styledText.text.length();
240:
241: y = textY;
242:
243: float size = (style != null && (style.isSuperscript() || style
244: .isSubscript())) ? Math.round((float) font
245: .getSize()
246: / style.getFontShrinkRatio()) : (float) font
247: .getSize();
248:
249: font = label.getFont();
250: if (style != null
251: && ((style.getFontStyle() != -1 && font.getStyle() != style
252: .getFontStyle()) || font.getSize() != size)) {
253: font = font.deriveFont(
254: style.getFontStyle() == -1 ? font.getStyle()
255: : style.getFontStyle(), size);
256: fm2 = label.getFontMetrics(font);
257: } else {
258: fm2 = fm;
259: }
260:
261: g.setFont(font);
262:
263: String s = styledText.text;
264:
265: int strWidth = fm2.stringWidth(s);
266:
267: boolean stop = false;
268: int widthLeft = label.getWidth() - x;
269: if (widthLeft < strWidth) {
270: // use this method to clip string
271: s = SwingUtilities.layoutCompoundLabel(label, fm2, s,
272: null, label.getVerticalAlignment(), label
273: .getHorizontalAlignment(), label
274: .getVerticalTextPosition(), label
275: .getHorizontalTextPosition(),
276: new Rectangle(x, y, widthLeft, label
277: .getHeight()), new Rectangle(),
278: new Rectangle(), 0);
279: strWidth = fm2.stringWidth(s);
280: stop = true;
281: }
282:
283: if (style != null && style.isSuperscript()) {
284: y -= fm.getHeight() - fm2.getHeight();
285: }
286:
287: if (style != null && style.getBackgroundColor() != null) {
288: g.setColor(style.getBackgroundColor());
289: g.fillRect(x, y - fm2.getHeight(), strWidth, fm2
290: .getHeight() + 4);
291: }
292:
293: Color textColor = (style != null
294: && !label.isIgnoreColorSettings() && style
295: .getFontColor() != null) ? style.getFontColor()
296: : label.getForeground();
297: g.setColor(textColor);
298:
299: if (displayMnemonic) {
300: JideSwingUtilities.drawStringUnderlineCharAt(label, g,
301: s, mneIndex, x, y);
302: } else {
303: JideSwingUtilities.drawString(label, g, s, x, y);
304: }
305:
306: if (style != null) {
307: Stroke oldStroke = ((Graphics2D) g).getStroke();
308: if (style.getLineStroke() != null) {
309: ((Graphics2D) g).setStroke(style.getLineStroke());
310: }
311:
312: if (!label.isIgnoreColorSettings()
313: && style.getLineColor() != null) {
314: g.setColor(style.getLineColor());
315: }
316:
317: if (style.isStrikethrough()) {
318: int lineY = y
319: + (fm2.getDescent() - fm2.getAscent()) / 2;
320: g.drawLine(x, lineY, x + strWidth - 1, lineY);
321: }
322: if (style.isDoublestrikethrough()) {
323: int lineY = y
324: + (fm2.getDescent() - fm2.getAscent()) / 2;
325: g.drawLine(x, lineY - 1, x + strWidth - 1,
326: lineY - 1);
327: g.drawLine(x, lineY + 1, x + strWidth - 1,
328: lineY + 1);
329: }
330: if (style.isUnderlined()) {
331: int lineY = y + 1;
332: g.drawLine(x, lineY, x + strWidth - 1, lineY);
333: }
334: if (style.isDotted()) {
335: int dotY = y + 1;
336: for (int dotX = x; dotX < x + strWidth; dotX += 4) {
337: g.drawRect(dotX, dotY, 1, 1);
338: }
339: }
340: if (style.isWaved()) {
341: int waveY = y + 1;
342: for (int waveX = x; waveX < x + strWidth; waveX += 4) {
343: if (waveX + 2 <= x + strWidth - 1)
344: g.drawLine(waveX, waveY + 2, waveX + 2,
345: waveY);
346: if (waveX + 4 <= x + strWidth - 1)
347: g.drawLine(waveX + 3, waveY + 1, waveX + 4,
348: waveY + 2);
349: }
350: }
351: if (style.getLineStroke() != null) {
352: ((Graphics2D) g).setStroke(oldStroke);
353: }
354: }
355:
356: if (stop) {
357: break;
358: }
359:
360: x += strWidth;
361: }
362:
363: g.setColor(oldColor);
364: }
365:
366: /**
367: * Compute and return the location of the icons origin, the
368: * location of origin of the text baseline, and a possibly clipped
369: * version of the compound labels string. Locations are computed
370: * relative to the viewR rectangle.
371: * The JComponents orientation (LEADING/TRAILING) will also be taken
372: * into account and translated into LEFT/RIGHT values accordingly.
373: */
374: public static String layoutCompoundLabel(JComponent c,
375: FontMetrics fm, String text, Icon icon,
376: int verticalAlignment, int horizontalAlignment,
377: int verticalTextPosition, int horizontalTextPosition,
378: Rectangle viewR, Rectangle iconR, Rectangle textR,
379: int textIconGap) {
380: boolean orientationIsLeftToRight = true;
381: int hAlign = horizontalAlignment;
382: int hTextPos = horizontalTextPosition;
383:
384: if (c != null) {
385: if (!(c.getComponentOrientation().isLeftToRight())) {
386: orientationIsLeftToRight = false;
387: }
388: }
389:
390: // Translate LEADING/TRAILING values in horizontalAlignment
391: // to LEFT/RIGHT values depending on the components orientation
392: switch (horizontalAlignment) {
393: case LEADING:
394: hAlign = (orientationIsLeftToRight) ? LEFT : RIGHT;
395: break;
396: case TRAILING:
397: hAlign = (orientationIsLeftToRight) ? RIGHT : LEFT;
398: break;
399: }
400:
401: // Translate LEADING/TRAILING values in horizontalTextPosition
402: // to LEFT/RIGHT values depending on the components orientation
403: switch (horizontalTextPosition) {
404: case LEADING:
405: hTextPos = (orientationIsLeftToRight) ? LEFT : RIGHT;
406: break;
407: case TRAILING:
408: hTextPos = (orientationIsLeftToRight) ? RIGHT : LEFT;
409: break;
410: }
411:
412: return layoutCompoundLabelImpl(c, fm, text, icon,
413: verticalAlignment, hAlign, verticalTextPosition,
414: hTextPos, viewR, iconR, textR, textIconGap);
415: }
416:
417: /**
418: * Compute and return the location of the icons origin, the
419: * location of origin of the text baseline, and a possibly clipped
420: * version of the compound labels string. Locations are computed
421: * relative to the viewR rectangle.
422: * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
423: * values in horizontalTextPosition (they will default to RIGHT) and in
424: * horizontalAlignment (they will default to CENTER).
425: * Use the other version of layoutCompoundLabel() instead.
426: */
427: public static String layoutCompoundLabel(FontMetrics fm,
428: String text, Icon icon, int verticalAlignment,
429: int horizontalAlignment, int verticalTextPosition,
430: int horizontalTextPosition, Rectangle viewR,
431: Rectangle iconR, Rectangle textR, int textIconGap) {
432: return layoutCompoundLabelImpl(null, fm, text, icon,
433: verticalAlignment, horizontalAlignment,
434: verticalTextPosition, horizontalTextPosition, viewR,
435: iconR, textR, textIconGap);
436: }
437:
438: /**
439: * Compute and return the location of the icons origin, the
440: * location of origin of the text baseline, and a possibly clipped
441: * version of the compound labels string. Locations are computed
442: * relative to the viewR rectangle.
443: * This layoutCompoundLabel() does not know how to handle LEADING/TRAILING
444: * values in horizontalTextPosition (they will default to RIGHT) and in
445: * horizontalAlignment (they will default to CENTER).
446: * Use the other version of layoutCompoundLabel() instead.
447: */
448: private static String layoutCompoundLabelImpl(JComponent c,
449: FontMetrics fm, String text, Icon icon,
450: int verticalAlignment, int horizontalAlignment,
451: int verticalTextPosition, int horizontalTextPosition,
452: Rectangle viewR, Rectangle iconR, Rectangle textR,
453: int textIconGap) {
454: /* Initialize the icon bounds rectangle iconR.
455: */
456:
457: if (icon != null) {
458: iconR.width = icon.getIconWidth();
459: iconR.height = icon.getIconHeight();
460: } else {
461: iconR.width = iconR.height = 0;
462: }
463:
464: /* Initialize the text bounds rectangle textR. If a null
465: * or and empty String was specified we substitute "" here
466: * and use 0,0,0,0 for textR.
467: */
468:
469: boolean textIsEmpty = (text == null) || text.equals("");
470: int lsb = 0;
471: /* Unless both text and icon are non-null, we effectively ignore
472: * the value of textIconGap.
473: */
474: int gap;
475:
476: View v = null;
477: if (textIsEmpty) {
478: textR.width = textR.height = 0;
479: text = "";
480: gap = 0;
481: } else {
482: int availTextWidth;
483: gap = (icon == null) ? 0 : textIconGap;
484:
485: if (horizontalTextPosition == CENTER) {
486: availTextWidth = viewR.width;
487: } else {
488: availTextWidth = viewR.width - (iconR.width + gap);
489: }
490: v = (c != null) ? (View) c.getClientProperty("html") : null;
491: if (v != null) {
492: textR.width = Math.min(availTextWidth, (int) v
493: .getPreferredSpan(View.X_AXIS));
494: textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
495: } else {
496: // this is only place that is changed for StyledLabel
497: // textR.width = SwingUtilities2.stringWidth(c, fm, text);
498: // lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
499: // if (lsb < 0) {
500: // // If lsb is negative, add it to the width and later
501: // // adjust the x location. This gives more space than is
502: // // actually needed.
503: // // This is done like this for two reasons:
504: // // 1. If we set the width to the actual bounds all
505: // // callers would have to account for negative lsb
506: // // (pref size calculations ONLY look at width of
507: // // textR)
508: // // 2. You can do a drawString at the returned location
509: // // and the text won't be clipped.
510: // textR.width -= lsb;
511: // }
512: // if (textR.width > availTextWidth) {
513: // text = SwingUtilities2.clipString(c, fm, text,
514: // availTextWidth);
515: // textR.width = SwingUtilities2.stringWidth(c, fm, text);
516: // }
517: // textR.height = fm.getHeight();
518: }
519: }
520:
521: /* Compute textR.x,y given the verticalTextPosition and
522: * horizontalTextPosition properties
523: */
524:
525: if (verticalTextPosition == TOP) {
526: if (horizontalTextPosition != CENTER) {
527: textR.y = 0;
528: } else {
529: textR.y = -(textR.height + gap);
530: }
531: } else if (verticalTextPosition == CENTER) {
532: textR.y = (iconR.height / 2) - (textR.height / 2);
533: } else { // (verticalTextPosition == BOTTOM)
534: if (horizontalTextPosition != CENTER) {
535: textR.y = iconR.height - textR.height;
536: } else {
537: textR.y = (iconR.height + gap);
538: }
539: }
540:
541: if (horizontalTextPosition == LEFT) {
542: textR.x = -(textR.width + gap);
543: } else if (horizontalTextPosition == CENTER) {
544: textR.x = (iconR.width / 2) - (textR.width / 2);
545: } else { // (horizontalTextPosition == RIGHT)
546: textR.x = (iconR.width + gap);
547: }
548:
549: /* labelR is the rectangle that contains iconR and textR.
550: * Move it to its proper position given the labelAlignment
551: * properties.
552: *
553: * To avoid actually allocating a Rectangle, Rectangle.union
554: * has been inlined below.
555: */
556: int labelR_x = Math.min(iconR.x, textR.x);
557: int labelR_width = Math.max(iconR.x + iconR.width, textR.x
558: + textR.width)
559: - labelR_x;
560: int labelR_y = Math.min(iconR.y, textR.y);
561: int labelR_height = Math.max(iconR.y + iconR.height, textR.y
562: + textR.height)
563: - labelR_y;
564:
565: int dx, dy;
566:
567: if (verticalAlignment == TOP) {
568: dy = viewR.y - labelR_y;
569: } else if (verticalAlignment == CENTER) {
570: dy = (viewR.y + (viewR.height / 2))
571: - (labelR_y + (labelR_height / 2));
572: } else { // (verticalAlignment == BOTTOM)
573: dy = (viewR.y + viewR.height) - (labelR_y + labelR_height);
574: }
575:
576: if (horizontalAlignment == LEFT) {
577: dx = viewR.x - labelR_x;
578: } else if (horizontalAlignment == RIGHT) {
579: dx = (viewR.x + viewR.width) - (labelR_x + labelR_width);
580: } else { // (horizontalAlignment == CENTER)
581: dx = (viewR.x + (viewR.width / 2))
582: - (labelR_x + (labelR_width / 2));
583: }
584:
585: /* Translate textR and glypyR by dx,dy.
586: */
587:
588: textR.x += dx;
589: textR.y += dy;
590:
591: iconR.x += dx;
592: iconR.y += dy;
593:
594: if (lsb < 0) {
595: // lsb is negative. Shift the x location so that the text is
596: // visually drawn at the right location.
597: textR.x -= lsb;
598: }
599:
600: return text;
601: }
602: }
|